From e53cb23190ba97ca36407e25ac57d28e1713fbc2 Mon Sep 17 00:00:00 2001 From: tomtwinkle Date: Wed, 10 Jun 2026 23:13:16 +0900 Subject: [PATCH 1/2] Cleanup organize the directory --- quiz-app/.gitignore | 26 + quiz-app/Makefile | 29 +- quiz-app/README.md | 285 +- quiz-app/cmd/generatequizdata/main.go | 43 - quiz-app/cmd/local/main.go | 48 - quiz-app/gas/telemetry.gs | 732 ---- quiz-app/go.mod | 5 - quiz-app/go.sum | 4 - quiz-app/index.html | 20 + quiz-app/internal/quizdata/quizdata.go | 115 - quiz-app/internal/quizdata/quizdata_test.go | 113 - quiz-app/internal/quizhandler/handler.go | 105 - quiz-app/internal/quizhandler/handler_test.go | 243 -- quiz-app/package-lock.json | 3245 +++++++++++++++++ quiz-app/package.json | 30 + quiz-app/public/assets/extra-mode.css | 792 ---- quiz-app/public/assets/extra-mode.js | 322 -- quiz-app/public/assets/progress-gopher.png | Bin 4703 -> 0 bytes quiz-app/public/favicon.jpg | Bin 0 -> 15945 bytes quiz-app/public/icons.svg | 24 + quiz-app/public/index.html | 2450 ------------- quiz-app/public/preview/index.html | 1076 ------ quiz-app/public/quiz-config.js | 4 - quiz-app/quizzes/code/Rtosshy_q1_answer.go | 26 - quiz-app/quizzes/code/Rtosshy_q1_question.go | 25 - quiz-app/quizzes/code/Rtosshy_q3_question.go | 14 - .../code/hasegawa_itsuki_q3_question.go | 43 - quiz-app/quizzes/code/makki_d_q2_answer.go | 29 - quiz-app/quizzes/code/makki_d_q2_question.go | 14 - quiz-app/quizzes/code/makki_d_q3_question.go | 14 - quiz-app/quizzes/code/ryokotmng_q1.go | 12 - quiz-app/quizzes/code/sat0ken_q3_question.go | 38 - quiz-app/quizzes/code/sivchari_q1_answer.go | 19 - quiz-app/quizzes/code/sivchari_q1_question.go | 10 - quiz-app/quizzes/code/sivchari_q2_answer.go | 16 - quiz-app/quizzes/code/sivchari_q2_question.go | 16 - quiz-app/quizzes/code/tesso57_q1_answer.go | 16 - quiz-app/quizzes/code/tesso57_q1_question.go | 15 - quiz-app/quizzes/code/tesso57_q2_answer.go | 17 - quiz-app/quizzes/code/tesso57_q2_question.go | 16 - quiz-app/quizzes/code/tesso57_q3_answer.go | 16 - quiz-app/quizzes/code/tesso57_q3_question.go | 11 - quiz-app/quizzes/code/tesso57_q4_answer.go | 17 - quiz-app/quizzes/code/tesso57_q5_answer.go | 21 - quiz-app/quizzes/code/tesso57_q5_question.go | 14 - .../quizzes/code/tomtwinkle_q10_question.go | 23 - quiz-app/quizzes/code/tomtwinkle_q1_answer.go | 29 - quiz-app/quizzes/code/tomtwinkle_q2_answer.go | 17 - .../quizzes/code/tomtwinkle_q2_question.go | 12 - quiz-app/quizzes/code/tomtwinkle_q4_answer.go | 49 - .../quizzes/code/tomtwinkle_q4_question.go | 25 - quiz-app/quizzes/code/tomtwinkle_q5_answer.go | 22 - .../quizzes/code/tomtwinkle_q5_question.go | 11 - quiz-app/quizzes/code/tomtwinkle_q6_answer.go | 24 - .../quizzes/code/tomtwinkle_q6_question.go | 14 - quiz-app/quizzes/code/tomtwinkle_q7_answer.go | 22 - .../quizzes/code/tomtwinkle_q7_question.go | 11 - quiz-app/quizzes/code/tomtwinkle_q8_answer.go | 32 - .../quizzes/code/tomtwinkle_q8_question.go | 18 - quiz-app/quizzes/code/tomtwinkle_q9_answer.go | 22 - quiz-app/quizzes/code/wancom_q1_answer.go | 34 - quiz-app/quizzes/code/wancom_q1_question.go | 25 - quiz-app/quizzes/code/wancom_q3_answer.go | 35 - quiz-app/quizzes/quizes.yaml | 1310 ------- quiz-app/src/App.vue | 1443 ++++++++ quiz-app/src/assets/bluesky-logo.svg | 3 + .../assets/go-conference-2026-logo.svg | 0 quiz-app/src/assets/x-logo.svg | 3 + .../src/components/games/ChoiceSelectGame.vue | 181 + .../src/components/games/FillBlankTapGame.vue | 205 ++ quiz-app/src/components/ui/PressButton.vue | 38 + .../src/components/ui/StageOutputPanel.vue | 72 + quiz-app/src/data/stages.toml | 243 ++ quiz-app/src/data/stages.ts | 304 ++ quiz-app/src/main.ts | 5 + quiz-app/src/style.css | 1068 ++++++ quiz-app/src/types.ts | 50 + quiz-app/src/vite-env.d.ts | 6 + quiz-app/test/app.ui.test.ts | 227 ++ quiz-app/test/browser-ui.test.js | 1422 -------- quiz-app/test/build-check.mjs | 54 + quiz-app/test/game-edge-cases.test.ts | 286 ++ quiz-app/test/layout.ui.test.ts | 99 + quiz-app/tsconfig.app.json | 14 + quiz-app/tsconfig.json | 4 + quiz-app/tsconfig.node.json | 24 + quiz-app/vite.config.ts | 32 + 87 files changed, 7797 insertions(+), 9851 deletions(-) create mode 100644 quiz-app/.gitignore delete mode 100644 quiz-app/cmd/generatequizdata/main.go delete mode 100644 quiz-app/cmd/local/main.go delete mode 100644 quiz-app/gas/telemetry.gs delete mode 100644 quiz-app/go.mod delete mode 100644 quiz-app/go.sum create mode 100644 quiz-app/index.html delete mode 100644 quiz-app/internal/quizdata/quizdata.go delete mode 100644 quiz-app/internal/quizdata/quizdata_test.go delete mode 100644 quiz-app/internal/quizhandler/handler.go delete mode 100644 quiz-app/internal/quizhandler/handler_test.go create mode 100644 quiz-app/package-lock.json create mode 100644 quiz-app/package.json delete mode 100644 quiz-app/public/assets/extra-mode.css delete mode 100644 quiz-app/public/assets/extra-mode.js delete mode 100644 quiz-app/public/assets/progress-gopher.png create mode 100644 quiz-app/public/favicon.jpg create mode 100644 quiz-app/public/icons.svg delete mode 100644 quiz-app/public/index.html delete mode 100644 quiz-app/public/preview/index.html delete mode 100644 quiz-app/public/quiz-config.js delete mode 100644 quiz-app/quizzes/code/Rtosshy_q1_answer.go delete mode 100644 quiz-app/quizzes/code/Rtosshy_q1_question.go delete mode 100644 quiz-app/quizzes/code/Rtosshy_q3_question.go delete mode 100644 quiz-app/quizzes/code/hasegawa_itsuki_q3_question.go delete mode 100644 quiz-app/quizzes/code/makki_d_q2_answer.go delete mode 100644 quiz-app/quizzes/code/makki_d_q2_question.go delete mode 100644 quiz-app/quizzes/code/makki_d_q3_question.go delete mode 100644 quiz-app/quizzes/code/ryokotmng_q1.go delete mode 100644 quiz-app/quizzes/code/sat0ken_q3_question.go delete mode 100644 quiz-app/quizzes/code/sivchari_q1_answer.go delete mode 100644 quiz-app/quizzes/code/sivchari_q1_question.go delete mode 100644 quiz-app/quizzes/code/sivchari_q2_answer.go delete mode 100644 quiz-app/quizzes/code/sivchari_q2_question.go delete mode 100644 quiz-app/quizzes/code/tesso57_q1_answer.go delete mode 100644 quiz-app/quizzes/code/tesso57_q1_question.go delete mode 100644 quiz-app/quizzes/code/tesso57_q2_answer.go delete mode 100644 quiz-app/quizzes/code/tesso57_q2_question.go delete mode 100644 quiz-app/quizzes/code/tesso57_q3_answer.go delete mode 100644 quiz-app/quizzes/code/tesso57_q3_question.go delete mode 100644 quiz-app/quizzes/code/tesso57_q4_answer.go delete mode 100644 quiz-app/quizzes/code/tesso57_q5_answer.go delete mode 100644 quiz-app/quizzes/code/tesso57_q5_question.go delete mode 100644 quiz-app/quizzes/code/tomtwinkle_q10_question.go delete mode 100644 quiz-app/quizzes/code/tomtwinkle_q1_answer.go delete mode 100644 quiz-app/quizzes/code/tomtwinkle_q2_answer.go delete mode 100644 quiz-app/quizzes/code/tomtwinkle_q2_question.go delete mode 100644 quiz-app/quizzes/code/tomtwinkle_q4_answer.go delete mode 100644 quiz-app/quizzes/code/tomtwinkle_q4_question.go delete mode 100644 quiz-app/quizzes/code/tomtwinkle_q5_answer.go delete mode 100644 quiz-app/quizzes/code/tomtwinkle_q5_question.go delete mode 100644 quiz-app/quizzes/code/tomtwinkle_q6_answer.go delete mode 100644 quiz-app/quizzes/code/tomtwinkle_q6_question.go delete mode 100644 quiz-app/quizzes/code/tomtwinkle_q7_answer.go delete mode 100644 quiz-app/quizzes/code/tomtwinkle_q7_question.go delete mode 100644 quiz-app/quizzes/code/tomtwinkle_q8_answer.go delete mode 100644 quiz-app/quizzes/code/tomtwinkle_q8_question.go delete mode 100644 quiz-app/quizzes/code/tomtwinkle_q9_answer.go delete mode 100644 quiz-app/quizzes/code/wancom_q1_answer.go delete mode 100644 quiz-app/quizzes/code/wancom_q1_question.go delete mode 100644 quiz-app/quizzes/code/wancom_q3_answer.go delete mode 100644 quiz-app/quizzes/quizes.yaml create mode 100644 quiz-app/src/App.vue create mode 100755 quiz-app/src/assets/bluesky-logo.svg rename quiz-app/{public => src}/assets/go-conference-2026-logo.svg (100%) create mode 100755 quiz-app/src/assets/x-logo.svg create mode 100644 quiz-app/src/components/games/ChoiceSelectGame.vue create mode 100644 quiz-app/src/components/games/FillBlankTapGame.vue create mode 100644 quiz-app/src/components/ui/PressButton.vue create mode 100644 quiz-app/src/components/ui/StageOutputPanel.vue create mode 100644 quiz-app/src/data/stages.toml create mode 100644 quiz-app/src/data/stages.ts create mode 100644 quiz-app/src/main.ts create mode 100644 quiz-app/src/style.css create mode 100644 quiz-app/src/types.ts create mode 100644 quiz-app/src/vite-env.d.ts create mode 100644 quiz-app/test/app.ui.test.ts delete mode 100644 quiz-app/test/browser-ui.test.js create mode 100644 quiz-app/test/build-check.mjs create mode 100644 quiz-app/test/game-edge-cases.test.ts create mode 100644 quiz-app/test/layout.ui.test.ts create mode 100644 quiz-app/tsconfig.app.json create mode 100644 quiz-app/tsconfig.json create mode 100644 quiz-app/tsconfig.node.json create mode 100644 quiz-app/vite.config.ts diff --git a/quiz-app/.gitignore b/quiz-app/.gitignore new file mode 100644 index 0000000..6e6cfc0 --- /dev/null +++ b/quiz-app/.gitignore @@ -0,0 +1,26 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-pages +dist-pages-check +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/quiz-app/Makefile b/quiz-app/Makefile index c277932..7ded3ac 100644 --- a/quiz-app/Makefile +++ b/quiz-app/Makefile @@ -1,21 +1,18 @@ -.PHONY: build-pages clean local test-browser +.PHONY: clean local test -GO := go +NPM ?= npm +HOST ?= localhost +PORT ?= 8788 -CODE_FILES := $(wildcard quizzes/code/*.go) -PAGES_DIST = gh-pages-dist +node_modules: package.json package-lock.json + $(NPM) ci -build-pages: - rm -rf $(PAGES_DIST) - mkdir -p $(PAGES_DIST) - cp -r public/* $(PAGES_DIST)/ - $(GO) run ./cmd/generatequizdata -out $(PAGES_DIST)/quiz-data.js +local: node_modules + $(NPM) run dev -- --host $(HOST) --port $(PORT) -clean: - rm -rf $(PAGES_DIST) - -local: - $(GO) run ./cmd/local/ +test: node_modules + $(NPM) run test + $(NPM) run test:build -test-browser: - node --test test/browser-ui.test.js +clean: + rm -rf dist dist-pages dist-pages-check diff --git a/quiz-app/README.md b/quiz-app/README.md index ab2c823..3354899 100644 --- a/quiz-app/README.md +++ b/quiz-app/README.md @@ -1,242 +1,113 @@ -# quiz-app - -Go Conference 2026 のコードラボ向け Go クイズアプリです。 -公開 UI は GitHub Pages に静的配信し、回答ログと問題ごとの正答率集計は Google Apps Script + Google スプレッドシートで扱います。 - -## 機能 - -- ランダムに選ばれた 5 問を出題 -- ヘッダー右上の地球儀メニューから日本語 / English を切り替え可能 -- 問題文・選択肢・解説・タイトルも選択言語に合わせて切り替え -- 問題ごとに `normal` / `extra` / `both` の出題プールを持てる -- 問題・選択肢・Go コードのシンタックスハイライト表示 -- 選択肢順は出題ごとにランダムシャッフル -- 回答後に正誤・解説・解答コード・Go Playground リンクを表示 -- 全問正解でスペシャルページへのリンクを表示 -- 回答開始からの経過秒数を計測し、全問正解時はクリアタイム表示とニックネーム送信が可能 -- エクストラモードで未出問題へ継続挑戦、累計スコアを表示 -- 問題プレビューモードを公開 UI に統合 - - フッターを 10 回タップすると hidden Keyword ポップアップが開き、Keyword で問題プレビューモードを開ける - - またはエクストラモード全問正解で解放 -- 回答イベントを Google Apps Script に送信し、スプレッドシートで問題ごとの集計を更新 - -## 技術スタック - -| 層 | 技術 | -|---|---| -| フロントエンド | バニラ HTML/CSS/JS、[highlight.js](https://highlightjs.org/) | -| 静的データ生成 | Go | -| ホスティング | GitHub Pages | -| 集計 | Google Apps Script + Google スプレッドシート | -| ローカル開発 | 標準 Go HTTP サーバー | - -## ディレクトリ構成 - -```text -quiz-app/ -├── cmd/ -│ ├── generatequizdata/ # quizes.yaml から quiz-data.js を生成 -│ └── local/ # ローカル開発用サーバー -├── quizzes/ -│ ├── quizes.yaml # 問題データ -│ └── code/ # 問題・解答コード -├── gas/ -│ └── telemetry.gs # Apps Script の集計エンドポイント -├── internal/ -│ ├── quizdata/ # 静的クイズデータ生成の共通処理 -│ └── quizhandler/ # quizes.yaml の型・mode 正規化 -├── public/ -│ ├── index.html # 公開クイズ画面 -│ ├── preview/index.html # 解放後の問題プレビューモード -│ └── quiz-config.js # telemetry / 秘密コード設定 -├── Makefile -└── go.mod -``` +# Go CodeLab 2026 -## ローカル開発 +穴埋め問題方式 の Go CodeLabアプリです。 +Vue 3 + TypeScript + Tailwind CSS で構成されています。 -> Go 1.25 以上が必要です。 +## UI -```bash -cd quiz-app -make local -# http://localhost:8788 -``` +- duolingo形式の長文を読まずにサクサクプレイ出来る問題構成 +- ワードプールをクリックすることで穴埋め問題の穴埋めを行います +- 問題をすべて回答するとスコアページを表示 -`make local` は `public/` を配信しつつ、`/quiz-data.js` を動的生成します。 -`PORT=3000 make local` のようにポート変更も可能です。 +## クイズデータ -## GitHub Pages 用の静的ビルド +本アプリケーションのクイズ問題は、`src/data/stages.toml` を編集することで、追加・変更・削除が可能です。 -```bash -cd quiz-app -make build-pages -``` +### データ構造 -成果物は `gh-pages-dist/` に出力されます。 +Go Conference 2026バージョンでは難易度は1つだけとして、ステージの部分は空欄で設定されています。 +追加でステージを設定することで次のステージへ遷移していくことができます。 -```text -gh-pages-dist/ -├── index.html -├── preview/index.html -├── quiz-config.js -└── quiz-data.js -``` +TOMLファイルは「キャンペーンティア(難易度レベル)」と、その中に紐づく「ステージ(各問題)」の二層構造になっています。 -`quiz-data.js` は `quizzes/quizes.yaml` と `quizzes/code/*` から生成されます。 - -## Google Apps Script + スプレッドシート設定 - -### 1. スプレッドシートを作成 - -任意の Google スプレッドシートを 1 つ用意します。 -Apps Script は `Logs`、`Summary`、`Attempts`、`PerfectScores` の 4 シートを自動で作成・更新します。 - -### 2. Apps Script を作成 - -1. スプレッドシートで **拡張機能 > Apps Script** を開く -2. `quiz-app/gas/telemetry.gs` の内容を貼り付ける -3. **デプロイ > 新しいデプロイ** -4. 種類を **ウェブアプリ** にする -5. 実行ユーザーを **自分** -6. アクセスできるユーザーを **全員** -7. 発行された Web アプリ URL を控える - -回答ログは以下のような JSON です。 - -```json -{ - "event_type": "answer", - "question_id": "alice_q1", - "question_title": "range の仕様", - "selected_answer": 1, - "correct_answer": 1, - "selected_display_index": 2, - "correct_display_index": 2, - "selected_choice_text": "for range", - "correct_choice_text": "for range", - "choice_order": [3, 0, 1, 2], - "is_correct": true, - "mode": "normal", - "session_id": "same-browser-session-id", - "attempt_id": "single-quiz-run-id", - "question_index": 3, - "session_size": 5, - "elapsed_seconds": 42, - "question_elapsed_seconds": 11, - "answered_at": "2026-04-15T12:34:56.000Z" -} -``` +* `[[campaignTiers]]` : 難易度グループを定義します。 +* `[[campaignTiers.stages]]` : 各ティアに属するクイズ問題を定義します。 -- `session_id`: 同じブラウザセッション内の再挑戦をまたいで共通の ID -- `attempt_id`: 1 回の 5 問チャレンジごとに新しく採番される ID -- `selected_answer` / `correct_answer`: `quizes.yaml` 上の元の選択肢 index -- `selected_display_index` / `correct_display_index`: シャッフル後に画面へ表示された index -- `choice_order`: 表示順から元の index への対応(例: `[3,0,1,2]` は「表示1番目=元の4番目」) - -スコア記録送信用フォームから送られる payload は以下です。 - -```json -{ - "event_type": "perfect_score", - "nickname": "gopher", - "elapsed_seconds": 42, - "correct_count": 4, - "completed_at": "2026-04-15T12:39:56.000Z", - "mode": "extra", - "session_id": "same-browser-session-id", - "attempt_id": "single-quiz-run-id" -} -``` -- `Logs` シートには全回答が追記され、`session_id`、`attempt_id`、各回答時点の `elapsed_seconds`、各問題にかかった `question_elapsed_seconds` に加えて、元の選択肢 index / 表示 index / 選択肢テキスト / `choice_order` が保存されます -- `Summary` シートには問題ごとの **初回回答時の正答率** と **最終回答時の正答率**、および初回/最終回答時点の平均所要秒数が再計算されます -- `Attempts` シートには各 `attempt_id` ごとの回答数、完走有無、最後に到達した問題番号、総経過秒数がまとまり、途中離脱の分析に使えます -- `PerfectScores` シートには normal の全問正解時と extra の完走時に送信されたニックネーム、完走タイム、正解数、回答完了日時、`mode`(`normal` / `extra`)、`attempt_id` が保存され、**extra → normal → 正解数の多い順 → 完走タイムの短い順** のランキングになるよう再構築されます +### 各フィールドの解説 -## GitHub Pages デプロイについて +#### 1. ティア定義 (`[[campaignTiers]]`) +| フィールド名 | 型 | 必須 | 説明 | +| :--- | :--- | :---: | :--- | +| `id` | `string` | ✅ | ティアの一意な識別子(他のティアと重複不可) | +| `title` | `string` | ✅ | 画面に表示されるタイトル(例: `"Stage 1"`) | +| `difficultyLabel` | `string` | ✅ | 難易度バッジのラベル(例: `"Tier 1"`) | +| `description` | `string` | ✅ | ティアの概要説明 | +| `unlocksSpecial` | `boolean` | | `true` にすると、このティアを全問正解した際にスペシャルページへのリンクが解放されます | -GitHub Pages 向けの静的成果物は `make build-pages` で生成できます。 -`.github/workflows/quiz-app-github-pages.yml` では **build job と artifact upload だけ動かし、deploy job は公開日まで skip** しています。 -リポジトリの GitHub Pages は公開日まで有効化しない前提のため、workflow は `actions/configure-pages` を呼ばずに静的成果物の build 確認だけを行います。 +#### 2. ステージ定義 (`[[campaignTiers.stages]]`) +| フィールド名 | 型 | 必須 | 説明 | +| :--- | :--- | :---: | :--- | +| `id` | `string` | ✅ | 問題の一意な識別子 | +| `kind` | `string` | ✅ | 出題形式 "fill"(穴埋め方式)のみサポート | +| `label` | `string` | ✅ | 出題クイズのラベル | +| `title` | `string` | ✅ | 問題のタイトル | +| `prompt` | `string` | ✅ | プレイヤーへの指示・お題のテキスト | +| `outputLines` | `string` | ✅ | **ヒアドキュメント(`"""`)** で記述。期待されるコンソール出力結果(空の場合は空のまま) | +| `templateLines` | `string` | ✅ | **ヒアドキュメント(`"""`)** で記述。出題するコード。空欄にしたい部分に `[1]`, `[2]` と記述します | +| `pool` | `string[]` | ✅ | 選択肢プール(ボタンとして並ぶ単語のリスト) | +| `correctAnswers` | `string[]` | ✅ | 正解の配列。`[1]`, `[2]` の順番に一致する文字列を並べます | +| `why` | `string` | ✅ | 解説テキスト。`` `code` `` のようにバッククォートで囲むとインラインコード装飾になります | +| `takeaway` | `string` | ✅ | 今回のポイント(まとめ)テキスト | +| `playgroundUrl` | `string` | | 解説画面から遷移できる Go Playground の絶対URL(`https://go.dev/play/...` のみ許可) | -workflow では次の Variables を使います。 +- `templateLines` 内のプレースホルダーは、必ず `[1]`, `[2]`, `[3]`... と **1から始まる連番かつ登場順** に並べてください。飛び番や順序の狂いがある場合、パースエラーになります。 +- `correctAnswers` に指定した文字列は、プレイヤーが選ぶ `pool`(選択肢)の中に過不足なく存在している必要があります。 -| Variable 名 | 必須 | 説明 | -|---|---|---| -| `QUIZ_TELEMETRY_ENDPOINT` | 任意 | Apps Script の Web アプリ URL(未設定時は `public/quiz-config.js` の既定値を使用) | -| `QUIZ_PREVIEW_UNLOCK_CODE` | 任意 | フッター 10 タップ後に開く hidden Keyword ポップアップで使うコード(未設定時は `gofar,gotogether`) | +## 開発コマンド -- `QUIZ_TELEMETRY_ENDPOINT` が空でも静的サイト自体はビルドでき、`public/quiz-config.js` に含まれる既定の Apps Script URL が使われます -- `QUIZ_PREVIEW_UNLOCK_CODE` を未設定のまま使う場合、既定の hidden Keyword は `gofar,gotogether` です - -workflow の build job は `public/quiz-config.js` の既定値を読み込み、Actions Variables が設定されている項目だけ `gh-pages-dist/quiz-config.js` へ上書きします。 +```bash +cd quiz-app +make local +``` -公開を再開する場合は、リポジトリ設定で GitHub Pages を **GitHub Actions** ソースとして有効化したうえで、deploy job の guard を外してください。 +デフォルトでは `http://localhost:8788/` で起動します。 -## テスト +ローカル preview: ```bash cd quiz-app -go test ./... -make test-browser +npm run build +npm run preview -- --host ``` -## 問題の追加方法 - -### 1. コードファイルを配置 +ビルド確認: -`quizzes/code/` に Go ファイルを追加します。 +```bash +cd quiz-app +npm run build +``` -```go -//go:build ignore +build path チェック: -package main +```bash +cd quiz-app +npm run test:build +``` -import "fmt" +ローカルのフルテスト: -func main() { - fmt.Println("Hello") -} +```bash +cd quiz-app +make test ``` -`//go:build ignore` は公開用コード表示時に自動で除去されます。 - -### 2. `quizes.yaml` に問題を追加 - -```yaml -- id: "author_q1" - title: "問題のタイトル" - title_en: "Question title" - text: "問題文" - text_en: "Question text" - mode: "extra" - question_code_ref: "code/author_q1_question.go" - choices: - - "選択肢 A" - - "選択肢 B" - - "選択肢 C" - - "選択肢 D" - choices_en: - - "Choice A" - - "Choice B" - - "Choice C" - - "Choice D" - answer: 2 - explanation: "解説文" - explanation_en: "Explanation" - answer_code_ref: "code/author_q1_answer.go" - answer_code_play_ref: "https://go.dev/play/p/..." -``` +UI 動作テスト: -`*_en` フィールドを省略した場合は日本語の値がそのまま英語表示にも使われます。公開用の問題は日本語・英語の両方を埋める前提です。 +```bash +cd quiz-app +npm run test +``` -`mode` は省略可能です。値は次の 3 つです。 +## GitHub Pages デプロイ -- `both`(既定値): ノーマル / エクストラの両方で出題候補に入る -- `normal`: ノーマルモードのみで出題する -- `extra`: エクストラモードのみで出題する +- `.github/workflows/quiz-app-build.yml` が branch push / pull request / manual dispatch で `make test` を実行します +- `.github/workflows/quiz-app-pages.yml` が `quiz-app/dist` を GitHub Pages に配備します +- Pages 向け build は `VITE_BASE_PATH` を明示して repository path 付きで生成します +- ローカルの `npm run dev` / `npm run build` は `/` base のまま動きます -`answer` は **元の choices 配列に対する正解 index** を指定します。公開 UI 側では表示時に choices をシャッフルしますが、telemetry には元 index と表示 index の両方が送られます。 +## 補足 -`make build-pages` または `make local` 実行時に静的データが再生成されます。 +- バックエンドや telemetry 連携はまだ入れていません +- special page URL は現在 placeholder です +- 画面操作の自動テストは Vitest + Vue Test Utils + jsdom を使っています diff --git a/quiz-app/cmd/generatequizdata/main.go b/quiz-app/cmd/generatequizdata/main.go deleted file mode 100644 index 7f21fc2..0000000 --- a/quiz-app/cmd/generatequizdata/main.go +++ /dev/null @@ -1,43 +0,0 @@ -package main - -import ( - "flag" - "log/slog" - "os" - "path/filepath" - - "github.com/GoCon/2026-codelab/quiz-app/internal/quizdata" -) - -func main() { - logger := slog.New(slog.NewTextHandler(os.Stderr, nil)) - basePath := flag.String("base", "quizzes", "quiz data base directory") - outPath := flag.String("out", "", "output path") - flag.Parse() - - if *outPath == "" { - logger.Error("-out is required") - os.Exit(1) - } - - quizzes, codeFiles, err := quizdata.LoadFromBase(*basePath) - if err != nil { - logger.Error("failed to load quiz data", "error", err) - os.Exit(1) - } - staticQuizzes := quizdata.BuildStaticQuizzes(quizzes, codeFiles) - js, err := quizdata.MarshalJavaScript(staticQuizzes) - if err != nil { - logger.Error("failed to marshal quiz data", "error", err) - os.Exit(1) - } - - if err := os.MkdirAll(filepath.Dir(*outPath), 0o755); err != nil { - logger.Error("failed to create output directory", "error", err, "path", filepath.Dir(*outPath)) - os.Exit(1) - } - if err := os.WriteFile(*outPath, js, 0o644); err != nil { - logger.Error("failed to write quiz data", "error", err, "path", *outPath) - os.Exit(1) - } -} diff --git a/quiz-app/cmd/local/main.go b/quiz-app/cmd/local/main.go deleted file mode 100644 index feaa3e6..0000000 --- a/quiz-app/cmd/local/main.go +++ /dev/null @@ -1,48 +0,0 @@ -// local はローカル開発用の HTTP サーバー。 -// quiz-data.js を起動時に生成して public/ と一緒に配信する。 -// 使用方法: make local (quiz-app/ ディレクトリから実行) -package main - -import ( - "log/slog" - "net/http" - "os" - - "github.com/GoCon/2026-codelab/quiz-app/internal/quizdata" -) - -func fail(logger *slog.Logger, msg string, err error) { - logger.Error(msg, "error", err) - os.Exit(1) -} - -func main() { - const quizDataBase = "quizzes" - logger := slog.New(slog.NewTextHandler(os.Stderr, nil)) - - quizzes, codeFiles, err := quizdata.LoadFromBase(quizDataBase) - if err != nil { - fail(logger, "failed to load quiz data", err) - } - staticQuizzes := quizdata.BuildStaticQuizzes(quizzes, codeFiles) - quizDataJS, err := quizdata.MarshalJavaScript(staticQuizzes) - if err != nil { - fail(logger, "failed to marshal quiz data", err) - } - - mux := http.NewServeMux() - mux.HandleFunc("GET /quiz-data.js", func(w http.ResponseWriter, r *http.Request) { - w.Header().Set("Content-Type", "application/javascript; charset=utf-8") - w.Write(quizDataJS) - }) - mux.Handle("/", http.FileServer(http.Dir("public"))) - - addr := ":8788" - if p := os.Getenv("PORT"); p != "" { - addr = ":" + p - } - logger.Info("starting local server", "url", "http://localhost"+addr) - if err := http.ListenAndServe(addr, mux); err != nil { - fail(logger, "local server exited with error", err) - } -} diff --git a/quiz-app/gas/telemetry.gs b/quiz-app/gas/telemetry.gs deleted file mode 100644 index 8d323b0..0000000 --- a/quiz-app/gas/telemetry.gs +++ /dev/null @@ -1,732 +0,0 @@ -const LOG_SHEET_NAME = 'Logs'; -const SUMMARY_SHEET_NAME = 'Summary'; -const ATTEMPTS_SHEET_NAME = 'Attempts'; -const PERFECT_SCORE_SHEET_NAME = 'PerfectScores'; -const MAX_NICKNAME_LENGTH = 40; - -const LOG_HEADERS = [ - 'question_id', - 'question_title', - 'mode', - 'selected_answer', - 'correct_answer', - 'is_correct', - 'session_id', - 'attempt_id', - 'question_index', - 'session_size', - 'elapsed_seconds', - 'question_elapsed_seconds', - 'answered_at', - 'received_at', - 'selected_display_index', - 'correct_display_index', - 'selected_choice_text', - 'correct_choice_text', - 'choice_order', -]; - -const LOG_COL = { - QUESTION_ID: 0, - QUESTION_TITLE: 1, - MODE: 2, - SELECTED_ANSWER: 3, - CORRECT_ANSWER: 4, - IS_CORRECT: 5, - SESSION_ID: 6, - ATTEMPT_ID: 7, - QUESTION_INDEX: 8, - SESSION_SIZE: 9, - ELAPSED_SECONDS: 10, - QUESTION_ELAPSED_SECONDS: 11, - ANSWERED_AT: 12, - RECEIVED_AT: 13, - SELECTED_DISPLAY_INDEX: 14, - CORRECT_DISPLAY_INDEX: 15, - SELECTED_CHOICE_TEXT: 16, - CORRECT_CHOICE_TEXT: 17, - CHOICE_ORDER: 18, -}; - -const SUMMARY_HEADERS = [ - 'question_id', - 'question_title', - 'first_answer_sessions', - 'first_correct_sessions', - 'first_accuracy', - 'avg_first_question_seconds', - 'avg_first_elapsed_seconds', - 'final_answer_sessions', - 'final_correct_sessions', - 'final_accuracy', - 'avg_final_question_seconds', - 'avg_final_elapsed_seconds', -]; - -const ATTEMPT_HEADERS = [ - 'attempt_id', - 'session_id', - 'mode', - 'answers_submitted', - 'unique_questions', - 'expected_questions', - 'completed', - 'last_question_index', - 'total_elapsed_seconds', - 'avg_question_seconds', - 'first_answered_at', - 'last_answered_at', -]; - -const PERFECT_SCORE_HEADERS = [ - 'nickname', - 'elapsed_seconds', - 'correct_count', - 'completed_at', - 'mode', - 'session_id', - 'attempt_id', - 'received_at', -]; - -function doPost(e) { - try { - const payload = parsePayload_(e); - const spreadsheet = SpreadsheetApp.getActiveSpreadsheet(); - if (payload.event_type === 'perfect_score') { - const perfectScoreSheet = ensureSheet_(spreadsheet, PERFECT_SCORE_SHEET_NAME, PERFECT_SCORE_HEADERS); - appendPerfectScoreRow_(perfectScoreSheet, payload); - rebuildPerfectScoreSheet_(perfectScoreSheet); - } else { - const logSheet = ensureSheet_(spreadsheet, LOG_SHEET_NAME, LOG_HEADERS); - appendLogRow_(logSheet, payload); - - const summarySheet = ensureSheet_(spreadsheet, SUMMARY_SHEET_NAME, SUMMARY_HEADERS); - const attemptsSheet = ensureSheet_(spreadsheet, ATTEMPTS_SHEET_NAME, ATTEMPT_HEADERS); - rebuildAnalytics_(logSheet, summarySheet, attemptsSheet); - } - - return jsonResponse_(200, { ok: true }); - } catch (err) { - return jsonResponse_(400, { - ok: false, - error: String(err && err.message ? err.message : err), - }); - } -} - -function doGet() { - return jsonResponse_(200, { - ok: true, - message: 'POST answer or perfect-score telemetry JSON to this Apps Script web app URL.', - }); -} - -function parsePayload_(e) { - if (!e || !e.postData || !e.postData.contents) { - throw new Error('post body is required'); - } - - const raw = e.postData.contents.trim(); - if (!raw) { - throw new Error('post body is empty'); - } - - const payload = JSON.parse(raw); - if (!payload || typeof payload !== 'object' || Array.isArray(payload)) { - throw new Error('payload must be a JSON object'); - } - - if (payload.event_type === 'perfect_score') { - return parsePerfectScorePayload_(payload); - } - return parseAnswerPayload_(payload); -} - -function parseAnswerPayload_(payload) { - payload.event_type = 'answer'; - payload.question_id = normalizeString_(payload.question_id); - if (!payload.question_id) { - throw new Error('question_id is required'); - } - - payload.question_title = normalizeString_(payload.question_title); - payload.mode = normalizeMode_(payload.mode); - - if (!isFiniteNumber_(payload.selected_answer)) { - throw new Error('selected_answer must be a number'); - } - if (!isFiniteNumber_(payload.correct_answer)) { - throw new Error('correct_answer must be a number'); - } - if (typeof payload.is_correct !== 'boolean') { - throw new Error('is_correct must be a boolean'); - } - - payload.session_id = normalizeString_(payload.session_id); - payload.attempt_id = normalizeString_(payload.attempt_id); - payload.question_index = normalizeNonNegativeNumber_(payload.question_index); - payload.session_size = normalizePositiveNumber_(payload.session_size); - payload.elapsed_seconds = normalizeNonNegativeNumber_(payload.elapsed_seconds); - payload.question_elapsed_seconds = normalizeNonNegativeNumber_(payload.question_elapsed_seconds); - payload.selected_display_index = normalizeNonNegativeNumber_(payload.selected_display_index); - payload.correct_display_index = normalizeNonNegativeNumber_(payload.correct_display_index); - payload.selected_choice_text = normalizeString_(payload.selected_choice_text); - payload.correct_choice_text = normalizeString_(payload.correct_choice_text); - payload.choice_order = normalizeIndexArray_(payload.choice_order); - - if (!payload.answered_at) { - payload.answered_at = new Date().toISOString(); - } else { - payload.answered_at = normalizeString_(payload.answered_at) || new Date().toISOString(); - } - - return payload; -} - -function parsePerfectScorePayload_(payload) { - payload.event_type = 'perfect_score'; - payload.nickname = normalizeString_(payload.nickname); - if (!payload.nickname) { - throw new Error('nickname is required'); - } - if (payload.nickname.length > MAX_NICKNAME_LENGTH) { - throw new Error(`nickname must be ${MAX_NICKNAME_LENGTH} characters or less`); - } - - payload.mode = normalizeMode_(payload.mode); - payload.session_id = normalizeString_(payload.session_id); - payload.attempt_id = normalizeString_(payload.attempt_id); - payload.elapsed_seconds = normalizeNonNegativeNumber_(payload.elapsed_seconds); - if (payload.elapsed_seconds === '') { - throw new Error('elapsed_seconds must be a non-negative number'); - } - payload.correct_count = normalizeNonNegativeNumber_(payload.correct_count); - if (payload.correct_count === '') { - throw new Error('correct_count must be a non-negative number'); - } - - payload.completed_at = normalizeString_(payload.completed_at) || new Date().toISOString(); - return payload; -} - -function ensureSheet_(spreadsheet, name, headers) { - let sheet = spreadsheet.getSheetByName(name); - if (!sheet) { - sheet = spreadsheet.insertSheet(name); - } - - const currentHeaders = sheet.getRange(1, 1, 1, headers.length).getValues()[0]; - const needsHeaders = headers.some((header, index) => currentHeaders[index] !== header); - if (needsHeaders) { - sheet.getRange(1, 1, 1, headers.length).setValues([headers]); - sheet.getRange(1, 1, 1, headers.length).setFontWeight('bold'); - sheet.setFrozenRows(1); - } - - return sheet; -} - -function appendLogRow_(sheet, payload) { - sheet.appendRow([ - payload.question_id, - payload.question_title, - payload.mode, - payload.selected_answer, - payload.correct_answer, - payload.is_correct, - payload.session_id, - payload.attempt_id, - payload.question_index, - payload.session_size, - payload.elapsed_seconds, - payload.question_elapsed_seconds, - payload.answered_at, - new Date().toISOString(), - payload.selected_display_index, - payload.correct_display_index, - payload.selected_choice_text, - payload.correct_choice_text, - payload.choice_order.length > 0 ? JSON.stringify(payload.choice_order) : '', - ]); -} - -function appendPerfectScoreRow_(sheet, payload) { - sheet.appendRow([ - payload.nickname, - payload.elapsed_seconds, - payload.correct_count, - payload.completed_at, - payload.mode, - payload.session_id, - payload.attempt_id, - new Date().toISOString(), - ]); -} - -function rebuildPerfectScoreSheet_(sheet) { - const rankedRows = buildPerfectScoreRows_(loadPerfectScoreRows_(sheet)); - rewriteSheet_(sheet, PERFECT_SCORE_HEADERS, rankedRows); -} - -function rebuildAnalytics_(logSheet, summarySheet, attemptsSheet) { - const rows = loadLogRows_(logSheet); - - const summaryRows = buildSummaryRows_(rows); - rewriteSheet_(summarySheet, SUMMARY_HEADERS, summaryRows); - if (summaryRows.length > 0) { - summarySheet.getRange(2, 5, summaryRows.length, 1).setNumberFormat('0.0%'); - summarySheet.getRange(2, 6, summaryRows.length, 2).setNumberFormat('0.0'); - summarySheet.getRange(2, 10, summaryRows.length, 1).setNumberFormat('0.0%'); - summarySheet.getRange(2, 11, summaryRows.length, 2).setNumberFormat('0.0'); - } - - const attemptRows = buildAttemptRows_(rows); - rewriteSheet_(attemptsSheet, ATTEMPT_HEADERS, attemptRows); - if (attemptRows.length > 0) { - attemptsSheet.getRange(2, 9, attemptRows.length, 2).setNumberFormat('0.0'); - } -} - -function rewriteSheet_(sheet, headers, rows) { - sheet.clearContents(); - sheet.getRange(1, 1, 1, headers.length).setValues([headers]); - sheet.getRange(1, 1, 1, headers.length).setFontWeight('bold'); - sheet.setFrozenRows(1); - - if (rows.length === 0) { - return; - } - - sheet.getRange(2, 1, rows.length, headers.length).setValues(rows); -} - -function loadLogRows_(sheet) { - const lastRow = sheet.getLastRow(); - if (lastRow < 2) { - return []; - } - return sheet.getRange(2, 1, lastRow - 1, LOG_HEADERS.length).getValues(); -} - -function loadPerfectScoreRows_(sheet) { - const lastRow = sheet.getLastRow(); - if (lastRow < 2) { - return []; - } - return sheet.getRange(2, 1, lastRow - 1, PERFECT_SCORE_HEADERS.length).getValues(); -} - -function buildPerfectScoreRows_(rows) { - return rows - .map((row, rowIndex) => ({ - row: normalizePerfectScoreRow_(row), - rowIndex, - })) - .sort((left, right) => { - const byMode = comparePerfectScoreModes_(left.row[4], right.row[4]); - if (byMode !== 0) { - return byMode; - } - - const byCorrectCount = compareNumbersDesc_( - left.row[2], - right.row[2], - ); - if (byCorrectCount !== 0) { - return byCorrectCount; - } - - const byElapsedSeconds = compareNumbersAsc_( - left.row[1], - right.row[1], - ); - if (byElapsedSeconds !== 0) { - return byElapsedSeconds; - } - - const byCompletedAt = compareStringsAsc_(left.row[3], right.row[3]); - if (byCompletedAt !== 0) { - return byCompletedAt; - } - - const byReceivedAt = compareStringsAsc_(left.row[7], right.row[7]); - if (byReceivedAt !== 0) { - return byReceivedAt; - } - - return left.rowIndex - right.rowIndex; - }) - .map(entry => entry.row); -} - -function buildSummaryRows_(rows) { - const stats = new Map(); - - rows.forEach((row, rowIndex) => { - const questionID = normalizeString_(row[LOG_COL.QUESTION_ID]); - if (!questionID) { - return; - } - - const responderKey = getResponderKey_(row, rowIndex); - let stat = stats.get(questionID); - if (!stat) { - stat = { - title: '', - firstByResponder: new Map(), - finalByResponder: new Map(), - }; - stats.set(questionID, stat); - } - - const questionTitle = normalizeString_(row[LOG_COL.QUESTION_TITLE]); - if (!stat.title && questionTitle) { - stat.title = questionTitle; - } - - const event = { - isCorrect: row[LOG_COL.IS_CORRECT] === true, - elapsedSeconds: normalizeNonNegativeNumber_(row[LOG_COL.ELAPSED_SECONDS]), - questionElapsedSeconds: normalizeNonNegativeNumber_(row[LOG_COL.QUESTION_ELAPSED_SECONDS]), - }; - - if (!stat.firstByResponder.has(responderKey)) { - stat.firstByResponder.set(responderKey, event); - } - stat.finalByResponder.set(responderKey, event); - }); - - return Array.from(stats.entries()) - .sort((a, b) => a[0].localeCompare(b[0])) - .map(([questionID, stat]) => { - const firstEvents = Array.from(stat.firstByResponder.values()); - const finalEvents = Array.from(stat.finalByResponder.values()); - - return [ - questionID, - stat.title, - firstEvents.length, - countCorrect_(firstEvents), - rate_(countCorrect_(firstEvents), firstEvents.length), - averageNumbers_(firstEvents.map(event => event.questionElapsedSeconds)), - averageNumbers_(firstEvents.map(event => event.elapsedSeconds)), - finalEvents.length, - countCorrect_(finalEvents), - rate_(countCorrect_(finalEvents), finalEvents.length), - averageNumbers_(finalEvents.map(event => event.questionElapsedSeconds)), - averageNumbers_(finalEvents.map(event => event.elapsedSeconds)), - ]; - }); -} - -function buildAttemptRows_(rows) { - const attempts = new Map(); - - rows.forEach((row, rowIndex) => { - const attemptKey = getAttemptKey_(row, rowIndex); - let attempt = attempts.get(attemptKey); - if (!attempt) { - attempt = { - sessionID: '', - mode: 'normal', - answersSubmitted: 0, - questionIDs: new Set(), - expectedQuestions: '', - lastQuestionIndex: '', - totalElapsedSeconds: '', - questionElapsedTotal: 0, - questionElapsedCount: 0, - firstAnsweredAt: '', - lastAnsweredAt: '', - }; - attempts.set(attemptKey, attempt); - } - - const sessionID = normalizeString_(row[LOG_COL.SESSION_ID]); - if (!attempt.sessionID && sessionID) { - attempt.sessionID = sessionID; - } - - attempt.mode = normalizeMode_(row[LOG_COL.MODE]); - attempt.answersSubmitted += 1; - - const questionID = normalizeString_(row[LOG_COL.QUESTION_ID]); - if (questionID) { - attempt.questionIDs.add(questionID); - } - - const sessionSize = normalizePositiveNumber_(row[LOG_COL.SESSION_SIZE]); - if (sessionSize !== '') { - attempt.expectedQuestions = sessionSize; - } - - const questionIndex = normalizePositiveNumber_(row[LOG_COL.QUESTION_INDEX]); - if (questionIndex !== '' && (attempt.lastQuestionIndex === '' || questionIndex > attempt.lastQuestionIndex)) { - attempt.lastQuestionIndex = questionIndex; - } - - const elapsedSeconds = normalizeNonNegativeNumber_(row[LOG_COL.ELAPSED_SECONDS]); - if (elapsedSeconds !== '' && (attempt.totalElapsedSeconds === '' || elapsedSeconds > attempt.totalElapsedSeconds)) { - attempt.totalElapsedSeconds = elapsedSeconds; - } - - const questionElapsedSeconds = normalizeNonNegativeNumber_(row[LOG_COL.QUESTION_ELAPSED_SECONDS]); - if (questionElapsedSeconds !== '') { - attempt.questionElapsedTotal += questionElapsedSeconds; - attempt.questionElapsedCount += 1; - } - - const answeredAt = normalizeString_(row[LOG_COL.ANSWERED_AT]); - if (answeredAt) { - if (!attempt.firstAnsweredAt) { - attempt.firstAnsweredAt = answeredAt; - } - attempt.lastAnsweredAt = answeredAt; - } - }); - - return Array.from(attempts.entries()) - .sort((a, b) => compareAttemptRows_(a[1], b[1], a[0], b[0])) - .map(([attemptID, attempt]) => { - const uniqueQuestions = attempt.questionIDs.size; - const expectedQuestions = attempt.expectedQuestions; - const completed = expectedQuestions === '' ? '' : uniqueQuestions >= expectedQuestions; - - return [ - attemptID, - attempt.sessionID, - attempt.mode, - attempt.answersSubmitted, - uniqueQuestions, - expectedQuestions, - completed, - attempt.lastQuestionIndex, - attempt.totalElapsedSeconds, - attempt.questionElapsedCount === 0 ? '' : attempt.questionElapsedTotal / attempt.questionElapsedCount, - attempt.firstAnsweredAt, - attempt.lastAnsweredAt, - ]; - }); -} - -function getResponderKey_(row, rowIndex) { - const sessionID = normalizeString_(row[LOG_COL.SESSION_ID]); - if (sessionID) { - return sessionID; - } - - const attemptID = normalizeString_(row[LOG_COL.ATTEMPT_ID]); - if (attemptID) { - return attemptID; - } - - return `legacy-row-${rowIndex}`; -} - -function getAttemptKey_(row, rowIndex) { - const attemptID = normalizeString_(row[LOG_COL.ATTEMPT_ID]); - if (attemptID) { - return attemptID; - } - - const sessionID = normalizeString_(row[LOG_COL.SESSION_ID]); - if (sessionID) { - return sessionID; - } - - return `legacy-attempt-${rowIndex}`; -} - -function compareAttemptRows_(left, right, leftKey, rightKey) { - if (left.firstAnsweredAt && right.firstAnsweredAt && left.firstAnsweredAt !== right.firstAnsweredAt) { - return left.firstAnsweredAt.localeCompare(right.firstAnsweredAt); - } - if (left.firstAnsweredAt && !right.firstAnsweredAt) { - return -1; - } - if (!left.firstAnsweredAt && right.firstAnsweredAt) { - return 1; - } - return leftKey.localeCompare(rightKey); -} - -function countCorrect_(events) { - let total = 0; - events.forEach(event => { - if (event.isCorrect) { - total += 1; - } - }); - return total; -} - -function averageNumbers_(values) { - let total = 0; - let count = 0; - - values.forEach(value => { - if (value !== '') { - total += value; - count += 1; - } - }); - - if (count === 0) { - return ''; - } - - return total / count; -} - -function rate_(numerator, denominator) { - if (!denominator) { - return 0; - } - return numerator / denominator; -} - -function normalizePerfectScoreRow_(row) { - const nickname = normalizeString_(row[0]); - const elapsedSeconds = normalizeNonNegativeNumber_(row[1]); - const maybeCorrectCount = normalizeNonNegativeNumber_(row[2]); - const maybeLegacyMode = normalizeString_(row[3]); - if (maybeCorrectCount === '' && (maybeLegacyMode === 'normal' || maybeLegacyMode === 'extra' || maybeLegacyMode === 'challenge')) { - return [ - nickname, - elapsedSeconds, - '', - normalizeString_(row[2]), - normalizeMode_(row[3]), - normalizeString_(row[4]), - normalizeString_(row[5]), - normalizeString_(row[6]), - ]; - } - - return [ - nickname, - elapsedSeconds, - maybeCorrectCount, - normalizeString_(row[3]), - normalizeMode_(row[4]), - normalizeString_(row[5]), - normalizeString_(row[6]), - normalizeString_(row[7]), - ]; -} - -function compareNumbersAsc_(left, right) { - const leftHasValue = left !== ''; - const rightHasValue = right !== ''; - if (leftHasValue && !rightHasValue) { - return -1; - } - if (!leftHasValue && rightHasValue) { - return 1; - } - if (!leftHasValue && !rightHasValue) { - return 0; - } - if (left === right) { - return 0; - } - return left < right ? -1 : 1; -} - -function compareNumbersDesc_(left, right) { - const leftHasValue = left !== ''; - const rightHasValue = right !== ''; - if (leftHasValue && !rightHasValue) { - return -1; - } - if (!leftHasValue && rightHasValue) { - return 1; - } - if (!leftHasValue && !rightHasValue) { - return 0; - } - if (left === right) { - return 0; - } - return left > right ? -1 : 1; -} - -function compareStringsAsc_(left, right) { - const leftValue = normalizeString_(left); - const rightValue = normalizeString_(right); - if (leftValue && !rightValue) { - return -1; - } - if (!leftValue && rightValue) { - return 1; - } - if (!leftValue && !rightValue) { - return 0; - } - if (leftValue === rightValue) { - return 0; - } - return leftValue < rightValue ? -1 : 1; -} - -function comparePerfectScoreModes_(left, right) { - const rank = mode => (normalizeMode_(mode) === 'extra' ? 0 : 1); - const leftRank = rank(left); - const rightRank = rank(right); - if (leftRank === rightRank) { - return 0; - } - return leftRank < rightRank ? -1 : 1; -} - -function normalizeString_(value) { - return typeof value === 'string' ? value.trim() : ''; -} - -function normalizeNonNegativeNumber_(value) { - if (!isFiniteNumber_(value) || value < 0) { - return ''; - } - return value; -} - -function normalizePositiveNumber_(value) { - if (!isFiniteNumber_(value) || value <= 0) { - return ''; - } - return value; -} - -function normalizeIndexArray_(value) { - if (value === undefined || value === null || value === '') { - return []; - } - if (!Array.isArray(value)) { - throw new Error('choice_order must be an array'); - } - return value.map((item, index) => { - const normalized = normalizeNonNegativeNumber_(item); - if (normalized === '') { - throw new Error(`choice_order[${index}] must be a non-negative number`); - } - return normalized; - }); -} - -function isFiniteNumber_(value) { - return typeof value === 'number' && isFinite(value); -} - -function normalizeMode_(value) { - if (value === 'extra' || value === 'challenge') { - return 'extra'; - } - return 'normal'; -} - -function jsonResponse_(statusCode, body) { - return ContentService - .createTextOutput(JSON.stringify(body)) - .setMimeType(ContentService.MimeType.JSON); -} diff --git a/quiz-app/go.mod b/quiz-app/go.mod deleted file mode 100644 index 05ec09f..0000000 --- a/quiz-app/go.mod +++ /dev/null @@ -1,5 +0,0 @@ -module github.com/GoCon/2026-codelab/quiz-app - -go 1.25.9 - -require gopkg.in/yaml.v3 v3.0.1 diff --git a/quiz-app/go.sum b/quiz-app/go.sum deleted file mode 100644 index a62c313..0000000 --- a/quiz-app/go.sum +++ /dev/null @@ -1,4 +0,0 @@ -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= -gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/quiz-app/index.html b/quiz-app/index.html new file mode 100644 index 0000000..df5a865 --- /dev/null +++ b/quiz-app/index.html @@ -0,0 +1,20 @@ + + + + + + + + + + + quiz-app | Go Sprint + + +
+ + + diff --git a/quiz-app/internal/quizdata/quizdata.go b/quiz-app/internal/quizdata/quizdata.go deleted file mode 100644 index 7f301e2..0000000 --- a/quiz-app/internal/quizdata/quizdata.go +++ /dev/null @@ -1,115 +0,0 @@ -package quizdata - -import ( - "encoding/json" - "fmt" - "os" - "path/filepath" - "strings" - - "github.com/GoCon/2026-codelab/quiz-app/internal/quizhandler" -) - -// StaticQuiz is the full quiz payload embedded into the static GitHub Pages build. -type StaticQuiz struct { - ID string `json:"id"` - Title string `json:"title"` - TitleEn string `json:"title_en,omitempty"` - Text string `json:"text"` - TextEn string `json:"text_en,omitempty"` - Mode string `json:"mode,omitempty"` - Choices []string `json:"choices"` - ChoicesEn []string `json:"choices_en,omitempty"` - Answer int `json:"answer"` - Explanation string `json:"explanation"` - ExplanationEn string `json:"explanation_en,omitempty"` - QuestionCode string `json:"question_code,omitempty"` - AnswerCode string `json:"answer_code,omitempty"` - AnswerCodePlayRef string `json:"answer_code_play_ref,omitempty"` -} - -// LoadFromBase reads quizzes and any referenced code files from the given base directory. -func LoadFromBase(basePath string) ([]quizhandler.Quiz, map[string]string, error) { - quizesYAML, err := os.ReadFile(filepath.Join(basePath, "quizes.yaml")) - if err != nil { - return nil, nil, fmt.Errorf("quizes.yaml の読み込みに失敗しました: %w", err) - } - quizzes, err := quizhandler.ParseQuizzes(quizesYAML) - if err != nil { - return nil, nil, fmt.Errorf("quizes.yaml のパースに失敗しました: %w", err) - } - codeFiles, err := LoadCodeFiles(quizzes, basePath) - if err != nil { - return nil, nil, err - } - return quizzes, codeFiles, nil -} - -// LoadCodeFiles loads every referenced question/answer code file from the base directory. -func LoadCodeFiles(quizzes []quizhandler.Quiz, basePath string) (map[string]string, error) { - files := make(map[string]string) - for _, q := range quizzes { - for _, ref := range []string{q.QuestionCodeRef, q.AnswerCodeRef} { - if ref == "" || files[ref] != "" { - continue - } - data, err := os.ReadFile(filepath.Join(basePath, ref)) - if err != nil { - return nil, fmt.Errorf("コードファイルの読み込みに失敗しました %s: %w", ref, err) - } - files[ref] = StripBuildIgnore(string(data)) - } - } - return files, nil -} - -// BuildStaticQuizzes expands quiz metadata and referenced code into a static payload. -func BuildStaticQuizzes(quizzes []quizhandler.Quiz, codeFiles map[string]string) []StaticQuiz { - staticQuizzes := make([]StaticQuiz, 0, len(quizzes)) - for _, q := range quizzes { - mode := q.Mode - if mode == quizhandler.QuizModeBoth { - mode = "" - } - staticQuiz := StaticQuiz{ - ID: q.ID, - Title: q.Title, - TitleEn: q.TitleEn, - Text: q.Text, - TextEn: q.TextEn, - Mode: mode, - Choices: append([]string(nil), q.Choices...), - ChoicesEn: append([]string(nil), q.ChoicesEn...), - Answer: q.Answer, - Explanation: q.Explanation, - ExplanationEn: q.ExplanationEn, - AnswerCodePlayRef: q.AnswerCodePlayRef, - } - if q.QuestionCodeRef != "" { - staticQuiz.QuestionCode = codeFiles[q.QuestionCodeRef] - } - if q.AnswerCodeRef != "" { - staticQuiz.AnswerCode = codeFiles[q.AnswerCodeRef] - } - staticQuizzes = append(staticQuizzes, staticQuiz) - } - return staticQuizzes -} - -// MarshalJavaScript serializes quiz data as a browser-ready JavaScript asset. -func MarshalJavaScript(quizzes []StaticQuiz) ([]byte, error) { - data, err := json.Marshal(quizzes) - if err != nil { - return nil, fmt.Errorf("クイズデータのシリアライズに失敗しました: %w", err) - } - return []byte("window.__QUIZ_DATA__ = " + string(data) + ";\n"), nil -} - -// StripBuildIgnore removes the leading //go:build ignore directive from code snippets. -func StripBuildIgnore(s string) string { - const directive = "//go:build ignore" - if !strings.HasPrefix(s, directive) { - return s - } - return strings.TrimLeft(strings.TrimPrefix(s, directive), "\r\n") -} diff --git a/quiz-app/internal/quizdata/quizdata_test.go b/quiz-app/internal/quizdata/quizdata_test.go deleted file mode 100644 index 500f55e..0000000 --- a/quiz-app/internal/quizdata/quizdata_test.go +++ /dev/null @@ -1,113 +0,0 @@ -package quizdata_test - -import ( - "os" - "path/filepath" - "strings" - "testing" - - "github.com/GoCon/2026-codelab/quiz-app/internal/quizdata" - "github.com/GoCon/2026-codelab/quiz-app/internal/quizhandler" -) - -func TestStripBuildIgnore(t *testing.T) { - got := quizdata.StripBuildIgnore("//go:build ignore\n\npackage main\n") - if got != "package main\n" { - t.Fatalf("got %q", got) - } -} - -func TestBuildStaticQuizzes(t *testing.T) { - quizzes := []quizhandler.Quiz{{ - ID: "q1", - Title: "title", - TitleEn: "title en", - Text: "text", - TextEn: "text en", - Mode: quizhandler.QuizModeExtra, - Choices: []string{"a", "b"}, - ChoicesEn: []string{"a en", "b en"}, - Answer: 1, - Explanation: "exp", - ExplanationEn: "exp en", - QuestionCodeRef: "code/q1_question.go", - AnswerCodeRef: "code/q1_answer.go", - AnswerCodePlayRef: "https://go.dev/play/p/example", - }} - codeFiles := map[string]string{ - "code/q1_question.go": "package main\n// question", - "code/q1_answer.go": "package main\n// answer", - } - - got := quizdata.BuildStaticQuizzes(quizzes, codeFiles) - if len(got) != 1 { - t.Fatalf("len = %d, want 1", len(got)) - } - if got[0].QuestionCode != codeFiles["code/q1_question.go"] { - t.Fatalf("question code mismatch") - } - if got[0].AnswerCode != codeFiles["code/q1_answer.go"] { - t.Fatalf("answer code mismatch") - } - if got[0].Mode != quizhandler.QuizModeExtra { - t.Fatalf("mode = %q, want %q", got[0].Mode, quizhandler.QuizModeExtra) - } - if got[0].TitleEn != "title en" || got[0].TextEn != "text en" || got[0].ExplanationEn != "exp en" { - t.Fatalf("english localized fields should be preserved: %+v", got[0]) - } - if strings.Join(got[0].ChoicesEn, ",") != "a en,b en" { - t.Fatalf("english choices mismatch: %#v", got[0].ChoicesEn) - } - if got[0].AnswerCodePlayRef == "" { - t.Fatalf("answer code play ref should be preserved") - } -} - -func TestLoadFromBase(t *testing.T) { - dir := t.TempDir() - if err := os.WriteFile(filepath.Join(dir, "quizes.yaml"), []byte(` -- id: "q1" - title: "Q1" - title_en: "Question 1" - text: "text" - text_en: "text en" - question_code_ref: "code/q1_question.go" - choices: ["A", "B"] - choices_en: ["A en", "B en"] - answer: 1 - explanation: "exp" - explanation_en: "exp en" - answer_code_ref: "code/q1_answer.go" -`), 0o644); err != nil { - t.Fatal(err) - } - if err := os.MkdirAll(filepath.Join(dir, "code"), 0o755); err != nil { - t.Fatal(err) - } - if err := os.WriteFile(filepath.Join(dir, "code", "q1_question.go"), []byte("//go:build ignore\n\npackage main\n"), 0o644); err != nil { - t.Fatal(err) - } - if err := os.WriteFile(filepath.Join(dir, "code", "q1_answer.go"), []byte("package main\n"), 0o644); err != nil { - t.Fatal(err) - } - - quizzes, codeFiles, err := quizdata.LoadFromBase(dir) - if err != nil { - t.Fatalf("LoadFromBase: %v", err) - } - if len(quizzes) != 1 { - t.Fatalf("quizzes len = %d, want 1", len(quizzes)) - } - if quizzes[0].Mode != quizhandler.QuizModeBoth { - t.Fatalf("mode = %q, want %q", quizzes[0].Mode, quizhandler.QuizModeBoth) - } - if quizzes[0].TitleEn != "Question 1" || quizzes[0].TextEn != "text en" || quizzes[0].ExplanationEn != "exp en" { - t.Fatalf("localized english fields should be loaded: %+v", quizzes[0]) - } - if strings.Join(quizzes[0].ChoicesEn, ",") != "A en,B en" { - t.Fatalf("english choices mismatch: %#v", quizzes[0].ChoicesEn) - } - if got := codeFiles["code/q1_question.go"]; strings.HasPrefix(got, "//go:build ignore") { - t.Fatalf("question code should strip build tag") - } -} diff --git a/quiz-app/internal/quizhandler/handler.go b/quiz-app/internal/quizhandler/handler.go deleted file mode 100644 index e87c349..0000000 --- a/quiz-app/internal/quizhandler/handler.go +++ /dev/null @@ -1,105 +0,0 @@ -package quizhandler - -import ( - "fmt" - "strings" - - "gopkg.in/yaml.v3" -) - -const ( - QuizModeBoth = "both" - QuizModeNormal = "normal" - QuizModeExtra = "extra" -) - -// Quiz represents a quiz question loaded from quizes.yaml. -type Quiz struct { - ID string `yaml:"id"` - Title string `yaml:"title"` - TitleEn string `yaml:"title_en"` - Text string `yaml:"text"` - TextEn string `yaml:"text_en"` - Mode string `yaml:"mode"` - Choices []string `yaml:"choices"` - ChoicesEn []string `yaml:"choices_en"` - Answer int `yaml:"answer"` - Explanation string `yaml:"explanation"` - ExplanationEn string `yaml:"explanation_en"` - QuestionCodeRef string `yaml:"question_code_ref"` - AnswerCodeRef string `yaml:"answer_code_ref"` - AnswerCodePlayRef string `yaml:"answer_code_play_ref"` -} - -// ParseQuizzes unmarshals YAML quiz data. -func ParseQuizzes(data []byte) ([]Quiz, error) { - var quizzes []Quiz - if err := yaml.Unmarshal(data, &quizzes); err != nil { - return nil, err - } - for i := range quizzes { - mode, err := NormalizeQuizMode(quizzes[i].Mode) - if err != nil { - label := quizzes[i].ID - if label == "" { - label = fmt.Sprintf("#%d", i+1) - } - return nil, fmt.Errorf("quiz %s: %w", label, err) - } - quizzes[i].Mode = mode - if err := normalizeLocalizedFields(&quizzes[i]); err != nil { - label := quizzes[i].ID - if label == "" { - label = fmt.Sprintf("#%d", i+1) - } - return nil, fmt.Errorf("quiz %s: %w", label, err) - } - } - return quizzes, nil -} - -func normalizeLocalizedFields(quiz *Quiz) error { - quiz.TitleEn = defaultLocalizedText(quiz.TitleEn, quiz.Title) - quiz.TextEn = defaultLocalizedText(quiz.TextEn, quiz.Text) - quiz.ExplanationEn = defaultLocalizedText(quiz.ExplanationEn, quiz.Explanation) - - switch { - case len(quiz.ChoicesEn) == 0: - quiz.ChoicesEn = append([]string(nil), quiz.Choices...) - case len(quiz.ChoicesEn) != len(quiz.Choices): - return fmt.Errorf("choices_en count %d does not match choices count %d", len(quiz.ChoicesEn), len(quiz.Choices)) - default: - normalizedChoicesEn := append([]string(nil), quiz.ChoicesEn...) - for i := range normalizedChoicesEn { - if strings.TrimSpace(normalizedChoicesEn[i]) == "" { - normalizedChoicesEn[i] = quiz.Choices[i] - } - } - quiz.ChoicesEn = normalizedChoicesEn - } - - return nil -} - -func defaultLocalizedText(value, fallback string) string { - if strings.TrimSpace(value) == "" { - return fallback - } - return value -} - -// NormalizeQuizMode normalizes quiz pool metadata from YAML/static data. -// Empty values default to "both" so legacy quiz definitions stay available in -// both normal and extra sessions until authors opt into pool-specific behavior. -func NormalizeQuizMode(mode string) (string, error) { - switch strings.ToLower(strings.TrimSpace(mode)) { - case "", QuizModeBoth: - return QuizModeBoth, nil - case QuizModeNormal: - return QuizModeNormal, nil - case QuizModeExtra: - return QuizModeExtra, nil - default: - return "", fmt.Errorf("unsupported mode %q", mode) - } -} diff --git a/quiz-app/internal/quizhandler/handler_test.go b/quiz-app/internal/quizhandler/handler_test.go deleted file mode 100644 index 446fd08..0000000 --- a/quiz-app/internal/quizhandler/handler_test.go +++ /dev/null @@ -1,243 +0,0 @@ -package quizhandler_test - -import ( - "os" - "path/filepath" - "strings" - "testing" - - "github.com/GoCon/2026-codelab/quiz-app/internal/quizhandler" -) - -func TestNormalizeQuizMode(t *testing.T) { - t.Parallel() - - tests := []struct { - name string - input string - want string - wantErr bool - }{ - {name: "empty defaults to both", input: "", want: quizhandler.QuizModeBoth}, - {name: "both is preserved", input: "both", want: quizhandler.QuizModeBoth}, - {name: "normal is preserved", input: "normal", want: quizhandler.QuizModeNormal}, - {name: "extra is preserved", input: "extra", want: quizhandler.QuizModeExtra}, - {name: "whitespace is trimmed", input: " EXTRA ", want: quizhandler.QuizModeExtra}, - {name: "unknown value errors", input: "surprise", wantErr: true}, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - t.Parallel() - - got, err := quizhandler.NormalizeQuizMode(tt.input) - if tt.wantErr { - if err == nil { - t.Fatal("expected error") - } - return - } - if err != nil { - t.Fatalf("NormalizeQuizMode: %v", err) - } - if got != tt.want { - t.Fatalf("got %q, want %q", got, tt.want) - } - }) - } -} - -func TestParseQuizzes_DefaultModeIsBoth(t *testing.T) { - t.Parallel() - - quizzes, err := quizhandler.ParseQuizzes([]byte(` -- id: "q1" - title: "Q1" - text: "text" - choices: ["A", "B"] - answer: 0 - explanation: "exp" -`)) - if err != nil { - t.Fatalf("ParseQuizzes: %v", err) - } - if len(quizzes) != 1 { - t.Fatalf("len = %d, want 1", len(quizzes)) - } - if quizzes[0].Mode != quizhandler.QuizModeBoth { - t.Fatalf("mode = %q, want %q", quizzes[0].Mode, quizhandler.QuizModeBoth) - } - if quizzes[0].TitleEn != quizzes[0].Title { - t.Fatalf("title_en = %q, want fallback %q", quizzes[0].TitleEn, quizzes[0].Title) - } - if quizzes[0].TextEn != quizzes[0].Text { - t.Fatalf("text_en = %q, want fallback %q", quizzes[0].TextEn, quizzes[0].Text) - } - if quizzes[0].ExplanationEn != quizzes[0].Explanation { - t.Fatalf("explanation_en = %q, want fallback %q", quizzes[0].ExplanationEn, quizzes[0].Explanation) - } - if strings.Join(quizzes[0].ChoicesEn, ",") != strings.Join(quizzes[0].Choices, ",") { - t.Fatalf("choices_en = %#v, want fallback %#v", quizzes[0].ChoicesEn, quizzes[0].Choices) - } -} - -func TestParseQuizzes_InvalidModeReturnsError(t *testing.T) { - t.Parallel() - - _, err := quizhandler.ParseQuizzes([]byte(` -- id: "q1" - title: "Q1" - text: "text" - mode: "surprise" - choices: ["A", "B"] - answer: 0 - explanation: "exp" -`)) - if err == nil { - t.Fatal("expected invalid mode error") - } -} - -func TestParseQuizzes_ChoiceLocalizationLengthMustMatch(t *testing.T) { - t.Parallel() - - _, err := quizhandler.ParseQuizzes([]byte(` -- id: "q1" - title: "Q1" - text: "text" - choices: ["A", "B"] - choices_en: ["A only"] - answer: 0 - explanation: "exp" -`)) - if err == nil { - t.Fatal("expected choices_en length error") - } -} - -func TestParseQuizzes_EnglishFallbacksAndPartialChoices(t *testing.T) { - t.Parallel() - - quizzes, err := quizhandler.ParseQuizzes([]byte(` -- id: "q1" - title: "Q1" - title_en: "Question 1" - text: "text" - choices: ["A", "B"] - choices_en: ["A en", ""] - answer: 0 - explanation: "exp" -`)) - if err != nil { - t.Fatalf("ParseQuizzes: %v", err) - } - if len(quizzes) != 1 { - t.Fatalf("len = %d, want 1", len(quizzes)) - } - if quizzes[0].TitleEn != "Question 1" { - t.Fatalf("title_en = %q, want Question 1", quizzes[0].TitleEn) - } - if quizzes[0].TextEn != "text" { - t.Fatalf("text_en = %q, want fallback text", quizzes[0].TextEn) - } - if quizzes[0].ExplanationEn != "exp" { - t.Fatalf("explanation_en = %q, want fallback exp", quizzes[0].ExplanationEn) - } - if strings.Join(quizzes[0].ChoicesEn, ",") != "A en,B" { - t.Fatalf("choices_en = %#v, want fallback on empty values", quizzes[0].ChoicesEn) - } -} - -func loadRealQuizzes(t *testing.T) []quizhandler.Quiz { - t.Helper() - - data, err := os.ReadFile("../../quizzes/quizes.yaml") - if err != nil { - t.Fatalf("quizes.yaml を読み込めません: %v", err) - } - quizzes, err := quizhandler.ParseQuizzes(data) - if err != nil { - t.Fatalf("quizes.yaml のパースに失敗しました: %v", err) - } - return quizzes -} - -func TestQuizesYAML_UniqueIDs(t *testing.T) { - quizzes := loadRealQuizzes(t) - seen := make(map[string]int) - for _, q := range quizzes { - seen[q.ID]++ - } - for id, count := range seen { - if count > 1 { - t.Errorf("ID %q が %d 回重複しています", id, count) - } - } -} - -func TestQuizesYAML_NoEmptyIDs(t *testing.T) { - for _, q := range loadRealQuizzes(t) { - if q.ID == "" { - t.Errorf("空の ID を持つクイズがあります: %+v", q) - } - } -} - -func TestQuizesYAML_IDsMatchEnglishTitles(t *testing.T) { - for _, q := range loadRealQuizzes(t) { - if q.ID != q.TitleEn { - t.Errorf("quiz title_en=%q: id=%q, want %q", q.TitleEn, q.ID, q.TitleEn) - } - } -} - -func TestQuizesYAML_AnswerIndexInRange(t *testing.T) { - for _, q := range loadRealQuizzes(t) { - if q.Answer < 0 || q.Answer >= len(q.Choices) { - t.Errorf("quiz %q: answer=%d は choices の範囲外 (len=%d)", q.ID, q.Answer, len(q.Choices)) - } - } -} - -func TestQuizesYAML_SufficientChoices(t *testing.T) { - for _, q := range loadRealQuizzes(t) { - if len(q.Choices) < 2 { - t.Errorf("quiz %q: choices が %d 個しかありません(最低 2 個必要)", q.ID, len(q.Choices)) - } - } -} - -func TestQuizesYAML_EnglishChoicesMatchJapaneseCount(t *testing.T) { - for _, q := range loadRealQuizzes(t) { - if len(q.ChoicesEn) != len(q.Choices) { - t.Errorf("quiz %q: choices_en が %d 個で choices の %d 個と一致しません", q.ID, len(q.ChoicesEn), len(q.Choices)) - } - } -} - -func TestQuizesYAML_NoEmptyExplanations(t *testing.T) { - for _, q := range loadRealQuizzes(t) { - if strings.TrimSpace(q.Explanation) == "" { - t.Errorf("quiz %q: explanation が空です", q.ID) - } - } -} - -func TestQuizesYAML_CodeRefsExist(t *testing.T) { - for _, q := range loadRealQuizzes(t) { - for _, ref := range []struct { - name string - path string - }{ - {name: "question_code_ref", path: q.QuestionCodeRef}, - {name: "answer_code_ref", path: q.AnswerCodeRef}, - } { - if ref.path == "" { - continue - } - if _, err := os.Stat(filepath.Join("../../quizzes", ref.path)); err != nil { - t.Errorf("quiz %q: %s=%q の参照先が存在しません: %v", q.ID, ref.name, ref.path, err) - } - } - } -} diff --git a/quiz-app/package-lock.json b/quiz-app/package-lock.json new file mode 100644 index 0000000..3886358 --- /dev/null +++ b/quiz-app/package-lock.json @@ -0,0 +1,3245 @@ +{ + "name": "quiz-app", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "quiz-app", + "version": "0.1.0", + "dependencies": { + "vue": "^3.5.34" + }, + "devDependencies": { + "@tailwindcss/vite": "^4.3.0", + "@types/node": "^24.12.3", + "@vitejs/plugin-vue": "^6.0.6", + "@vue/test-utils": "^2.4.10", + "@vue/tsconfig": "^0.9.1", + "jsdom": "^29.1.1", + "smol-toml": "^1.6.1", + "tailwindcss": "^4.3.0", + "typescript": "~6.0.2", + "vite": "^8.0.12", + "vitest": "^4.1.7", + "vue-tsc": "^3.2.8" + } + }, + "node_modules/@asamuzakjp/css-color": { + "version": "5.1.11", + "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-5.1.11.tgz", + "integrity": "sha512-KVw6qIiCTUQhByfTd78h2yD1/00waTmm9uy/R7Ck/ctUyAPj+AEDLkQIdJW0T8+qGgj3j5bpNKK7Q3G+LedJWg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@asamuzakjp/generational-cache": "^1.0.1", + "@csstools/css-calc": "^3.2.0", + "@csstools/css-color-parser": "^4.1.0", + "@csstools/css-parser-algorithms": "^4.0.0", + "@csstools/css-tokenizer": "^4.0.0" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + } + }, + "node_modules/@asamuzakjp/dom-selector": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/@asamuzakjp/dom-selector/-/dom-selector-7.1.1.tgz", + "integrity": "sha512-67RZDnYRc8H/8MLDgQCDE//zoqVFwajkepHZgmXrbwybzXOEwOWGPYGmALYl9J2DOLfFPPs6kKCqmbzV895hTQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@asamuzakjp/generational-cache": "^1.0.1", + "@asamuzakjp/nwsapi": "^2.3.9", + "bidi-js": "^1.0.3", + "css-tree": "^3.2.1", + "is-potential-custom-element-name": "^1.0.1" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + } + }, + "node_modules/@asamuzakjp/generational-cache": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@asamuzakjp/generational-cache/-/generational-cache-1.0.1.tgz", + "integrity": "sha512-wajfB8KqzMCN2KGNFdLkReeHncd0AslUSrvHVvvYWuU8ghncRJoA50kT3zP9MVL0+9g4/67H+cdvBskj9THPzg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + } + }, + "node_modules/@asamuzakjp/nwsapi": { + "version": "2.3.9", + "resolved": "https://registry.npmjs.org/@asamuzakjp/nwsapi/-/nwsapi-2.3.9.tgz", + "integrity": "sha512-n8GuYSrI9bF7FFZ/SjhwevlHc8xaVlb/7HmHelnc/PZXBD2ZR49NnN9sMMuDdEGPeeRQ5d0hqlSlEpgCX3Wl0Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.3", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.3.tgz", + "integrity": "sha512-b3ctpQwp+PROvU/cttc4OYl4MzfJUWy6FZg+PMXfzmt/+39iHVF0sDfqay8TQM3JA2EUOyKcFZt75jWriQijsA==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.0" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", + "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@bramus/specificity": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/@bramus/specificity/-/specificity-2.4.2.tgz", + "integrity": "sha512-ctxtJ/eA+t+6q2++vj5j7FYX3nRu311q1wfYH3xjlLOsczhlhxAg2FWNUXhpGvAw3BWo1xBcvOV6/YLc2r5FJw==", + "dev": true, + "license": "MIT", + "dependencies": { + "css-tree": "^3.0.0" + }, + "bin": { + "specificity": "bin/cli.js" + } + }, + "node_modules/@csstools/color-helpers": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-6.0.2.tgz", + "integrity": "sha512-LMGQLS9EuADloEFkcTBR3BwV/CGHV7zyDxVRtVDTwdI2Ca4it0CCVTT9wCkxSgokjE5Ho41hEPgb8OEUwoXr6Q==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "engines": { + "node": ">=20.19.0" + } + }, + "node_modules/@csstools/css-calc": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-3.2.1.tgz", + "integrity": "sha512-DtdHlgXh5ZkA43cwBcAm+huzgJiwx3ZTWVjBs94kwz2xKqSimDA3lBgCjphYgwgVUMWatSM0pDd8TILB1yrVVg==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=20.19.0" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^4.0.0", + "@csstools/css-tokenizer": "^4.0.0" + } + }, + "node_modules/@csstools/css-color-parser": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-4.1.1.tgz", + "integrity": "sha512-eZ5XOtyhK+mggRafYUWzA0tvaYOFgdY8AkgQiCJF9qNAePnUo/zmsqqYubBBb3sQ8uNUaSKTY9s9klfRaAXL0g==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "dependencies": { + "@csstools/color-helpers": "^6.0.2", + "@csstools/css-calc": "^3.2.1" + }, + "engines": { + "node": ">=20.19.0" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^4.0.0", + "@csstools/css-tokenizer": "^4.0.0" + } + }, + "node_modules/@csstools/css-parser-algorithms": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-4.0.0.tgz", + "integrity": "sha512-+B87qS7fIG3L5h3qwJ/IFbjoVoOe/bpOdh9hAjXbvx0o8ImEmUsGXN0inFOnk2ChCFgqkkGFQ+TpM5rbhkKe4w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=20.19.0" + }, + "peerDependencies": { + "@csstools/css-tokenizer": "^4.0.0" + } + }, + "node_modules/@csstools/css-syntax-patches-for-csstree": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@csstools/css-syntax-patches-for-csstree/-/css-syntax-patches-for-csstree-1.1.4.tgz", + "integrity": "sha512-wgsqt92b7C7tQhIdPNxj0n9zuUbQlvAuI1exyzeNrOKOi62SD7ren8zqszmpVREjAOqg8cD2FqYhQfAuKjk4sw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "peerDependencies": { + "css-tree": "^3.2.1" + }, + "peerDependenciesMeta": { + "css-tree": { + "optional": true + } + } + }, + "node_modules/@csstools/css-tokenizer": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-4.0.0.tgz", + "integrity": "sha512-QxULHAm7cNu72w97JUNCBFODFaXpbDg+dP8b/oWFAZ2MTRppA3U00Y2L1HqaS4J6yBqxwa/Y3nMBaxVKbB/NsA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=20.19.0" + } + }, + "node_modules/@emnapi/core": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.10.0.tgz", + "integrity": "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/wasi-threads": "1.2.1", + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.10.0.tgz", + "integrity": "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/wasi-threads": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz", + "integrity": "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@exodus/bytes": { + "version": "1.15.1", + "resolved": "https://registry.npmjs.org/@exodus/bytes/-/bytes-1.15.1.tgz", + "integrity": "sha512-S6mL0yNB/Abt9Ei4tq8gDhcczc4S3+vQ4ra7vxnAf+YHC02srtqxKKZghx2Dq6p0e66THKwR6r8N6P95wEty7Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + }, + "peerDependencies": { + "@noble/hashes": "^1.8.0 || ^2.0.0" + }, + "peerDependenciesMeta": { + "@noble/hashes": { + "optional": true + } + } + }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@napi-rs/wasm-runtime": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.4.tgz", + "integrity": "sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@tybys/wasm-util": "^0.10.1" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + }, + "peerDependencies": { + "@emnapi/core": "^1.7.1", + "@emnapi/runtime": "^1.7.1" + } + }, + "node_modules/@one-ini/wasm": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@one-ini/wasm/-/wasm-0.1.1.tgz", + "integrity": "sha512-XuySG1E38YScSJoMlqovLru4KTUNSjgVTIjyh7qMX6aNN5HY5Ct5LhRJdxO79JtTzKfzV/bnWpz+zquYrISsvw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@oxc-project/types": { + "version": "0.132.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.132.0.tgz", + "integrity": "sha512-FESMOxil5Se014ui/Eq8fT5uHJo6nIRwH0PfJrZJXs6Gek3ZVFOrpUv3YIZT20m+extU98Hg1Ym72U58rlsxUQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/Boshen" + } + }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/@rolldown/binding-android-arm64": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.2.tgz", + "integrity": "sha512-ZS4D1JPGn/MYQN/SYDWftIE/nVsM8j/AFOYEzAoOE2O3NktQOZru+/vYXGbR/qtdLdIfGCP0lcoJiYVzsEz+iQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-arm64": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.2.tgz", + "integrity": "sha512-vdFA9+C/rekyGce7WqHs/xoT0ioZEWaOFyZLIV1mEeNFaFDUQrPIo8Vs2GvJ6eetb3rzDUtUBgzto3ExpXJB3w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-x64": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.2.tgz", + "integrity": "sha512-BewSOwTHazv77DTYiAZXSqqKZ4KP/KonFisDMVU7PImxoWfB2aepnPhd2E4SWz3zDzYgDNbs6jBmTdgNnF02GA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-freebsd-x64": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.2.tgz", + "integrity": "sha512-m41o7M0YWtUdqk61Tb+jnKb2rN++iRdIASlExkUoKfIAH30DOHCB8fVLzSUpbWHHU8esmEioY62PxzexE8MBuA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm-gnueabihf": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.2.tgz", + "integrity": "sha512-jcojB9H7W/jS29pMKWAK1N+fU99vXodHDTatS3b3y/XSOCiHo0kkA74pL3jJmkoQtYpOCxDvaKs1fo2Ij/1X5w==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-gnu": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.2.tgz", + "integrity": "sha512-1jn6qDU5iiOgFgygDzKUuKP0maTi0/f1+sBLgvij/76C77Nm3ts6ufz9Bjg5q5dduxiUIxtq86JIoBvo1xQ4Ig==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-musl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.2.tgz", + "integrity": "sha512-QVLO/czFMdoMFSqlX3bcswcJNm/23r+qoa/jgtmFc/qEp6/jXmIkDjF/XIo8dPfGaiwy1xfQn8o77L79GeXFgw==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-ppc64-gnu": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.2.tgz", + "integrity": "sha512-hgO5Abm0w5UL6FEa2iFnZqo2KlK7TQ5QhV5x09hujBf7t5KzHQ1VmfPuTpqRy/rNlSxua3eWH374xxiVrP+lcA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-s390x-gnu": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.2.tgz", + "integrity": "sha512-fy8rXxuYEu602abC8MUNaPjYLIFzReOaEIEMKMUa0rFEUxNpVXhs15KSSQ4qlqSaM7B6rcj9rDZgADh/IGDzLQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-gnu": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.2.tgz", + "integrity": "sha512-0+bOkiQ779+r1WpoHOWHqncvyySci0vKph+myNDYb+im6meJAzHQXay6oEgnkHuUGouM1LKTZwqKpBow6Kj7CQ==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-musl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.2.tgz", + "integrity": "sha512-mjSkrzZK5Qsl0a9d1JgILOiuZOSDTVdKENcSXBoqbzSrspLR/4/IRVDo5wd2GgZjNss/viBFJdeq+j7qH2nypw==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-openharmony-arm64": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.2.tgz", + "integrity": "sha512-1v5vHasdfQAZoEHakBV72LIFAC9JjnymsiKxp+GEr/ma3+NJCPSaYK+qavInOovJkgwFrs7GccX2d6IgDA3Z5w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-wasm32-wasi": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.2.tgz", + "integrity": "sha512-mb1VobWn6NheziTk5/WEaR6AKVbrwT5sOi6C7zk3gy/pD1qtJfU1j4PgTo2NJnOtbL9Dl3Aeei8w9jJ7qC2jZQ==", + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "1.10.0", + "@emnapi/runtime": "1.10.0", + "@napi-rs/wasm-runtime": "^1.1.4" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-win32-arm64-msvc": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.2.tgz", + "integrity": "sha512-SqKonF56vA/L2yHwHYcEp2P34URpOZ7d1fS635cTkpDnUtEGdUbhI6NzsPdqeSWvAAeGDrxjWjNmibDIdFf9/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-win32-x64-msvc": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.2.tgz", + "integrity": "sha512-v7qRI7gXLRINcOGXt+7YmAZ6iFuyZVMIoXAxhd8oP+DR9dLfL9GfNIx7PLMxmhZdvq8waUJBQiWN9EKNy+TRBQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.1.tgz", + "integrity": "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@standard-schema/spec": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", + "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tailwindcss/node": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.3.0.tgz", + "integrity": "sha512-aFb4gUhFOgdh9AXo4IzBEOzBkkAxm9VigwDJnMIYv3lcfXCJVesNfbEaBl4BNgVRyid92AmdviqwBUBRKSeY3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/remapping": "^2.3.5", + "enhanced-resolve": "^5.21.0", + "jiti": "^2.6.1", + "lightningcss": "1.32.0", + "magic-string": "^0.30.21", + "source-map-js": "^1.2.1", + "tailwindcss": "4.3.0" + } + }, + "node_modules/@tailwindcss/oxide": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.3.0.tgz", + "integrity": "sha512-F7HZGBeN9I0/AuuJS5PwcD8xayx5ri5GhjYUDBEVYUkexyA/giwbDNjRVrxSezE3T250OU2K/wp/ltWx3UOefg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 20" + }, + "optionalDependencies": { + "@tailwindcss/oxide-android-arm64": "4.3.0", + "@tailwindcss/oxide-darwin-arm64": "4.3.0", + "@tailwindcss/oxide-darwin-x64": "4.3.0", + "@tailwindcss/oxide-freebsd-x64": "4.3.0", + "@tailwindcss/oxide-linux-arm-gnueabihf": "4.3.0", + "@tailwindcss/oxide-linux-arm64-gnu": "4.3.0", + "@tailwindcss/oxide-linux-arm64-musl": "4.3.0", + "@tailwindcss/oxide-linux-x64-gnu": "4.3.0", + "@tailwindcss/oxide-linux-x64-musl": "4.3.0", + "@tailwindcss/oxide-wasm32-wasi": "4.3.0", + "@tailwindcss/oxide-win32-arm64-msvc": "4.3.0", + "@tailwindcss/oxide-win32-x64-msvc": "4.3.0" + } + }, + "node_modules/@tailwindcss/oxide-android-arm64": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.3.0.tgz", + "integrity": "sha512-TJPiq67tKlLuObP6RkwvVGDoxCMBVtDgKkLfa/uyj7/FyxvQwHS+UOnVrXXgbEsfUaMgiVvC4KbJnRr26ho4Ng==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-darwin-arm64": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.3.0.tgz", + "integrity": "sha512-oMN/WZRb+SO37BmUElEgeEWuU8E/HXRkiODxJxLe1UTHVXLrdVSgfaJV7pSlhRGMSOiXLuxTIjfsF3wYvz8cgQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-darwin-x64": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.3.0.tgz", + "integrity": "sha512-N6CUmu4a6bKVADfw77p+iw6Yd9Q3OBhe0veaDX+QazfuVYlQsHfDgxBrsjQ/IW+zywL8mTrNd0SdJT/zgtvMdA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-freebsd-x64": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.3.0.tgz", + "integrity": "sha512-zDL5hBkQdH5C6MpqbK3gQAgP80tsMwSI26vjOzjJtNCMUo0lFgOItzHKBIupOZNQxt3ouPH7RPhvNhiTfCe5CQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.3.0.tgz", + "integrity": "sha512-R06HdNi7A7OEoMsf6d4tjZ71RCWnZQPHj2mnotSFURjNLdBC+cIgXQ7l81CqeoiQftjf6OOblxXMInMgN2VzMA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm64-gnu": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.3.0.tgz", + "integrity": "sha512-qTJHELX8jetjhRQHCLilkVLmybpzNQAtaI/gaoVoidn/ufbNDbAo8KlK2J+yPoc8wQxvDxCmh/5lr8nC1+lTbg==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm64-musl": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.3.0.tgz", + "integrity": "sha512-Z6sukiQsngnWO+l39X4pPbiWT81IC+PLKF+PHxIlyZbGNb9MODfYlXEVlFvej5BOZInWX01kVyzeLvHsXhfczQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-x64-gnu": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.3.0.tgz", + "integrity": "sha512-DRNdQRpSGzRGfARVuVkxvM8Q12nh19l4BF/G7zGA1oe+9wcC6saFBHTISrpIcKzhiXtSrlSrluCfvMuledoCTQ==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-x64-musl": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.3.0.tgz", + "integrity": "sha512-Z0IADbDo8bh6I7h2IQMx601AdXBLfFpEdUotft86evd/8ZPflZe9COPO8Q1vw+pfLWIUo9zN/JGZvwuAJqduqg==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-wasm32-wasi": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.3.0.tgz", + "integrity": "sha512-HNZGOUxEmElksYR7S6sC5jTeNGpobAsy9u7Gu0AskJ8/20FR9GqebUyB+HBcU/ax6BHuiuJi+Oda4B+YX6H1yA==", + "bundleDependencies": [ + "@napi-rs/wasm-runtime", + "@emnapi/core", + "@emnapi/runtime", + "@tybys/wasm-util", + "@emnapi/wasi-threads", + "tslib" + ], + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "^1.10.0", + "@emnapi/runtime": "^1.10.0", + "@emnapi/wasi-threads": "^1.2.1", + "@napi-rs/wasm-runtime": "^1.1.4", + "@tybys/wasm-util": "^0.10.1", + "tslib": "^2.8.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@tailwindcss/oxide-win32-arm64-msvc": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.3.0.tgz", + "integrity": "sha512-Pe+RPVTi1T+qymuuRpcdvwSVZjnll/f7n8gBxMMh3xLTctMDKqpdfGimbMyioqtLhUYZxdJ9wGNhV7MKHvgZsQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-win32-x64-msvc": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.3.0.tgz", + "integrity": "sha512-Mvrf2kXW/yeW/OTezZlCGOirXRcUuLIBx/5Y12BaPM7wJoryG6dfS/NJL8aBPqtTEx/Vm4T4vKzFUcKDT+TKUA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/vite": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.3.0.tgz", + "integrity": "sha512-t6J3OrB5Fc0ExuhohouH0fWUGMYL6PTLhW+E7zIk/pdbnJARZDCwjBznFnkh5ynRnIRSI4YjtTH0t6USjJISrw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@tailwindcss/node": "4.3.0", + "@tailwindcss/oxide": "4.3.0", + "tailwindcss": "4.3.0" + }, + "peerDependencies": { + "vite": "^5.2.0 || ^6 || ^7 || ^8" + } + }, + "node_modules/@tybys/wasm-util": { + "version": "0.10.2", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.2.tgz", + "integrity": "sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@types/chai": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", + "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/deep-eql": "*", + "assertion-error": "^2.0.1" + } + }, + "node_modules/@types/deep-eql": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", + "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/estree": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "24.12.4", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.12.4.tgz", + "integrity": "sha512-GUUEShf+PBCGW2KaXwcIt3Yk+e3pkKwWKb9GSyM9WQVE+ep2jzmHdGsHzu4wgcZy5fN9FBdVzjpBQsYlpfpgLA==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~7.16.0" + } + }, + "node_modules/@vitejs/plugin-vue": { + "version": "6.0.7", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-6.0.7.tgz", + "integrity": "sha512-km+p+XdSz9Sxm5rqUbqcSfZYaAniKxWBj1KURl+Jr7UaPvvX7BmaWMdP69I5rrFDeQGyxAG7NXdc57vz+snhWg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@rolldown/pluginutils": "^1.0.1" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "peerDependencies": { + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0", + "vue": "^3.2.25" + } + }, + "node_modules/@vitest/expect": { + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.7.tgz", + "integrity": "sha512-1R+tw0ortHEbZDGMymm+pN7/AFQ/RkFFdtd7EN+VBpynKmLbP8A3rpEXdshBJ7+8hQ9zBJh/i1s0yKNtxAnU7w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@standard-schema/spec": "^1.1.0", + "@types/chai": "^5.2.2", + "@vitest/spy": "4.1.7", + "@vitest/utils": "4.1.7", + "chai": "^6.2.2", + "tinyrainbow": "^3.1.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/mocker": { + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.7.tgz", + "integrity": "sha512-vY7nuamKgfvpA1Koa3oYIw/k7D6kZnpGyNMZW8loow2bsBYla1TFdqTaXncWdRn4pgwNs+90RhnXhJScDwQeJA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "4.1.7", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.21" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "msw": "^2.4.9", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, + "node_modules/@vitest/mocker/node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "node_modules/@vitest/pretty-format": { + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.7.tgz", + "integrity": "sha512-umgCarTOYQWIaDMvGDRZij+6b9oVeLIyJzfN+AS88e0ZOU3QTgNNSTtjQOpcvWr3np1N0j4WgZj+sb3oYBDscw==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyrainbow": "^3.1.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner": { + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.7.tgz", + "integrity": "sha512-BapjmAQ2aI78WdMEfeUWivnfVzB+VPGwWRQcJE0OUq7qEeEcBsCSf+0T5iREBNE5nBb4wA5Ya0W6IA+sghdEFw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/utils": "4.1.7", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/snapshot": { + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.7.tgz", + "integrity": "sha512-ZacLzja+TmJeZ1h14xW2FB/WpeimUD3haBXQPyJqxvo8jQTmfeA8zv58mtjN2C7EHXZDYVcVYdYmAxjkWVvKCw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "4.1.7", + "@vitest/utils": "4.1.7", + "magic-string": "^0.30.21", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/spy": { + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.7.tgz", + "integrity": "sha512-kbkI5LMWakyuTIvs6fUJ5qdIVb1XVKsYJAT4OJ938cHMROYMSfmoQdZy0aaAnjbbc8F61vkoTqz/Az+/HiIu5Q==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/utils": { + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.7.tgz", + "integrity": "sha512-T532WBu791cBxJlCl6SO+J14l81DQx6uQHm1bQbmCDY7nqlEIgkza/UFnSBNaUtSf41unldDFjdOBYEQC4b5Hw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "4.1.7", + "convert-source-map": "^2.0.0", + "tinyrainbow": "^3.1.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@volar/language-core": { + "version": "2.4.28", + "resolved": "https://registry.npmjs.org/@volar/language-core/-/language-core-2.4.28.tgz", + "integrity": "sha512-w4qhIJ8ZSitgLAkVay6AbcnC7gP3glYM3fYwKV3srj8m494E3xtrCv6E+bWviiK/8hs6e6t1ij1s2Endql7vzQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@volar/source-map": "2.4.28" + } + }, + "node_modules/@volar/source-map": { + "version": "2.4.28", + "resolved": "https://registry.npmjs.org/@volar/source-map/-/source-map-2.4.28.tgz", + "integrity": "sha512-yX2BDBqJkRXfKw8my8VarTyjv48QwxdJtvRgUpNE5erCsgEUdI2DsLbpa+rOQVAJYshY99szEcRDmyHbF10ggQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@volar/typescript": { + "version": "2.4.28", + "resolved": "https://registry.npmjs.org/@volar/typescript/-/typescript-2.4.28.tgz", + "integrity": "sha512-Ja6yvWrbis2QtN4ClAKreeUZPVYMARDYZl9LMEv1iQ1QdepB6wn0jTRxA9MftYmYa4DQ4k/DaSZpFPUfxl8giw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@volar/language-core": "2.4.28", + "path-browserify": "^1.0.1", + "vscode-uri": "^3.0.8" + } + }, + "node_modules/@vue/compiler-core": { + "version": "3.5.34", + "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.34.tgz", + "integrity": "sha512-s9cLyK5mLcvZ4Agva5QgRsQyLKvts9WbU9DB6NqiZkkGEdwmcEiylj5Jbwkp680drF/NNCV8OlAJSe+yMLxaJw==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.3", + "@vue/shared": "3.5.34", + "entities": "^7.0.1", + "estree-walker": "^2.0.2", + "source-map-js": "^1.2.1" + } + }, + "node_modules/@vue/compiler-dom": { + "version": "3.5.34", + "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.34.tgz", + "integrity": "sha512-EbF/T++k0e2MMZlJsBhzK8Sgwt0HcIPOhzn1CTB/lv6sQcyk+OWf8YeiLxZp3ro7MbbLcAfAJ6sEvjFWuNgUCw==", + "license": "MIT", + "dependencies": { + "@vue/compiler-core": "3.5.34", + "@vue/shared": "3.5.34" + } + }, + "node_modules/@vue/compiler-sfc": { + "version": "3.5.34", + "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.34.tgz", + "integrity": "sha512-D/ihr6uZeIt6r+pVZf46RWT1fAsLFMbUP7k8G1VkiiWexriED9GrX3echHd4Abbt17zjlfiFJ8z7a3BxZOPNjg==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.3", + "@vue/compiler-core": "3.5.34", + "@vue/compiler-dom": "3.5.34", + "@vue/compiler-ssr": "3.5.34", + "@vue/shared": "3.5.34", + "estree-walker": "^2.0.2", + "magic-string": "^0.30.21", + "postcss": "^8.5.14", + "source-map-js": "^1.2.1" + } + }, + "node_modules/@vue/compiler-ssr": { + "version": "3.5.34", + "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.34.tgz", + "integrity": "sha512-cDtTHKibkThKGHH1SP+WdccquNRYQDFH6rRjQCqT9G2ltFAfoR5pUftpab/z+aM5mW9HLLVQW7hfKKQe/1GBeQ==", + "license": "MIT", + "dependencies": { + "@vue/compiler-dom": "3.5.34", + "@vue/shared": "3.5.34" + } + }, + "node_modules/@vue/language-core": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/@vue/language-core/-/language-core-3.3.1.tgz", + "integrity": "sha512-NP8g6V7x81NVOXbLupUvYY6i6LqUkjkVowe2epRedmpgaFCOdjgWHE/rQBvEJ4r7koAYODIjGeBWEdt6n7jYXQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@volar/language-core": "2.4.28", + "@vue/compiler-dom": "^3.5.0", + "@vue/shared": "^3.5.0", + "alien-signals": "^3.2.0", + "muggle-string": "^0.4.1", + "path-browserify": "^1.0.1", + "picomatch": "^4.0.4" + } + }, + "node_modules/@vue/reactivity": { + "version": "3.5.34", + "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.34.tgz", + "integrity": "sha512-y9XDjCEuBp+98k+UL5dbYkh57AHU4o6cxZedOPXw3bmrZZYLQsVHguGurq7hVrPCSrQtrnz1f9dssyFr+dMXfQ==", + "license": "MIT", + "dependencies": { + "@vue/shared": "3.5.34" + } + }, + "node_modules/@vue/runtime-core": { + "version": "3.5.34", + "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.34.tgz", + "integrity": "sha512-mKeBYvu8tcMSLhypAHBmriUFfWXKTCF/23Z4jiCoYK3UtWepkliViNLuR90V9XOyD62mUxs9p1jsrpK3CCGIzw==", + "license": "MIT", + "dependencies": { + "@vue/reactivity": "3.5.34", + "@vue/shared": "3.5.34" + } + }, + "node_modules/@vue/runtime-dom": { + "version": "3.5.34", + "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.34.tgz", + "integrity": "sha512-e8kZzERmCwUnBRVsgSQlAfrfU2rGoy0FFKPBXSlfEjc/O3KfA7QP0t1/2ZylrbchjmIKB4dPTd07A6WPr0eOrg==", + "license": "MIT", + "dependencies": { + "@vue/reactivity": "3.5.34", + "@vue/runtime-core": "3.5.34", + "@vue/shared": "3.5.34", + "csstype": "^3.2.3" + } + }, + "node_modules/@vue/server-renderer": { + "version": "3.5.34", + "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.34.tgz", + "integrity": "sha512-nHxmJoTrKsmrkbILRhkC9gY1G3moZbJTqCzDd7DOOzG5KH9oeJ0Unqrff5f9v0pW//jES05ZkJcNtfE8JjOIew==", + "license": "MIT", + "dependencies": { + "@vue/compiler-ssr": "3.5.34", + "@vue/shared": "3.5.34" + }, + "peerDependencies": { + "vue": "3.5.34" + } + }, + "node_modules/@vue/shared": { + "version": "3.5.34", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.34.tgz", + "integrity": "sha512-24uqU4OIiX29ryC3MeWid/Xf2fa2EFRUVLb77nRhk+UrTVrh/XiGtFAFmJBAtBRbjwNdsPRP+jj/OL27Eg1NDA==", + "license": "MIT" + }, + "node_modules/@vue/test-utils": { + "version": "2.4.10", + "resolved": "https://registry.npmjs.org/@vue/test-utils/-/test-utils-2.4.10.tgz", + "integrity": "sha512-SmoZ5EA1kYiAFs9NkYdiFFQF+cSnUwnvlYEbY+DogWQZUiqOm/Y29eSbc5T6yi75SgSF9863SBeXniIEoPajCA==", + "dev": true, + "license": "MIT", + "dependencies": { + "js-beautify": "^1.14.9", + "vue-component-type-helpers": "^3.0.0" + }, + "peerDependencies": { + "@vue/compiler-dom": "3.x", + "@vue/server-renderer": "3.x", + "vue": "3.x" + }, + "peerDependenciesMeta": { + "@vue/server-renderer": { + "optional": true + } + } + }, + "node_modules/@vue/tsconfig": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/@vue/tsconfig/-/tsconfig-0.9.1.tgz", + "integrity": "sha512-buvjm+9NzLCJL29KY1j1991YYJ5e6275OiK+G4jtmfIb+z4POywbdm0wXusT9adVWqe0xqg70TbI7+mRx4uU9w==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "typescript": ">= 5.8", + "vue": "^3.4.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + }, + "vue": { + "optional": true + } + } + }, + "node_modules/abbrev": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-2.0.0.tgz", + "integrity": "sha512-6/mh1E2u2YgEsCHdY0Yx5oW+61gZU+1vXaoiHHrpKeuRNNgFvS+/jrwHiQhB5apAf5oB7UB7E19ol2R2LKH8hQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/alien-signals": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/alien-signals/-/alien-signals-3.2.1.tgz", + "integrity": "sha512-I8FjmltrfnDFoZedi5CG8DghVYNhzb/Ijluz7tCSJH0xpd0484Kowhbb1XDYOxfJpU1p5wnM2X54dA+IfGyD1g==", + "dev": true, + "license": "MIT" + }, + "node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/assertion-error": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/bidi-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/bidi-js/-/bidi-js-1.0.3.tgz", + "integrity": "sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw==", + "dev": true, + "license": "MIT", + "dependencies": { + "require-from-string": "^2.0.2" + } + }, + "node_modules/brace-expansion": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.0.tgz", + "integrity": "sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/chai": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz", + "integrity": "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/commander": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz", + "integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + } + }, + "node_modules/config-chain": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/config-chain/-/config-chain-1.1.13.tgz", + "integrity": "sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ini": "^1.3.4", + "proto-list": "~1.2.1" + } + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/css-tree": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.2.1.tgz", + "integrity": "sha512-X7sjQzceUhu1u7Y/ylrRZFU2FS6LRiFVp6rKLPg23y3x3c3DOKAwuXGDp+PAGjh6CSnCjYeAul8pcT8bAl+lSA==", + "dev": true, + "license": "MIT", + "dependencies": { + "mdn-data": "2.27.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0" + } + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "license": "MIT" + }, + "node_modules/data-urls": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-7.0.0.tgz", + "integrity": "sha512-23XHcCF+coGYevirZceTVD7NdJOqVn+49IHyxgszm+JIiHLoB2TkmPtsYkNWT1pvRSGkc35L6NHs0yHkN2SumA==", + "dev": true, + "license": "MIT", + "dependencies": { + "whatwg-mimetype": "^5.0.0", + "whatwg-url": "^16.0.0" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + } + }, + "node_modules/decimal.js": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.6.0.tgz", + "integrity": "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==", + "dev": true, + "license": "MIT" + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "dev": true, + "license": "MIT" + }, + "node_modules/editorconfig": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/editorconfig/-/editorconfig-1.0.7.tgz", + "integrity": "sha512-e0GOtq/aTQhVdNyDU9e02+wz9oDDM+SIOQxWME2QRjzRX5yyLAuHDE+0aE8vHb9XRC8XD37eO2u57+F09JqFhw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@one-ini/wasm": "0.1.1", + "commander": "^10.0.0", + "minimatch": "^9.0.1", + "semver": "^7.5.3" + }, + "bin": { + "editorconfig": "bin/editorconfig" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true, + "license": "MIT" + }, + "node_modules/enhanced-resolve": { + "version": "5.22.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.22.0.tgz", + "integrity": "sha512-xYcDWrpELkFzz9SpZ3PlI6Eu6eD93Yf0WLDRxikGhWJ3MAir2SNZTIVCVZqZ/NUyx8AdMc2gT9C0gPiw18kG+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.3.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/entities": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-7.0.1.tgz", + "integrity": "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/es-module-lexer": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.1.0.tgz", + "integrity": "sha512-n27zTYMjYu1aj4MjCWzSP7G9r75utsaoc8m61weK+W8JMBGGQybd43GstCXZ3WNmSFtGT9wi59qQTW6mhTR5LQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "license": "MIT" + }, + "node_modules/expect-type": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.3.0.tgz", + "integrity": "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/foreground-child": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", + "dev": true, + "license": "ISC", + "dependencies": { + "cross-spawn": "^7.0.6", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/glob": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", + "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "dev": true, + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/html-encoding-sniffer": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-6.0.0.tgz", + "integrity": "sha512-CV9TW3Y3f8/wT0BRFc1/KAVQ3TUHiXmaAb6VW9vtiMFf7SLoMd1PdAc4W3KFOFETBJUb90KatHqlsZMWV+R9Gg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@exodus/bytes": "^1.6.0" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + } + }, + "node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "dev": true, + "license": "ISC" + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-potential-custom-element-name": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", + "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, + "node_modules/jiti": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.7.0.tgz", + "integrity": "sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==", + "dev": true, + "license": "MIT", + "bin": { + "jiti": "lib/jiti-cli.mjs" + } + }, + "node_modules/js-beautify": { + "version": "1.15.4", + "resolved": "https://registry.npmjs.org/js-beautify/-/js-beautify-1.15.4.tgz", + "integrity": "sha512-9/KXeZUKKJwqCXUdBxFJ3vPh467OCckSBmYDwSK/EtV090K+iMJ7zx2S3HLVDIWFQdqMIsZWbnaGiba18aWhaA==", + "dev": true, + "license": "MIT", + "dependencies": { + "config-chain": "^1.1.13", + "editorconfig": "^1.0.4", + "glob": "^10.4.2", + "js-cookie": "^3.0.5", + "nopt": "^7.2.1" + }, + "bin": { + "css-beautify": "js/bin/css-beautify.js", + "html-beautify": "js/bin/html-beautify.js", + "js-beautify": "js/bin/js-beautify.js" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/js-cookie": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-3.0.7.tgz", + "integrity": "sha512-z/wZZgDrkNV1eA0ULjM/F9/50Ya8fbzgKneSpoPsXSGd0KnpdtHfOZWK+GcwLk+EZbS4F9RBhU+K2RgzuDaItw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20" + } + }, + "node_modules/jsdom": { + "version": "29.1.1", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-29.1.1.tgz", + "integrity": "sha512-ECi4Fi2f7BdJtUKTflYRTiaMxIB0O6zfR1fX0GXpUrf6flp8QIYn1UT20YQqdSOfk2dfkCwS8LAFoJDEppNK5Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@asamuzakjp/css-color": "^5.1.11", + "@asamuzakjp/dom-selector": "^7.1.1", + "@bramus/specificity": "^2.4.2", + "@csstools/css-syntax-patches-for-csstree": "^1.1.3", + "@exodus/bytes": "^1.15.0", + "css-tree": "^3.2.1", + "data-urls": "^7.0.0", + "decimal.js": "^10.6.0", + "html-encoding-sniffer": "^6.0.0", + "is-potential-custom-element-name": "^1.0.1", + "lru-cache": "^11.3.5", + "parse5": "^8.0.1", + "saxes": "^6.0.0", + "symbol-tree": "^3.2.4", + "tough-cookie": "^6.0.1", + "undici": "^7.25.0", + "w3c-xmlserializer": "^5.0.0", + "webidl-conversions": "^8.0.1", + "whatwg-mimetype": "^5.0.0", + "whatwg-url": "^16.0.1", + "xml-name-validator": "^5.0.0" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24.0.0" + }, + "peerDependencies": { + "canvas": "^3.0.0" + }, + "peerDependenciesMeta": { + "canvas": { + "optional": true + } + } + }, + "node_modules/lightningcss": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz", + "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==", + "dev": true, + "license": "MPL-2.0", + "dependencies": { + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-android-arm64": "1.32.0", + "lightningcss-darwin-arm64": "1.32.0", + "lightningcss-darwin-x64": "1.32.0", + "lightningcss-freebsd-x64": "1.32.0", + "lightningcss-linux-arm-gnueabihf": "1.32.0", + "lightningcss-linux-arm64-gnu": "1.32.0", + "lightningcss-linux-arm64-musl": "1.32.0", + "lightningcss-linux-x64-gnu": "1.32.0", + "lightningcss-linux-x64-musl": "1.32.0", + "lightningcss-win32-arm64-msvc": "1.32.0", + "lightningcss-win32-x64-msvc": "1.32.0" + } + }, + "node_modules/lightningcss-android-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz", + "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz", + "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz", + "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-freebsd-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz", + "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz", + "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz", + "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz", + "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz", + "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz", + "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz", + "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz", + "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lru-cache": { + "version": "11.5.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.5.0.tgz", + "integrity": "sha512-5YgH9UJd7wVb9hIouI2adWpgqrrICkt070Dnj8EUY1+B4B2P9eRLPAkAAo6NICA7CEhOIeBHl46u9zSNpNu7zA==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/mdn-data": { + "version": "2.27.1", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.27.1.tgz", + "integrity": "sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ==", + "dev": true, + "license": "CC0-1.0" + }, + "node_modules/minimatch": { + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.2" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/minipass": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", + "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/muggle-string": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/muggle-string/-/muggle-string-0.4.1.tgz", + "integrity": "sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.12", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.12.tgz", + "integrity": "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/nopt": { + "version": "7.2.1", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-7.2.1.tgz", + "integrity": "sha512-taM24ViiimT/XntxbPyJQzCG+p4EKOpgD3mxFwW38mGjVUrfERQOeY4EDHjdnptttfHuHQXFx+lTP08Q+mLa/w==", + "dev": true, + "license": "ISC", + "dependencies": { + "abbrev": "^2.0.0" + }, + "bin": { + "nopt": "bin/nopt.js" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/obug": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.1.tgz", + "integrity": "sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==", + "dev": true, + "funding": [ + "https://github.com/sponsors/sxzz", + "https://opencollective.com/debug" + ], + "license": "MIT" + }, + "node_modules/package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "dev": true, + "license": "BlueOak-1.0.0" + }, + "node_modules/parse5": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-8.0.1.tgz", + "integrity": "sha512-z1e/HMG90obSGeidlli3hj7cbocou0/wa5HacvI3ASx34PecNjNQeaHNo5WIZpWofN9kgkqV1q5YvXe3F0FoPw==", + "dev": true, + "license": "MIT", + "dependencies": { + "entities": "^8.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/parse5/node_modules/entities": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-8.0.0.tgz", + "integrity": "sha512-zwfzJecQ/Uej6tusMqwAqU/6KL2XaB2VZ2Jg54Je6ahNBGNH6Ek6g3jjNCF0fG9EWQKGZNddNjU5F1ZQn/sBnA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=20.19.0" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/path-browserify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-1.0.1.tgz", + "integrity": "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==", + "dev": true, + "license": "MIT" + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/path-scurry/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true, + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/postcss": { + "version": "8.5.15", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.15.tgz", + "integrity": "sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.12", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/proto-list": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz", + "integrity": "sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==", + "dev": true, + "license": "ISC" + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/rolldown": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.2.tgz", + "integrity": "sha512-oZx5zVDtVB44AW3eaifgDml1gWRDZGvjcfdxonE4swNPG98PrrXjaO/KrnUjzlMnztCCRVlUueA1kCXhARGk6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@oxc-project/types": "=0.132.0", + "@rolldown/pluginutils": "^1.0.0" + }, + "bin": { + "rolldown": "bin/cli.mjs" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "optionalDependencies": { + "@rolldown/binding-android-arm64": "1.0.2", + "@rolldown/binding-darwin-arm64": "1.0.2", + "@rolldown/binding-darwin-x64": "1.0.2", + "@rolldown/binding-freebsd-x64": "1.0.2", + "@rolldown/binding-linux-arm-gnueabihf": "1.0.2", + "@rolldown/binding-linux-arm64-gnu": "1.0.2", + "@rolldown/binding-linux-arm64-musl": "1.0.2", + "@rolldown/binding-linux-ppc64-gnu": "1.0.2", + "@rolldown/binding-linux-s390x-gnu": "1.0.2", + "@rolldown/binding-linux-x64-gnu": "1.0.2", + "@rolldown/binding-linux-x64-musl": "1.0.2", + "@rolldown/binding-openharmony-arm64": "1.0.2", + "@rolldown/binding-wasm32-wasi": "1.0.2", + "@rolldown/binding-win32-arm64-msvc": "1.0.2", + "@rolldown/binding-win32-x64-msvc": "1.0.2" + } + }, + "node_modules/saxes": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", + "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==", + "dev": true, + "license": "ISC", + "dependencies": { + "xmlchars": "^2.2.0" + }, + "engines": { + "node": ">=v12.22.7" + } + }, + "node_modules/semver": { + "version": "7.8.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.1.tgz", + "integrity": "sha512-rkVq3IXh+4FDGch+KwzX3aV9W3kO54GyEgpvBzSyctDA6Xtd7RJQV1xmXbeQp5v7+VzLOfVqiutSE6GICgPFvg==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/siginfo": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", + "dev": true, + "license": "ISC" + }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/smol-toml": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/smol-toml/-/smol-toml-1.6.1.tgz", + "integrity": "sha512-dWUG8F5sIIARXih1DTaQAX4SsiTXhInKf1buxdY9DIg4ZYPZK5nGM1VRIYmEbDbsHt7USo99xSLFu5Q1IqTmsg==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">= 18" + }, + "funding": { + "url": "https://github.com/sponsors/cyyynthia" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/stackback": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", + "dev": true, + "license": "MIT" + }, + "node_modules/std-env": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-4.1.0.tgz", + "integrity": "sha512-Rq7ybcX2RuC55r9oaPVEW7/xu3tj8u4GeBYHBWCychFtzMIr86A7e3PPEBPT37sHStKX3+TiX/Fr/ACmJLVlLQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/string-width-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.2.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/symbol-tree": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", + "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", + "dev": true, + "license": "MIT" + }, + "node_modules/tailwindcss": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.3.0.tgz", + "integrity": "sha512-y6nxMGB1nMW9R6k96e5gdIFzcfL/gTJRNaqGes1YvkLnPVXzWgbqFF2yLC0T8G774n24cx3Pe8XrKoniCOAH+Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/tapable": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.3.tgz", + "integrity": "sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/tinybench": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", + "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyexec": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.1.2.tgz", + "integrity": "sha512-dAqSqE/RabpBKI8+h26GfLq6Vb3JVXs30XYQjdMjaj/c2tS8IYYMbIzP599KtRj7c57/wYApb3QjgRgXmrCukA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.16", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.16.tgz", + "integrity": "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinyrainbow": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.1.0.tgz", + "integrity": "sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tldts": { + "version": "7.0.30", + "resolved": "https://registry.npmjs.org/tldts/-/tldts-7.0.30.tgz", + "integrity": "sha512-ELrFxuqsDdHUwoh0XxDbxuLD3Wnz49Z57IFvTtvWy1hJdcMZjXLIuonjilCiWHlT2GbE4Wlv1wKVTzDFnXH1aw==", + "dev": true, + "license": "MIT", + "dependencies": { + "tldts-core": "^7.0.30" + }, + "bin": { + "tldts": "bin/cli.js" + } + }, + "node_modules/tldts-core": { + "version": "7.0.30", + "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-7.0.30.tgz", + "integrity": "sha512-uiHN8PIB1VmWyS98eZYja4xzlYqeFZVjb4OuYlJQnZAuJhMw4PbKQOKgHKhBdJR3FE/t5mUQ1Kd80++B+qhD1Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/tough-cookie": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-6.0.1.tgz", + "integrity": "sha512-LktZQb3IeoUWB9lqR5EWTHgW/VTITCXg4D21M+lvybRVdylLrRMnqaIONLVb5mav8vM19m44HIcGq4qASeu2Qw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "tldts": "^7.0.5" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/tr46": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-6.0.0.tgz", + "integrity": "sha512-bLVMLPtstlZ4iMQHpFHTR7GAGj2jxi8Dg0s2h2MafAE4uSWF98FC/3MomU51iQAMf8/qDUbKWf5GxuvvVcXEhw==", + "dev": true, + "license": "MIT", + "dependencies": { + "punycode": "^2.3.1" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, + "license": "0BSD", + "optional": true + }, + "node_modules/typescript": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.3.tgz", + "integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==", + "devOptional": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici": { + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-7.25.0.tgz", + "integrity": "sha512-xXnp4kTyor2Zq+J1FfPI6Eq3ew5h6Vl0F/8d9XU5zZQf1tX9s2Su1/3PiMmUANFULpmksxkClamIZcaUqryHsQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20.18.1" + } + }, + "node_modules/undici-types": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", + "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", + "dev": true, + "license": "MIT" + }, + "node_modules/vite": { + "version": "8.0.14", + "resolved": "https://registry.npmjs.org/vite/-/vite-8.0.14.tgz", + "integrity": "sha512-s4BJJ+5y1pYL6Otw51FHhVJQhPnuRinKig64g/1+EUNaJsd3gCKdD31IPFvswUgW9/60QT9oFHbZHbQK5imcxw==", + "dev": true, + "license": "MIT", + "dependencies": { + "lightningcss": "^1.32.0", + "picomatch": "^4.0.4", + "postcss": "^8.5.15", + "rolldown": "1.0.2", + "tinyglobby": "^0.2.16" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "@vitejs/devtools": "^0.1.18", + "esbuild": "^0.27.0 || ^0.28.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "@vitejs/devtools": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vitest": { + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.7.tgz", + "integrity": "sha512-flYyaFd2CgoCoU+0UKt3pxksgC+S02iTDN0n3LtqaMeXsI9SBcdNujc2k0DeFLzUn/0k538yNjOSdwgCqcrwJA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/expect": "4.1.7", + "@vitest/mocker": "4.1.7", + "@vitest/pretty-format": "4.1.7", + "@vitest/runner": "4.1.7", + "@vitest/snapshot": "4.1.7", + "@vitest/spy": "4.1.7", + "@vitest/utils": "4.1.7", + "es-module-lexer": "^2.0.0", + "expect-type": "^1.3.0", + "magic-string": "^0.30.21", + "obug": "^2.1.1", + "pathe": "^2.0.3", + "picomatch": "^4.0.3", + "std-env": "^4.0.0-rc.1", + "tinybench": "^2.9.0", + "tinyexec": "^1.0.2", + "tinyglobby": "^0.2.15", + "tinyrainbow": "^3.1.0", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0", + "why-is-node-running": "^2.3.0" + }, + "bin": { + "vitest": "vitest.mjs" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@opentelemetry/api": "^1.9.0", + "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", + "@vitest/browser-playwright": "4.1.7", + "@vitest/browser-preview": "4.1.7", + "@vitest/browser-webdriverio": "4.1.7", + "@vitest/coverage-istanbul": "4.1.7", + "@vitest/coverage-v8": "4.1.7", + "@vitest/ui": "4.1.7", + "happy-dom": "*", + "jsdom": "*", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@opentelemetry/api": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser-playwright": { + "optional": true + }, + "@vitest/browser-preview": { + "optional": true + }, + "@vitest/browser-webdriverio": { + "optional": true + }, + "@vitest/coverage-istanbul": { + "optional": true + }, + "@vitest/coverage-v8": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + }, + "vite": { + "optional": false + } + } + }, + "node_modules/vscode-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.1.0.tgz", + "integrity": "sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/vue": { + "version": "3.5.34", + "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.34.tgz", + "integrity": "sha512-WdLBG9gm02OgJIG9axd5Hpx0TFLdzVgfG2evFFu8Rur5O/IoGc5cMjnjh3tPL6GnRGsYvUhBSKVPYVcxRKpMCA==", + "license": "MIT", + "dependencies": { + "@vue/compiler-dom": "3.5.34", + "@vue/compiler-sfc": "3.5.34", + "@vue/runtime-dom": "3.5.34", + "@vue/server-renderer": "3.5.34", + "@vue/shared": "3.5.34" + }, + "peerDependencies": { + "typescript": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/vue-component-type-helpers": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/vue-component-type-helpers/-/vue-component-type-helpers-3.3.1.tgz", + "integrity": "sha512-pu58kqxmVyEH6VfNYW1UyEfR3XAnJ27ZXT3yzXxxpjLxVzAbyC35Zk/nm/RMs7ijWnJNSd9fWkeex2OhUsx3MA==", + "dev": true, + "license": "MIT" + }, + "node_modules/vue-tsc": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/vue-tsc/-/vue-tsc-3.3.1.tgz", + "integrity": "sha512-webBP3jhlxzhELZ2g+11KJ6pg5OVY1xWhWrj7N/yQMi1CrtxJnW+tUACyRVeDK0cQNLP2Va5HNYK8pe+7c+msw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@volar/typescript": "2.4.28", + "@vue/language-core": "3.3.1" + }, + "bin": { + "vue-tsc": "bin/vue-tsc.js" + }, + "peerDependencies": { + "typescript": ">=5.0.0" + } + }, + "node_modules/w3c-xmlserializer": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz", + "integrity": "sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "xml-name-validator": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/webidl-conversions": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-8.0.1.tgz", + "integrity": "sha512-BMhLD/Sw+GbJC21C/UgyaZX41nPt8bUTg+jWyDeg7e7YN4xOM05YPSIXceACnXVtqyEw/LMClUQMtMZ+PGGpqQ==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=20" + } + }, + "node_modules/whatwg-mimetype": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-5.0.0.tgz", + "integrity": "sha512-sXcNcHOC51uPGF0P/D4NVtrkjSU2fNsm9iog4ZvZJsL3rjoDAzXZhkm2MWt1y+PUdggKAYVoMAIYcs78wJ51Cw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20" + } + }, + "node_modules/whatwg-url": { + "version": "16.0.1", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-16.0.1.tgz", + "integrity": "sha512-1to4zXBxmXHV3IiSSEInrreIlu02vUOvrhxJJH5vcxYTBDAx51cqZiKdyTxlecdKNSjj8EcxGBxNf6Vg+945gw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@exodus/bytes": "^1.11.0", + "tr46": "^6.0.0", + "webidl-conversions": "^8.0.1" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/why-is-node-running": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", + "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", + "dev": true, + "license": "MIT", + "dependencies": { + "siginfo": "^2.0.0", + "stackback": "0.0.2" + }, + "bin": { + "why-is-node-running": "cli.js" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/wrap-ansi-cjs/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/xml-name-validator": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz", + "integrity": "sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/xmlchars": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", + "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", + "dev": true, + "license": "MIT" + } + } +} diff --git a/quiz-app/package.json b/quiz-app/package.json new file mode 100644 index 0000000..efae9e7 --- /dev/null +++ b/quiz-app/package.json @@ -0,0 +1,30 @@ +{ + "name": "quiz-app", + "private": true, + "version": "0.1.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "vue-tsc -b && vite build", + "preview": "vite preview", + "test": "vitest run", + "test:build": "node ./test/build-check.mjs" + }, + "dependencies": { + "vue": "^3.5.34" + }, + "devDependencies": { + "@tailwindcss/vite": "^4.3.0", + "@types/node": "^24.12.3", + "@vitejs/plugin-vue": "^6.0.6", + "@vue/test-utils": "^2.4.10", + "@vue/tsconfig": "^0.9.1", + "jsdom": "^29.1.1", + "smol-toml": "^1.6.1", + "tailwindcss": "^4.3.0", + "typescript": "~6.0.2", + "vite": "^8.0.12", + "vitest": "^4.1.7", + "vue-tsc": "^3.2.8" + } +} diff --git a/quiz-app/public/assets/extra-mode.css b/quiz-app/public/assets/extra-mode.css deleted file mode 100644 index 7023f66..0000000 --- a/quiz-app/public/assets/extra-mode.css +++ /dev/null @@ -1,792 +0,0 @@ -.extra-mode-overlay { - position: fixed; - inset: 0; - z-index: 4000; - opacity: 0; - visibility: hidden; - pointer-events: none; - background: #000; - color: #f4f7f3; - font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", monospace; - transition: opacity 280ms ease, visibility 280ms ease; -} -.extra-mode-overlay::before, -.extra-mode-overlay::after { - content: ""; - position: absolute; - inset: 0; - pointer-events: none; - opacity: 0; -} -.extra-mode-overlay.active { - opacity: 1; - visibility: visible; - pointer-events: auto; -} -.extra-mode-overlay.glitching, -.extra-mode-overlay.inverting { - background: transparent; -} -.extra-mode-overlay.glitching .extra-mode-screen, -.extra-mode-overlay.inverting .extra-mode-screen { - background: transparent; -} -.extra-mode-overlay.glitching::before, -.extra-mode-overlay.glitching::after, -.extra-mode-overlay.inverting::before, -.extra-mode-overlay.inverting::after { - opacity: 1; -} -.extra-mode-overlay.glitching::before { - background: - repeating-linear-gradient(0deg, rgba(255, 255, 255, 0.12) 0 2px, transparent 2px 6px), - linear-gradient(90deg, rgba(255, 0, 119, 0.14), rgba(0, 255, 255, 0.1), rgba(255, 255, 255, 0.08)); - mix-blend-mode: screen; - animation: extra-mode-preboot-noise 140ms steps(4, end) infinite; -} -.extra-mode-overlay.glitching::after { - background: - linear-gradient(180deg, transparent 0%, rgba(255, 255, 255, 0.22) 40%, transparent 100%), - radial-gradient(circle at 24% 18%, rgba(255, 255, 255, 0.18), transparent 26%); - mix-blend-mode: difference; - animation: extra-mode-preboot-bands 180ms steps(3, end) infinite; -} -.extra-mode-overlay.inverting::before { - background: #ffffff; - mix-blend-mode: difference; - animation: extra-mode-preboot-invert 180ms ease forwards; -} -.extra-mode-overlay.inverting::after { - background: linear-gradient(180deg, rgba(255, 255, 255, 0.44), rgba(0, 0, 0, 0.92)); - animation: extra-mode-preboot-blackout 180ms ease forwards; -} -.extra-mode-screen { - position: relative; - width: 100%; - height: 100%; - overflow: hidden; - background: #000; -} -.extra-mode-crash-view, -.extra-mode-boot-view { - position: absolute; - inset: 0; - padding: 28px 24px 32px; -} -.extra-mode-crash-view { - display: flex; - flex-direction: column; - gap: 20px; - transition: opacity 220ms ease; -} -.extra-mode-stack-shell { - flex: 1; - overflow: hidden; -} -.extra-mode-stack-stream { - display: flex; - flex-direction: column; - gap: 4px; - min-height: auto; - transform: none; -} -.extra-mode-stack-line { - font-size: clamp(0.76rem, 1.8vw, 1rem); - line-height: 1.5; - white-space: pre-wrap; - word-break: break-word; -} -.extra-mode-stack-line--panic { - color: #ff5f73; - font-weight: 700; -} -.extra-mode-stack-line--meta { - color: #ffffff; - font-weight: 700; -} -.extra-mode-stack-line--func { - color: #f3f4f6; -} -.extra-mode-stack-line--file { - color: #9ca3af; -} -.extra-mode-stack-line--blank { - min-height: 0.9em; -} -.extra-mode-recover-line { - color: #7dff9b; - font-size: clamp(0.84rem, 1.9vw, 1.08rem); - font-weight: 700; - opacity: 0; - transform: translate3d(0, 12px, 0); - transition: opacity 220ms ease, transform 220ms ease; -} -.extra-mode-overlay.show-recover .extra-mode-recover-line { - opacity: 1; - transform: translate3d(0, 0, 0); -} -.extra-mode-boot-view { - opacity: 0; - transform: translate3d(0, 20px, 0); - transition: opacity 220ms ease, transform 220ms ease; -} -.extra-mode-overlay.booting .extra-mode-crash-view { - opacity: 0; -} -.extra-mode-overlay.booting .extra-mode-boot-view { - opacity: 1; - transform: translate3d(0, 0, 0); -} -.extra-mode-prompt-line { - min-height: 1.8em; - color: #d7ffd9; - font-size: clamp(0.86rem, 1.9vw, 1.1rem); - line-height: 1.6; - white-space: pre-wrap; - word-break: break-word; - margin-top: 20px; -} -.extra-mode-command-text { - color: #ffffff; -} -.extra-mode-cursor { - display: inline-block; - width: 0.68em; - height: 1.1em; - margin-left: 0.08em; - vertical-align: -0.16em; - background: #7dff9b; - animation: extra-mode-cursor-blink 960ms steps(1, end) infinite; -} -.extra-mode-boot-output { - margin-top: 0; - padding: 0; - border: none; - background: transparent; - color: #b8ffca; - font-family: inherit; - font-size: clamp(0.78rem, 1.8vw, 1rem); - line-height: 1.65; - white-space: pre-wrap; - opacity: 0; - transform: translate3d(0, 8px, 0); - transition: opacity 220ms ease, transform 220ms ease; -} -.extra-mode-overlay.show-output .extra-mode-boot-output { - opacity: 1; - transform: translate3d(0, 0, 0); -} -.extra-mode-toolbar { - display: flex; - justify-content: flex-start; - margin-top: 18px; - opacity: 0; - transform: translate3d(0, 8px, 0); - transition: opacity 220ms ease, transform 220ms ease; -} -.extra-mode-overlay.boot-ready .extra-mode-toolbar { - opacity: 1; - transform: translate3d(0, 0, 0); -} -.extra-mode-download-btn { - appearance: none; - display: inline-flex; - flex-direction: column; - align-items: flex-start; - gap: 4px; - min-width: 212px; - max-width: min(100%, 320px); - padding: 12px 22px 13px; - border: 1px solid rgba(125, 255, 155, 0.34); - background: rgba(0, 0, 0, 0.92); - color: #f4fff6; - font-family: inherit; - font-size: 0.96rem; - font-weight: 700; - line-height: 1.4; - text-align: left; - white-space: normal; - overflow-wrap: anywhere; - cursor: pointer; - box-shadow: 4px 4px 0 rgba(125, 255, 155, 0.14); - transition: background-color 160ms ease, border-color 160ms ease, opacity 160ms ease, transform 160ms ease, box-shadow 160ms ease; -} -.extra-mode-download-btn:hover:not(:disabled) { - background: rgba(255, 255, 255, 0.05); - border-color: #7dff9b; - box-shadow: 6px 6px 0 rgba(125, 255, 155, 0.18); - transform: translate3d(-1px, -1px, 0); -} -.extra-mode-download-btn:disabled { - opacity: 0.52; - cursor: default; - box-shadow: none; -} -.extra-mode-download-btn-label { - color: #ffffff; - font-size: 1.18rem; - font-weight: 700; - line-height: 1.1; - letter-spacing: 0.08em; -} -.extra-mode-download-btn-hint { - color: #8ac89a; - font-size: 0.72rem; - font-weight: 700; - line-height: 1.3; - letter-spacing: 0.16em; - text-transform: uppercase; -} -.extra-mode-install-log { - margin-top: 18px; - min-height: 8.8em; - padding: 0; - border: none; - background: transparent; - color: #effff3; - font-family: inherit; - font-size: clamp(0.78rem, 1.8vw, 1rem); - line-height: 1.65; - white-space: pre-wrap; - opacity: 0; - transform: translate3d(0, 8px, 0); - transition: opacity 220ms ease, transform 220ms ease; -} -.extra-mode-overlay.show-install-log .extra-mode-install-log { - opacity: 1; - transform: translate3d(0, 0, 0); -} -.extra-mode-status-line { - min-height: 1.5em; - margin-top: 8px; - color: #7dff9b; - font-size: clamp(0.84rem, 1.9vw, 1.04rem); - font-weight: 700; - opacity: 0; - transform: translate3d(0, 8px, 0); - transition: opacity 180ms ease, transform 180ms ease; -} -.extra-mode-status-line.success { - opacity: 1; - transform: translate3d(0, 0, 0); -} -.extra-mode-overlay.hacking .extra-mode-screen { - animation: extra-mode-screen-hack 620ms steps(4, end) forwards; -} -.extra-mode-overlay.hacking .extra-mode-boot-view { - animation: extra-mode-hack-flash 620ms linear forwards; -} -.extra-mode-overlay.finishing { - opacity: 0; - visibility: hidden; -} -@keyframes extra-mode-cursor-blink { - 0%, 48% { - opacity: 1; - } - 49%, 100% { - opacity: 0; - } -} -@keyframes extra-mode-screen-hack { - 0% { - filter: brightness(1) contrast(1); - } - 18% { - filter: brightness(1.35) contrast(1.28); - } - 36% { - filter: brightness(0.2) contrast(1.7); - } - 58% { - filter: brightness(0.92) contrast(1.46); - } - 100% { - filter: brightness(0) contrast(1); - } -} -@keyframes extra-mode-hack-flash { - 0% { - opacity: 1; - transform: translate3d(0, 0, 0) scale(1); - } - 18% { - opacity: 1; - transform: translate3d(0, -2px, 0) scale(1.01); - } - 40% { - opacity: 0.3; - transform: translate3d(0, 2px, 0) scale(0.995); - } - 100% { - opacity: 0; - transform: translate3d(0, 0, 0) scale(0.98); - } -} -@keyframes extra-mode-collapse { - 0% { - opacity: 1; - transform: translate3d(0, 0, 0) rotate(0deg) scale(1); - filter: saturate(1) brightness(1); - } - 34% { - opacity: 1; - transform: translate3d(0, 16px, 0) rotate(-1.6deg) scale(0.99); - filter: saturate(0.82) brightness(0.9); - } - 100% { - opacity: 0; - transform: translate3d(0, 220px, 0) rotate(8deg) scale(0.96); - filter: saturate(0.3) brightness(0.46); - } -} -@keyframes extra-mode-fragment-drop { - 0% { - opacity: 1; - transform: translate3d(0, 0, 0) rotate(0deg) scale(1); - } - 100% { - opacity: 0; - transform: translate3d(16px, 260px, 0) rotate(16deg) scale(0.7); - } -} -body.extra-mode-transition .header, -body.extra-mode-transition .progress-subheader, -body.extra-mode-transition .card, -body.extra-mode-transition #next-btn, -body.extra-mode-transition .footer-gradient, -body.extra-mode-transition footer { - animation: extra-mode-preboot-page-glitch 360ms steps(4, end) both; - transform-origin: top center; -} -body.extra-mode-transition .c-button, -body.extra-mode-transition .header-conference-logo, -body.extra-mode-transition .footer-logo, -body.extra-mode-transition #progress-mascot-anchor { - animation: extra-mode-preboot-fragment-glitch 360ms steps(4, end) both; - transform-origin: center; -} -@keyframes extra-mode-preboot-noise { - 0% { - opacity: 0.14; - transform: translate3d(0, 0, 0); - } - 25% { - opacity: 0.28; - transform: translate3d(-2px, 1px, 0); - } - 50% { - opacity: 0.2; - transform: translate3d(2px, -1px, 0); - } - 75% { - opacity: 0.34; - transform: translate3d(-1px, 2px, 0); - } - 100% { - opacity: 0.16; - transform: translate3d(0, 0, 0); - } -} -@keyframes extra-mode-preboot-bands { - 0% { - transform: translate3d(0, 0, 0) scaleY(1); - } - 50% { - transform: translate3d(0, -8px, 0) scaleY(1.06); - } - 100% { - transform: translate3d(0, 8px, 0) scaleY(0.96); - } -} -@keyframes extra-mode-preboot-invert { - 0% { - opacity: 0; - } - 40% { - opacity: 0.92; - } - 100% { - opacity: 1; - } -} -@keyframes extra-mode-preboot-blackout { - 0% { - opacity: 0.18; - } - 100% { - opacity: 1; - } -} -@keyframes extra-mode-preboot-page-glitch { - 0% { - filter: none; - transform: translate3d(0, 0, 0); - } - 18% { - filter: contrast(1.28) saturate(1.22); - transform: translate3d(2px, -1px, 0); - } - 34% { - filter: hue-rotate(55deg) contrast(1.5); - transform: translate3d(-3px, 1px, 0); - } - 54% { - filter: contrast(0.9) brightness(1.5); - transform: translate3d(1px, 2px, 0); - } - 72% { - filter: invert(1) contrast(1.55) brightness(1.05); - transform: translate3d(-1px, 0, 0); - } - 100% { - filter: brightness(0) contrast(1); - transform: translate3d(0, 0, 0); - } -} -@keyframes extra-mode-preboot-fragment-glitch { - 0% { - filter: none; - transform: translate3d(0, 0, 0); - } - 22% { - filter: contrast(1.36) saturate(1.4); - transform: translate3d(3px, -2px, 0); - } - 42% { - filter: hue-rotate(75deg) contrast(1.55); - transform: translate3d(-2px, 2px, 0); - } - 68% { - filter: invert(1) contrast(1.5) brightness(1.08); - transform: translate3d(2px, 1px, 0); - } - 100% { - filter: brightness(0) contrast(1); - transform: translate3d(0, 0, 0); - } -} -body.extra-mode-active { - background: #000; - color: #d7ffd9; - font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", monospace; -} -body.extra-mode-active .header { - position: static; - width: auto; - height: auto; - padding: 14px 24px 14px 132px; - background: rgba(0, 0, 0, 0.94); - box-shadow: none; - border-bottom: 1px solid rgba(125, 255, 155, 0.18); - backdrop-filter: blur(10px); -} -body.extra-mode-active .header-logo { - gap: 14px; -} -body.extra-mode-active .header-conference-logo, -body.extra-mode-active .footer-logo { - filter: grayscale(1) brightness(2.3) contrast(0.82); -} -body.extra-mode-active .header-product-mark, -body.extra-mode-active .logo-container, -body.extra-mode-active .logo-container span { - color: #f5fff3; -} -body.extra-mode-active .header-tagline { - color: #8ac89a; -} -body.extra-mode-active .language-toggle { - border-color: rgba(125, 255, 155, 0.24); - background: rgba(0, 0, 0, 0.34); - color: #d7ffd9; - box-shadow: none; -} -body.extra-mode-active .language-toggle:hover, -body.extra-mode-active .language-switch.open .language-toggle { - background: rgba(125, 255, 155, 0.08); - color: #f7fff8; - box-shadow: none; - border-color: #7dff9b; -} -body.extra-mode-active .language-toggle:focus-visible { - outline-color: #7dff9b; -} -body.extra-mode-active .language-menu { - border-color: rgba(125, 255, 155, 0.24); - background: rgba(0, 0, 0, 0.98); - box-shadow: none; -} -body.extra-mode-active .language-menu-item { - color: #8ac89a; -} -body.extra-mode-active .language-menu-item:hover, -body.extra-mode-active .language-menu-item.active { - background: rgba(125, 255, 155, 0.08); - color: #f7fff8; -} -body.extra-mode-active .footer-gradient { - height: 1px; - background: linear-gradient(90deg, rgba(125, 255, 155, 0), rgba(125, 255, 155, 0.9), rgba(125, 255, 155, 0)); -} -body.extra-mode-active footer { - padding: 18px 24px 28px; - border-top: none; - background: rgba(0, 0, 0, 0.94); -} -body.extra-mode-active .footer-info { - width: min(960px, 100%); -} -body.extra-mode-active .copyright { - color: #8ac89a; - white-space: normal; -} -body.extra-mode-active .copyright a { - color: #7ee7ff; -} -body.extra-mode-active .main-content { - padding-top: 0; -} -body.extra-mode-active .progress-subheader { - position: static; - height: auto; - padding: 24px 24px 0; - background: transparent; - box-shadow: none; -} -body.extra-mode-active .progress-reward { - width: 100%; - max-width: 960px; - padding: 18px 20px 16px; - border: 1px solid rgba(125, 255, 155, 0.28); - border-radius: 0; - background: rgba(0, 0, 0, 0.92); - box-shadow: none; -} -body.extra-mode-active .progress-caption, -body.extra-mode-active .progress-summary-label, -body.extra-mode-active #score-label, -body.extra-mode-active .secret-note, -body.extra-mode-active .unlock-note, -body.extra-mode-active #special-link-section p, -body.extra-mode-active #challenge-complete-section p, -body.extra-mode-active #explanation { - color: #8ac89a; -} -body.extra-mode-active #progress, -body.extra-mode-active #score-value, -body.extra-mode-active #perfect-time-value, -body.extra-mode-active .home-title, -body.extra-mode-active .secret-title { - color: #f5fff3; - font-family: inherit; -} -body.extra-mode-active .progress-track { - background: rgba(125, 255, 155, 0.12); -} -body.extra-mode-active #progress-fill { - background: linear-gradient(90deg, #39ff88, #7dff9b); -} -body.extra-mode-active .progress-mascot-anchor { - display: none; -} -body.extra-mode-active main { - max-width: 960px; - padding: 24px; -} -body.extra-mode-active .card, -body.extra-mode-active #next-btn, -body.extra-mode-active #error-msg { - border: 1px solid rgba(125, 255, 155, 0.24); - border-radius: 0; - background: rgba(0, 0, 0, 0.92); - color: #d7ffd9; - box-shadow: none; -} -body.extra-mode-active #error-msg { - color: #ff8a94; - border-color: rgba(255, 95, 115, 0.4); - background: rgba(0, 0, 0, 0.92); -} -body.extra-mode-active .chip, -body.extra-mode-active #result-badge, -body.extra-mode-active .home-lead, -body.extra-mode-active #correct-answer-text, -body.extra-mode-active #answer-code-label { - font-family: inherit; -} -body.extra-mode-active #question-text { - color: #f7fff8; - font-family: inherit; - text-shadow: 0 0 12px rgba(255, 255, 255, 0.08); -} -body.extra-mode-active .chip { - background: rgba(0, 0, 0, 0.92); - color: #7dff9b; - border: 1px solid rgba(125, 255, 155, 0.24); - border-radius: 0; - letter-spacing: 0.08em; -} -body.extra-mode-active pre { - background: #000; - border: 1px solid rgba(125, 255, 155, 0.24); - border-left: 1px solid #7dff9b; - border-radius: 0; - color: #ecffed; -} -body.extra-mode-active pre code.hljs, -body.extra-mode-active pre code.hljs span, -body.extra-mode-active pre code.hljs .hljs-subst { - color: #f7fff8 !important; -} -body.extra-mode-active pre code.hljs .hljs-comment, -body.extra-mode-active pre code.hljs .hljs-quote { - color: #bbcfbf !important; -} -body.extra-mode-active pre code.hljs .hljs-keyword, -body.extra-mode-active pre code.hljs .hljs-built_in, -body.extra-mode-active pre code.hljs .hljs-title, -body.extra-mode-active pre code.hljs .hljs-type, -body.extra-mode-active pre code.hljs .hljs-literal { - color: #ffffff !important; - font-weight: 700; -} -body.extra-mode-active pre code.hljs .hljs-string, -body.extra-mode-active pre code.hljs .hljs-number, -body.extra-mode-active pre code.hljs .hljs-symbol, -body.extra-mode-active pre code.hljs .hljs-bullet { - color: #effff3 !important; -} -body.extra-mode-active .inline-code { - border: 1px solid rgba(125, 255, 155, 0.24); - border-radius: 0; - background: rgba(255, 255, 255, 0.04); - color: #9bffb2; - font-family: inherit; -} -.extra-mode-clock { - position: fixed; - top: 14px; - left: 14px; - z-index: 3200; - display: none; - min-width: 9.4ch; - padding: 6px 10px; - border: 1px solid rgba(125, 255, 155, 0.28); - background: rgba(0, 0, 0, 0.92); - color: #f7fff8; - font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", monospace; - font-size: 0.96rem; - font-weight: 700; - letter-spacing: 0.12em; - line-height: 1.2; - text-align: center; - text-shadow: 0 0 10px rgba(255, 255, 255, 0.08); -} -body.extra-mode-active .extra-mode-clock { - display: block; -} -body.extra-mode-active .choice-btn, -body.extra-mode-active .c-button, -body.extra-mode-active .special-link, -body.extra-mode-active #nickname-input, -body.extra-mode-active #admin-keyword-input { - border: 1px solid rgba(125, 255, 155, 0.28); - border-radius: 0; - background: rgba(0, 0, 0, 0.34); - color: #d7ffd9; - box-shadow: none; - font-family: inherit; -} -body.extra-mode-active .choice-btn:hover:not(:disabled), -body.extra-mode-active .c-button:hover, -body.extra-mode-active .special-link:hover { - opacity: 1; - background: rgba(255, 255, 255, 0.05); - border-color: #7dff9b; -} -body.extra-mode-active .choice-btn.correct, -body.extra-mode-active #result-badge.correct { - color: #8fffaf; - border-color: rgba(143, 255, 175, 0.36); - background: rgba(0, 0, 0, 0.92); -} -body.extra-mode-active .choice-btn.incorrect, -body.extra-mode-active #result-badge.incorrect { - color: #ff8a94; - border-color: rgba(255, 95, 115, 0.4); - background: rgba(0, 0, 0, 0.92); -} -body.extra-mode-active #correct-answer-text { - background: rgba(0, 0, 0, 0.92); - color: #dfffe4; - border-radius: 0; - border: 1px solid rgba(125, 255, 155, 0.18); -} -body.extra-mode-active .helper-message.error { - color: #ff8a94; -} -body.extra-mode-active .helper-message.success { - color: #8fffaf; -} -body.extra-mode-active .helper-message.pending, -body.extra-mode-active #play-link, -body.extra-mode-active a { - color: #7ee7ff; -} -body.extra-mode-active #challenge-complete-section, -body.extra-mode-active #special-link-section, -body.extra-mode-active #perfect-score-section { - border-top-color: rgba(125, 255, 155, 0.18); -} - -@media (max-width: 768px) { - .extra-mode-crash-view, - .extra-mode-boot-view { - padding: 24px 16px 28px; - } - .extra-mode-download-btn { - width: 100%; - } - .extra-mode-clock { - top: 10px; - left: 10px; - font-size: 0.88rem; - } - body.extra-mode-active .header { - padding: 12px 16px 12px 102px; - } - body.extra-mode-active .progress-subheader, - body.extra-mode-active main { - padding-left: 16px; - padding-right: 16px; - } - body.extra-mode-active footer { - padding: 18px 16px 24px; - } -} - -@media (prefers-reduced-motion: reduce) { - .extra-mode-overlay, - .extra-mode-overlay::before, - .extra-mode-overlay::after, - .extra-mode-crash-view, - .extra-mode-boot-view, - .extra-mode-recover-line, - .extra-mode-boot-output, - .extra-mode-cursor, - body.extra-mode-transition .header, - body.extra-mode-transition .progress-subheader, - body.extra-mode-transition .card, - body.extra-mode-transition #next-btn, - body.extra-mode-transition .footer-gradient, - body.extra-mode-transition footer, - body.extra-mode-transition .c-button, - body.extra-mode-transition .header-conference-logo, - body.extra-mode-transition .footer-logo, - body.extra-mode-transition #progress-mascot-anchor { - animation-duration: 1ms !important; - animation-delay: 0ms !important; - transition-duration: 1ms !important; - } -} diff --git a/quiz-app/public/assets/extra-mode.js b/quiz-app/public/assets/extra-mode.js deleted file mode 100644 index 7f0df09..0000000 --- a/quiz-app/public/assets/extra-mode.js +++ /dev/null @@ -1,322 +0,0 @@ -(function (global) { - function createExtraModeController(options) { - if (!options || typeof options !== 'object') { - throw new Error('extra mode controller options are required'); - } - - const closeLanguageMenu = requireOption(options, 'closeLanguageMenu'); - const getElapsedMilliseconds = requireOption(options, 'getElapsedMilliseconds'); - const hideError = requireOption(options, 'hideError'); - const onSequenceOverlayHidden = requireOption(options, 'onSequenceOverlayHidden'); - const scrollPageToTop = requireOption(options, 'scrollPageToTop'); - const getIsChallenge = requireOption(options, 'getIsChallenge'); - const onSequenceFinish = requireOption(options, 'onSequenceFinish'); - - const overlay = document.getElementById('extra-mode-overlay'); - const downloadBtn = document.getElementById('extra-mode-download-btn'); - const commandText = document.getElementById('extra-mode-command-text'); - const bootOutput = document.getElementById('extra-mode-boot-output'); - const installLog = document.getElementById('extra-mode-install-log'); - const statusLine = document.getElementById('extra-mode-status-line'); - const clock = document.getElementById('extra-mode-clock'); - - const command = '$ go mod download'; - const clockDefaultText = '00:00.000'; - const buildSuccessText = 'Build successful.'; - const preBootGlitchDelay = 180; - const preBootInvertDelay = 180; - const bootText = [ - 'extra mode bootstrap terminal ready', - 'staged command: $ go mod download', - 'hidden source: hidden.gocon.jp/extra/mode/...', - '', - 'Press Enter to run the hidden installer.', - 'Type carefully. Panic is recoverable.', - ].join('\n'); - const downloadLines = [ - 'go: downloading github.com/gopher/brain-juice v1.0.0', - 'go: downloading golang.org/x/super-hard-mode v0.9.9', - 'go: downloading github.com/gocon/secret-weapon v2.0.1+incompatible', - 'go: downloading hidden.gocon.jp/extra/mode/runtime v0.0.7', - 'go: downloading hidden.gocon.jp/extra/mode/terminal v0.0.3', - 'go: found hidden.gocon.jp/extra/mode/runtime in hidden.gocon.jp/extra/mode/runtime v0.0.7', - 'go: found hidden.gocon.jp/extra/mode/terminal in hidden.gocon.jp/extra/mode/terminal v0.0.3', - ]; - - let introRunning = false; - let installRunning = false; - let sequenceToken = 0; - let pendingStart = null; - let clockFrameID = 0; - - function formatClock(elapsedMilliseconds) { - const total = Math.max(0, elapsedMilliseconds); - const minutes = String(Math.floor(total / 60000)).padStart(2, '0'); - const seconds = String(Math.floor((total % 60000) / 1000)).padStart(2, '0'); - const milliseconds = String(total % 1000).padStart(3, '0'); - return `${minutes}:${seconds}.${milliseconds}`; - } - - function renderClock(now) { - clock.textContent = formatClock(getElapsedMilliseconds(now)); - } - - function stopClock(resetDisplay) { - const shouldReset = resetDisplay !== false; - if (clockFrameID && typeof global.cancelAnimationFrame === 'function') { - global.cancelAnimationFrame(clockFrameID); - } - clockFrameID = 0; - if (shouldReset) { - clock.textContent = clockDefaultText; - } - } - - function scheduleClockTick() { - if (!getIsChallenge() || !document.body || !document.body.classList.contains('extra-mode-active')) { - clockFrameID = 0; - return; - } - renderClock(); - if (typeof global.requestAnimationFrame !== 'function') { - clockFrameID = 0; - return; - } - clockFrameID = global.requestAnimationFrame(() => { - clockFrameID = 0; - scheduleClockTick(); - }); - } - - function startClock() { - stopClock(false); - renderClock(); - if (typeof global.requestAnimationFrame !== 'function') { - return; - } - clockFrameID = global.requestAnimationFrame(() => { - clockFrameID = 0; - scheduleClockTick(); - }); - } - - function freezeClockAtElapsedMilliseconds(elapsedMilliseconds) { - stopClock(false); - clock.textContent = formatClock(Math.max(0, elapsedMilliseconds)); - } - - function pauseClock(now) { - renderClock(now); - stopClock(false); - } - - function prefersReducedMotion() { - try { - return Boolean(global.matchMedia && global.matchMedia('(prefers-reduced-motion: reduce)').matches); - } catch (_) { - return false; - } - } - - function scheduleStep(token, callback, delay) { - if (typeof global.setTimeout === 'function') { - global.setTimeout(() => { - if (token !== sequenceToken) { - return; - } - callback(); - }, delay); - return; - } - if (token === sequenceToken) { - callback(); - } - } - - function resetOverlay() { - overlay.className = 'extra-mode-overlay'; - overlay.setAttribute('aria-hidden', 'true'); - } - - function cancelSequence() { - sequenceToken += 1; - introRunning = false; - installRunning = false; - pendingStart = null; - if (document.body) { - document.body.classList.remove('extra-mode-transition'); - } - resetOverlay(); - } - - function setActive(value) { - if (document.body) { - document.body.classList.toggle('extra-mode-active', Boolean(value)); - if (!value) { - document.body.classList.remove('extra-mode-transition'); - } - } - if (!value) { - stopClock(); - cancelSequence(); - } - } - - function typeCommand(token, text, index, onComplete) { - if (token !== sequenceToken) { - return; - } - if (!text) { - onComplete(); - return; - } - commandText.textContent = text.slice(0, index + 1); - if (index >= text.length - 1) { - onComplete(); - return; - } - scheduleStep(token, () => { - typeCommand(token, text, index + 1, onComplete); - }, prefersReducedMotion() ? 0 : 56); - } - - function appendInstallLine(line) { - installLog.textContent = installLog.textContent - ? `${installLog.textContent}\n${line}` - : line; - } - - function finishSequence(token) { - if (token !== sequenceToken) { - return; - } - if (document.body) { - document.body.classList.remove('extra-mode-transition'); - } - const nextStart = pendingStart; - pendingStart = null; - const shouldStartTiming = Boolean(nextStart); - if (nextStart) { - onSequenceFinish(nextStart); - scrollPageToTop(); - } - overlay.classList.add('finishing'); - scheduleStep(token, () => { - resetOverlay(); - introRunning = false; - installRunning = false; - if (shouldStartTiming) { - onSequenceOverlayHidden(); - } - }, prefersReducedMotion() ? 0 : 360); - } - - function streamInstallLines(token, index) { - const lineIndex = index === undefined ? 0 : index; - if (token !== sequenceToken) { - return; - } - appendInstallLine(downloadLines[lineIndex]); - if (lineIndex >= downloadLines.length - 1) { - statusLine.textContent = buildSuccessText; - statusLine.className = 'extra-mode-status-line success'; - scheduleStep(token, () => { - overlay.classList.add('hacking'); - }, prefersReducedMotion() ? 0 : 180); - scheduleStep(token, () => { - finishSequence(token); - }, prefersReducedMotion() ? 0 : 720); - return; - } - scheduleStep(token, () => { - streamInstallLines(token, lineIndex + 1); - }, prefersReducedMotion() ? 0 : 70); - } - - function triggerInstall() { - if (!introRunning || installRunning || !pendingStart) { - return; - } - installRunning = true; - downloadBtn.disabled = true; - commandText.textContent = ''; - installLog.textContent = ''; - statusLine.textContent = ''; - statusLine.className = 'extra-mode-status-line'; - overlay.classList.add('installing', 'show-install-log'); - const token = sequenceToken; - typeCommand(token, command, 0, () => { - streamInstallLines(token); - }); - } - - function beginBootSequence(token) { - if (token !== sequenceToken) { - return; - } - bootOutput.textContent = bootText; - downloadBtn.disabled = false; - overlay.classList.remove('glitching', 'inverting'); - overlay.classList.add('booting', 'boot-ready', 'show-output'); - } - - function startSequence(excludeIds, nextPastCorrect, forceChallenge) { - if (introRunning) { - return; - } - introRunning = true; - sequenceToken += 1; - const token = sequenceToken; - pendingStart = { - excludeIds, - pastCorrect: nextPastCorrect, - forceChallenge: Boolean(forceChallenge), - }; - if (document.body) { - document.body.classList.add('extra-mode-transition'); - } - closeLanguageMenu(); - hideError(); - downloadBtn.disabled = true; - commandText.textContent = ''; - bootOutput.textContent = ''; - installLog.textContent = ''; - statusLine.textContent = ''; - statusLine.className = 'extra-mode-status-line'; - overlay.className = 'extra-mode-overlay active glitching'; - overlay.setAttribute('aria-hidden', 'false'); - scheduleStep(token, () => { - overlay.classList.remove('glitching'); - overlay.classList.add('inverting'); - }, prefersReducedMotion() ? 0 : preBootGlitchDelay); - scheduleStep(token, () => { - beginBootSequence(token); - }, prefersReducedMotion() ? 0 : preBootGlitchDelay + preBootInvertDelay); - } - - downloadBtn.addEventListener('click', triggerInstall); - - return { - freezeClockAtElapsedMilliseconds, - isRunning() { - return introRunning; - }, - pauseClock, - setActive, - startClock, - startSequence(excludeIds, nextPastCorrect, forceChallenge) { - startSequence(excludeIds, nextPastCorrect, forceChallenge); - }, - stopClock, - }; - } - - function requireOption(options, name) { - if (typeof options[name] !== 'function') { - throw new Error(`extra mode controller option "${name}" must be a function`); - } - return options[name]; - } - - global.createExtraModeController = createExtraModeController; -})(window); diff --git a/quiz-app/public/assets/progress-gopher.png b/quiz-app/public/assets/progress-gopher.png deleted file mode 100644 index dca3116ff94dd74a60c61bcc6b955102e7c26aad..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4703 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 diff --git a/quiz-app/public/favicon.jpg b/quiz-app/public/favicon.jpg new file mode 100644 index 0000000000000000000000000000000000000000..932ce34782bc1e32e02b9b36ff574099b3fa678a GIT binary patch literal 15945 zcmch-cTkht+cp}zfQU*DAX}wZm5!931&|;_LJv(uNszE5_(M#5Tqp3Kted#-}}ycX3or+Idi_5@5yA=A9-dy>t4^DdtLW+U8j?$i-2n& zU431^nX_jAPnjRU=?vg8;LQK#_3tGAn^T-QbNU@{{W9Qhz~AT2TnC)Je&*cuGpAhu zVF2LFh5wox@ZZ;&v**rVxOnL@3o9G*2Fx|U*)!+PojrfB<|Gqy%VpmNd)o;L4ntlC_H~is`Sr;`9`PBf6WR)Fe8uM9p4mPc*D0Xm(dXC`jltV1_EUP@1-gxb_HK-kbp3m7 zkUsz12UfFssOA)WEC zoB{}uQuV?IH(_oGM#AL$k74V)D?X^+ror10R>R?8M#hEk2sEs5V znBy@D^XsP2$d?2Uy{>j?Y;A(1Gd^6Q$f`1X8ZrGT@#!o7ryFR|wp!l9!1w^phWau> zXh4T0d^&mpr zxG7D^AVhHCMB{?-r--|bw*ugrs#$sS))NyrmOUcPcgNWG@srX)+M>INj^Wk!hTP((7Z znyNB!;5~+D7(WHLo&u&74%$viHOvmeGC2JS9ufLqIr@U`A1y%oV4>}HJSejqwv!fA!5-*#>Z zV=k|d!7_0cEdQ>Oxj_4vR)cXf;9s~>p^J%W14zbG48qm3sO3vA{7+^e7}e8&_? z=v_$uJY$|O69EHB9WEzyg#}w}92JRso$_P;!EYpKB5q@E_&U{mZ1WUQSLvUQplTLOZ0yP&lpHil$V3*( zP!ck&@W{enrjGpj>!lhb%9FUl(zffo5pPuGM;VQ(ZlVjO~Cr~v48yZ zJIied-l7})7S6S&{RZAx-C;dERYfC`5(&9p3#!&>W9@%$+)ngdjaHHjAdsy8jtEPR{WHF-&-L1P^BBbC_vS=M2+3hSA8 zkHT&{`H8{&p%0707uO!muk1p`17;E>F68IW4H$ToPP?gB?X$rA6zubit+U3}|&R~p$ZIPI?+n;=<@4f|~a zJntplZd=cEkr*vLziRH2(0ri%U7mXTNgdRbs0mt%TCtO z&8UhqrmV8=ay7@M)E?17p{rzU^@3KYD#))6B#3d_nRfFRJGyh?($RkD*OZx*YSo6BxuVjwd>wu(@5mw55D{_aTn{En%^RhP)x zZ<=mRM!SRUQglJ8A!WGJqWvs&kUa5yfs4*xCY@M)g-Yu@)=sDo{7~!~Lr{he=KnHp z{8^EOA;>;a8TZ}H9VhXB5V33JaYz|(@8!;3x~D4hL<=|x9g{G{Z1*o48T%&Eo@XwJ zmZU=kgEz2ea)`?@KfHrq2Lj>Tf@Z*gwfXTCV-&wcUqnvNXyUl6Vh%NqQo*?VK2ms| z`7*$C&Zwo{M7Z<9jaj&>yD)i3Oa2u#iY84(nxt79^!c}`bRJhJRmbuIH^z56!bq|L z#^dvyYKxekl^WWwvct(h+C!XQHle zecn1D-JEWbsKNeEos&K92eT}Tq_m8ssItsw=T|b9SWV2k9JmUVwlxg-8o61oCD}4$ zsds2k=7MLspxYhxN%I%S+s`rxrvMI*FZS`m=jd)9rre^OU#52#09xJSH(PzkG~*9C zJ)cOLS67Ye4ctGeq^*DIL;Ri}X0gO3fV9kkcfZ@P&ua_9SZiH93&pTRyAbC%)SR6}IL zCpQN|rT^S3OKM@)gzioVMs@qI5xIg(1f}g8rvPoA;rB|rYlQyVIE|YRLB;yw5-%0h zPTH*ZRz7kME;@ms_r^6z$upH==RaGl(srqO*itCZvI0flEc zu^yA&<&Q0s8fIe6ZX=%(GeU~RL6sKYph>fin-XIt2xDoZ8_=79uuP|^@vBwm6gz(Q z5T%fVpjNJCrvVA6dIZI|`hr@R`W}L{e#VnCk-&hqx{w!0adhUEES&;ku+G?o(9%ZA zxr4`4BI#Qj+kqN&2?h2m|5I>jY&A_yabc>Ll-|odHZ`XByJ~aq6mT*4%gR$3r8Z>( z6Bc?n2)|dfk1Heyig#Pu-5o$!NjU6QUXak3xWqlcIzVLn?A*u))*2E@t7SCx!_=C^ zbP3w}6N1*~;G<4!%Don9)UVoa9I#eKNDftO$5t0< z-(~{(bE^x}E=&2<--TmxQxiS*DzIF^Gu8xu?D=ph^36;En?KlVTH?XAYyQ=jsTMk4 z<3*d>n_I9MwsSc5D~ETU)wQL~&y$D_jh{e1NqGo}58_dVxb=0Yhi*QQ>&vQn<*$um zJ{{#5m>gQVuKHXQIo-Y@cDUTLWs-;Bdn9RRk^z}LabTt> zl!=rzola6BZ~iIdWGJ|K(;uGHVO=lRp8~Eke!_2TY2gB=UoSYov}g|P)yc=a;jb(c_q8}6rey^;y$`vj|;_hx3sSaJJFG?muU*fb(J)h;>5jhY-iQIAKn?14;>EV`adSgP({ke7XJzYQWe!{*DqCE z@(V$JDFlp14qvWNIcjhu4PRKE?Efg~=%ZSm;RecdYtj$}HHDhpQeo+x9vn3@wTaYF zM;qx^>G#daS9}pR?Ex6sGC!FCZNg{8Sfzv3cM-|b|zb6nPNZm?D z)kcGu?+my>xCz=EOQscya_0%>gxqf{7 zn$6sL;El)S;R=+!9at{%AR9N#SH{!$73|{c@7|Zom{*hWQrc9oq7__R0?taOW=Nx6 z-nR)y;qG@%^&2NSZIQ?$tM5|861>XI9JUPRO_AW;g=KzbvT@uz=UM>Jt5rn2ZYn z)vrHC1660yTZd$|dn$rQ@)z&t(}!}km?nlm2K#%gk}4q&*f-C_Db-0?OBa} ztzklpc!(Gc_@*i^x0$weF{^)Tr_D``nA3u+3soMr1XT7fjdKAH6CUX6<)U>wFI~g^>uC9N0SZY-Q%kH`i+xA zIos50AZZYbBm^(Nc2PyY=8Ml98K(db@YEgR#O62J1M0%!r7o}fNo#k?^dBt_N@VtO zH%UGYer@kk*utgYU7QM9V8E%u6`DPu@Sm3vk9EI|)$1dyH6d%(v&0`Xeq2a9uX2at zs!o%6vDitLG&^LZI<`FBBIfCObtLEm(oD|v2{|}Bj%))?C+&p$!-L+ZDoLTlP1xgO z<^*-LUTsR~V?<8@@{%BgWEe&SuDno#?B;lXQ-K;nvX2uM@Tl@<_Kv&<{*=}C?H9wn zYFlZy3Rb)4*_xFb=5MFCOK!jN0Kz8E!NBV7c!-3TU>eCKO4^A*1bgGu}5D!Saj|OTH9`TB>Wc^LzgV z36{i_XV_$Y=y|y;r3!-{HyI;R^5pp?hNlQNbxHguPc3#~Cy`udj;=F%iV^C>((s!F zvY~v+{9S5zZb@iLT^O16UF$DGec4v{iNf~+HOhPxkx3uKd!emFM~WyMv2qysi|o#7 z9$WsjR8H%G(z&U1?2DGTZ4IS?ug!&gP8s*YQ@EBAx*;*TXbmF)cJnIWNT{iQg)U#J zGv34$n&))Y75j#wa7!E6rs>~B~ z84#sI{w1SM0WjgoVbbt-dqA+>Vt#v08FfH^d=LE?NBLdAPS6l5I7=qM>ZbnyjS!;a z*@zjH8EzRFc#Z!d!6TQ{&#sSnx>4!e%t$BB)~R=_@Fh&HN{r-*1M~Kat^aVVrAIHp z25Iek&CC6p;sRl#21nWNcQw-avx&bKO`YtxZ%IWCMVD1-%31Jt8g{Z->uL>Y^iv>H zI@ogT;)Hzy^R(Bv$J|o@Dsxc+hk(lR^-P$=XpBKT72%tAb|b4TQ|^Su)#_xD+7b3sQo_MTEvF z9Oj8~RjBIFO&!J1JQD<}WJ>o4sk{jYN$+&@-FQ-Cx-EWr;0jS)>Byo5$&Y=B6eW*m zn6#$Be}nD&SNGpMKl2~qo^84i2T42Go<&QA(V;D@Au|pqM+aXOe(xRZI&YWw={iN8 zW`S!d_~uYajpVJtMyS}5ldC?w^78PSoSmL=8#jIg|yeb z&$?k2;s}c{d{i&l&uy<5f||(G_id_BVS#?}czm?lGqu8}fs0j;vca0+dU=61qpgw6 z!@2=?AZg3lvY_k8o56i^x;|~n>4iHJGz-QwmP_0HQz|Pmc@N7*4SGU$ThK3djpVIs zdumiv-tEMLMA_TBWw*ra|G=~HIX1i%dp9~dd;Xe%H{N!T=4_ni;2U5)X@u4vUp7IR z8)Ibz^Bnm)?=~xuzwWmu*|Nt zYm>8A@#&MU@~%7ZD&kyBOI@=peS692wIVh93@E!UI6=)&8x7)FF)9_mE~U7Rsn&oY z6cOb}bM~-Wy*>C_g3n!IUMJe#_3M6JXrWoscK?`$@=c|32-ry9%=1Que}JT6xbcS* zt%)VQ&0GJu0D#LZ)GtK!em@0JW#W2-1a#szHAHE)kpq$EPKuNi^C1s-sKw;rZeHQb zO4hbFENY@P=O$v&vaNbmYvYZLei6kc9xwMqf|P4IPE?3hT2^rvMi9m*)4p99qq9KT zDZm_k*%8)U<-AdDfdb{_CM+ZR1a=xu0ZHLu{gR8pRjTXH)2vE4*b_h1{W=(!Ui>4` zZpJm;@v}lHL`i+fu-f%Y7<&YY`k&{Qir^h>4fA{y-i zcgAJqh%IJ##iw*7*y7363o(h)u-wSx(Bt+Tk5G0>T$isc|Erpoua>6(7D@^xsO#eu z5qz?sn0~bBZPKVG#^%W%vk6iLlH15gYkSMoV~~G=7WBPU^osT+Gqj<&{q|^E|opq`%16NUM*e+9cei zoJ9m5>8ly>{g>67!G0Z^sTb@_;K6%C^ zp8LX2P)_R}#*GhTT4O3MvN;@whP1Kx>O(!bi&#uN@FwW~^5G-oeX{m@m?IJ_-Rm^j zdcI^%P*)CZBX|3n8~tPM-6A`tdhsrwg^>ItQ;$_+C`0=TGZ962>DNDfFC;%}yVF&m zNzqGu9Vs0-Iq3grF?&#`j-TXu)(Ak6$uqj3R0JuKT4pUs(?* zQ~y!^Wso;w(3l2F2}=FbdWN{d8znj3ZBZ>4=$BCXd%W8udhKUXtY@ZP46eu5H_sMo zjDoZ-v5YO3wq+_^M9}=~^9oC10936F_5;#V|`$Lbp@a0}uDd*qU~l;$a}CfpUIC7|dtgiGRrD?S%y2k7d*-!X(KX^IQatf2N-XTs8+wYCrFyPIY0vZeQs&UNM03l*h33dCFIBd3t%eG^Ou6b5`K|__AWj2ebat z$1=ATZP#WILO`j6=x?O0@pVgdO6p48)0dyT8>}H`A5-+M<2f?6(*dTN%*vH5BZ+@k z|1t~RUYqeLgFUC*tq!kE4e^rMsi)@C_EO^^vdgY=a!hurxb-jGOiW$>PV@w3 z5Z0sM2I4fmOATzc`;aYDVF;QSE7^a_Fg|`{p8O0ii`(tA!%3LgaN3abUk8|llx$@z zfdtDzLxbpHg!|r|qt6oOdbAhEx4FBSSSz})VS8f`HPe=z)P6M*U)&wKUzVBHcbkHZ zeMvrRJvn8--a4WgTR}FkNI5W%RlaAl_QBu^2e2Yib1CwMyV5^(N-lc5nS{>Ah9TtO zoR^}5vS^8wYsuk^e5fYBpyeU--bn5X3fd zTv*-3G5`M}qiVlriM)-Y*}*$}wB4&yfI35k0gHuGyEu8WW9Ol{MbI=FP0wXX{C;np zhd1r9m%BR;Dy%ek+br#JM^wNJx^M<(<7Rde&@uI@*6vat8N*d<4=iwi-cN`wmaz_w z;TM?+&zR%01Ao#95b%{J+Vj>mtSH6jj*-y%f7Me)znRiu9-omJ^^g;PJRXVa|QSm#fcfM+%J-w!c#ZZlMavHts}M z+Ur1LrQ|Qg7o_k-1_iOoF=~yx+#7;Ekm!(nix|WO7E^EcL*Wa-rCH!E)34MP#sJI5 z(GYXd{*q$d{Jl^YD|!jJkqQSODD)U!y1xk_&~v}PU*kJb!{H( zV$&&rY+QB#egfz@w%z7DpV`6@n@611`F(fHp z2RpWAQJ0h_Kws@@m$#Ard(gqHY~=`9-|4gl8j}zgK1d3MuEivMK{X=B-F~)Yn2s{c zw9eBl$l7$y?Sp&NRJ8j|1@ivtrVmB8-%9|uh=x+v88Yd1V_L%qYQ+AWhQ^`&D{`IN z#PJs9YK3MaQ#-qPD{Fqstcjg>KlTCZ`pdgbPM5A|^65>hBKRH-n}L*T&5^9$3DzL< zT5~Kp$;m>shtLJ}!%5X5fzwzRYxrn|b^af5bEI}##quxT@zo{IHkuP-Z2k^KgK?Jt zcZDZ@{WEce4O|@j$DX{O=UrN)g<{Wa|L}Q!twpCH-z&mLreWHj=icND%F^NOtrXRX z8VB!@)Z0Y{*+Z;gce@?K`GK(6gK+^4zMXQ#qcBxTf6Nv>sL^`%q>fqJLf{<`|Grj! z_@e#vpWeoXd#nDWI(R8( zme0uU|5Sf9;YX!zaG6H$SO=z3Ie}?U84~ zp_?kQkH5l7>|~}-Y0K^qliivcz+6WWW4J-Me)#(~I~__j4k-?x=U+EblhtJEVCr>cU=U;?(7%_#> z*Gw7ySojT`w^b)dWz&rI;P-foeEdvTP?|w?IG7sdbbl>UnEGC<-D{H_Y&TltK}>xg z8`y$PwN5mo+(h`T1h-4bo&r9GKVR(g-tv$cxS*paIw;lepQ-AL|GotDLr3`}EF+D; zxeeT+d^D}lyQ#DXUbKqNcICJm%hv{cFh{$URT;M+X5xbykt|Tfdo}5vW>Px7Bi#@B z?eJ;!2*%Ge##{Ft+`jjJIato^NHbUunH{A88A;~^FQycR6kD#%-#saAm)0v^I|ZC; z7hVsXP99IxnARLh^&)ZbQM>D#(Vnt&C1Mlin_eTnxV?rDZc%CARSYwE?8SqraK#3F zx8b%=QjmP)n|}p)o+#7HZP;OEH)87VO$|^Il^V-e6W7x3j8R-SR9?fQ$b#@*zQ`*B zcT{!r!)JiydUBlvh(FeUt$Z@iASyALRjLGgK#iuyfsny=otEWA`5uBtMa}NRK_|5$C5&JvCjy zrd5_{rE>FwfIGVRl4S;m6=W|~x<#y&9((3WfSFA!8kC}n3JaZVEv;O`V=i6p59)a2 zvy#c`AAwLa&kyxQT|{BGtcls(+OZ4#blxv?9jd`nH7i|@EdSoa;oC^7+2Wih&VIz} zOYY^Wv!Yx18Wf!XE|czq3dtW94ZbSrL0;$kM^aa=IJ5C)Gr5_2lWx>!hLWm;qW4HC zv)g!gVyUK#=wv=G?G8#e%dbn7&x^mraQ_ zk@TP2?iuU<0+t;|1}m7E1<@xt@Ws4#PRoElf9w;n0+{%HAr)NVIXVjoeia+9_~(zI z^d@n;LO0X5#pD3SP+b@dz7D{O&{` zh+E(98mZWD;s{vjro3O3-d~(2vD^eD zMAVD&`AL23{jnVVc_un!*(gA4nO1JbC8Y{y@=8LjaGGi4+`>(|Ce1uNxMp+@oB$VO zh|;yFY&6+mHWS0gI%F5zgH+D->Pzo16LoEkge@k@B3e1M5d3#hQ|r*`#8zSH#%hoxgVmM8haA~812B4om$!lfzDW0WTJf#*>IJ&bAIqAk!B;}6&Xsay=E9lnJ zo~@N#H9Zhf0oxJaU>R_VD1#~6@z*B^aBPHqdyi2h$a`TP%q%Q2?o$*JRB_5#S`Cf~ z-yI~zAOp1)Bf9hi% zHIBS~A?<~PWqJ4B5dZ0*5lrD_d54H%jC8z2=t%SmBo6!@@9DpuD&uI5UNWlF7h5M3 zOYMtpn^mYOFAO_srM3ed@9oztOf_xDxT_gi3f^>t#-8QO$U{geqSfn7JjzB8P`*%~ zdYCbiEA8Yw)rY1Mewk`Wu8Cx!vJow!6zyY@1(>Ba%6SBh$(Odvvj1y1jFh_BO{mf^ zL(P7&w#T>?+%7aB$X!5}4|uP6ob5dta_OQ!%yS;r{NWcK5I&{2wp46qD(x`3vW$#A zxSHXeQ9P^#vUD9q-VDKTEsLE3p7l4G#ND^a_hwk9zmRyfsbq3S>RPoPr})623IBeK zxn)28SDfZ+(+aa}5w23g?d+xDUie#e`3-i;3^5W-?%m`mtQFr3qiBPvvJ`>E($(e> zEg;>V;*mer`-bjC9`TgxAfFVV!Vq)%)s~NL4ukRa6`75gHc6rZ{0iKHszB0p>Nc$j z(eg2#7_OB8{_qYdX2@yFAW$ z!)d=~!Zj@1x%vjRjyTcFe(pJA0k83R5glYo&R6SIeV@}qZThz<28W5;34^kklHQCUn)`6^6H zn9B6uToC;k?pQ)S1w1Y^#qXz1-^AepYkC9GeuBr`jj8RtR2ST|c~SAg^pw%KMebpo zBCTl~GT*ElyEecThObeP~@woG$~34gda(Hz&^E|BZG zN1Iw~nAo#bCb?U~FH3hhx%cc8F!1LdQboLWGH#JisH2zUFoulUjR z1K(Tq4JtPSeqco;HoF5}kX2`UTD_aDh=~it=Vlbgbv{SEM@w|uK}^?lO?(o@jm8bk zNBWX`7i(uVYC>Y=+27(Gc7v}cR)aZo@2A(lTsO;jfe@Dz>s z;}Fgcci6sc8%faS_1gOMc%;>;+|nJ&+cCiIYn><^cE{_oxc4h5g5TNd-UaaB_ z>Q=0^JddH5dY^r@_e4}kkAsxi?lE>sRbRC+retmWM|t;d7}k!GMnbb58#*`T`s7nG3BR5EZC=mLL=Lq}N&4BE99#=DQjN`_N-~KZ2kHQ2 zd#MyjGh&Q|GLTE#)0cW9#o)Z6DaFC*Gr^(DAClBlTE@R^$U2U^P(EM?k$e9B`aALl zzJ5-te?;_y$KIy&vHO{9r&-Ocks{rsyGB+Zi1-OfU7 zEH=Eb8e?tq^Gs7)0=rWCFp1;RLhbDVII-fNLtAv)ce9WU$b$;!yD}CHL4nd-=2WLC zw9FG80U;$|O)U}%4SRA7GOu^8w;3!Ogfe!*=XvGiWh*IGes1>}vI2e&5oiP(jXXbo zxH$6*rm200ex2-ouzC_lxAIxSPQnl3D1iy10;m9Xwr686$n=)jJD+$WMaXt-Vkq)z z?*~10bdq;p)g-US6YIWj%+46`DEgL?IM64#=xUZcRlVXMmQepSS9^oy^XwmvN;hM~hW)ixuQC{X>QMi{ zivw0{gdv}|fc)WUNwRnhX#-YzyR-04$@u5r3`h%DOnqyq@}T{%9`*402^rsGP~?a{ zP2catp*IWEpQk2RB;9QE*`uEpXA&G00SRPoLDa`RTbYK2)rV z8DI@7WDjSuHb*6CT8p(^teEW|`p)IDQN-o4>Hv`<-3*TaZl^EC33YA~bZg)sQ5*`7H0v9ykW`x}D_u~mF!w0iUH?@aq8*~1ZlSHK4Z}|1dfds`#On8vY)n%! zIV;?O%1^-)gL(%Aj0{f1bz3Q(#fu(BIt2d*4^Cj(buSxjrH$JQdW{Iq*49}KPHECJ zGnw_@sI&73t-o;2IiBi9S(g+Ce5P!^WFR(Nmt2jK%AxlY10eacBtp7Dma(y67+v7w zK#W3bg&J;(To+Y_Swa=Pq6XcVc50 zqLc~xUEb~mc9j?s{?-I$IKDudJfFUp%=?^x31h!;$1AL;3Q-NABrC9HPztW(w4f)o@ADy6JJDO))Hk!e=v}o5uQ{u1?_8Hrz7%I*Yqium5HYj? z_jc6EJklVKq!{B=%bR1KNX%K2qq#_u(wiak0S|ivN?Qe zX31qEp6p2Cjl#azWa}zyP>HWca}YBd@+;^j<9igECKuH)cRA<4A@+yrI7TBjdfic6d!t+-9SLgF zZxy}pHXS7{sJP5$k*{+xjL~1!tih@}&ih5&5H#!a z6!~krm&<)1-6lz^kGKVwjeL;;s*(rCahuDuU^GSTD4vjcj_l z26O)H(rhQ7Ml6u(CEle@2h4M_7dq9uo95dnK|h9mtxotpQN()fVDDFRaZHzz(980< ziwbLZa>=DWCbyU5_y*ixmYE{& z{G49uZjUz8x#cCFR9KfkUqjQN>z)+CT}}aSw6B#=r%>y{JXPT;rPP^T`)hPfWS`?A zyEK>_TNnbb_}!3eR2+hux1XIgl#OBJ$jra`X`3%68A-$0vkS-V4%Sd-Cs=Y;Lb9&% z2S_a5vUMOg=~OuH_>-bjfJ5Px;N!7R-33>_QudS4=q?_uzwnD; z!o045VdHSQ2Z{Q;kd%AU8l4aHt6y8C!KysIa4LH2PrjbHkPPA;#f^{11#R_Ve^oen zME*Dms$Cf?@KDNe^7ZQ&&qFm1ONa)4gVFjSEuPbUIx0>rwc0WP!&I?0Y#d$3g(M%n zz^7rq$R4CaAQAR*Fw)9}mLu|;h-6xm;`$yrn3V>vfB zX;O<4B+34~W~q@1{zv}nkCb49=u8B^4y`WH!cY+pRha1XiIrwoHATfOS2O8ZX@?3h zhj|`po4<#?lZ0P}_4vT%LDQmm`*3#V2@w4s{|6Cw4)&-u0WQRE`MTRUX77@|WS1%T z)(5QtpRoYSD7UPXw}elp#fITwex6>hhNlG-^#9rsX89FVHg1@%{kjNde@aCLzHCeh{6^T%cd>%k3J~3IPz!s3z44_xvgSLwZ;etvGN#~M zb{4sKgWtsauX$LthS}n94zM|_OEhj2*Rz9}L-pKiZFYnQ*_lOrSaKE&NbS`hf?IZt zjYv(!$~8!tCm)$WvX%4jwFeD0gXn4PC)zHlli8h&O9_{#0c6H7?w-x}tmC*8%px7e3j9gd(WvrY#NMXd1lsQ^%0$-}mjFh+K!Z+dk8Ec=KISRDw{MZa4I4HJ@e# zXIJuXPKTih{JUdrD>?P)U8aRKD=mp9b9~z_tZuWLl1ls-NozfYz1{Cv4u4_5+NKAh z2R}b61$T9uIAgf{=QFMKmGyM-D;(_lWX{|j!+RlxzkHgGsq1-T_A+?6?;Sxq}+kd2=HO1m`GV6l)_Bb#^HeiFma-9e~F z@>uB1Me#f9nAf+Yd(#dB@$LLyzc;v#bgneb-ZqXg3UyofR^Ghigh+46v&eXr)hqg% z=Vse%(SSIdX=MGjeyo3tBv6=5+EjAul=U-;+j};(Sb-Cf9An=e#jKV2J(0?b-67`V z*Nj#z^{!9Ufa4YICfK%NK}J=n-;6WlTBg*rCf2AvobeF&+nfCvQxo!D&cyu4H?qSfsupqbQXOqJ!8;G$qNk) z+{*YwtHdh&)X=1REDPfjhrB(qsPnIA5{0+_e;pWud5~?0iJ9%$`A6K+iph-OwfqMLfgWpw)C91nog{jJ3^{n5NRitUkjeFQV zXq~l!&R34xpXt=Nljmdw79iHCI*?ThZWo)dz0G`P2CP>@#{F394B!ElikaRMsbl+Wxb-O@){k6`t7Z%LBCli;wFN}8 zJMCkS8xpGr zhp$qxX3U4%+YbeaUQXx+RjN6eXY$W09>FkDK9Q@+qk6v>Vic`QiNU$;KH0Qjq8q-5 z)s%7%0VFKxvB!NTi~+~iszNi$O+iWmAeh$ny3~H^@V_Ey=UD4K;$WEY43sCm{Qgd@ zy`{Cyv*;L;ET7#odjhQetL;Mtg z(D~TP!(;t3uAg!W7=@}R7M=}Od6WT01iL@2Ych^$vQ{l=2InmNYWk>uKd*V&MyQDM z!JT9a)}{^HfB}o^<~|I&z%K{T9%Zm+#kaX{vv>C-zjbEZrtOq64uh!qPQIU*PKf^z PCI4HS{69JXPN)7aw7LtM literal 0 HcmV?d00001 diff --git a/quiz-app/public/icons.svg b/quiz-app/public/icons.svg new file mode 100644 index 0000000..e952219 --- /dev/null +++ b/quiz-app/public/icons.svg @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/quiz-app/public/index.html b/quiz-app/public/index.html deleted file mode 100644 index e7206a2..0000000 --- a/quiz-app/public/index.html +++ /dev/null @@ -1,2450 +0,0 @@ - - - - - - Go Conference 2026 CodeLab - - - - - - - - - - - - -
- -
-
- -
- - -
-
-
-
-
-
-
- Answered -
- 0 / 5 - 回答済み -
-
-
-
-
-
- -
-
-
- -
-
-
- -
-
WELCOME
-

Go Conference 2026 CodeLab

-

- ランダムに選ばれた 5 問に答える Go クイズです。
- 全問正解で素敵なご褒美が貰えます。 -

- -
- -
- -
- -

このブラウザでは問題一覧モードが解放されています。

-
-
- - - - - - - -
- -
クイズ終了! あなたのスコア
-
-
-

エクストラモード全問正解! スペシャルページが解放されました。

- - スペシャルページへ進む - -
- -
-
解き終わるまでの時間
-
-

全問正解の記録を残せます。ニックネームを入力して送信してください。

-
- - -
-

-
-
- - - - - -
-
-
-
- - - - -
- -
- - - - - - - - diff --git a/quiz-app/public/preview/index.html b/quiz-app/public/preview/index.html deleted file mode 100644 index d2b3682..0000000 --- a/quiz-app/public/preview/index.html +++ /dev/null @@ -1,1076 +0,0 @@ - - - - - - Go Conference 2026 CodeLab - 問題一覧 - - - - - - - - - - - -
- -
-
- -
- - -
-
-
-
- -
-
- -

クイズ出題一覧

-

一覧から問題を選ぶと、解説・コード付きで内容を閲覧できます。

-
- -
-
- - - - - - - -
問題ID
-
-
-
-
- - - - - - - - - diff --git a/quiz-app/public/quiz-config.js b/quiz-app/public/quiz-config.js deleted file mode 100644 index 55dc32f..0000000 --- a/quiz-app/public/quiz-config.js +++ /dev/null @@ -1,4 +0,0 @@ -window.__QUIZ_APP_CONFIG__ = Object.assign(window.__QUIZ_APP_CONFIG__ || {}, { - telemetryEndpoint: 'https://script.google.com/macros/s/AKfycbzDHX2qkFfNKfvAhpq1h79DluzYIsLJP4zGmGWL5P0CKSDhTJlyw4dATADdQEK4T-zd/exec', - previewUnlockCode: 'gofar,gotogether', -}); diff --git a/quiz-app/quizzes/code/Rtosshy_q1_answer.go b/quiz-app/quizzes/code/Rtosshy_q1_answer.go deleted file mode 100644 index 1455820..0000000 --- a/quiz-app/quizzes/code/Rtosshy_q1_answer.go +++ /dev/null @@ -1,26 +0,0 @@ -//go:build ignore - -package main - -import ( - "fmt" - "reflect" -) - -type MyError struct{} - -func (e *MyError) Error() string { - return "error occur" -} - -func DoSomething() error { - var err *MyError - return err -} - -func main() { - err := DoSomething() - - fmt.Println("errの型情報:", reflect.TypeOf(err)) - fmt.Println("errの値はnil:", reflect.ValueOf(err).IsNil()) -} diff --git a/quiz-app/quizzes/code/Rtosshy_q1_question.go b/quiz-app/quizzes/code/Rtosshy_q1_question.go deleted file mode 100644 index cc4a2ce..0000000 --- a/quiz-app/quizzes/code/Rtosshy_q1_question.go +++ /dev/null @@ -1,25 +0,0 @@ -//go:build ignore - -package main - -import ( - "fmt" -) - -type MyError struct{} - -func (e *MyError) Error() string { - return "error occur" -} - -func DoSomething() error { - var err *MyError - return err -} - -func main() { - err := DoSomething() - - isErrNil := err == nil - fmt.Println(isErrNil) -} diff --git a/quiz-app/quizzes/code/Rtosshy_q3_question.go b/quiz-app/quizzes/code/Rtosshy_q3_question.go deleted file mode 100644 index f1533ab..0000000 --- a/quiz-app/quizzes/code/Rtosshy_q3_question.go +++ /dev/null @@ -1,14 +0,0 @@ -//go:build ignore - -package main - -import "fmt" - -func main() { - slice := make([]int, 10) - for n := range 10 { - slice = append(slice, n) - } - - fmt.Println(slice) -} diff --git a/quiz-app/quizzes/code/hasegawa_itsuki_q3_question.go b/quiz-app/quizzes/code/hasegawa_itsuki_q3_question.go deleted file mode 100644 index 7d5f83f..0000000 --- a/quiz-app/quizzes/code/hasegawa_itsuki_q3_question.go +++ /dev/null @@ -1,43 +0,0 @@ -//go:build ignore - -package main - -import "fmt" - -func updateA(s string) { - s = "Changed" -} - -func updateB(s *string) { - newValue := "Changed" - s = &newValue -} - -func updateC(s *string) { - *s = "Changed" -} - -func updateD(s **string) { - newValue := "Changed" - *s = &newValue -} - -func main() { - msg := "Original" - - updateA(msg) - fmt.Printf("A: %s\n", msg) - - msg = "Original" - updateB(&msg) - fmt.Printf("B: %s\n", msg) - - msg = "Original" - updateC(&msg) - fmt.Printf("C: %s\n", msg) - - msg = "Original" - ptr := &msg - updateD(&ptr) - fmt.Printf("D: %s\n", msg) -} diff --git a/quiz-app/quizzes/code/makki_d_q2_answer.go b/quiz-app/quizzes/code/makki_d_q2_answer.go deleted file mode 100644 index 1178cf6..0000000 --- a/quiz-app/quizzes/code/makki_d_q2_answer.go +++ /dev/null @@ -1,29 +0,0 @@ -//go:build ignore - -package main - -import ( - "errors" - "fmt" -) - -type MyErr struct{ error } - -func f1() error { return nil } -func f2() *MyErr { return nil } - -var err1 = errors.Join() -var err2 = errors.Join(f1(), f1()) -var err3 = errors.Join(f1(), f2()) - -func main() { - if err1 != nil { - fmt.Println("err1 != nil") - } - if err2 != nil { - fmt.Println("err2 != nil") - } - if err3 != nil { - fmt.Println("err3 != nil") // 表示される - } -} diff --git a/quiz-app/quizzes/code/makki_d_q2_question.go b/quiz-app/quizzes/code/makki_d_q2_question.go deleted file mode 100644 index 9e3f83e..0000000 --- a/quiz-app/quizzes/code/makki_d_q2_question.go +++ /dev/null @@ -1,14 +0,0 @@ -//go:build ignore - -package main - -import "errors" - -type MyErr struct{ error } - -func f1() error { return nil } -func f2() *MyErr { return nil } - -var err1 = errors.Join() -var err2 = errors.Join(f1(), f1()) -var err3 = errors.Join(f1(), f2()) diff --git a/quiz-app/quizzes/code/makki_d_q3_question.go b/quiz-app/quizzes/code/makki_d_q3_question.go deleted file mode 100644 index c8429e1..0000000 --- a/quiz-app/quizzes/code/makki_d_q3_question.go +++ /dev/null @@ -1,14 +0,0 @@ -//go:build ignore - -package main - -type MyType1 map[int]string -type MyType2 = MyType1 -type MyType3 MyType1 -type MyType4 struct{ MyType1 } - -func (MyType1) m1() - -type MyIface interface{ m1() } - -func MyFunc[T MyIface](v []T) {} diff --git a/quiz-app/quizzes/code/ryokotmng_q1.go b/quiz-app/quizzes/code/ryokotmng_q1.go deleted file mode 100644 index ccba043..0000000 --- a/quiz-app/quizzes/code/ryokotmng_q1.go +++ /dev/null @@ -1,12 +0,0 @@ -//go:build ignore - -package main - -import "fmt" - -func main() { - a := []int{1, 2, 3} - b := a[:2] - b = append(b, 99) - fmt.Println(a) -} diff --git a/quiz-app/quizzes/code/sat0ken_q3_question.go b/quiz-app/quizzes/code/sat0ken_q3_question.go deleted file mode 100644 index e355590..0000000 --- a/quiz-app/quizzes/code/sat0ken_q3_question.go +++ /dev/null @@ -1,38 +0,0 @@ -//go:build ignore - -// See: https://gopherbadge.com/ - -package main - -import ( - "image/color" - "machine" - - "tinygo.org/x/drivers/st7789" -) - -var colors = make([]color.RGBA, 2) - -func main() { - // configure the display - machine.SPI0.Configure(machine.SPIConfig{ - Mode: 3, - SCK: machine.SPI0_SCK_PIN, - SDO: machine.SPI0_SDO_PIN, - SDI: machine.SPI0_SDI_PIN, - Frequency: 62_500_000, // 62.5MHz - }) - display := st7789.New( - machine.SPI0, - machine.TFT_RST, // TFT_RESET - machine.TFT_WRX, // TFT_DC - machine.TFT_CS, // TFT_CS - machine.TFT_BACKLIGHT, // TFT_LITE - ) - display.Configure(st7789.Config{ - Rotation: st7789.ROTATION_270, - Height: 320, - }) - - display.FillScreen(color.RGBA{0, 0, 0, 255}) -} diff --git a/quiz-app/quizzes/code/sivchari_q1_answer.go b/quiz-app/quizzes/code/sivchari_q1_answer.go deleted file mode 100644 index 74518aa..0000000 --- a/quiz-app/quizzes/code/sivchari_q1_answer.go +++ /dev/null @@ -1,19 +0,0 @@ -//go:build ignore - -package main - -import "fmt" - -func main() { - // [...]T{index: value, ...} では、最大インデックス + 1 が配列の長さになる - // インデックス指定がない要素は、直前のインデックス + 1 に配置される - a := [...]int{ - 0, // index 0: 値 0 - 3: 3, // index 3: 値 3 - 4, // index 4: 値 4 (直前の index 3 + 1) - 1: 1, // index 1: 値 1 - // index 2: 誰も指定していないのでゼロ値 0 - } - // max index = 4 → len = 5 - fmt.Println(len(a), a) // 5 [0 1 0 3 4] -} diff --git a/quiz-app/quizzes/code/sivchari_q1_question.go b/quiz-app/quizzes/code/sivchari_q1_question.go deleted file mode 100644 index b6db686..0000000 --- a/quiz-app/quizzes/code/sivchari_q1_question.go +++ /dev/null @@ -1,10 +0,0 @@ -//go:build ignore - -package main - -import "fmt" - -func main() { - a := [...]int{0, 3: 3, 4, 1: 1} - fmt.Println(len(a), a) -} diff --git a/quiz-app/quizzes/code/sivchari_q2_answer.go b/quiz-app/quizzes/code/sivchari_q2_answer.go deleted file mode 100644 index 86e8b93..0000000 --- a/quiz-app/quizzes/code/sivchari_q2_answer.go +++ /dev/null @@ -1,16 +0,0 @@ -//go:build ignore - -package main - -import "fmt" - -const ( - a = iota * 2 // iota=0 → 0*2 = 0 - b // iota=1, 式 iota*2 を引き継ぐ → 1*2 = 2 - c = iota // iota=2 → 2 (リセットされない) - d // iota=3, 式 iota を引き継ぐ → 3 -) - -func main() { - fmt.Println(a, b, c, d) // 0 2 2 3 -} diff --git a/quiz-app/quizzes/code/sivchari_q2_question.go b/quiz-app/quizzes/code/sivchari_q2_question.go deleted file mode 100644 index 8169c86..0000000 --- a/quiz-app/quizzes/code/sivchari_q2_question.go +++ /dev/null @@ -1,16 +0,0 @@ -//go:build ignore - -package main - -import "fmt" - -const ( - a = iota * 2 - b - c = iota - d -) - -func main() { - fmt.Println(a, b, c, d) -} diff --git a/quiz-app/quizzes/code/tesso57_q1_answer.go b/quiz-app/quizzes/code/tesso57_q1_answer.go deleted file mode 100644 index f3ae396..0000000 --- a/quiz-app/quizzes/code/tesso57_q1_answer.go +++ /dev/null @@ -1,16 +0,0 @@ -//go:build ignore - -package main - -import "fmt" - -type Name string - -// %v ではなく string(n) にキャストすることで無限再帰を回避 -func (n Name) String() string { - return fmt.Sprintf("Name: %s", string(n)) -} - -func main() { - fmt.Println(Name("Go")) // Name: Go -} diff --git a/quiz-app/quizzes/code/tesso57_q1_question.go b/quiz-app/quizzes/code/tesso57_q1_question.go deleted file mode 100644 index 8cc7045..0000000 --- a/quiz-app/quizzes/code/tesso57_q1_question.go +++ /dev/null @@ -1,15 +0,0 @@ -//go:build ignore - -package main - -import "fmt" - -type Name string - -func (n Name) String() string { - return fmt.Sprintf("Name: %v", n) -} - -func main() { - fmt.Println(Name("Go")) -} diff --git a/quiz-app/quizzes/code/tesso57_q2_answer.go b/quiz-app/quizzes/code/tesso57_q2_answer.go deleted file mode 100644 index 43259b7..0000000 --- a/quiz-app/quizzes/code/tesso57_q2_answer.go +++ /dev/null @@ -1,17 +0,0 @@ -//go:build ignore - -package main - -import "fmt" - -type User struct { - Name string - Age int -} - -func main() { - u := User{"Gopher", 10} - fmt.Printf("%v\n", u) // {Gopher 10} - fmt.Printf("%+v\n", u) // {Name:Gopher Age:10} - fmt.Printf("%#v\n", u) // main.User{Name:"Gopher", Age:10} -} diff --git a/quiz-app/quizzes/code/tesso57_q2_question.go b/quiz-app/quizzes/code/tesso57_q2_question.go deleted file mode 100644 index 317cb05..0000000 --- a/quiz-app/quizzes/code/tesso57_q2_question.go +++ /dev/null @@ -1,16 +0,0 @@ -//go:build ignore - -package main - -import "fmt" - -type User struct { - Name string - Age int -} - -func main() { - u := User{"Gopher", 10} - fmt.Printf("????\n", u) // 出力: main.User{Name:"Gopher", Age:10} - // ???? に入る verb はどれ? -} diff --git a/quiz-app/quizzes/code/tesso57_q3_answer.go b/quiz-app/quizzes/code/tesso57_q3_answer.go deleted file mode 100644 index 1629262..0000000 --- a/quiz-app/quizzes/code/tesso57_q3_answer.go +++ /dev/null @@ -1,16 +0,0 @@ -//go:build ignore - -package main - -import "fmt" - -func main() { - // nil map は読み取りはゼロ値を返すが、書き込みは panic する - var m map[string]int - fmt.Println(m["hello"]) // 0(ゼロ値) - - // 書き込みには make で初期化が必要 - m = make(map[string]int) - m["hello"] = 1 - fmt.Println(m["hello"]) // 1 -} diff --git a/quiz-app/quizzes/code/tesso57_q3_question.go b/quiz-app/quizzes/code/tesso57_q3_question.go deleted file mode 100644 index 9d7a048..0000000 --- a/quiz-app/quizzes/code/tesso57_q3_question.go +++ /dev/null @@ -1,11 +0,0 @@ -//go:build ignore - -package main - -import "fmt" - -func main() { - var m map[string]int - fmt.Println(m["hello"]) - m["hello"] = 1 -} diff --git a/quiz-app/quizzes/code/tesso57_q4_answer.go b/quiz-app/quizzes/code/tesso57_q4_answer.go deleted file mode 100644 index 58fedd9..0000000 --- a/quiz-app/quizzes/code/tesso57_q4_answer.go +++ /dev/null @@ -1,17 +0,0 @@ -//go:build ignore - -package main - -import "fmt" - -func main() { - byString := map[string]int{"go": 1} - byInt := map[int]string{1: "one"} - byArray := map[[2]int]string{{1, 2}: "pair"} - - fmt.Println(byString, byInt, byArray) - - // slice は comparable ではないので map のキーにできない - // invalid := map[[]int]string{{1, 2}: "slice"} - // _ = invalid -} diff --git a/quiz-app/quizzes/code/tesso57_q5_answer.go b/quiz-app/quizzes/code/tesso57_q5_answer.go deleted file mode 100644 index 2f8dc38..0000000 --- a/quiz-app/quizzes/code/tesso57_q5_answer.go +++ /dev/null @@ -1,21 +0,0 @@ -//go:build ignore - -package main - -import "fmt" - -type User struct { - Age int -} - -func main() { - m := map[string]User{ - "gopher": {Age: 10}, - } - - u := m["gopher"] - u.Age = 11 - m["gopher"] = u - - fmt.Println(m["gopher"].Age) // 11 -} diff --git a/quiz-app/quizzes/code/tesso57_q5_question.go b/quiz-app/quizzes/code/tesso57_q5_question.go deleted file mode 100644 index c3f682f..0000000 --- a/quiz-app/quizzes/code/tesso57_q5_question.go +++ /dev/null @@ -1,14 +0,0 @@ -//go:build ignore - -package main - -type User struct { - Age int -} - -func main() { - m := map[string]User{ - "gopher": {Age: 10}, - } - m["gopher"].Age = 11 -} diff --git a/quiz-app/quizzes/code/tomtwinkle_q10_question.go b/quiz-app/quizzes/code/tomtwinkle_q10_question.go deleted file mode 100644 index 2ab14ad..0000000 --- a/quiz-app/quizzes/code/tomtwinkle_q10_question.go +++ /dev/null @@ -1,23 +0,0 @@ -//go:build ignore - -package main - -import ( - "testing" - "testing/synctest" -) - -func TestSynctestIsolation(t *testing.T) { - ch := make(chan int) - - // synctest の外側から channel に送信 - go func() { - ch <- 42 - }() - - synctest.Run(func() { - // 内側で外側の channel を受信 - v := <-ch // ? - t.Log("received:", v) - }) -} diff --git a/quiz-app/quizzes/code/tomtwinkle_q1_answer.go b/quiz-app/quizzes/code/tomtwinkle_q1_answer.go deleted file mode 100644 index 853e7f3..0000000 --- a/quiz-app/quizzes/code/tomtwinkle_q1_answer.go +++ /dev/null @@ -1,29 +0,0 @@ -//go:build ignore - -package main - -import ( - "fmt" - "testing" -) - -// go test のキャッシュを無効化するには -count=1 を使います。 -// -// キャッシュあり (2回目以降は "(cached)" と表示): -// -// go test ./... -// -// キャッシュ無効 (毎回強制再実行): -// -// go test -count=1 ./... -// -// テストキャッシュ全消去: -// -// go clean -testcache -func ExampleTestCaching() { - fmt.Println("go test -count=1 でキャッシュを無視できます") - // Output: - // go test -count=1 でキャッシュを無視できます -} - -var _ = testing.Verbose diff --git a/quiz-app/quizzes/code/tomtwinkle_q2_answer.go b/quiz-app/quizzes/code/tomtwinkle_q2_answer.go deleted file mode 100644 index 556c930..0000000 --- a/quiz-app/quizzes/code/tomtwinkle_q2_answer.go +++ /dev/null @@ -1,17 +0,0 @@ -//go:build ignore - -package main - -import "fmt" - -// untyped int の最大値を超える定数 -// 型無し定数はコンパイル時に少なくとも 256ビットの精度を持つ -const max = 999999999999999999999 - -func main() { - // 計算結果がuntyped intの上限値を超えないのでコンパイルエラーにならない - fmt.Printf("max / 1000000000 = %v\n", max/1000000000) - - // 以下はコンパイルエラー - // var x int = max // constant 999999999999999999999 overflows int -} diff --git a/quiz-app/quizzes/code/tomtwinkle_q2_question.go b/quiz-app/quizzes/code/tomtwinkle_q2_question.go deleted file mode 100644 index 8772a60..0000000 --- a/quiz-app/quizzes/code/tomtwinkle_q2_question.go +++ /dev/null @@ -1,12 +0,0 @@ -//go:build ignore - -package main - -import "fmt" - -// untyped int の最大値を超える定数 -const max = 999999999999999999999 - -func main() { - fmt.Printf("max / 1000000000 = %v\n", max/1000000000) -} diff --git a/quiz-app/quizzes/code/tomtwinkle_q4_answer.go b/quiz-app/quizzes/code/tomtwinkle_q4_answer.go deleted file mode 100644 index b0acf3a..0000000 --- a/quiz-app/quizzes/code/tomtwinkle_q4_answer.go +++ /dev/null @@ -1,49 +0,0 @@ -//go:build ignore - -package main - -import ( - "fmt" - "os" -) - -// 悪い例: defer が関数終了まで溜まり続ける -func badPattern(names []string) error { - for _, name := range names { - f, err := os.CreateTemp("", name) - if err != nil { - return err - } - defer f.Close() // ループが終わっても Close されない - fmt.Println("opened:", f.Name()) - } - return nil // ← ここで初めて全 defer が実行される -} - -// 良い例: ループ本体を関数に切り出す -func goodPattern(names []string) error { - for _, name := range names { - if err := openAndProcess(name); err != nil { - return err - } - } - return nil -} - -func openAndProcess(name string) error { - f, err := os.CreateTemp("", name) - if err != nil { - return err - } - defer f.Close() // この関数が終わると即 Close される - fmt.Println("opened and closed:", f.Name()) - return nil -} - -func main() { - names := []string{"a", "b", "c"} - fmt.Println("=== bad pattern ===") - badPattern(names) - fmt.Println("=== good pattern ===") - goodPattern(names) -} diff --git a/quiz-app/quizzes/code/tomtwinkle_q4_question.go b/quiz-app/quizzes/code/tomtwinkle_q4_question.go deleted file mode 100644 index a014484..0000000 --- a/quiz-app/quizzes/code/tomtwinkle_q4_question.go +++ /dev/null @@ -1,25 +0,0 @@ -//go:build ignore - -package main - -import ( - "fmt" - "os" -) - -func processFiles(names []string) error { - for _, name := range names { - file, err := os.CreateTemp("", name) - if err != nil { - return err - } - defer file.Close() - fmt.Println("opened:", file.Name()) - } - return nil -} - -func main() { - names := []string{"a", "b", "c", "d", "e"} - processFiles(names) -} diff --git a/quiz-app/quizzes/code/tomtwinkle_q5_answer.go b/quiz-app/quizzes/code/tomtwinkle_q5_answer.go deleted file mode 100644 index a6ffbcf..0000000 --- a/quiz-app/quizzes/code/tomtwinkle_q5_answer.go +++ /dev/null @@ -1,22 +0,0 @@ -//go:build ignore - -package main - -import "fmt" - -func main() { - // clear のスライスに対する動作 (Go 1.21+) - s := []int{1, 2, 3} - fmt.Printf("before: len=%d cap=%d val=%v\n", len(s), cap(s), s) - - clear(s) // 全要素をゼロ値に。len と cap は変わらない。 - - fmt.Printf("after: len=%d cap=%d val=%v\n", len(s), cap(s), s) - // after: len=3 cap=3 val=[0 0 0] - - // マップの場合は要素が削除されて len=0 になる - m := map[string]int{"a": 1, "b": 2} - fmt.Printf("map before: len=%d\n", len(m)) - clear(m) - fmt.Printf("map after: len=%d\n", len(m)) -} diff --git a/quiz-app/quizzes/code/tomtwinkle_q5_question.go b/quiz-app/quizzes/code/tomtwinkle_q5_question.go deleted file mode 100644 index f1a86d4..0000000 --- a/quiz-app/quizzes/code/tomtwinkle_q5_question.go +++ /dev/null @@ -1,11 +0,0 @@ -//go:build ignore - -package main - -import "fmt" - -func main() { - s := []int{1, 2, 3} - clear(s) - fmt.Println(len(s)) // ? -} diff --git a/quiz-app/quizzes/code/tomtwinkle_q6_answer.go b/quiz-app/quizzes/code/tomtwinkle_q6_answer.go deleted file mode 100644 index aa490aa..0000000 --- a/quiz-app/quizzes/code/tomtwinkle_q6_answer.go +++ /dev/null @@ -1,24 +0,0 @@ -//go:build ignore - -package main - -import "fmt" - -// 名前付き戻り値 + defer の実行順序: -// 1. return 1 → 戻り値変数 n に 1 が代入される -// 2. defer 実行 → n++ が呼ばれて n = 2 になる -// 3. n の値 (2) が返される - -func f() (n int) { - defer func() { - fmt.Printf("defer: n before++ = %d\n", n) - n++ - fmt.Printf("defer: n after++ = %d\n", n) - }() - return 1 // n = 1 を設定してから defer へ -} - -func main() { - result := f() - fmt.Println("result:", result) // 2 -} diff --git a/quiz-app/quizzes/code/tomtwinkle_q6_question.go b/quiz-app/quizzes/code/tomtwinkle_q6_question.go deleted file mode 100644 index bc18151..0000000 --- a/quiz-app/quizzes/code/tomtwinkle_q6_question.go +++ /dev/null @@ -1,14 +0,0 @@ -//go:build ignore - -package main - -import "fmt" - -func f() (n int) { - defer func() { n++ }() - return 1 -} - -func main() { - fmt.Println(f()) // ? -} diff --git a/quiz-app/quizzes/code/tomtwinkle_q7_answer.go b/quiz-app/quizzes/code/tomtwinkle_q7_answer.go deleted file mode 100644 index 13e94c7..0000000 --- a/quiz-app/quizzes/code/tomtwinkle_q7_answer.go +++ /dev/null @@ -1,22 +0,0 @@ -//go:build ignore - -package main - -import ( - "fmt" - "unicode/utf8" -) - -func main() { - s := "日本語" - fmt.Printf("バイト数: %d\n", len(s)) // 9 (各 3 バイト) - fmt.Printf("文字数: %d\n", utf8.RuneCountInString(s)) // 3 - - fmt.Println("\nrange でのインデックスはバイト位置:") - for i, v := range s { - fmt.Printf("i=%d v=%v\n", i, v) - } - // i=0 v=26085 - // i=3 v=26412 - // i=6 v=35486 -} diff --git a/quiz-app/quizzes/code/tomtwinkle_q7_question.go b/quiz-app/quizzes/code/tomtwinkle_q7_question.go deleted file mode 100644 index f7562c9..0000000 --- a/quiz-app/quizzes/code/tomtwinkle_q7_question.go +++ /dev/null @@ -1,11 +0,0 @@ -//go:build ignore - -package main - -import "fmt" - -func main() { - for i, v := range "日本語" { - fmt.Printf("i=%d v=%v\n", i, v) // ? - } -} diff --git a/quiz-app/quizzes/code/tomtwinkle_q8_answer.go b/quiz-app/quizzes/code/tomtwinkle_q8_answer.go deleted file mode 100644 index 7c97ace..0000000 --- a/quiz-app/quizzes/code/tomtwinkle_q8_answer.go +++ /dev/null @@ -1,32 +0,0 @@ -//go:build ignore - -package main - -import ( - "testing" -) - -// t.Fatal は runtime.Goexit() を呼ぶため goroutine だけが終了する -// close(done) に到達しないため <-done がブロックし続け、タイムアウトまで止まらない、もしくはdeadlockする。 -func TestBadPattern(t *testing.T) { - done := make(chan struct{}) - - go func() { - t.Fatal("goroutine からエラー!") - close(done) // runtime.Goexit() により到達しない - }() - - <-done // タイムアウトまでブロックし続ける、もしくはdeadlockする -} - -// goroutine 内では t.Errorf を使い、defer で完了を通知する -func TestGoodPattern(t *testing.T) { - done := make(chan struct{}) - - go func() { - defer close(done) // defer はGoexit後も実行されるため確実に完了通知できる - t.Errorf("goroutine からエラー!") - }() - - <-done // 確実にハンドラの完了を待てる -} diff --git a/quiz-app/quizzes/code/tomtwinkle_q8_question.go b/quiz-app/quizzes/code/tomtwinkle_q8_question.go deleted file mode 100644 index a3d28f3..0000000 --- a/quiz-app/quizzes/code/tomtwinkle_q8_question.go +++ /dev/null @@ -1,18 +0,0 @@ -//go:build ignore - -package main - -import ( - "testing" -) - -func TestFatalInGoroutine(t *testing.T) { - done := make(chan struct{}) - - go func() { - t.Fatal("goroutine からエラー!") - close(done) // ← このテストはどうなる? - }() - - <-done -} diff --git a/quiz-app/quizzes/code/tomtwinkle_q9_answer.go b/quiz-app/quizzes/code/tomtwinkle_q9_answer.go deleted file mode 100644 index 1ed7dbe..0000000 --- a/quiz-app/quizzes/code/tomtwinkle_q9_answer.go +++ /dev/null @@ -1,22 +0,0 @@ -//go:build ignore - -package main - -import ( - "fmt" - "runtime" -) - -// Go 1.25 以降はランタイムが cgroup の CPU quota を自動検知するため -// uber/automaxprocs や手動設定は不要になりました。 -// -// 以前の定石 (Go 1.24 以前): -// -// import _ "go.uber.org/automaxprocs" -// -// Go 1.25+ では何もしなくてよい -func main() { - fmt.Printf("GOMAXPROCS: %d\n", runtime.GOMAXPROCS(0)) - fmt.Printf("NumCPU: %d\n", runtime.NumCPU()) - fmt.Println("Go 1.25+ ではコンテナの CPU 制限を自動検知します") -} diff --git a/quiz-app/quizzes/code/wancom_q1_answer.go b/quiz-app/quizzes/code/wancom_q1_answer.go deleted file mode 100644 index 1be8089..0000000 --- a/quiz-app/quizzes/code/wancom_q1_answer.go +++ /dev/null @@ -1,34 +0,0 @@ -//go:build ignore - -package main - -import "fmt" - -func main() { - chA := make(chan string, 4) - chB := make(chan string, 4) - chC := make(chan string, 4) - - chB <- "msgB-1" - chA <- "msgA" - chB <- "msgB-2" - chC <- "msgC" - - // まずは chA を先に確認する。 - select { - case msg := <-chA: - fmt.Printf("Message from chA: %s\n", msg) - default: - // chA がなければ chA、chB、chC のどれかから取得する。 - select { - case msg := <-chA: - fmt.Printf("Message from chA: %s\n", msg) - case msg := <-chB: - fmt.Printf("Message from chB: %s\n", msg) - case msg := <-chC: - fmt.Printf("Message from chC: %s\n", msg) - } - } - - // この場合は必ず "Message from chA: msgA" が出力される。 -} diff --git a/quiz-app/quizzes/code/wancom_q1_question.go b/quiz-app/quizzes/code/wancom_q1_question.go deleted file mode 100644 index 435f826..0000000 --- a/quiz-app/quizzes/code/wancom_q1_question.go +++ /dev/null @@ -1,25 +0,0 @@ -//go:build ignore - -package main - -import "fmt" - -func main() { - chA := make(chan string, 4) - chB := make(chan string, 4) - chC := make(chan string, 4) - - chB <- "msgB-1" - chA <- "msgA" - chB <- "msgB-2" - chC <- "msgC" - - select { - case msg := <-chA: - fmt.Printf("Message from chA: %s\n", msg) - case msg := <-chB: - fmt.Printf("Message from chB: %s\n", msg) - case msg := <-chC: - fmt.Printf("Message from chC: %s\n", msg) - } -} diff --git a/quiz-app/quizzes/code/wancom_q3_answer.go b/quiz-app/quizzes/code/wancom_q3_answer.go deleted file mode 100644 index 74661ec..0000000 --- a/quiz-app/quizzes/code/wancom_q3_answer.go +++ /dev/null @@ -1,35 +0,0 @@ -//go:build ignore - -// 参考: https://pkg.go.dev/net/http/pprof@go1.26.2 - -package main - -import ( - "log" - "net/http" - "time" - - // import するだけで pprof ハンドラが登録される。 - _ "net/http/pprof" -) - -func main() { - // プロファイリングデータを返すための HTTP サーバーを開始する。 - go func() { - log.Println("Starting pprof server...") - log.Println(http.ListenAndServe("localhost:6060", nil)) - }() - - // ここに色々処理を追加する。 - go func() { - log.Println("Sub goroutine started.") - for { - time.Sleep(time.Second) - } - }() - - log.Println("Now main goroutine will enter the loop.") - for { - time.Sleep(time.Second) - } -} diff --git a/quiz-app/quizzes/quizes.yaml b/quiz-app/quizzes/quizes.yaml deleted file mode 100644 index c16c50e..0000000 --- a/quiz-app/quizzes/quizes.yaml +++ /dev/null @@ -1,1310 +0,0 @@ - # sivchari Q1 - - id: "Indexed elements in array literals" - title: "配列リテラルのインデックス指定" - title_en: Indexed elements in array literals - mode: "extra" - text: "このコードの出力は何?" - text_en: What does this code print? - question_code_ref: "code/sivchari_q1_question.go" - choices: - - "5 [0 1 0 3 4]" - - "4 [0 3 4 1]" - - "6 [0 1 0 3 4 0]" - - "コンパイルエラー" - choices_en: - - 5 [0 1 0 3 4] - - 4 [0 3 4 1] - - 6 [0 1 0 3 4 0] - - Compilation error - answer: 0 - explanation: | - Go の配列リテラルでは、要素にインデックスを指定して初期化できます。 - インデックス指定がない要素は、直前のインデックス + 1 の位置に配置されます。 - - この問題では: - - 0 → index 0 - - 3:3 → index 3 - - 4 → index 4 (直前の index 3 + 1) - - 1:1 → index 1 - - [...]T{} の長さは仕様で "The notation ... specifies an array length equal to the maximum element index plus one." と定められているため、max index 4 + 1 = 5 になります。 - 明示的に指定されなかった index 2 にはゼロ値が入ります。 - - 公式仕様 (Array types): https://go.dev/ref/spec#Array_types - 公式仕様 (Composite literals): https://go.dev/ref/spec#Composite_literals - explanation_en: |- - In a Go array literal, you can initialize elements with explicit indexes. - Elements without an explicit index are placed at the previous index + 1. - - In this quiz: - - 0 -> index 0 - - 3:3 -> index 3 - - 4 -> index 4 (previous index 3 + 1) - - 1:1 -> index 1 - - The spec says, "The notation ... specifies an array length equal to the maximum element index plus one.", - so the length is max index 4 + 1 = 5. - Index 2, which is not specified explicitly, gets the zero value. - - Go spec (Array types): https://go.dev/ref/spec#Array_types - Go spec (Composite literals): https://go.dev/ref/spec#Composite_literals - answer_code_ref: "code/sivchari_q1_answer.go" - - # sivchari Q2 - - id: "How iota continues across lines" - title: "iota の継続と式の引き継ぎ" - title_en: How iota continues across lines - mode: "extra" - text: "このコードの出力は何?" - text_en: What does this code print? - question_code_ref: "code/sivchari_q2_question.go" - choices: - - "0 2 0 1" - - "0 2 2 3" - - "0 2 0 3" - - "コンパイルエラー" - choices_en: - - 0 2 0 1 - - 0 2 2 3 - - 0 2 0 3 - - Compilation error - answer: 1 - explanation: | - iota は const ブロック内で 0 から始まり、各行ごとに 1 ずつ増加します。 - 途中で `c = iota` と書き直しても iota はリセットされず、その行の位置 (2) がそのまま値になります。 - - - a = iota * 2: iota=0 → 0 - - b: 式 iota*2 を引き継ぐ → iota=1 → 2 - - c = iota: 新しい式。iota=2 → 2 - - d: 式 iota を引き継ぐ → iota=3 → 3 - - b と c が同じ値 2 になるのがポイントです。 - iota がリセットされるのは新しい const ブロックが始まったときだけです。 - - 公式仕様 (Iota): https://go.dev/ref/spec#Iota - explanation_en: |- - `iota` starts at 0 inside a const block and increases by 1 on each line. - Even if you rewrite a line as `c = iota`, `iota` is not reset; it simply becomes the line index, 2. - - - a = iota * 2: iota=0 -> 0 - - b: inherits the expression iota*2 -> iota=1 -> 2 - - c = iota: a new expression. iota=2 -> 2 - - d: inherits the expression iota -> iota=3 -> 3 - - The key point is that `b` and `c` both become 2. - `iota` is reset only when a new const block begins. - - Go spec (Iota): https://go.dev/ref/spec#Iota - answer_code_ref: "code/sivchari_q2_answer.go" - - # tomtwinkle Q1 - - id: "go test cache" - title: "go test キャッシュ" - title_en: go test cache - mode: "extra" - text: | - go test は、同じコードに対して2回目を実行すると「(cached)」と表示され一瞬で終わります。 - このキャッシュを無視して強制的に再テストするフラグはどれ? - text_en: |- - When you run `go test` twice against the same code, the second run may finish instantly with `(cached)`. - Which flag forces the tests to run again instead of using the cache? - choices: - - "go test -nocache" - - "go test -force" - - "go test -count=1" - - "go test -clear" - choices_en: - - go test -nocache - - go test -force - - go test -count=1 - - go test -clear - answer: 2 - explanation: | - キャッシュを無効化する専用のフラグはなく、「このテストを1回実行する」という意味の -count=1 を指定するのが、キャッシュ回避の公式イディオムです。 - go clean -testcache でキャッシュ全消去も可能です。 - - 公式ドキュメント: https://pkg.go.dev/cmd/go#hdr-Testing_flags - explanation_en: |- - There is no dedicated flag to disable the cache. - The official idiom is to pass `-count=1`, which means "run this test once" and bypasses the cache. - You can also clear the entire test cache with `go clean -testcache`. - - Official docs: https://pkg.go.dev/cmd/go#hdr-Testing_flags - answer_code_ref: "code/tomtwinkle_q1_answer.go" - answer_code_play_ref: "https://go.dev/play/p/q3esYHlP6lG" - - # tomtwinkle Q2 - - id: "Precision of untyped constants" - title: "型無し定数の精度" - title_en: Precision of untyped constants - mode: "extra" - text: | - 変数を使わず、定数で64ビットの限界を超える巨大な数値を定義するとどうなる? - text_en: What happens if you define a huge constant, without using a - variable, that exceeds the 64-bit limit? - question_code_ref: "code/tomtwinkle_q2_question.go" - choices: - - "オーバーフローでコンパイルエラー" - - "0 になる" - - "負の最大値にラップアラウンドする" - - "コンパイルは通る" - choices_en: - - Compilation error because of overflow - - Becomes 0 - - Wraps around to the largest negative value - - Still compiles - answer: 3 - explanation: | - Goの「型無し定数」は、コンパイル時には少なくとも256ビットの精度を持ちます。 - 変数に代入する段階になって初めて型の限界値のチェックが行われるため、定数同士の計算中であればオーバーフローしません。 - - 公式仕様: https://go.dev/ref/spec#Constants - Go Blog: https://go.dev/blog/constants - explanation_en: |- - Go's untyped constants keep at least 256 bits of precision during compilation. - Overflow is checked only when the value is assigned to a typed variable, - so calculations on constants alone do not overflow. - - Go spec: https://go.dev/ref/spec#Constants - Go Blog: https://go.dev/blog/constants - answer_code_ref: "code/tomtwinkle_q2_answer.go" - answer_code_play_ref: "https://go.dev/play/p/vb4924cUKsl" - - # tomtwinkle Q4 - - id: "defer inside a loop" - title: "defer in loop" - title_en: defer inside a loop - mode: "extra" - text: | - 以下のコードの問題点は? - text_en: What is the problem with this code? - question_code_ref: "code/tomtwinkle_q4_question.go" - choices: - - "ループごとにファイルが上書きされる" - - "ファイルディスクリプタが枯渇する" - - "os.Open は defer で閉じることができない" - - "コンパイルエラーになる" - choices_en: - - The file is overwritten on each loop iteration - - File descriptors are exhausted - - '`os.Open` cannot be closed with defer' - - It fails to compile - answer: 1 - explanation: | - defer のスコープは「ブロック」ではなく「関数」です。 - ループの中で defer を大量に呼ぶと、関数が終わるまで一切クローズされず、OSのリソース制限に引っかかってパニックになることがあります。 - 解決策はループ本体を別関数に切り出すことです。 - - Go Blog (defer の仕様): https://go.dev/blog/defer-panic-and-recover - 公式仕様: https://go.dev/ref/spec#Defer_statements - explanation_en: |- - The scope of `defer` is the function, not the block. - If you call `defer` many times inside a loop, nothing is closed until the function returns, - so you can hit the OS resource limit and panic. - The fix is to move the loop body into a separate function. - - Go Blog (how defer works): https://go.dev/blog/defer-panic-and-recover - Go spec: https://go.dev/ref/spec#Defer_statements - answer_code_ref: "code/tomtwinkle_q4_answer.go" - answer_code_play_ref: "https://go.dev/play/p/hM0PTI0iko7" - - # tomtwinkle Q5 - - id: "clear() on a slice" - title: "clear() on slice" - title_en: clear() on a slice - mode: "extra" - text: | - 以下のコードの実行結果は? - text_en: What is the result of running this code? - question_code_ref: "code/tomtwinkle_q5_question.go" - choices: - - "0" - - "3" - - "nil" - - "コンパイルエラー" - choices_en: - - '0' - - '3' - - nil - - Compilation error - answer: 1 - explanation: | - Go 1.21 で追加された組み込み関数 clear()。 - マップに使うと要素を空(len=0)にしますが、スライスに使うと長さ(len)と容量(cap)はそのままに、全要素を「ゼロ値」に上書きします。 - 要素自体が消えるわけではありません。 - - 公式仕様 (clear): https://go.dev/ref/spec#Clear - pkg.go.dev: https://pkg.go.dev/builtin#clear - explanation_en: |- - `clear()` was added in Go 1.21. - When you use it on a map, it removes all elements (`len=0`). - When you use it on a slice, it keeps the same length and capacity and simply overwrites every element with the zero value. - The elements themselves are not removed. - - Go spec (`clear`): https://go.dev/ref/spec#Clear - pkg.go.dev: https://pkg.go.dev/builtin#clear - answer_code_ref: "code/tomtwinkle_q5_answer.go" - answer_code_play_ref: "https://go.dev/play/p/NkAkv-tUNKr" - - # tomtwinkle Q6 - - id: "Named returns and defer" - title: "named return + defer" - title_en: Named returns and defer - mode: "extra" - text: | - 以下の関数を呼び出したとき、呼び出し元が受け取る値は? - text_en: When this function is called, what value does the caller receive? - question_code_ref: "code/tomtwinkle_q6_question.go" - choices: - - "0" - - "1" - - "2" - - "コンパイルエラー" - choices_en: - - '0' - - '1' - - '2' - - Compilation error - answer: 2 - explanation: | - 名前付き戻り値の罠です。 - return 1 が実行されると、まず変数 n に 1 が代入され、その後に defer が実行されて n が 2 になり、それが返されます。 - 実行順序: return 1 → n=1 代入 → defer で n++ → n=2 が返る - - Go Blog (defer の仕様): https://go.dev/blog/defer-panic-and-recover - 公式仕様: https://go.dev/ref/spec#Return_statements - explanation_en: |- - This is the classic named return trap. - When `return 1` runs, Go first assigns 1 to the named return variable `n`, - then the deferred function runs and changes `n` to 2, and that value is returned. - Execution order: `return 1` -> assign n=1 -> `defer` runs and increments n -> n=2 is returned - - Go Blog (how defer works): https://go.dev/blog/defer-panic-and-recover - Go spec: https://go.dev/ref/spec#Return_statements - answer_code_ref: "code/tomtwinkle_q6_answer.go" - answer_code_play_ref: "https://go.dev/play/p/973xesVL17U" - - # tomtwinkle Q7 - - id: "range over a string" - title: "range over string" - title_en: range over a string - mode: "extra" - text: | - 以下のコードの出力はどれ? - text_en: Which output does this code produce? - question_code_ref: "code/tomtwinkle_q7_question.go" - choices: - - |- - i=0 v=日 - i=1 v=本 - i=2 v=語 - - |- - i=0 v=26085 - i=3 v=26412 - i=6 v=35486 - - |- - i=0 v=230 - i=1 v=151 - i=2 v=165 - i=3 v=230 - i=4 v=156 - i=5 v=172 - i=6 v=232 - i=7 v=170 - i=8 v=158 - - "コンパイルエラー" - choices_en: - - |- - i=0 v=日 - i=1 v=本 - i=2 v=語 - - |- - i=0 v=26085 - i=3 v=26412 - i=6 v=35486 - - |- - i=0 v=230 - i=1 v=151 - i=2 v=165 - i=3 v=230 - i=4 v=156 - i=5 v=172 - i=6 v=232 - i=7 v=170 - i=8 v=158 - - Compilation error - answer: 1 - explanation: | - Go の string range は UTF-8 デコードを行い、インデックスはバイト位置、値は rune(Unicode コードポイント)です。 - 「日」(U+65E5=26085) は index=0、「本」(U+672C=26412) は index=3、「語」(U+8A9E=35486) は index=6 から始まります。 - 各漢字は UTF-8 で 3 バイトです。 - - Go Blog (文字列と rune): https://go.dev/blog/strings - 公式仕様 (range): https://go.dev/ref/spec#For_range - explanation_en: |- - Ranging over a Go string decodes UTF-8. - The index is the byte position, and the value is a rune (Unicode code point). - `日` (U+65E5 = 26085) starts at index 0, `本` (U+672C = 26412) at index 3, and `語` (U+8A9E = 35486) at index 6. - Each kanji uses 3 bytes in UTF-8. - - Go Blog (strings and runes): https://go.dev/blog/strings - Go spec (`range`): https://go.dev/ref/spec#For_range - answer_code_ref: "code/tomtwinkle_q7_answer.go" - answer_code_play_ref: "https://go.dev/play/p/BVyqOwrA1tg" - - # tomtwinkle Q8 - - id: "t.Fatal inside a goroutine" - title: "t.Fatal in goroutine" - title_en: t.Fatal inside a goroutine - mode: "extra" - text: | - テスト goroutine ではない「別途起動した goroutine」の中で t.Fatal() を呼ぶとどうなる? - text_en: What happens if you call `t.Fatal()` inside a goroutine that is not - the test goroutine itself? - question_code_ref: "code/tomtwinkle_q8_question.go" - choices: - - "テストが正しく終了し、失敗としてマークされる" - - "テストがタイムアウトするまで止まらなくなる" - - "panic になりプロセスがクラッシュする" - - "何も起こらずテストは成功扱いになる" - choices_en: - - The test ends correctly and is marked failed - - The test keeps running until it times out - - It panics and crashes the process - - Nothing happens and the test is reported as success - answer: 1 - explanation: | - t.Fatal は runtime.Goexit() を呼ぶため、別 goroutine で呼ぶとその goroutine だけが静かに終了し、テスト自体は止まりません。 - Go 1.24 からは go vet で検出されるようになりました。 - 正しいアプローチ: goroutine 内では t.Error を使い channel で完了を通知するか、errgroup などを活用します。 - - pkg.go.dev (testing.T.Fatal): https://pkg.go.dev/testing#T.Fatal - Go 1.24 リリースノート: https://go.dev/doc/go1.24 - explanation_en: |- - `t.Fatal` calls `runtime.Goexit()`. - If you call it in another goroutine, only that goroutine exits quietly, and the test itself keeps running. - Since Go 1.24, `go vet` can detect this. - Correct approaches include using `t.Error` inside the goroutine and signaling completion through a channel, or using `errgroup`. - - pkg.go.dev (`testing.T.Fatal`): https://pkg.go.dev/testing#T.Fatal - Go 1.24 release notes: https://go.dev/doc/go1.24 - answer_code_ref: "code/tomtwinkle_q8_answer.go" - answer_code_play_ref: "https://go.dev/play/p/uP543Km7JNK" - - # tomtwinkle Q9 - - id: "GOMAXPROCS in containers" - title: "GOMAXPROCS in containers" - title_en: GOMAXPROCS in containers - mode: "extra" - text: | - Docker や Kubernetes 上で Go アプリを動かす際、CPU のコア数を認識させて GOMAXPROCS を最適化するために、 - 最新の Go バージョンにおいて推奨されるコードはどれ? - text_en: |- - When running a Go application on Docker or Kubernetes, - which code is recommended in modern Go to let GOMAXPROCS respect CPU limits? - choices: - - 'import _ "go.uber.org/automaxprocs" を入れる' - - "環境変数 GOMAXPROCS をシェルで計算して渡す" - - "runtime.GOMAXPROCS(runtime.NumCPU()) を呼ぶ" - - "何もしなくてよい" - choices_en: - - Add `import _ "go.uber.org/automaxprocs"` - - Compute `GOMAXPROCS` in the shell and pass it as an environment variable - - Call `runtime.GOMAXPROCS(runtime.NumCPU())` - - Do nothing - answer: 3 - explanation: | - 長年 Uber の automaxprocs を入れるのが Kubernetes 時代の定石でしたが、 - Go 1.25 からランタイムがコンテナの CPU 制限(cgroup CPU quota)を自動検知するようになったため、完全に不要になりました。 - - Go 1.25 リリースノート: https://go.dev/doc/go1.25 - pkg.go.dev (runtime.GOMAXPROCS): https://pkg.go.dev/runtime#GOMAXPROCS - explanation_en: |- - For a long time, adding Uber's `automaxprocs` was the common approach in Kubernetes. - But since Go 1.25, the runtime automatically detects container CPU limits (cgroup CPU quota), - so you no longer need it. - - Go 1.25 release notes: https://go.dev/doc/go1.25 - pkg.go.dev (`runtime.GOMAXPROCS`): https://pkg.go.dev/runtime#GOMAXPROCS - answer_code_ref: "code/tomtwinkle_q9_answer.go" - answer_code_play_ref: "https://go.dev/play/p/3Az4nyarOeI" - - # tomtwinkle Q10 - - id: "synctest isolation" - title: "synctest isolation" - title_en: synctest isolation - mode: "extra" - text: | - synctest.Run ブロックの「外側」で起動した goroutine と、「内側」で起動した goroutine 同士が - channel で同期通信しようとするとどうなる? - text_en: |- - What happens if a goroutine started outside a `synctest.Run` block and a goroutine started inside it - try to synchronize over a channel? - question_code_ref: "code/tomtwinkle_q10_question.go" - choices: - - "正常に通信できる" - - "外側の goroutine が強制終了させられる" - - "deadlock を検知して panic する" - - "常に非同期通信(buffered channel)に変換される" - choices_en: - - They communicate normally - - The outer goroutine is forcefully terminated - - It detects a deadlock and panics - - It is always converted to asynchronous communication (a buffered - channel) - answer: 2 - explanation: | - synctest は独自の仮想時計とスケジューラ内で隔離されて動くため、リアルタイムで動く通常の goroutine や本物のネットワーク I/O と直接的な同期通信を行おうとすると、テストの決定性が壊れるためエラーとなります。 - - pkg.go.dev (testing/synctest): https://pkg.go.dev/testing/synctest - Go Blog (synctest): https://go.dev/blog/synctest - Go 1.24 リリースノート: https://go.dev/doc/go1.24 - - explanation_en: |- - `synctest` runs inside its own isolated virtual clock and scheduler. - If it tries to synchronize directly with ordinary goroutines running in real time, or with real network I/O, - the test would lose determinism, so Go reports it as an error. - - pkg.go.dev (`testing/synctest`): https://pkg.go.dev/testing/synctest - Go Blog (`synctest`): https://go.dev/blog/synctest - Go 1.24 release notes: https://go.dev/doc/go1.24 - # tomtwinkle Q11 - - id: "Go keyword" - title: "Go keyword" - title_en: Go keyword - mode: "normal" - text: | - この中から Go の keyword を選択してください。 - text_en: Which of these is a Go keyword? - choices: - - "new" - - "goto" - - "nil" - - "error" - choices_en: - - new - - goto - - nil - - error - answer: 1 - explanation: | - goto だけが Go の keyword です。 - new は組み込み関数、nil は事前宣言済み識別子、error は事前宣言済みのインターフェース型で、いずれも keyword ではありません。 - - 公式仕様 (Keywords): https://go.dev/ref/spec#Keywords - 公式仕様 (Predeclared identifiers): https://go.dev/ref/spec#Predeclared_identifiers - - explanation_en: |- - Only `goto` is a Go keyword. - `new` is a built-in function, `nil` is a predeclared identifier, and `error` is a predeclared interface type. - None of those are keywords. - - Go spec (Keywords): https://go.dev/ref/spec#Keywords - Go spec (Predeclared identifiers): https://go.dev/ref/spec#Predeclared_identifiers - # tomtwinkle Q12 - - id: "time.Format layout" - title: "time.Format layout" - title_en: time.Format layout - mode: "extra" - text: | - Go の time.Format で時刻レイアウトを指定する方法として正しいのはどれ? - text_en: Which one is the correct way to specify a time layout in Go's - `time.Format`? - choices: - - "1970-01-02" - - "0001-02-03" - - "2006-01-02" - - "1999-12-31" - choices_en: - - '1970-01-02' - - '0001-02-03' - - '2006-01-02' - - '1999-12-31' - answer: 2 - explanation: | - Go の time.Format / time.Parse は、独自のフォーマット記号ではなく基準時刻 - "Mon Jan 2 15:04:05 MST 2006" を並べたレイアウト文字列で指定します。 - たとえば "2006-01-02" は年-月-日、"15:04:05" は時:分:秒を表します。 - - pkg.go.dev (time): https://pkg.go.dev/time - - explanation_en: |- - `time.Format` and `time.Parse` use a layout string built from the reference time - `Mon Jan 2 15:04:05 MST 2006`, not symbolic format tokens. - For example, `2006-01-02` means year-month-day, and `15:04:05` means hour:minute:second. - - pkg.go.dev (`time`): https://pkg.go.dev/time - # tomtwinkle Q13 - - id: "A legendary Go founder" - title: "Go founding member" - title_en: A legendary Go founder - mode: "normal" - text: | - Go 言語は Google のエンジニアによって作られましたが、初期メンバーの中には - IT の歴史を作った「生きる伝説」とも呼べる偉大なプログラマが含まれています。 - それは誰でしょう? - text_en: |- - Go was created by Google engineers, but one of the original members was a legendary programmer - who helped shape computing history. Who was it? - choices: - - "Linus Torvalds" - - "Ken Thompson" - - "Guido van Rossum" - - "James Gosling" - choices_en: - - Linus Torvalds - - Ken Thompson - - Guido van Rossum - - James Gosling - answer: 1 - explanation: | - 正解は Ken Thompson です。Go の作者としては Robert Griesemer、Rob Pike、 - Ken Thompson の 3 名が知られています。 - Ken Thompson は Bell Labs で Unix を共同開発したことで特に有名で、 - その流れの中で C 言語のもとになった B 言語の設計や Plan 9 の開発にも関わった、 - 計算機史に残るプログラマです。 - システムソフトウェアの設計に深く関わってきた人物が、Go の初期設計メンバーにも入っています。 - - Go FAQ: https://go.dev/doc/faq#history - - explanation_en: |- - The correct answer is Ken Thompson. - The three Go creators usually cited are Robert Griesemer, Rob Pike, and Ken Thompson. - Ken Thompson is especially famous for co-creating Unix at Bell Labs, and he also worked on the B language that led to C and on Plan 9. - A programmer with deep roots in system software design was part of Go's original design team. - - Go FAQ: https://go.dev/doc/faq#history - # tomtwinkle Q14 - - id: "Initial goroutine stack size" - title: "goroutine initial stack" - title_en: Initial goroutine stack size - mode: "extra" - text: | - ゴルーチンは 1 つ起動するのに、初期状態でどれくらいのメモリを消費すると考えるのが近いでしょうか? - text_en: Roughly how much memory does a newly started goroutine use at - first? - choices: - - "約 2 KB" - - "約 64KB" - - "約 1MB" - - "OS スレッドと同じ数 MB" - choices_en: - - About 2 KB - - About 64 KB - - About 1 MB - - Several MB, like an OS thread - answer: 0 - explanation: | - goroutine は軽量で、初期スタックはごく小さく、現行ランタイムでは約 2 KB から始まります。 - 必要に応じてスタックが伸縮するため、大量の goroutine を比較的低コストで扱えます。 - - Go FAQ (Goroutines): https://go.dev/doc/faq#goroutines - - explanation_en: |- - Goroutines are lightweight. - Their initial stack is very small, starting at around 2 KB in the current runtime. - The stack grows and shrinks as needed, so you can handle many goroutines at relatively low cost. - - Go FAQ (Goroutines): https://go.dev/doc/faq#goroutines - # tomtwinkle Q15 - - id: "Pointer arithmetic" - title: "pointer arithmetic" - title_en: Pointer arithmetic - mode: "extra" - text: | - Go 言語には C 言語でおなじみの「ポインタ」がありますが、 - C 言語では当たり前にできて、Go 言語では安全のために意図的にできなくしているポインタ操作はどれでしょう? - text_en: |- - Go has pointers like C, but which pointer operation is intentionally disallowed in Go for safety - even though it is commonplace in C? - choices: - - "ポインタに整数を足し引きして任意のアドレスへ進める" - - "変数のアドレスを取得する" - - "ポインタ経由で値を書き換える" - - "nil と比較する" - choices_en: - - Add or subtract integers from a pointer to move to an arbitrary address - - Take the address of a variable - - Modify a value through a pointer - - Compare with nil - answer: 0 - explanation: | - Go では安全性を保つため、通常のコードでポインタ演算はできません。 - 変数のアドレス取得、参照先の読み書き、nil との比較はできますが、 - C 言語のようなポインタの加減算は `unsafe` を使わない限り行えません。 - - Go FAQ (Why is there no pointer arithmetic?): https://go.dev/doc/faq#no_pointer_arithmetic - pkg.go.dev (unsafe): https://pkg.go.dev/unsafe - - explanation_en: |- - To preserve safety, Go does not allow pointer arithmetic in ordinary code. - You can take an address, read and write through a pointer, and compare it with `nil`, - but you cannot do C-style pointer addition or subtraction unless you step into `unsafe`. - - Go FAQ (Why is there no pointer arithmetic?): https://go.dev/doc/faq#no_pointer_arithmetic - pkg.go.dev (`unsafe`): https://pkg.go.dev/unsafe - # tomtwinkle Q16 - - id: "Compiler warnings" - title: "compiler warnings" - title_en: Compiler warnings - mode: "extra" - text: | - 他のコンパイル言語と比較した場合の Go 言語のコンパイラの特徴として正しいのはどれ? - text_en: Which statement correctly describes the Go compiler compared with - many other compiled languages? - choices: - - "コンパイラがワーニングを出さない" - - "fmt.Printf の書式指定ミスのような問題は、コンパイラが error にする" - - "実行時にだけ型チェックを行う" - - "コンパイル時に自動でメモリリークを検出する" - choices_en: - - The compiler does not emit warnings - - The compiler turns issues like `fmt.Printf` format mistakes into errors - - Type checking happens only at runtime - - It automatically detects memory leaks at compile time - answer: 0 - explanation: | - Go のコンパイラは「警告を出して通す」のではなく、問題があればエラーとして止める方針です。 - たとえば未使用の変数や import は warning ではなく compile error になります。 - fmt.Printf の書式指定ミスのような、より高度な静的解析は `go vet` の役割です。 - - Go FAQ (Can I stop these complaints about my unused variable/import?): https://go.dev/doc/faq#unused_variables_and_imports - pkg.go.dev (go vet): https://pkg.go.dev/cmd/vet - - explanation_en: |- - The Go compiler follows a "no warnings" policy: it stops with errors instead of emitting warnings and continuing. - For example, unused variables and imports are compile errors, not warnings. - More advanced static checks, such as `fmt.Printf` format issues, are the job of `go vet`. - - Go FAQ (Can I stop these complaints about my unused variable/import?): https://go.dev/doc/faq#unused_variables_and_imports - pkg.go.dev (`go vet`): https://pkg.go.dev/cmd/vet - # tomtwinkle Q17 - - id: "Who designed the Gopher?" - title: "Gopher designer" - title_en: Who designed the Gopher? - mode: "extra" - text: | - 愛らしいマスコット「Gopher」君ですが、このキャラクターをデザインしたのは誰でしょう? - text_en: The adorable Go mascot is the Gopher. Who designed this character? - choices: - - "Renée French" - - "Rob Pike" - - "Ken Thompson" - - "Russ Cox" - choices_en: - - Renée French - - Rob Pike - - Ken Thompson - - Russ Cox - answer: 0 - explanation: | - Go のマスコットである Gopher をデザインしたのは Renée French です。 - Renée French は Rob Pike の妻でもあります。 - Gopher は Go コミュニティを象徴するキャラクターとして広く親しまれています。 - - The Go Gopher: https://go.dev/blog/gopher - - explanation_en: |- - The Go mascot was designed by Renée French. - She is also Rob Pike's wife. - The Gopher is widely loved as a symbol of the Go community. - - The Go Gopher: https://go.dev/blog/gopher - # wancom Q1 - - id: "Execution order of select" - title: "select の実行順序" - title_en: Execution order of select - mode: "extra" - text: "このコードの出力は何?" - text_en: What does this code print? - question_code_ref: "code/wancom_q1_question.go" - choices: - - "Message from chA: msgA" - - "Message from chB: msgB-1" - - "Message from chC: msgC" - - "上記のどれか(どれが出るかはわからない)" - choices_en: - - 'Message from chA: msgA' - - 'Message from chB: msgB-1' - - 'Message from chC: msgC' - - One of the above (you cannot know which one) - answer: 3 - explanation: | - select はどの Channel から処理するかの順序を保証しません。 - 必要に応じて二重 select などを使用します。 - explanation_en: |- - `select` does not guarantee the order in which ready channels are chosen. - Use techniques such as nested `select` statements if you need stricter control. - answer_code_ref: "code/wancom_q1_answer.go" - - # wancom Q2 - - id: "Formats supported by the standard library" - title: "標準パッケージの対応フォーマット" - title_en: Formats supported by the standard library - mode: "extra" - text: "標準パッケージでサポートされていないデータフォーマットは?" - text_en: Which data format is NOT supported by Go's standard library? - choices: - - "gzip" - - "png" - - "mp3" - - "pem" - choices_en: - - gzip - - png - - mp3 - - pem - answer: 2 - explanation: | - それぞれ、compress/gzip、image/png、encoding/pem が存在します。 - mp3 だけ標準パッケージでは提供されていません。 - ・ compress/gzip: https://pkg.go.dev/compress/gzip@go1.26.2 - ・ image/png: https://pkg.go.dev/image/png@go1.26.2 - ・ encoding/pem: https://pkg.go.dev/encoding/pem@go1.26.2 - - explanation_en: |- - Go provides `compress/gzip`, `image/png`, and `encoding/pem` in the standard library. - Only mp3 is not provided there. - - `compress/gzip`: https://pkg.go.dev/compress/gzip@go1.26.2 - - `image/png`: https://pkg.go.dev/image/png@go1.26.2 - - `encoding/pem`: https://pkg.go.dev/encoding/pem@go1.26.2 - # wancom Q3 - - id: "pprof data provided by both packages" - title: "pprof の提供データ" - title_en: pprof data provided by both packages - mode: "extra" - text: "net/http/pprofとruntime/pprof両方で提供しているプロファイリングデータは?" - text_en: Which profiling data is provided by both `net/http/pprof` and - `runtime/pprof`? - choices: - - "cmdline" - - "goroutine" - - "profile" - - "symbol" - choices_en: - - cmdline - - goroutine - - profile - - symbol - answer: 1 - explanation: | - goroutine は runtime/pprof から提供されるデータです。 - cmdline、profile、symbol は net/http/pprof で独自に提供しているデータです。 - explanation_en: |- - `goroutine` is a profile provided by `runtime/pprof`, and `net/http/pprof` also exposes it over HTTP. - `cmdline`, `profile`, and `symbol` are special endpoints that exist only in `net/http/pprof`. - answer_code_ref: "code/wancom_q3_answer.go" - - # sat0ken Q1 - - id: "Microcontrollers supported by TinyGo" - title: "TinyGo 対応マイコン" - title_en: Microcontrollers supported by TinyGo - mode: "extra" - text: "TinyGoでサポートされているマイコンは?" - text_en: Which microcontrollers are supported by TinyGo? - choices: - - "Arduino Uno" - - "Raspberry Pi Pico" - - "ESP32" - - "全て" - choices_en: - - Arduino Uno - - Raspberry Pi Pico - - ESP32 - - All of the above - answer: 3 - explanation: | - 1~3番目の全てのマイコンがサポートされています。 - 公式ドキュメント: https://tinygo.org/docs/reference/microcontrollers/ - - explanation_en: |- - All of the first three microcontrollers are supported. - Official docs: https://tinygo.org/docs/reference/microcontrollers/ - # sat0ken Q2 - - id: "machine.PinMode" - title: "machine.PinMode" - title_en: machine.PinMode - mode: "extra" - text: | - TinyGoでLEDを光らせるには、machineパッケージでピンの入出力を指定する必要があります。 - machine.PinModeで指定するタイプは次のうちどれですか? - text_en: |- - To light an LED in TinyGo, you need to set the pin direction in the `machine` package. - Which `machine.PinMode` value should you use? - choices: - - "machine.PinInput" - - "machine.PinOutput" - - "machine.PinAnalog" - - "machine.PinDigital" - choices_en: - - machine.PinInput - - machine.PinOutput - - machine.PinAnalog - - machine.PinDigital - answer: 1 - explanation: | - LEDを光らせるには、ピンを出力モードである machine.PinOutput に設定する必要があります。 - 公式ドキュメント: https://pkg.go.dev/github.com/tinygo-org/tinygo/src/machine#PinMode - - explanation_en: |- - To light an LED, the pin must be configured as output using `machine.PinOutput`. - Official docs: https://pkg.go.dev/github.com/tinygo-org/tinygo/src/machine#PinMode - # sat0ken Q3 - - id: "Color on the Gopher Badge" - title: "Gopher Badge の色指定" - title_en: Color on the Gopher Badge - mode: "normal" - text: | - Gopher Badgeの画面の色を黒から赤に変えてみましょう。 - color.RGBAでどのように指定すればよいでしょうか? - - https://play.tinygo.org/ - text_en: |- - Let's change the Gopher Badge screen color from black to red. - How should you specify it with `color.RGBA`? - - https://play.tinygo.org/ - question_code_ref: "code/sat0ken_q3_question.go" - choices: - - "color.RGBA{0, 0, 0, 0}" - - "color.RGBA{255, 0, 0, 0}" - - "color.RGBA{255, 255, 0, 0}" - - "color.RGBA{255, 255, 255, 255}" - choices_en: - - color.RGBA{0, 0, 0, 0} - - color.RGBA{255, 0, 0, 0} - - color.RGBA{255, 255, 0, 0} - - color.RGBA{255, 255, 255, 255} - answer: 1 - explanation: | - TinyGoでもimage/colorパッケージの使い方はGoと同じように、RGBAを指定します。 - 255, 0, 0, 0 を指定することで Gopher Badge の画面の色が黒から赤に変わります。 - - explanation_en: |- - In TinyGo, you use the `image/color` package the same way as in Go and specify RGBA values. - Setting `255, 0, 0, 0` changes the Gopher Badge screen from black to red. - # ryokotmng Q1 - - id: "append and shared arrays" - title: "append と共有配列" - title_en: append and shared arrays - mode: "extra" - text: "このコードの出力は何?" - text_en: What does this code print? - question_code_ref: "code/ryokotmng_q1.go" - choices: - - "[1 2 3]" - - "[1 2 99]" - - "[99 2 3]" - - "[1 2 3 99]" - choices_en: - - '[1 2 3]' - - '[1 2 99]' - - '[99 2 3]' - - '[1 2 3 99]' - answer: 1 - explanation: "b := a[:2] はコピーではなく同じ配列を参照するスライス。append がキャパシティ内(cap=3)なので新しい配列を確保せず、a[2] - を 99 で上書きしてしまう。" - - explanation_en: '`b := a[:2]` is not a copy; it is a slice over the same underlying - array. Because `append` stays within the existing capacity (`cap=3`), no new - array is allocated, and `a[2]` gets overwritten with `99`.' - - id: "Go loop syntax" - title: "Go のループ構文" - title_en: Go loop syntax - mode: "extra" - text: "Goにおいて、繰り返し処理を実現するために用意されているものは次のうちどれですか?" - text_en: In Go, which construct is provided for repeated processing? - choices: - - "foreach と for" - - "for のみ" - - "for と while" - - "for, while, do-while" - choices_en: - - foreach and for - - for only - - for and while - - for, while, do-while - answer: 1 - explanation: "for のみです。while や do-while, foreach に相当する構文も for を使って表現します。ループ構文を - for に一本化することで、誰が書いても同じようなコードになり、可読性・一貫性が高まります。" - - explanation_en: Only `for`. Even patterns equivalent to `while`, `do-while`, - and `foreach` are all expressed with `for` in Go. By unifying loops around - `for`, Go encourages consistent, readable code. - - id: "What animal is the Gopher?" - title: "Gopher のモチーフ" - title_en: What animal is the Gopher? - mode: "normal" - text: "Goのマスコットキャラクター「Gopher」は何の生き物をモチーフにしている?" - text_en: Go's mascot character, the Gopher, is based on which animal? - choices: - - "ビーバー" - - "プレーリードッグ" - - "ウォンバット" - - "ホリネズミ" - choices_en: - - Beaver - - Prairie dog - - Wombat - - Pocket gopher - answer: 3 - explanation: "Gopherは英語でホリネズミ(pocket gopher)を指します。Go のマスコットキャラクターはRenee French - がデザインしたホリネズミがモチーフです。" - - explanation_en: In English, "gopher" refers to a pocket gopher. The Go - mascot is based on a pocket gopher designed by Renée French. - - id: "Updating through a pointer" - title: "ポインタ渡しの更新" - title_en: Updating through a pointer - mode: "extra" - text: "以下の A〜D のうち、関数を呼んだ後に msg の値が \"Original\" から\"Changed\" に変わるのはどれ?" - text_en: Among A-D below, which call changes `msg` from "Original" to - "Changed" after the function returns? - question_code_ref: "code/hasegawa_itsuki_q3_question.go" - choices: - - "A. updateA(msg)" - - "B. updateB(&msg)" - - "C. updateC(&msg)" - - "D. updateD(&ptr)" - choices_en: - - A. updateA(msg) - - B. updateB(&msg) - - C. updateC(&msg) - - D. updateD(&ptr) - answer: 2 - explanation: "A は値渡しなので msg は変わりません。B は *string のコピーを差し替えるだけで msg に影響しません。C は - *s = \"Changed\" でポインタの指す先を直接書き換えるため msg が Changed になります。D は ptr の向き先を変えるだけで - msg は不変です。" - - explanation_en: A passes by value, so `msg` does not change. B only - reassigns a copied `*string`, so it does not affect `msg`. C writes - directly to the value pointed to by `s` with `*s = "Changed"`, so `msg` - becomes `Changed`. D only changes what `ptr` points to, so `msg` stays the - same. - - id: "The typed nil trap" - title: "typed nil の罠" - title_en: The typed nil trap - mode: "extra" - text: "このコードの出力は何?" - text_en: What does this code print? - question_code_ref: "code/Rtosshy_q1_question.go" - choices: - - "true" - - "false" - - "コンパイルエラー" - - "実行時エラー" - choices_en: - - 'true' - - 'false' - - Compilation error - - Runtime error - answer: 1 - explanation: "Interfaceの内部には型情報と値の情報の2種類のポインタがあります。値がnilであっても型情報がnilでなければ、Interface全体としてはnilではありません。参考:https://go.dev/src/internal/abi/iface.go" - explanation_en: 'An interface stores two pointers internally: one for type information - and one for value data. Even if the value is nil, the interface itself is not - nil unless the type information is also nil. Reference: https://go.dev/src/internal/abi/iface.go' - answer_code_ref: "code/Rtosshy_q1_answer.go" - - - id: "The clear built-in" - title: "clear 組み込み関数" - title_en: The clear built-in - mode: "extra" - text: "Goで存在する組み込み関数は?" - text_en: Which of the following is a real Go built-in function? - choices: - - "erase" - - "remove" - - "clear" - - "purge" - choices_en: - - erase - - remove - - clear - - purge - answer: 2 - explanation: "clearはGo 1.21で追加された組み込み関数で、sliceの要素をゼロ値に、mapの要素をすべて削除します。" - - explanation_en: '`clear` was added in Go 1.21. It zeroes all elements in a slice - and removes all elements from a map.' - - id: "append after make" - title: "make 後の append" - title_en: append after make - mode: "extra" - text: "このコードの出力は何?" - text_en: What does this code print? - question_code_ref: "code/Rtosshy_q3_question.go" - choices: - - "[0 1 2 3 4 5 6 7 8 9]" - - "[]" - - "コンパイルエラー" - - "[0 0 0 0 0 0 0 0 0 0 0 1 2 3 4 5 6 7 8 9]" - choices_en: - - '[0 1 2 3 4 5 6 7 8 9]' - - '[]' - - Compilation error - - '[0 0 0 0 0 0 0 0 0 0 0 1 2 3 4 5 6 7 8 9]' - answer: 3 - explanation: "make([]int, 10) は長さ10のスライスを作るため、ゼロ値で埋められた要素が10個入った状態になります。そこに append - すると末尾に追加されるため、計20要素になります。空のスライスに append したい場合は make([]int, 0, 10) のように長さ0・容量10で初期化しましょう。" - - explanation_en: '`make([]int, 10)` creates a slice of length 10, already filled - with zero values. Appending to it adds new values at the end, so the result - has 20 elements. If you want to append to an empty slice with capacity 10, initialize - it as `make([]int, 0, 10)`.' - # tesso57 Q1 - - id: "Infinite recursion in Stringer" - title: "Stringer の無限再帰" - title_en: Infinite recursion in Stringer - mode: "extra" - text: "このコードの挙動はどれ?" - text_en: What does this code do? - question_code_ref: "code/tesso57_q1_question.go" - choices: - - "Name: Go" - - "Name: Name: Go" - - "スタックオーバーフローで runtime error" - - "コンパイルエラー" - choices_en: - - 'Name: Go' - - 'Name: Name: Go' - - Runtime error from stack overflow - - Compilation error - answer: 2 - explanation: | - %v は Stringer interface を実装している型に対して String() を呼びます。 - String() の中で fmt.Sprintf("%v", n) を使うと、再び String() が呼ばれて無限再帰になります。 - 回避策は string(n) にキャストすることです。 - explanation_en: |- - `%v` calls `String()` for types that implement the `fmt.Stringer` interface. - If `String()` itself uses `fmt.Sprintf("%v", n)`, it calls `String()` again and recurses forever. - The fix is to cast to `string(n)`. - answer_code_ref: "code/tesso57_q1_answer.go" - answer_code_play_ref: "https://go.dev/play/p/jmW7kd-T0UB" - - # tesso57 Q2 - - id: "Reverse lookup for fmt verbs" - title: "fmt verb 逆引き" - title_en: Reverse lookup for fmt verbs - mode: "extra" - text: | - fmt.Printf("????", u) の出力が main.User{Name:"Gopher", Age:10} になるとき、 - ???? に入るのはどれ? - text_en: |- - If `fmt.Printf("????", u)` prints `main.User{Name:"Gopher", Age:10}`, - what should `????` be? - question_code_ref: "code/tesso57_q2_question.go" - choices: - - "%v" - - "%+v" - - "%#v" - - "%s" - choices_en: - - '%v' - - '%+v' - - '%#v' - - '%s' - answer: 2 - explanation: | - %v は {Gopher 10}、%+v はフィールド名付きの {Name:Gopher Age:10}、 - %#v は Go の構文そのままの main.User{Name:"Gopher", Age:10} を出力します。 - %#v はパッケージ名・型名・フィールド名・値の引用符まで含むので、デバッグ時に型情報まで確認したいときに便利です。 - explanation_en: |- - `%v` prints `{Gopher 10}`, `%+v` prints `{Name:Gopher Age:10}`, - and `%#v` prints the Go-syntax form `main.User{Name:"Gopher", Age:10}`. - `%#v` includes the package name, type name, field names, and even quotes around string values, - so it's useful when you want full type information while debugging. - answer_code_ref: "code/tesso57_q2_answer.go" - answer_code_play_ref: "https://go.dev/play/p/8nztK-oSFWT" - - # tesso57 Q3 - - id: "Asymmetry of reading and writing a nil map" - title: "nil map の読み書き非対称" - title_en: Asymmetry of reading and writing a nil map - mode: "normal" - text: "このコードの挙動はどれ?" - text_en: What does this code do? - question_code_ref: "code/tesso57_q3_question.go" - choices: - - "0 を出力した後、正常終了" - - "0 を出力した後、panic" - - "1行目の fmt.Println の時点で panic" - - "コンパイルエラー" - choices_en: - - Prints 0 and exits normally - - Prints 0 and then panics - - Panics at the first `fmt.Println` - - Compilation error - answer: 1 - explanation: | - nil map は読み取り時にゼロ値を返しますが、書き込み時には panic します。 - var m map[string]int は nil で初期化され、m["hello"] の読み取りは int のゼロ値 0 を返します。 - しかし m["hello"] = 1 の書き込みで panic: assignment to entry in nil map が発生します。 - make(map[string]int) で初期化することで回避できます。 - explanation_en: |- - A nil map returns the zero value when you read from it, but panics when you write to it. - `var m map[string]int` initializes `m` to nil, so reading `m["hello"]` returns the zero value `0`. - However, writing `m["hello"] = 1` panics with `assignment to entry in nil map`. - You can avoid this by initializing it with `make(map[string]int)`. - answer_code_ref: "code/tesso57_q3_answer.go" - answer_code_play_ref: "https://go.dev/play/p/xL4MGI0wqHP" - - # tesso57 Q4 - - id: "Types that can be map keys" - title: "map のキーにできる型" - title_en: Types that can be map keys - mode: "extra" - text: "Go の map のキーとして使えない型はどれ?" - text_en: Which type cannot be used as a key in a Go map? - choices: - - "string" - - "int" - - "[2]int" - - "[]int" - choices_en: - - string - - int - - '[2]int' - - '[]int' - answer: 3 - explanation: | - map のキーには == と != で比較できる comparable な型だけが使えます。 - string、int、配列は比較可能ですが、slice は比較できないため map のキーにできません。 - 同じ理由で map や関数もキーにできません。 - explanation_en: |- - Only comparable types, which can be compared with `==` and `!=`, can be used as map keys. - `string`, `int`, and arrays are comparable, but slices are not, so slices cannot be map keys. - For the same reason, maps and functions also cannot be map keys. - answer_code_ref: "code/tesso57_q4_answer.go" - answer_code_play_ref: "https://go.dev/play/p/s0DAFLy5Ebh" - - # tesso57 Q5 - - id: "Map elements cannot be updated directly" - title: "map 要素は直接更新できない" - title_en: Map elements cannot be updated directly - mode: "extra" - text: "このコードの挙動はどれ?" - text_en: What does this code do? - question_code_ref: "code/tesso57_q5_question.go" - choices: - - "11 に更新される" - - "新しい要素が追加される" - - "panic が発生する" - - "コンパイルエラー" - choices_en: - - It updates the value to 11 - - It adds a new element - - It panics - - Compilation error - answer: 3 - explanation: | - map の要素はアドレス可能ではないため、m["gopher"].Age = 11 のように構造体フィールドを直接更新できません。 - map の再配置で要素のアドレスが変わりうるためです。 - 一度ローカル変数に取り出して更新し、m["gopher"] = u のように代入し直す必要があります。 - あるいは map[string]*User のようにポインタを持たせる設計にする方法もあります。 - explanation_en: |- - Map elements are not addressable, so you cannot update a struct field directly like `m["gopher"].Age = 11`. - This is because the address of a map element can change when the map is resized. - You must first copy the value into a local variable, update it, and assign it back with `m["gopher"] = u`. - Another option is to use a design like `map[string]*User` and store pointers. - answer_code_ref: "code/tesso57_q5_answer.go" - answer_code_play_ref: "https://go.dev/play/p/ESlQsu20rXB" - - # makki_d Q1 - - id: "Keywords vs predeclared identifiers" - title: "Goの予約語と事前定義識別子" - title_en: Keywords vs predeclared identifiers - mode: "extra" - text: "Goの予約語ではないものはどれ?" - text_en: Which of the following is not a Go keyword? - choices: - - "go" - - "nil" - - "goto" - - "fallthrough" - choices_en: - - go - - nil - - goto - - fallthrough - answer: 1 - explanation: | - Goの予約語は次の25個のみです: - break case chan const continue default defer else - fallthrough for func go goto if import interface map - package range return select struct switch type var - - nilの他、trueやfalseのような定数、stringやerrorのような型名は、予約語ではなく「事前定義識別子(Predeclared identifier)」で、変数名などに使うことができます(当然推奨されません)。 - explanation_en: |- - Go has only 25 reserved keywords: - break case chan const continue default defer else - fallthrough for func go goto if import interface map - package range return select struct switch type var - - `nil`, as well as constants like `true` and `false` and type names like `string` and `error`, - are not keywords. They are predeclared identifiers, so they can technically be used as names for variables and other identifiers - (although that is not recommended). - - # makki_d Q2 - - id: "errors.Join and typed nil" - title: "errors.Joinとtyped nil" - title_en: errors.Join and typed nil - mode: "extra" - text: "非nilとなるerrorはどれ?" - text_en: Which error becomes non-nil? - question_code_ref: "code/makki_d_q2_question.go" - choices: - - "すべてnil" - - "err1のみnil" - - "err3のみ非nil" - - "すべて非nil" - choices_en: - - All of them are nil - - Only err1 is nil - - Only err3 is non-nil - - All of them are non-nil - answer: 2 - explanation: | - 複数の error をまとめる標準パッケージの関数 errors.Join は、引数に非nilなerrorが一つもないときはnilを返します。 - この問題では、f2()の戻り値は*MyErr型のtyped nilであるため、非nilです。 - explanation_en: |- - `errors.Join`, the standard library function that combines multiple errors, - returns `nil` when none of its arguments are non-nil. - In this example, however, `f2()` returns a typed nil of type `*MyErr`, - so the resulting interface value is non-nil. - answer_code_ref: "code/makki_d_q2_answer.go" - answer_code_play_ref: "https://go.dev/play/p/g-_VO1PYt6b" - - # makki_d Q3 - - id: "Generics and method sets" - title: "Genericsとメソッド集合" - title_en: Generics and method sets - mode: "extra" - text: "型パラメータ付き関数「MyFunc」の引数にできない型はどれ?" - text_en: Which type cannot be passed to the type-parameterized function `MyFunc`? - question_code_ref: "code/makki_d_q3_question.go" - choices: - - "[]MyType1" - - "[]MyType2" - - "[]MyType3" - - "[]MyType4" - choices_en: - - "[]MyType1" - - "[]MyType2" - - "[]MyType3" - - "[]MyType4" - answer: 2 - explanation: | - MyFuncの引数は型制約によりMyIfaceを実装している型のスライスとなっています。 - MyIface、つまりm1()メソッドの実装があるのはMyType1のみですが、MyType2はMyType1の単なる別名であり、またMyType4は埋め込みによりインターフェイスを満たします。 - 一方、新たな型として定義しているMyType3はm1()メソッドが無いため、MyFuncの引数にはできません。 - - Genericsが使えるようになったことで、このようなスライスを受け取る関数を利用するときにスライスを詰め直したり、型ごとに関数を用意する必要がなくなりました。 - explanation_en: |- - `MyFunc` accepts a slice of types that satisfy the `MyIface` constraint. - `MyType1` has the `m1()` method, `MyType2` is just an alias of `MyType1`, - and `MyType4` also satisfies the interface through embedding. - `MyType3`, on the other hand, is defined as a new type and does not have an `m1()` method, - so it cannot be passed to `MyFunc`. - - With generics, functions like this can accept slices of multiple related types - without repacking the slice or writing a separate function for each type. diff --git a/quiz-app/src/App.vue b/quiz-app/src/App.vue new file mode 100644 index 0000000..6a1a450 --- /dev/null +++ b/quiz-app/src/App.vue @@ -0,0 +1,1443 @@ + + + diff --git a/quiz-app/src/assets/bluesky-logo.svg b/quiz-app/src/assets/bluesky-logo.svg new file mode 100755 index 0000000..199dd9e --- /dev/null +++ b/quiz-app/src/assets/bluesky-logo.svg @@ -0,0 +1,3 @@ + + + diff --git a/quiz-app/public/assets/go-conference-2026-logo.svg b/quiz-app/src/assets/go-conference-2026-logo.svg similarity index 100% rename from quiz-app/public/assets/go-conference-2026-logo.svg rename to quiz-app/src/assets/go-conference-2026-logo.svg diff --git a/quiz-app/src/assets/x-logo.svg b/quiz-app/src/assets/x-logo.svg new file mode 100755 index 0000000..56368dd --- /dev/null +++ b/quiz-app/src/assets/x-logo.svg @@ -0,0 +1,3 @@ + + + diff --git a/quiz-app/src/components/games/ChoiceSelectGame.vue b/quiz-app/src/components/games/ChoiceSelectGame.vue new file mode 100644 index 0000000..f9c4922 --- /dev/null +++ b/quiz-app/src/components/games/ChoiceSelectGame.vue @@ -0,0 +1,181 @@ + + + diff --git a/quiz-app/src/components/games/FillBlankTapGame.vue b/quiz-app/src/components/games/FillBlankTapGame.vue new file mode 100644 index 0000000..560c549 --- /dev/null +++ b/quiz-app/src/components/games/FillBlankTapGame.vue @@ -0,0 +1,205 @@ + + + diff --git a/quiz-app/src/components/ui/PressButton.vue b/quiz-app/src/components/ui/PressButton.vue new file mode 100644 index 0000000..8850f3b --- /dev/null +++ b/quiz-app/src/components/ui/PressButton.vue @@ -0,0 +1,38 @@ + + + diff --git a/quiz-app/src/components/ui/StageOutputPanel.vue b/quiz-app/src/components/ui/StageOutputPanel.vue new file mode 100644 index 0000000..1c5fdce --- /dev/null +++ b/quiz-app/src/components/ui/StageOutputPanel.vue @@ -0,0 +1,72 @@ + + + diff --git a/quiz-app/src/data/stages.toml b/quiz-app/src/data/stages.toml new file mode 100644 index 0000000..e58b964 --- /dev/null +++ b/quiz-app/src/data/stages.toml @@ -0,0 +1,243 @@ +[[campaignTiers]] +id = "" +title = { ja = "", en = "" } +difficultyLabel = { ja = "", en = "" } +description = { ja = "Go Conference 2026 CodeLab", en = "Go Conference 2026 CodeLab" } +unlocksSpecial = true + +[[campaignTiers.stages]] +id = "fill-builtin-infinite-loop" +kind = "fill" +label = { ja = "builtin", en = "builtin" } +title = { ja = "無限ループの作成", en = "Build an infinite loop" } +prompt = { ja = "Go で無限ループを作るためのキーワードを埋めてください。", en = "Fill in the keyword to create an infinite loop in Go." } +outputLines = """ +""" +why = { ja = "Go には `while` や `loop` といったキーワードは存在せず、繰り返し構文はすべて `for` に統一されています。", en = "Go does not have `while` or `loop` keywords; instead, it uses only `for` for all loops." } +takeaway = { ja = "`for` の条件式を省略して `for {}` と書くだけで、もっともシンプルでスッキリとした無限ループを作成できます。", en = "By omitting the condition from a `for` statement and simply writing `for {}`, you can create an infinite loop more simply and clearly." } +templateLines = """ +[1] { + +} +""" +pool = [ "for", "while", "loop", "(true)", "True:" ] +correctAnswers = [ "for" ] + +[[campaignTiers.stages]] +id = "select-result-error-shortdecl" +kind = "fill" +label = { ja = "error", en = "error" } +title = { ja = "result, err :=", en = "result, err :=" } +prompt = { ja = "calc(a, b) の結果と error を同時に受ける 3 か所。", en = "Three places for receiving the result and error from calc(a, b)." } +outputLines = """ +""" +playgroundUrl = "https://go.dev/play/p/bGgF19hsfbC" +why = { ja = "複数戻り値は左辺を並べ、初回代入なら `:=` を使います。", en = "We can receive multiple return values by listing variables on the left side. We use `:=` for declaration and assignment at once." } +takeaway = { ja = "Go は複数の戻り値を返すことが出来ます。 err を最後に返すのが一般的です。", en = "Functions in Go can return multiple values, and it is common practice to return `err` as the last value." } +templateLines = """ +[1][2][3]calc(a, b) +if err != nil { + fmt.Println(err) +} +fmt.Println(result) +""" +pool = [ "result,", "err", ":=", "=", "panic(err)" ] +correctAnswers = [ "result,", "err", ":=" ] + +[[campaignTiers.stages]] +id = "select-blank-import-pprof" +kind = "fill" +label = { ja = "import", en = "import" } +title = { ja = "pprof をブランク import", en = "pprof blank import" } +prompt = { ja = "pprof を blank import する 2 か所。", en = "Two places for pprof blank import." } +outputLines = """ +""" +playgroundUrl = "https://go.dev/play/p/oIKQdxUcgjq" +why = { ja = "blank import の `_` は名前を使わず、package の `init` だけを有効にします。", en = "A blank import `_` does not use the package name; it only enables the package's `init` function." } +takeaway = { ja = "`import _ \"net/http/pprof\"` は副作用だけを欲しいときの定番です。", en = "`import _ \"net/http/pprof\"` is a standard way to include a package for its side effects only." } +templateLines = """ +import [1] [2] +""" +pool = [ "_", "\"net/http/pprof\"", "\"runtime/pprof\"", "." ] +correctAnswers = [ "_", "\"net/http/pprof\"" ] + +[[campaignTiers.stages]] +id = "select-struct-plusv" +kind = "fill" +label = { ja = "fmt", en = "fmt" } +title = { ja = "構造体の field 名", en = "The field name of a structure" } +prompt = { ja = "{Name:Gopher Age:10} を field 名つきで表示する 2 か所。", en = "Two places for printing {Name:Gopher Age:10} with the names of its fields." } +outputLines = """ +{Name:Gopher Age:10} +""" +playgroundUrl = "https://go.dev/play/p/bUZNxIr7BYy" +why = { ja = "`fmt.Printf` と `%+v` を組み合わせると、struct の field 名も含めて表示できます。", en = "By using `fmt.Printf` and `%+v`, we can print the value of a struct along with its field names." } +takeaway = { ja = "`fmt.Print` や `fmt.Println` ではなく、`fmt.Printf(\"%+v\", gopher)` を使うと field 名つきで表示できます。", en = "`fmt.Printf(\"%+v\", gopher)` prints the value of a struct including its field names, unlike `fmt.Print` or `fmt.Println`." } +templateLines = """ +gopher := Gopher{Name: "Gopher", Age: 10} +[1]([2], gopher) +""" +pool = [ "fmt.Printf", "fmt.Print", "fmt.Println", "\"%v\"", "\"%T\"", "\"%+v\"" ] +correctAnswers = [ "fmt.Printf", "\"%+v\"" ] + +[[campaignTiers.stages]] +id = "fill-fmt-hex-output" +kind = "fill" +label = { ja = "fmt", en = "fmt" } +title = { ja = "10進と16進", en = "Decimal and hexadecimal" } +prompt = { ja = "Value: 255, Hex: ff を出す 2 か所。", en = "Two places for printing Value: 255, Hex: ff." } +outputLines = """ +Value: 255, Hex: ff +""" +playgroundUrl = "https://go.dev/play/p/gKHeYb66kh3" +why = { ja = "`%d` は 10 進、`%x` は小文字の 16 進です。", en = "`%d` prints a value in decimal and `%x` prints it in hexadecimal." } +takeaway = { ja = "同じ数値でも verb を変えるだけで表示形式を切り替えられます。", en = "We can change the printing format for the same value by changing the verb." } +templateLines = """ +num := 255 +fmt.Printf( + "Value: [1], Hex: [2]", + num, + num, +) +""" +pool = [ "%d", "%b", "%o", "%x", "%X" ] +correctAnswers = [ "%d", "%x" ] + +[[campaignTiers.stages]] +id = "fill-fmt-indexed-order" +kind = "fill" +label = { ja = "fmt", en = "fmt" } +title = { ja = "引数番号で並べ替え", en = "Order by argument number" } +prompt = { ja = "Age: 20, Name: Alice を出す 2 か所。", en = "Two places for printing Age: 20, Name: Alice." } +outputLines = """ +Age: 20, Name: Alice +""" +playgroundUrl = "https://go.dev/play/p/qL2BSv7XrwK" +why = { ja = "`%[n]` を使うと、引数の順番を変えずに参照先だけを入れ替えられます。", en = "`%[n]` refers to the n-th argument without changing the order of arguments." } +takeaway = { ja = "`%[2]d` は 2 番目の引数、`%[1]s` は 1 番目の引数を使う指定です。", en = "`%[2]d` refers to the second argument, and `%[1]s` refers to the first argument." } +templateLines = """ +name := "Alice" +age := 20 +fmt.Printf( + "Age: [1], Name: [2]", + name, + age, +) +""" +pool = [ "%s", "%d", "%[1]s", "%[2]d", "%[1]d", "%[2]s" ] +correctAnswers = [ "%[2]d", "%[1]s" ] + +[[campaignTiers.stages]] +id = "fill-fmt-pi-report" +kind = "fill" +label = { ja = "fmt", en = "fmt" } +title = { ja = "fmt.Printf", en = "fmt.Printf" } +prompt = { ja = "Name: \"pi\", Value: 3.14, Type: float64 を出す 3 か所。", en = "Three places for printing Name: \"pi\", Value: 3.14, Type: float64." } +outputLines = """ +Name: "pi", Value: 3.14, Type: float64 +""" +playgroundUrl = "https://go.dev/play/p/ZtSoKQnvkam" +why = { ja = "`%q` は引用符つき文字列、`%.2f` は小数第 2 位まで、`%T` は値の型名を出します。", en = "`%q` prints a string with quotes, `%.2f` prints to two decimal places, and `%T` prints the type of the value." } +takeaway = { ja = "`%q` / `%.2f` / `%T` を使い分けると、文字列・丸めた小数・型名を一度に確認できます。", en = "By using `%q`, `%.2f`, and `%T`, we can see a quoted string, a rounded decimal, and the type of a value all at once." } +templateLines = """ +pi := 3.14159 +name := "pi" +fmt.Printf( + "Name: [1], ", + "Value: [2], ", + "Type: [3]", + name, + pi, + pi, +) +""" +pool = [ "%s", "%q", "%.2f", "%2f", "%f", "%v", "%T", "%t" ] +correctAnswers = [ "%q", "%.2f", "%T" ] + +[[campaignTiers.stages]] +id = "fill-defer-output-order" +kind = "fill" +label = { ja = "defer", en = "defer" } +title = { ja = "defer の実行順序", en = "Execution order of defer" } +prompt = { ja = "コードの実行結果(出力)の順番を上から完成させてください。", en = "Complete the execution order (output) from top to bottom." } +outputLines = """ +[1] +[2] +[3] +""" +playgroundUrl = "https://go.dev/play/p/p9uTA5pzU1G" +why = { ja = "`defer` に登録された関数は、関数が終了する際に「最後に追加されたものから順に(LIFO: Last-In, First-Out)」逆順で実行されます。", en = "`defer` statements are executed in reverse order (LIFO: Last-In, First-Out) when the function returns." } +takeaway = { ja = "通常の文(C)が先に実行され、その後に `defer` が下から上の順(B -> A)で実行されるため、出力は C、B、A の順になります。", en = "Since regular statements (C) execute first, followed by `defer` statements in reverse order (B -> A), the output will be C, B, then A." } +templateLines = """ +defer fmt.Println("A") +defer fmt.Println("B") +fmt.Println("C") +""" +pool = [ "A", "B", "C" ] +correctAnswers = [ "C", "B", "A" ] + +[[campaignTiers.stages]] +id = "fill-channel-recv-only" +kind = "fill" +label = { ja = "channel", en = "channel" } +title = { ja = "受信専用チャネル", en = "Receive-only channel" } +prompt = { ja = "受信専用チャネルから値を受け取って表示する 2 か所。", en = "Two places for printing a value from a receive-only channel." } +outputLines = """ +42 +""" +playgroundUrl = "https://go.dev/play/p/ZrQFACUb0_1" +why = { ja = "型の位置では `<-chan int`、値を読む式の位置では `<-ch` を使います。", en = "`<-chan int` indicates a receive-only channel with type, and `<-ch` receives the value from the channel." } +takeaway = { ja = "`<-chan` は型、`<-ch` は受信式です。", en = "`<-chan` is a type, while `<-ch` is an expression." } +templateLines = """ +func show_ch(ch [1] int) { + println([2]ch) +} + +func main() { + ch := make(chan int, 1) + ch <- 42 + show_ch(ch) +} +""" +pool = [ "<-chan", "<-", "chan<-", "&" ] +correctAnswers = [ "<-chan", "<-" ] + +[[campaignTiers.stages]] +id = "fill-unsafe-sizeof-struct" +kind = "fill" +label = { ja = "unsafe", en = "unsafe" } +title = { ja = "0バイトの構造体", en = "0-byte structure" } +prompt = { ja = "配列全体の占めるバイト数(出力)が 0 になるように型を埋めてください。", en = "Fill in the type so that the total size of the array (output) becomes 0." } +outputLines = """ +0 +""" +playgroundUrl = "https://go.dev/play/p/QZNiYpYMAMt" +why = { ja = "`struct{}`(空の構造体)はサイズが 0 バイトの特殊な型です。そのため、要素数が1億個あっても配列全体のサイズは 0 バイトになります。", en = "`struct{}` (an empty struct) is a special type with a size of 0 bytes. Therefore, even an array with 100 million elements will have a total size of 0 bytes." } +takeaway = { ja = "`unsafe.Sizeof` で確認すると、`struct{}` の配列はメモリを一切消費しないことが分かります。値を持たない「セット(集合)」を実装する際によく使われます。", en = "Using `unsafe.Sizeof`, you can see that an array of `struct{}` consumes no memory at all. It is often used to implement a 'set' (collection) that holds no values." } +templateLines = """ +var arr [100_000_000][1] +fmt.Println(unsafe.Sizeof(arr)) +""" +pool = [ "struct{}", "any", "bool", "byte", "0" ] +correctAnswers = [ "struct{}" ] + +[[campaignTiers.stages]] +id = "fill-unsafe-pointer-conversion" +kind = "fill" +label = { ja = "unsafe", en = "unsafe" } +title = { ja = "ポインタの型変換", en = "Type conversion of a pointer" } +prompt = { ja = "int型のポインタをfloat64型のポインタに変換する 2 か所。", en = "Two places for converting an int pointer to a float64 pointer." } +outputLines = """ +""" +playgroundUrl = "https://go.dev/play/p/oXl7EN0oW5T" +why = { ja = "Goでは異なるポインタ型同士を直接キャストできませんが、`unsafe.Pointer` を経由することで任意のポインタ型に無理やり変換できます。", en = "In Go, you cannot directly cast between different pointer types, but by going through `unsafe.Pointer`, you can convert to any pointer type." } +takeaway = { ja = "安全性を犠牲にして型を変換する際は、`unsafe.Pointer` と目的のポインタ型(`*float64` など)を組み合わせます。", en = "When converting types at the expense of safety, combine `unsafe.Pointer` with the target pointer type (e.g., `*float64`)." } +templateLines = """ +x := 42 +int_ptr := &x +float_ptr := ([1])( + [2](int_ptr), +) +""" +pool = [ "*float64", "&float64", "float64", "unsafe.Pointer", "itof64" ] +correctAnswers = [ "*float64", "unsafe.Pointer" ] diff --git a/quiz-app/src/data/stages.ts b/quiz-app/src/data/stages.ts new file mode 100644 index 0000000..8c8190b --- /dev/null +++ b/quiz-app/src/data/stages.ts @@ -0,0 +1,304 @@ +import type { + CampaignTier, + FillStage, + SelectStage, + Stage, + StageKind, + I18nText, +} from "../types"; +import stagesDocument from "./stages.toml"; + +export const STAGE_TIME_LIMIT_MS = 30_000; +export const PREVIEW_UNLOCK_KEYWORD = "gofar,gotogether"; +export const FOOTER_TAP_THRESHOLD = 10; +export const SPECIAL_PAGE_URL = "https://gocon.jp/2026/"; + +type TomlRecord = Record; + +const stageKinds = ["fill", "select"] as const satisfies readonly StageKind[]; + +const isRecord = (value: unknown): value is TomlRecord => + typeof value === "object" && value !== null && !Array.isArray(value); + +const expectRecord = (value: unknown, path: string): TomlRecord => { + if (!isRecord(value)) { + throw new Error(`${path} must be an object.`); + } + + return value; +}; + +const expectArray = (value: unknown, path: string): unknown[] => { + if (!Array.isArray(value)) { + throw new Error(`${path} must be an array.`); + } + + return value; +}; + +const expectString = (value: unknown, path: string): string => { + if (typeof value !== "string") { + throw new Error(`${path} must be a string.`); + } + + return value; +}; + +const expectI18nText = (value: unknown, path: string): I18nText => { + const record = expectRecord(value, path); + return { + ja: expectString(record.ja, `${path}.ja`), + en: expectString(record.en, `${path}.en`), + }; +}; + +const expectStringArray = (value: unknown, path: string): string[] => + expectArray(value, path).map((item, index) => + expectString(item, `${path}[${index}]`), + ); + +const expectOptionalBoolean = ( + value: unknown, + path: string, +): boolean | undefined => { + if (value === undefined) { + return undefined; + } + + if (typeof value !== "boolean") { + throw new Error(`${path} must be a boolean when present.`); + } + + return value; +}; + +const expectOptionalPlaygroundUrl = ( + value: unknown, + path: string, +): string | undefined => { + if (value === undefined) { + return undefined; + } + + const text = expectString(value, path); + let parsed: URL; + try { + parsed = new URL(text); + } catch { + throw new Error(`${path} must be a valid absolute URL when present.`); + } + + if ( + parsed.protocol !== "https:" || + parsed.hostname !== "go.dev" || + !parsed.pathname.startsWith("/play/") + ) { + throw new Error( + `${path} must be an https://go.dev/play/ URL when present.`, + ); + } + + return parsed.toString(); +}; + +const expectOneOf = ( + value: unknown, + allowed: readonly T[], + path: string, +): T => { + const text = expectString(value, path); + + if (!allowed.includes(text as T)) { + throw new Error(`${path} must be one of: ${allowed.join(", ")}.`); + } + + return text as T; +}; + +const expectUniqueStrings = (values: string[], path: string) => { + const seen = new Set(); + + for (const value of values) { + if (seen.has(value)) { + throw new Error(`${path} contains duplicate value "${value}".`); + } + + seen.add(value); + } +}; + +const parseStageBase = ( + record: TomlRecord, + kind: K, + path: string, +) => { + const outputLinesRaw = expectString( + record.outputLines, + `${path}.outputLines`, + ); + const outputLines = + outputLinesRaw === "" + ? [] + : outputLinesRaw.replace(/\r?\n$/, "").split(/\r?\n/); + + const playgroundUrl = expectOptionalPlaygroundUrl( + record.playgroundUrl, + `${path}.playgroundUrl`, + ); + + return { + id: expectString(record.id, `${path}.id`), + kind, + label: expectI18nText(record.label, `${path}.label`), + title: expectI18nText(record.title, `${path}.title`), + prompt: expectI18nText(record.prompt, `${path}.prompt`), + outputLines, + ...(playgroundUrl === undefined ? {} : { playgroundUrl }), + why: expectI18nText(record.why, `${path}.why`), + takeaway: expectI18nText(record.takeaway, `${path}.takeaway`), + }; +}; + +const validateFillTemplate = (stage: FillStage, path: string) => { + const allLines = [...stage.templateLines, ...stage.outputLines]; + + const placeholderIndexes = allLines.flatMap((line) => + Array.from(line.matchAll(/\[(\d+)\]/g), (match) => Number(match[1])), + ); + + if (placeholderIndexes.length !== stage.correctAnswers.length) { + throw new Error( + `${path} must contain exactly ${stage.correctAnswers.length} placeholders across templateLines and outputLines.`, + ); + } + + const expectedIndexes = Array.from( + { length: stage.correctAnswers.length }, + (_, index) => index + 1, + ); + + for (const [index, value] of placeholderIndexes.entries()) { + if (value !== expectedIndexes[index]) { + throw new Error( + `${path} placeholders must be numbered [1]...[${stage.correctAnswers.length}] in order across templateLines and outputLines.`, + ); + } + } +}; + +const parseFillStage = (record: TomlRecord, path: string): FillStage => { + const templateLinesRaw = expectString( + record.templateLines, + `${path}.templateLines`, + ); + const templateLines = templateLinesRaw.replace(/\r?\n$/, "").split(/\r?\n/); + + const stage = { + ...parseStageBase(record, "fill", path), + templateLines, + pool: expectStringArray(record.pool, `${path}.pool`), + correctAnswers: expectStringArray( + record.correctAnswers, + `${path}.correctAnswers`, + ), + }; + + validateFillTemplate(stage, path); + + for (const answer of stage.correctAnswers) { + if (!stage.pool.includes(answer)) { + throw new Error( + `${path}.correctAnswers contains "${answer}" that is not in ${path}.pool.`, + ); + } + } + + return stage; +}; + +const parseSelectStage = (record: TomlRecord, path: string): SelectStage => { + const stage = { + ...parseStageBase(record, "select", path), + snippetLines: expectStringArray( + record.snippetLines, + `${path}.snippetLines`, + ), + options: expectStringArray(record.options, `${path}.options`), + correctAnswers: expectStringArray( + record.correctAnswers, + `${path}.correctAnswers`, + ), + }; + + expectUniqueStrings(stage.options, `${path}.options`); + expectUniqueStrings(stage.correctAnswers, `${path}.correctAnswers`); + + if (stage.correctAnswers.length === 0) { + throw new Error(`${path}.correctAnswers must contain at least one item.`); + } + + for (const answer of stage.correctAnswers) { + if (!stage.options.includes(answer)) { + throw new Error( + `${path}.correctAnswers contains "${answer}" that is not in ${path}.options.`, + ); + } + } + + return stage; +}; + +const parseStage = (value: unknown, path: string): Stage => { + const record = expectRecord(value, path); + const kind = expectOneOf(record.kind, stageKinds, `${path}.kind`); + + switch (kind) { + case "fill": + return parseFillStage(record, path); + case "select": + return parseSelectStage(record, path); + } +}; + +const parseCampaignTier = (value: unknown, path: string): CampaignTier => { + const record = expectRecord(value, path); + const stages = expectArray(record.stages, `${path}.stages`).map( + (stage, index) => parseStage(stage, `${path}.stages[${index}]`), + ); + const unlocksSpecial = expectOptionalBoolean( + record.unlocksSpecial, + `${path}.unlocksSpecial`, + ); + + return { + id: expectString(record.id, `${path}.id`), + title: expectI18nText(record.title, `${path}.title`), + difficultyLabel: expectI18nText( + record.difficultyLabel, + `${path}.difficultyLabel`, + ), + description: expectI18nText(record.description, `${path}.description`), + ...(unlocksSpecial === undefined ? {} : { unlocksSpecial }), + stages, + }; +}; + +const loadCampaignTiers = (): CampaignTier[] => { + const root = expectRecord(stagesDocument, "src/data/stages.toml"); + const tiers = expectArray( + root.campaignTiers, + "src/data/stages.toml.campaignTiers", + ).map((tier, index) => parseCampaignTier(tier, `campaignTiers[${index}]`)); + const tierIds = tiers.map((tier) => tier.id); + const stageIds = tiers.flatMap((tier) => + tier.stages.map((stage) => stage.id), + ); + + expectUniqueStrings(tierIds, "campaignTiers"); + expectUniqueStrings(stageIds, "campaignTiers[].stages"); + + return tiers; +}; + +export const campaignTiers: CampaignTier[] = loadCampaignTiers(); +export const stages: Stage[] = campaignTiers.flatMap((tier) => tier.stages); diff --git a/quiz-app/src/main.ts b/quiz-app/src/main.ts new file mode 100644 index 0000000..3c9bfeb --- /dev/null +++ b/quiz-app/src/main.ts @@ -0,0 +1,5 @@ +import { createApp } from "vue"; +import "./style.css"; +import App from "./App.vue"; + +createApp(App).mount("#app"); diff --git a/quiz-app/src/style.css b/quiz-app/src/style.css new file mode 100644 index 0000000..ddcba56 --- /dev/null +++ b/quiz-app/src/style.css @@ -0,0 +1,1068 @@ +@import "tailwindcss"; + +:root { + color-scheme: light; + font-family: + "Noto Sans JP", + ui-sans-serif, + system-ui, + -apple-system, + BlinkMacSystemFont, + "Segoe UI", + sans-serif; + --quiz-black: #18232d; + --quiz-light-gray: #d5e1e8; + --quiz-dark-gray: #314455; + --quiz-yellow: #f4c928; + --quiz-cyan: #55d3c2; + --quiz-blue: #00c1e5; + --quiz-navy: #082a59; + --quiz-text-strong: #082a59; + --quiz-text-body: #18232d; + --quiz-text-muted: #314455; + --quiz-header-height: 81px; + --quiz-white: #ffffff; + --quiz-light-blue: #edfcff; + --quiz-light-cyan: #eefffd; + --quiz-red: #fd7045; + --quiz-shadow: 0 2px 12px rgba(16, 57, 114, 0.06); + --quiz-shadow-soft: 0 1px 0 rgba(16, 57, 114, 0.08); + background: var(--quiz-light-blue); + color: var(--quiz-text-body); + font-synthesis: none; + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +@layer base { + * { + @apply box-border; + } + + html, + body, + #app { + @apply min-h-full; + } + + body { + @apply m-0 min-h-[100svh]; + background: linear-gradient( + 180deg, + #f7feff 0%, + var(--quiz-light-blue) 100% + ); + color: var(--quiz-text-body); + } + + button { + @apply touch-manipulation; + } + + a { + color: inherit; + } +} + +@layer utilities { + .text-quiz-strong { + color: var(--quiz-text-strong); + } + + .text-quiz-body { + color: var(--quiz-text-body); + } + + .text-quiz-muted { + color: var(--quiz-text-muted); + } + + .text-quiz-success { + color: #156d61; + } + + .text-quiz-danger { + color: #c94f2b; + } + + .quiz-theme .text-slate-50, + .quiz-theme .text-slate-100, + .quiz-theme .text-slate-200 { + color: var(--quiz-text-strong); + } + + .quiz-theme .text-slate-300 { + color: var(--quiz-text-body); + } + + .quiz-theme .text-slate-400, + .quiz-theme .text-slate-500 { + color: var(--quiz-text-muted); + } + + .quiz-theme .text-emerald-300 { + color: #156d61; + } + + .quiz-theme .text-rose-300 { + color: #c94f2b; + } +} + +@layer components { + .quiz-theme { + color: var(--quiz-text-body); + } + + .quiz-theme .bg-white\/10 { + background-color: rgba(213, 225, 232, 0.85); + } + + .quiz-theme .bg-white\/5 { + background-color: rgba(237, 252, 255, 0.92); + } + + .quiz-theme .bg-white\/60 { + background-color: rgba(16, 57, 114, 0.24); + } + + .quiz-theme .border-white\/10 { + border-color: var(--quiz-light-gray); + } + + .surface-card { + background: var(--quiz-white); + border: 1px solid var(--quiz-light-gray); + border-radius: 16px; + box-shadow: var(--quiz-shadow); + } + + .surface-subpanel { + background: var(--quiz-light-blue); + border: 1px solid var(--quiz-light-gray); + border-radius: 16px; + } + + .info-pill { + display: inline-flex; + align-items: center; + justify-content: center; + border-radius: 16px; + background: var(--quiz-navy); + color: var(--quiz-white); + font-size: 0.75rem; + font-weight: 700; + letter-spacing: 0.05em; + padding: 4px 16px; + } + + .top-page-main { + width: 100%; + margin: 0 auto; + padding: 16px 8px; + } + + .card { + background: var(--quiz-white); + border-radius: 16px; + padding: 16px; + box-shadow: 0 2px 12px rgba(16, 57, 114, 0.06); + } + + .chip { + display: inline-block; + background: var(--quiz-navy); + color: var(--quiz-white); + font-size: 0.75rem; + font-weight: 700; + padding: 4px 16px; + border-radius: 16px; + margin-bottom: 8px; + letter-spacing: 0.05em; + } + + .home-title { + font-family: + "Barlow", + ui-sans-serif, + system-ui, + -apple-system, + BlinkMacSystemFont, + "Segoe UI", + sans-serif; + font-size: 2rem; + line-height: 1.2; + color: var(--quiz-navy); + margin: 0 0 16px; + } + + .home-lead { + color: var(--quiz-text-body); + margin: 0 0 16px; + line-height: 1.75; + } + + .home-actions { + display: flex; + flex-direction: column; + gap: 8px; + margin-top: 16px; + } + + #preview-entry { + margin-top: 16px; + } + + .unlock-note { + margin: 8px 0 0; + color: var(--quiz-text-muted); + font-size: 0.9rem; + } + + .c-button { + appearance: none; + width: 100%; + border: 2px solid transparent; + border-radius: 999px; + padding: 12px 0; + background: var(--quiz-red); + color: var(--quiz-white); + font-family: inherit; + font-size: 1rem; + font-weight: 700; + line-height: 1.75; + cursor: pointer; + display: block; + text-align: center; + transition: + background-color 160ms ease, + color 160ms ease, + border-color 160ms ease, + opacity 160ms ease; + } + + .c-button[data-variant="tertiary"] { + background: var(--quiz-white); + color: var(--quiz-navy); + border-color: var(--quiz-navy); + } + + .c-button:hover { + opacity: 0.88; + } + + .pressable { + @apply inline-flex items-center justify-center gap-2 rounded-full border-2 font-bold transition-all duration-200 ease-out active:translate-y-[2px] disabled:pointer-events-none disabled:translate-y-0 disabled:opacity-45; + } + + .code-surface { + background: var(--quiz-light-blue); + border: 1px solid var(--quiz-light-gray); + border-left: 4px solid var(--quiz-blue); + border-radius: 8px; + padding: 12px 10px 12px 12px; + color: var(--quiz-black); + font-family: + ui-monospace, SFMono-Regular, SFMono-Regular, Menlo, Monaco, + Consolas, "Liberation Mono", monospace; + font-size: 13px; + line-height: 1.7; + } + + .code-slot { + @apply inline-flex min-h-11 min-w-[60px] items-center justify-center px-3 py-2 text-[13px] font-semibold transition-all duration-200; + border: 2px dashed var(--quiz-blue); + border-radius: 12px; + background: var(--quiz-white); + color: var(--quiz-navy); + } + + .code-slot.is-filled { + border-style: solid; + border-color: var(--quiz-cyan); + background: var(--quiz-light-cyan); + color: #1a7a6e; + } + + .timeline-card { + background: var(--quiz-white); + border: 1px solid var(--quiz-light-gray); + border-radius: 16px; + box-shadow: var(--quiz-shadow-soft); + } + + .timeline-card.is-dragging { + border-color: var(--quiz-blue); + background: var(--quiz-light-blue); + box-shadow: 0 8px 24px rgba(0, 193, 229, 0.16); + } + + .timeline-node { + @apply flex h-11 w-11 items-center justify-center text-sm font-semibold; + border-radius: 14px; + background: var(--quiz-navy); + color: var(--quiz-white); + } + + .timeline-rail { + background: linear-gradient( + 180deg, + rgba(0, 193, 229, 0.24), + rgba(85, 211, 194, 0.7), + transparent + ); + } + + .timeline-drag-handle { + display: inline-flex; + align-items: center; + justify-content: center; + min-width: 44px; + min-height: 44px; + border: 2px solid var(--quiz-light-gray); + border-radius: 12px; + background: var(--quiz-light-blue); + color: var(--quiz-navy); + font-size: 1rem; + font-weight: 700; + line-height: 1; + touch-action: none; + cursor: grab; + transition: all 160ms ease; + } + + .timeline-drag-handle:active { + cursor: grabbing; + transform: translateY(2px); + } + + .timeline-drag-handle:disabled { + cursor: default; + opacity: 0.45; + transform: translateY(0); + } + + .word-circle-answer { + display: flex; + flex-direction: column; + gap: 6px; + } + + .word-circle-current { + margin: 0; + font-family: + "Barlow", + ui-sans-serif, + system-ui, + -apple-system, + BlinkMacSystemFont, + "Segoe UI", + sans-serif; + font-size: 1.75rem; + font-weight: 700; + letter-spacing: 0.18em; + color: var(--quiz-navy); + } + + .word-circle-helper { + margin: 0; + font-size: 0.85rem; + color: var(--quiz-text-muted); + } + + .word-circle-board { + position: relative; + width: min(280px, 72vw); + aspect-ratio: 1; + margin: 0 auto; + } + + .word-circle-lines { + position: absolute; + inset: 0; + width: 100%; + height: 100%; + overflow: visible; + } + + .word-circle-ring { + fill: rgba(237, 252, 255, 0.92); + stroke: rgba(0, 193, 229, 0.26); + stroke-width: 1.4; + } + + .word-circle-path { + fill: none; + stroke: var(--quiz-blue); + stroke-width: 3; + stroke-linecap: round; + stroke-linejoin: round; + } + + .word-circle-node { + position: absolute; + transform: translate(-50%, -50%); + display: inline-flex; + align-items: center; + justify-content: center; + width: 56px; + height: 56px; + border: 2px solid var(--quiz-light-gray); + border-radius: 999px; + background: var(--quiz-white); + color: var(--quiz-navy); + font-family: + "Barlow", + ui-sans-serif, + system-ui, + -apple-system, + BlinkMacSystemFont, + "Segoe UI", + sans-serif; + font-size: 1.3rem; + font-weight: 700; + box-shadow: var(--quiz-shadow-soft); + cursor: pointer; + touch-action: none; + transition: + transform 160ms ease, + border-color 160ms ease, + background-color 160ms ease, + color 160ms ease; + } + + .word-circle-node:hover:not(:disabled), + .word-circle-node.is-selected { + border-color: var(--quiz-blue); + background: var(--quiz-light-blue); + color: var(--quiz-navy); + } + + .word-circle-node:active:not(:disabled) { + transform: translate(-50%, -50%) scale(0.96); + } + + .feedback-chip { + @apply inline-flex rounded-full px-3 py-1 text-xs font-semibold uppercase tracking-[0.24em]; + } + + .feedback-chip-success { + background: var(--quiz-light-cyan); + border: 1px solid var(--quiz-cyan); + color: #1a7a6e; + } + + .feedback-chip-danger { + background: #fff4f1; + border: 1px solid var(--quiz-red); + color: var(--quiz-red); + } + + .slot-shell { + background: var(--quiz-white); + border: 2px dashed var(--quiz-light-gray); + border-radius: 16px; + } + + .magnet-piece { + background: var(--quiz-light-cyan); + border: 1px solid var(--quiz-cyan); + border-radius: 16px; + box-shadow: var(--quiz-shadow-soft); + } + + .choice-card { + display: inline-flex; + align-items: flex-start; + justify-content: center; + gap: 0.5rem; + width: 100%; + min-height: 44px; + flex-direction: column; + padding: 12px 16px; + text-align: left; + border-width: 2px; + border-style: solid; + border-radius: 16px; + font-weight: 700; + transition: all 200ms ease-out; + border-color: var(--quiz-light-gray); + background: var(--quiz-white); + color: var(--quiz-black); + box-shadow: var(--quiz-shadow-soft); + } + + .choice-card:active { + transform: translateY(2px); + } + + .choice-card:disabled { + pointer-events: none; + opacity: 0.45; + transform: translateY(0); + } + + .choice-card:hover:not(:disabled) { + border-color: var(--quiz-blue); + background: var(--quiz-light-blue); + } + + .choice-card.is-active { + border-color: var(--quiz-blue); + background: var(--quiz-light-blue); + color: var(--quiz-navy); + } + + .choice-card.is-linked, + .choice-card.is-correct { + border-color: var(--quiz-cyan); + background: var(--quiz-light-cyan); + color: #1a7a6e; + } + + .choice-card.is-wrong { + border-color: var(--quiz-red); + background: #fff4f1; + color: var(--quiz-red); + } + + .choice-card.is-muted { + color: var(--quiz-dark-gray); + opacity: 0.88; + } + + .choice-code { + margin-top: 16px; + overflow-x: auto; + background: var(--quiz-light-blue); + border: 1px solid var(--quiz-light-gray); + border-left: 4px solid var(--quiz-blue); + border-radius: 8px; + padding: 16px; + color: var(--quiz-black); + font-size: 13px; + line-height: 1.6; + } + + .problem-copy { + color: var(--quiz-text-strong); + font-size: 0.96rem; + font-weight: 700; + line-height: 1.75; + } + + .display-title { + font-family: + "Barlow", + ui-sans-serif, + system-ui, + -apple-system, + BlinkMacSystemFont, + "Segoe UI", + sans-serif; + font-weight: 700; + letter-spacing: 0.01em; + color: var(--quiz-navy); + } + + .quiz-shell { + padding: calc( + env(safe-area-inset-top) + var(--quiz-header-height) + 11px + ) + 16px calc(env(safe-area-inset-bottom) + 20px); + } + + .quiz-shell-wide-panels { + padding-inline: 10px; + } + + .header { + position: fixed; + top: 0; + width: 100%; + z-index: 1000; + display: flex; + align-items: center; + justify-content: space-between; + gap: 16px; + padding: 4px 40px; + height: var(--quiz-header-height); + background: var(--quiz-white); + box-shadow: 0 1px 0 var(--quiz-light-gray); + } + + .header-logo { + display: flex; + align-items: center; + gap: 16px; + min-width: 0; + } + + .header-brand { + display: flex; + align-items: center; + gap: 10px; + flex-shrink: 0; + min-width: 0; + } + + .header-conference-logo { + display: block; + width: min(136px, 42vw); + height: auto; + flex-shrink: 0; + } + + .header-product-mark { + font-family: + "Barlow", + ui-sans-serif, + system-ui, + -apple-system, + BlinkMacSystemFont, + "Segoe UI", + sans-serif; + font-size: 1.5rem; + font-weight: 700; + color: var(--quiz-navy); + letter-spacing: 0.02em; + white-space: nowrap; + } + + .header-tagline { + font-size: 0.8rem; + font-weight: 400; + color: var(--quiz-text-muted); + white-space: nowrap; + } + + .header-actions { + display: flex; + align-items: center; + gap: 16px; + margin-left: auto; + flex-shrink: 0; + } + + .language-switch { + position: relative; + display: inline-flex; + align-items: center; + justify-content: center; + } + + .language-toggle { + appearance: none; + display: inline-flex; + align-items: center; + justify-content: center; + width: 44px; + height: 44px; + border: 1px solid var(--quiz-light-gray); + border-radius: 999px; + background: var(--quiz-light-blue); + color: var(--quiz-text-muted); + cursor: pointer; + transition: + background-color 160ms ease, + color 160ms ease, + box-shadow 160ms ease, + border-color 160ms ease; + } + + .language-toggle:hover, + .language-switch.open .language-toggle { + background: var(--quiz-white); + color: var(--quiz-navy); + box-shadow: 0 1px 4px rgba(16, 57, 114, 0.12); + } + + .language-toggle:focus-visible { + outline: 2px solid var(--quiz-blue); + outline-offset: 2px; + } + + .language-toggle-icon { + width: 20px; + height: 20px; + flex-shrink: 0; + } + + .language-menu { + position: absolute; + top: calc(100% + 4px); + right: 0; + display: none; + flex-direction: column; + gap: 4px; + min-width: 148px; + padding: 8px; + border: 1px solid var(--quiz-light-gray); + border-radius: 16px; + background: var(--quiz-white); + box-shadow: 0 14px 32px rgba(16, 57, 114, 0.14); + } + + .language-menu.open { + display: flex; + } + + .language-menu-item { + appearance: none; + width: 100%; + border: none; + border-radius: 12px; + background: transparent; + color: var(--quiz-text-muted); + cursor: pointer; + font-size: 0.9rem; + font-weight: 700; + line-height: 1.4; + padding: 10px 12px; + text-align: left; + transition: + background-color 160ms ease, + color 160ms ease; + } + + .language-menu-item:hover, + .language-menu-item.active { + background: var(--quiz-light-blue); + color: var(--quiz-navy); + } + + .language-menu-item:focus-visible { + outline: 2px solid var(--quiz-blue); + outline-offset: 2px; + } + + .sr-only { + position: absolute; + width: 1px; + height: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + clip: rect(0, 0, 0, 0); + white-space: nowrap; + border: 0; + } + + .progress-panel { + background: linear-gradient( + 135deg, + rgba(237, 252, 255, 0.98), + rgba(238, 255, 253, 0.98) + ); + border: 1px solid rgba(0, 193, 229, 0.16); + border-radius: 20px; + box-shadow: 0 10px 24px rgba(16, 57, 114, 0.08); + } + + .progress-metric { + background: var(--quiz-white); + border: 1px solid var(--quiz-light-gray); + border-radius: 14px; + box-shadow: var(--quiz-shadow-soft); + } + + .progress-fill { + background: linear-gradient(90deg, var(--quiz-blue), var(--quiz-cyan)); + } + + .explanation-inline-code { + display: inline-block; + margin: 0 0.08em; + padding: 0.08em 0.45em; + border: 1px solid rgba(0, 193, 229, 0.22); + border-radius: 8px; + background: rgba(0, 193, 229, 0.12); + color: var(--quiz-navy); + font-family: + ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, + "Liberation Mono", monospace; + font-size: 0.92em; + font-weight: 700; + white-space: break-spaces; + } + + .primary-link-button { + display: inline-flex; + align-items: center; + justify-content: center; + gap: 0.5rem; + width: 100%; + min-height: 48px; + padding: 12px 16px; + border-width: 2px; + border-style: solid; + border-radius: 999px; + font-size: 0.875rem; + font-weight: 700; + transition: all 200ms ease-out; + border-color: transparent; + background: var(--quiz-red); + color: var(--quiz-white); + box-shadow: var(--quiz-shadow-soft); + text-decoration: none; + } + + .primary-link-button:active { + transform: translateY(2px); + } + + .primary-link-button:hover { + opacity: 0.88; + } + + .playground-link-button { + background: var(--quiz-navy); + } + + footer { + background: var(--quiz-light-blue); + border-top: 1px solid var(--quiz-light-gray); + padding: 16px 24px calc(env(safe-area-inset-bottom) + 16px); + } + + .footer-gradient { + width: 100%; + height: 8px; + background: linear-gradient(90deg, var(--quiz-blue), var(--quiz-cyan)); + } + + .footer-info { + width: min(760px, 100%); + margin: 0 auto; + display: flex; + align-items: center; + justify-content: space-between; + gap: 16px; + } + + .logo-container { + display: flex; + flex-direction: column; + align-items: flex-start; + gap: 10px; + color: var(--quiz-navy); + font-family: + "Barlow", + ui-sans-serif, + system-ui, + -apple-system, + BlinkMacSystemFont, + "Segoe UI", + sans-serif; + font-size: 1rem; + font-weight: 700; + letter-spacing: 0.02em; + flex-shrink: 0; + } + + .footer-logo { + display: block; + width: min(196px, 100%); + height: auto; + } + + .copyright { + margin: 0; + font-size: 0.875rem; + color: var(--quiz-text-body); + text-align: right; + white-space: nowrap; + } + + .copyright a { + color: var(--quiz-blue); + font-weight: 700; + text-decoration: none; + } + + .copyright a:hover { + text-decoration: underline; + } + + .quiz-input { + width: 100%; + min-height: 48px; + padding: 12px 16px; + border: 2px solid var(--quiz-light-gray); + border-radius: 14px; + background: var(--quiz-white); + color: var(--quiz-black); + font: inherit; + line-height: 1.4; + appearance: none; + transition: + border-color 160ms ease, + box-shadow 160ms ease; + } + + .quiz-input::placeholder { + color: rgba(24, 35, 45, 0.72); + } + + .quiz-input:focus { + outline: none; + border-color: var(--quiz-blue); + box-shadow: 0 0 0 4px rgba(0, 193, 229, 0.12); + } + + .app-modal-card { + background: var(--quiz-white); + border: 1px solid var(--quiz-light-gray); + box-shadow: 0 14px 32px rgba(16, 57, 114, 0.14); + } +} + +@media (max-width: 768px) { + :root { + --quiz-header-height: 56px; + } + + .quiz-shell { + padding-top: calc( + env(safe-area-inset-top) + var(--quiz-header-height) + 12px + ); + } + + .quiz-shell-wide-panels { + padding-inline: 8px; + } + + .header { + padding: 4px 16px; + } + + .header-conference-logo { + width: 112px; + } + + .header-product-mark { + font-size: 1.25rem; + } + + .header-actions { + gap: 8px; + } + + .header-tagline { + display: none; + } + + .home-title { + font-size: 1.6rem; + } + + .word-circle-board { + width: min(240px, 74vw); + } + + .word-circle-node { + width: 52px; + height: 52px; + font-size: 1.2rem; + } + + .language-toggle { + width: 40px; + height: 40px; + } + + .language-menu { + min-width: 136px; + } + + footer { + padding: 16px 16px calc(env(safe-area-inset-bottom) + 16px); + } + + .footer-info { + flex-direction: column; + align-items: center; + text-align: center; + } + + .logo-container { + align-items: center; + text-align: center; + } + + .copyright { + text-align: center; + white-space: normal; + } +} + +.stage-enter-active, +.stage-leave-active, +.drawer-enter-active, +.drawer-leave-active, +.snap-enter-active, +.snap-leave-active { + transition: all 240ms ease; +} + +.stage-enter-from, +.stage-leave-to { + opacity: 0; + transform: translateY(12px) scale(0.99); +} + +.drawer-enter-from, +.drawer-leave-to { + opacity: 0; + transform: translateY(18px); +} + +.snap-enter-from, +.snap-leave-to { + opacity: 0; + transform: scale(0.92); +} + +.reorder-move { + transition: transform 240ms ease; +} + +@keyframes shake { + 0%, + 100% { + transform: translateX(0); + } + 20% { + transform: translateX(-4px); + } + 40% { + transform: translateX(4px); + } + 60% { + transform: translateX(-3px); + } + 80% { + transform: translateX(3px); + } +} + +@keyframes pop { + 0% { + transform: scale(0.96); + } + 70% { + transform: scale(1.02); + } + 100% { + transform: scale(1); + } +} + +.option-shake { + animation: shake 320ms ease; +} + +.pop-in { + animation: pop 240ms ease; +} diff --git a/quiz-app/src/types.ts b/quiz-app/src/types.ts new file mode 100644 index 0000000..a9bda30 --- /dev/null +++ b/quiz-app/src/types.ts @@ -0,0 +1,50 @@ +export type Locale = "ja" | "en"; + +export interface I18nText { + ja: string; + en: string; +} + +export type StageKind = "fill" | "select"; + +export interface StageBase { + id: string; + kind: StageKind; + label: I18nText; + title: I18nText; + prompt: I18nText; + outputLines: string[]; + playgroundUrl?: string; + why: I18nText; + takeaway: I18nText; +} + +export interface FillStage extends StageBase { + kind: "fill"; + templateLines: string[]; + pool: string[]; + correctAnswers: string[]; +} + +export interface SelectStage extends StageBase { + kind: "select"; + snippetLines: string[]; + options: string[]; + correctAnswers: string[]; +} + +export type Stage = FillStage | SelectStage; + +export interface CampaignTier { + id: string; + title: I18nText; + difficultyLabel: I18nText; + description: I18nText; + unlocksSpecial?: boolean; + stages: Stage[]; +} + +export interface StageSubmission { + correct: boolean; + selectionSummary?: string; +} diff --git a/quiz-app/src/vite-env.d.ts b/quiz-app/src/vite-env.d.ts new file mode 100644 index 0000000..0df6bc0 --- /dev/null +++ b/quiz-app/src/vite-env.d.ts @@ -0,0 +1,6 @@ +/// + +declare module "*.toml" { + const value: unknown; + export default value; +} diff --git a/quiz-app/test/app.ui.test.ts b/quiz-app/test/app.ui.test.ts new file mode 100644 index 0000000..75dbbff --- /dev/null +++ b/quiz-app/test/app.ui.test.ts @@ -0,0 +1,227 @@ +import { flushPromises, mount } from "@vue/test-utils"; +import { afterEach, describe, expect, it, vi } from "vitest"; +import { nextTick } from "vue"; +import App from "../src/App.vue"; +import { + STAGE_TIME_LIMIT_MS, + stages, + campaignTiers, + PREVIEW_UNLOCK_KEYWORD, +} from "../src/data/stages"; + +const settle = async () => { + await nextTick(); + await flushPromises(); + await nextTick(); +}; + +const normalizeText = (value: string) => value.replace(/\s+/g, " ").trim(); + +const findEnabledButton = ( + wrapper: ReturnType, + label: string, +) => { + const button = wrapper + .findAll("button") + .find( + (candidate) => + candidate.attributes("disabled") === undefined && + normalizeText(candidate.text()) === label, + ); + + expect(button, `missing button: ${label}`).toBeTruthy(); + return button!; +}; + +const hasEnabledButton = (wrapper: ReturnType, label: string) => + wrapper + .findAll("button") + .some( + (candidate) => + candidate.attributes("disabled") === undefined && + normalizeText(candidate.text()) === label, + ); + +const clickButton = async ( + wrapper: ReturnType, + label: string, +) => { + await findEnabledButton(wrapper, label).trigger("click"); + await settle(); +}; + +const findButton = (wrapper: ReturnType, label: string) => { + const button = wrapper + .findAll("button") + .find((candidate) => normalizeText(candidate.text()) === label); + + expect(button, `missing button: ${label}`).toBeTruthy(); + return button!; +}; + +const unlockPreview = async (wrapper: ReturnType) => { + const footer = wrapper.find("footer"); + expect(footer.exists()).toBe(true); + + for (let i = 0; i < 10; i += 1) { + await footer.trigger("click"); + } + await settle(); + + const keywordInput = wrapper.find('input[placeholder="合言葉を入力"]'); + expect(keywordInput.exists()).toBe(true); + // ハードコードされていたキーワードをデータから参照 + await keywordInput.setValue(PREVIEW_UNLOCK_KEYWORD); + await settle(); + await clickButton(wrapper, "開く"); +}; + +describe("quiz-app campaign flow", () => { + afterEach(() => { + vi.clearAllTimers(); + vi.useRealTimers(); + }); + + it("keeps preview gated while a perfect Tier 1 clear unlocks the special page", async () => { + vi.useFakeTimers(); + + const wrapper = mount(App); + await settle(); + const shell = () => wrapper.find("[data-screen]"); + + expect(shell().attributes("data-screen")).toBe("home"); + expect(hasEnabledButton(wrapper, "問題一覧を見る")).toBe(false); + + await clickButton(wrapper, "クイズを始める"); + expect(shell().attributes("data-screen")).toBe("question"); + expect(findButton(wrapper, "リセット").classes()).toContain("bg-white"); + + for (let i = 0; i < stages.length; i++) { + const stage = stages[i]; + if (!stage) continue; + + if (i === 0) { + await clickButton(wrapper, stage.correctAnswers[0]!); + await clickButton(wrapper, "リセット"); + } + + for (const answer of stage.correctAnswers) { + await clickButton(wrapper, answer); + } + await clickButton(wrapper, "回答する"); + expect(shell().attributes("data-screen")).toBe("result"); + expect(wrapper.text()).toContain("◯ 正解"); + + if (i === stages.length - 1) { + await clickButton(wrapper, "結果を見る"); + } else { + await clickButton(wrapper, "つぎへ"); + } + } + + expect(shell().attributes("data-screen")).toBe("score"); + expect(wrapper.text()).toContain(`${stages.length}/${stages.length}`); + expect(wrapper.text()).toContain("おみやげをプレゼント"); + + // Tierが1つだけの場合はボタン名が「もう一度挑戦する」に変化する対応 + const isSingleTier = campaignTiers.length === 1; + const retryButtonLabel = isSingleTier + ? "もう一度挑戦する" + : "同じレベルでもう一度"; + await clickButton(wrapper, retryButtonLabel); + expect(shell().attributes("data-screen")).toBe("question"); + }); + + it("unlocks the preview list only through the hidden keyword flow", async () => { + const wrapper = mount(App); + await settle(); + const shell = () => wrapper.find("[data-screen]"); + + expect(hasEnabledButton(wrapper, "問題一覧を見る")).toBe(false); + await unlockPreview(wrapper); + expect(hasEnabledButton(wrapper, "問題一覧を見る")).toBe(true); + + await clickButton(wrapper, "問題一覧を見る"); + expect(shell().attributes("data-screen")).toBe("preview"); + + if (stages[0]) { + // prompt が多言語オブジェクト化されたため .ja を参照する + expect(wrapper.text()).toContain(stages[0].prompt.ja); + } + + const closeBtn = wrapper + .findAll("button") + .find((c) => c.text().includes("閉じる")); + if (closeBtn) { + await closeBtn.trigger("click"); + await settle(); + } + expect(shell().attributes("data-screen")).toBe("home"); + }); + + it("shows the retry headline without the tier subtitle on an imperfect clear", async () => { + const wrapper = mount(App); + await settle(); + const shell = () => wrapper.find("[data-screen]"); + + await clickButton(wrapper, "クイズを始める"); + + const firstStage = stages[0]; + if (firstStage) { + const wrongTokens = firstStage.pool.filter( + (token) => !firstStage.correctAnswers.includes(token), + ); + let tokensToClick = [...wrongTokens]; + + while (tokensToClick.length < firstStage.correctAnswers.length) { + tokensToClick.push(firstStage.pool[0]!); + } + tokensToClick = tokensToClick.slice(0, firstStage.correctAnswers.length); + + for (const token of tokensToClick) { + await clickButton(wrapper, token); + } + } + + await clickButton(wrapper, "回答する"); + expect(wrapper.text()).toContain("× 不正解"); + + for (let i = 0; i < stages.length; i++) { + if (i === 0) { + await clickButton(wrapper, "つぎへ"); + continue; + } + + const stage = stages[i]; + if (!stage) continue; + + for (const answer of stage.correctAnswers) { + await clickButton(wrapper, answer); + } + await clickButton(wrapper, "回答する"); + + if (i === stages.length - 1) { + await clickButton(wrapper, "結果を見る"); + } else { + await clickButton(wrapper, "つぎへ"); + } + } + + expect(shell().attributes("data-screen")).toBe("score"); + + const isSingleTier = campaignTiers.length === 1; + if (isSingleTier) { + expect(wrapper.text()).toContain("再挑戦"); + } else { + // title が多言語オブジェクト化されたため .ja を参照する + const tierTitle = campaignTiers[0]?.title.ja || ""; + const compactTitle = tierTitle.split(" / ")[0]?.trim() || ""; + + expect(wrapper.text()).toContain(`${compactTitle} を再挑戦`); + + if (tierTitle.includes("/")) { + expect(wrapper.text()).not.toContain(`${tierTitle} を再挑戦`); + } + } + }); +}); diff --git a/quiz-app/test/browser-ui.test.js b/quiz-app/test/browser-ui.test.js deleted file mode 100644 index d0219fe..0000000 --- a/quiz-app/test/browser-ui.test.js +++ /dev/null @@ -1,1422 +0,0 @@ -const assert = require('node:assert/strict'); -const fs = require('node:fs'); -const path = require('node:path'); -const test = require('node:test'); -const vm = require('node:vm'); - -const appHTML = fs.readFileSync(path.join(__dirname, '..', 'public', 'index.html'), 'utf8'); -const extraModeCSS = fs.readFileSync(path.join(__dirname, '..', 'public', 'assets', 'extra-mode.css'), 'utf8'); -const extraModeScript = fs.readFileSync(path.join(__dirname, '..', 'public', 'assets', 'extra-mode.js'), 'utf8'); -const telemetryGasScript = fs.readFileSync(path.join(__dirname, '..', 'gas', 'telemetry.gs'), 'utf8'); -const previewHTML = fs.readFileSync(path.join(__dirname, '..', 'public', 'preview', 'index.html'), 'utf8'); -const scripts = [...appHTML.matchAll(/ - - diff --git a/quiz-app2/package-lock.json b/quiz-app2/package-lock.json deleted file mode 100644 index 4765e4d..0000000 --- a/quiz-app2/package-lock.json +++ /dev/null @@ -1,3245 +0,0 @@ -{ - "name": "quiz-app2", - "version": "0.0.0", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "quiz-app2", - "version": "0.0.0", - "dependencies": { - "vue": "^3.5.34" - }, - "devDependencies": { - "@tailwindcss/vite": "^4.3.0", - "@types/node": "^24.12.3", - "@vitejs/plugin-vue": "^6.0.6", - "@vue/test-utils": "^2.4.10", - "@vue/tsconfig": "^0.9.1", - "jsdom": "^29.1.1", - "smol-toml": "^1.6.1", - "tailwindcss": "^4.3.0", - "typescript": "~6.0.2", - "vite": "^8.0.12", - "vitest": "^4.1.7", - "vue-tsc": "^3.2.8" - } - }, - "node_modules/@asamuzakjp/css-color": { - "version": "5.1.11", - "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-5.1.11.tgz", - "integrity": "sha512-KVw6qIiCTUQhByfTd78h2yD1/00waTmm9uy/R7Ck/ctUyAPj+AEDLkQIdJW0T8+qGgj3j5bpNKK7Q3G+LedJWg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@asamuzakjp/generational-cache": "^1.0.1", - "@csstools/css-calc": "^3.2.0", - "@csstools/css-color-parser": "^4.1.0", - "@csstools/css-parser-algorithms": "^4.0.0", - "@csstools/css-tokenizer": "^4.0.0" - }, - "engines": { - "node": "^20.19.0 || ^22.12.0 || >=24.0.0" - } - }, - "node_modules/@asamuzakjp/dom-selector": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/@asamuzakjp/dom-selector/-/dom-selector-7.1.1.tgz", - "integrity": "sha512-67RZDnYRc8H/8MLDgQCDE//zoqVFwajkepHZgmXrbwybzXOEwOWGPYGmALYl9J2DOLfFPPs6kKCqmbzV895hTQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@asamuzakjp/generational-cache": "^1.0.1", - "@asamuzakjp/nwsapi": "^2.3.9", - "bidi-js": "^1.0.3", - "css-tree": "^3.2.1", - "is-potential-custom-element-name": "^1.0.1" - }, - "engines": { - "node": "^20.19.0 || ^22.12.0 || >=24.0.0" - } - }, - "node_modules/@asamuzakjp/generational-cache": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@asamuzakjp/generational-cache/-/generational-cache-1.0.1.tgz", - "integrity": "sha512-wajfB8KqzMCN2KGNFdLkReeHncd0AslUSrvHVvvYWuU8ghncRJoA50kT3zP9MVL0+9g4/67H+cdvBskj9THPzg==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^20.19.0 || ^22.12.0 || >=24.0.0" - } - }, - "node_modules/@asamuzakjp/nwsapi": { - "version": "2.3.9", - "resolved": "https://registry.npmjs.org/@asamuzakjp/nwsapi/-/nwsapi-2.3.9.tgz", - "integrity": "sha512-n8GuYSrI9bF7FFZ/SjhwevlHc8xaVlb/7HmHelnc/PZXBD2ZR49NnN9sMMuDdEGPeeRQ5d0hqlSlEpgCX3Wl0Q==", - "dev": true, - "license": "MIT" - }, - "node_modules/@babel/helper-string-parser": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", - "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-validator-identifier": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", - "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/parser": { - "version": "7.29.3", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.3.tgz", - "integrity": "sha512-b3ctpQwp+PROvU/cttc4OYl4MzfJUWy6FZg+PMXfzmt/+39iHVF0sDfqay8TQM3JA2EUOyKcFZt75jWriQijsA==", - "license": "MIT", - "dependencies": { - "@babel/types": "^7.29.0" - }, - "bin": { - "parser": "bin/babel-parser.js" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@babel/types": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", - "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", - "license": "MIT", - "dependencies": { - "@babel/helper-string-parser": "^7.27.1", - "@babel/helper-validator-identifier": "^7.28.5" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@bramus/specificity": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/@bramus/specificity/-/specificity-2.4.2.tgz", - "integrity": "sha512-ctxtJ/eA+t+6q2++vj5j7FYX3nRu311q1wfYH3xjlLOsczhlhxAg2FWNUXhpGvAw3BWo1xBcvOV6/YLc2r5FJw==", - "dev": true, - "license": "MIT", - "dependencies": { - "css-tree": "^3.0.0" - }, - "bin": { - "specificity": "bin/cli.js" - } - }, - "node_modules/@csstools/color-helpers": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-6.0.2.tgz", - "integrity": "sha512-LMGQLS9EuADloEFkcTBR3BwV/CGHV7zyDxVRtVDTwdI2Ca4it0CCVTT9wCkxSgokjE5Ho41hEPgb8OEUwoXr6Q==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT-0", - "engines": { - "node": ">=20.19.0" - } - }, - "node_modules/@csstools/css-calc": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-3.2.1.tgz", - "integrity": "sha512-DtdHlgXh5ZkA43cwBcAm+huzgJiwx3ZTWVjBs94kwz2xKqSimDA3lBgCjphYgwgVUMWatSM0pDd8TILB1yrVVg==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT", - "engines": { - "node": ">=20.19.0" - }, - "peerDependencies": { - "@csstools/css-parser-algorithms": "^4.0.0", - "@csstools/css-tokenizer": "^4.0.0" - } - }, - "node_modules/@csstools/css-color-parser": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-4.1.1.tgz", - "integrity": "sha512-eZ5XOtyhK+mggRafYUWzA0tvaYOFgdY8AkgQiCJF9qNAePnUo/zmsqqYubBBb3sQ8uNUaSKTY9s9klfRaAXL0g==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT", - "dependencies": { - "@csstools/color-helpers": "^6.0.2", - "@csstools/css-calc": "^3.2.1" - }, - "engines": { - "node": ">=20.19.0" - }, - "peerDependencies": { - "@csstools/css-parser-algorithms": "^4.0.0", - "@csstools/css-tokenizer": "^4.0.0" - } - }, - "node_modules/@csstools/css-parser-algorithms": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-4.0.0.tgz", - "integrity": "sha512-+B87qS7fIG3L5h3qwJ/IFbjoVoOe/bpOdh9hAjXbvx0o8ImEmUsGXN0inFOnk2ChCFgqkkGFQ+TpM5rbhkKe4w==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT", - "engines": { - "node": ">=20.19.0" - }, - "peerDependencies": { - "@csstools/css-tokenizer": "^4.0.0" - } - }, - "node_modules/@csstools/css-syntax-patches-for-csstree": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/@csstools/css-syntax-patches-for-csstree/-/css-syntax-patches-for-csstree-1.1.4.tgz", - "integrity": "sha512-wgsqt92b7C7tQhIdPNxj0n9zuUbQlvAuI1exyzeNrOKOi62SD7ren8zqszmpVREjAOqg8cD2FqYhQfAuKjk4sw==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT-0", - "peerDependencies": { - "css-tree": "^3.2.1" - }, - "peerDependenciesMeta": { - "css-tree": { - "optional": true - } - } - }, - "node_modules/@csstools/css-tokenizer": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-4.0.0.tgz", - "integrity": "sha512-QxULHAm7cNu72w97JUNCBFODFaXpbDg+dP8b/oWFAZ2MTRppA3U00Y2L1HqaS4J6yBqxwa/Y3nMBaxVKbB/NsA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT", - "engines": { - "node": ">=20.19.0" - } - }, - "node_modules/@emnapi/core": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.10.0.tgz", - "integrity": "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@emnapi/wasi-threads": "1.2.1", - "tslib": "^2.4.0" - } - }, - "node_modules/@emnapi/runtime": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.10.0.tgz", - "integrity": "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, - "node_modules/@emnapi/wasi-threads": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz", - "integrity": "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, - "node_modules/@exodus/bytes": { - "version": "1.15.1", - "resolved": "https://registry.npmjs.org/@exodus/bytes/-/bytes-1.15.1.tgz", - "integrity": "sha512-S6mL0yNB/Abt9Ei4tq8gDhcczc4S3+vQ4ra7vxnAf+YHC02srtqxKKZghx2Dq6p0e66THKwR6r8N6P95wEty7Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^20.19.0 || ^22.12.0 || >=24.0.0" - }, - "peerDependencies": { - "@noble/hashes": "^1.8.0 || ^2.0.0" - }, - "peerDependenciesMeta": { - "@noble/hashes": { - "optional": true - } - } - }, - "node_modules/@isaacs/cliui": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", - "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", - "dev": true, - "license": "ISC", - "dependencies": { - "string-width": "^5.1.2", - "string-width-cjs": "npm:string-width@^4.2.0", - "strip-ansi": "^7.0.1", - "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", - "wrap-ansi": "^8.1.0", - "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.13", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", - "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.0", - "@jridgewell/trace-mapping": "^0.3.24" - } - }, - "node_modules/@jridgewell/remapping": { - "version": "2.3.5", - "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", - "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.24" - } - }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", - "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.5", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", - "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", - "license": "MIT" - }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.31", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", - "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" - } - }, - "node_modules/@napi-rs/wasm-runtime": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.4.tgz", - "integrity": "sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@tybys/wasm-util": "^0.10.1" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/Brooooooklyn" - }, - "peerDependencies": { - "@emnapi/core": "^1.7.1", - "@emnapi/runtime": "^1.7.1" - } - }, - "node_modules/@one-ini/wasm": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/@one-ini/wasm/-/wasm-0.1.1.tgz", - "integrity": "sha512-XuySG1E38YScSJoMlqovLru4KTUNSjgVTIjyh7qMX6aNN5HY5Ct5LhRJdxO79JtTzKfzV/bnWpz+zquYrISsvw==", - "dev": true, - "license": "MIT" - }, - "node_modules/@oxc-project/types": { - "version": "0.132.0", - "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.132.0.tgz", - "integrity": "sha512-FESMOxil5Se014ui/Eq8fT5uHJo6nIRwH0PfJrZJXs6Gek3ZVFOrpUv3YIZT20m+extU98Hg1Ym72U58rlsxUQ==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/Boshen" - } - }, - "node_modules/@pkgjs/parseargs": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", - "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", - "dev": true, - "license": "MIT", - "optional": true, - "engines": { - "node": ">=14" - } - }, - "node_modules/@rolldown/binding-android-arm64": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.2.tgz", - "integrity": "sha512-ZS4D1JPGn/MYQN/SYDWftIE/nVsM8j/AFOYEzAoOE2O3NktQOZru+/vYXGbR/qtdLdIfGCP0lcoJiYVzsEz+iQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-darwin-arm64": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.2.tgz", - "integrity": "sha512-vdFA9+C/rekyGce7WqHs/xoT0ioZEWaOFyZLIV1mEeNFaFDUQrPIo8Vs2GvJ6eetb3rzDUtUBgzto3ExpXJB3w==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-darwin-x64": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.2.tgz", - "integrity": "sha512-BewSOwTHazv77DTYiAZXSqqKZ4KP/KonFisDMVU7PImxoWfB2aepnPhd2E4SWz3zDzYgDNbs6jBmTdgNnF02GA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-freebsd-x64": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.2.tgz", - "integrity": "sha512-m41o7M0YWtUdqk61Tb+jnKb2rN++iRdIASlExkUoKfIAH30DOHCB8fVLzSUpbWHHU8esmEioY62PxzexE8MBuA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-linux-arm-gnueabihf": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.2.tgz", - "integrity": "sha512-jcojB9H7W/jS29pMKWAK1N+fU99vXodHDTatS3b3y/XSOCiHo0kkA74pL3jJmkoQtYpOCxDvaKs1fo2Ij/1X5w==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-linux-arm64-gnu": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.2.tgz", - "integrity": "sha512-1jn6qDU5iiOgFgygDzKUuKP0maTi0/f1+sBLgvij/76C77Nm3ts6ufz9Bjg5q5dduxiUIxtq86JIoBvo1xQ4Ig==", - "cpu": [ - "arm64" - ], - "dev": true, - "libc": [ - "glibc" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-linux-arm64-musl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.2.tgz", - "integrity": "sha512-QVLO/czFMdoMFSqlX3bcswcJNm/23r+qoa/jgtmFc/qEp6/jXmIkDjF/XIo8dPfGaiwy1xfQn8o77L79GeXFgw==", - "cpu": [ - "arm64" - ], - "dev": true, - "libc": [ - "musl" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-linux-ppc64-gnu": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.2.tgz", - "integrity": "sha512-hgO5Abm0w5UL6FEa2iFnZqo2KlK7TQ5QhV5x09hujBf7t5KzHQ1VmfPuTpqRy/rNlSxua3eWH374xxiVrP+lcA==", - "cpu": [ - "ppc64" - ], - "dev": true, - "libc": [ - "glibc" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-linux-s390x-gnu": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.2.tgz", - "integrity": "sha512-fy8rXxuYEu602abC8MUNaPjYLIFzReOaEIEMKMUa0rFEUxNpVXhs15KSSQ4qlqSaM7B6rcj9rDZgADh/IGDzLQ==", - "cpu": [ - "s390x" - ], - "dev": true, - "libc": [ - "glibc" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-linux-x64-gnu": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.2.tgz", - "integrity": "sha512-0+bOkiQ779+r1WpoHOWHqncvyySci0vKph+myNDYb+im6meJAzHQXay6oEgnkHuUGouM1LKTZwqKpBow6Kj7CQ==", - "cpu": [ - "x64" - ], - "dev": true, - "libc": [ - "glibc" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-linux-x64-musl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.2.tgz", - "integrity": "sha512-mjSkrzZK5Qsl0a9d1JgILOiuZOSDTVdKENcSXBoqbzSrspLR/4/IRVDo5wd2GgZjNss/viBFJdeq+j7qH2nypw==", - "cpu": [ - "x64" - ], - "dev": true, - "libc": [ - "musl" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-openharmony-arm64": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.2.tgz", - "integrity": "sha512-1v5vHasdfQAZoEHakBV72LIFAC9JjnymsiKxp+GEr/ma3+NJCPSaYK+qavInOovJkgwFrs7GccX2d6IgDA3Z5w==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-wasm32-wasi": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.2.tgz", - "integrity": "sha512-mb1VobWn6NheziTk5/WEaR6AKVbrwT5sOi6C7zk3gy/pD1qtJfU1j4PgTo2NJnOtbL9Dl3Aeei8w9jJ7qC2jZQ==", - "cpu": [ - "wasm32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@emnapi/core": "1.10.0", - "@emnapi/runtime": "1.10.0", - "@napi-rs/wasm-runtime": "^1.1.4" - }, - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-win32-arm64-msvc": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.2.tgz", - "integrity": "sha512-SqKonF56vA/L2yHwHYcEp2P34URpOZ7d1fS635cTkpDnUtEGdUbhI6NzsPdqeSWvAAeGDrxjWjNmibDIdFf9/A==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-win32-x64-msvc": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.2.tgz", - "integrity": "sha512-v7qRI7gXLRINcOGXt+7YmAZ6iFuyZVMIoXAxhd8oP+DR9dLfL9GfNIx7PLMxmhZdvq8waUJBQiWN9EKNy+TRBQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/pluginutils": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.1.tgz", - "integrity": "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==", - "dev": true, - "license": "MIT" - }, - "node_modules/@standard-schema/spec": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", - "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", - "dev": true, - "license": "MIT" - }, - "node_modules/@tailwindcss/node": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.3.0.tgz", - "integrity": "sha512-aFb4gUhFOgdh9AXo4IzBEOzBkkAxm9VigwDJnMIYv3lcfXCJVesNfbEaBl4BNgVRyid92AmdviqwBUBRKSeY3g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/remapping": "^2.3.5", - "enhanced-resolve": "^5.21.0", - "jiti": "^2.6.1", - "lightningcss": "1.32.0", - "magic-string": "^0.30.21", - "source-map-js": "^1.2.1", - "tailwindcss": "4.3.0" - } - }, - "node_modules/@tailwindcss/oxide": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.3.0.tgz", - "integrity": "sha512-F7HZGBeN9I0/AuuJS5PwcD8xayx5ri5GhjYUDBEVYUkexyA/giwbDNjRVrxSezE3T250OU2K/wp/ltWx3UOefg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 20" - }, - "optionalDependencies": { - "@tailwindcss/oxide-android-arm64": "4.3.0", - "@tailwindcss/oxide-darwin-arm64": "4.3.0", - "@tailwindcss/oxide-darwin-x64": "4.3.0", - "@tailwindcss/oxide-freebsd-x64": "4.3.0", - "@tailwindcss/oxide-linux-arm-gnueabihf": "4.3.0", - "@tailwindcss/oxide-linux-arm64-gnu": "4.3.0", - "@tailwindcss/oxide-linux-arm64-musl": "4.3.0", - "@tailwindcss/oxide-linux-x64-gnu": "4.3.0", - "@tailwindcss/oxide-linux-x64-musl": "4.3.0", - "@tailwindcss/oxide-wasm32-wasi": "4.3.0", - "@tailwindcss/oxide-win32-arm64-msvc": "4.3.0", - "@tailwindcss/oxide-win32-x64-msvc": "4.3.0" - } - }, - "node_modules/@tailwindcss/oxide-android-arm64": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.3.0.tgz", - "integrity": "sha512-TJPiq67tKlLuObP6RkwvVGDoxCMBVtDgKkLfa/uyj7/FyxvQwHS+UOnVrXXgbEsfUaMgiVvC4KbJnRr26ho4Ng==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">= 20" - } - }, - "node_modules/@tailwindcss/oxide-darwin-arm64": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.3.0.tgz", - "integrity": "sha512-oMN/WZRb+SO37BmUElEgeEWuU8E/HXRkiODxJxLe1UTHVXLrdVSgfaJV7pSlhRGMSOiXLuxTIjfsF3wYvz8cgQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 20" - } - }, - "node_modules/@tailwindcss/oxide-darwin-x64": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.3.0.tgz", - "integrity": "sha512-N6CUmu4a6bKVADfw77p+iw6Yd9Q3OBhe0veaDX+QazfuVYlQsHfDgxBrsjQ/IW+zywL8mTrNd0SdJT/zgtvMdA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 20" - } - }, - "node_modules/@tailwindcss/oxide-freebsd-x64": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.3.0.tgz", - "integrity": "sha512-zDL5hBkQdH5C6MpqbK3gQAgP80tsMwSI26vjOzjJtNCMUo0lFgOItzHKBIupOZNQxt3ouPH7RPhvNhiTfCe5CQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">= 20" - } - }, - "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.3.0.tgz", - "integrity": "sha512-R06HdNi7A7OEoMsf6d4tjZ71RCWnZQPHj2mnotSFURjNLdBC+cIgXQ7l81CqeoiQftjf6OOblxXMInMgN2VzMA==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 20" - } - }, - "node_modules/@tailwindcss/oxide-linux-arm64-gnu": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.3.0.tgz", - "integrity": "sha512-qTJHELX8jetjhRQHCLilkVLmybpzNQAtaI/gaoVoidn/ufbNDbAo8KlK2J+yPoc8wQxvDxCmh/5lr8nC1+lTbg==", - "cpu": [ - "arm64" - ], - "dev": true, - "libc": [ - "glibc" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 20" - } - }, - "node_modules/@tailwindcss/oxide-linux-arm64-musl": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.3.0.tgz", - "integrity": "sha512-Z6sukiQsngnWO+l39X4pPbiWT81IC+PLKF+PHxIlyZbGNb9MODfYlXEVlFvej5BOZInWX01kVyzeLvHsXhfczQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "libc": [ - "musl" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 20" - } - }, - "node_modules/@tailwindcss/oxide-linux-x64-gnu": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.3.0.tgz", - "integrity": "sha512-DRNdQRpSGzRGfARVuVkxvM8Q12nh19l4BF/G7zGA1oe+9wcC6saFBHTISrpIcKzhiXtSrlSrluCfvMuledoCTQ==", - "cpu": [ - "x64" - ], - "dev": true, - "libc": [ - "glibc" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 20" - } - }, - "node_modules/@tailwindcss/oxide-linux-x64-musl": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.3.0.tgz", - "integrity": "sha512-Z0IADbDo8bh6I7h2IQMx601AdXBLfFpEdUotft86evd/8ZPflZe9COPO8Q1vw+pfLWIUo9zN/JGZvwuAJqduqg==", - "cpu": [ - "x64" - ], - "dev": true, - "libc": [ - "musl" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 20" - } - }, - "node_modules/@tailwindcss/oxide-wasm32-wasi": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.3.0.tgz", - "integrity": "sha512-HNZGOUxEmElksYR7S6sC5jTeNGpobAsy9u7Gu0AskJ8/20FR9GqebUyB+HBcU/ax6BHuiuJi+Oda4B+YX6H1yA==", - "bundleDependencies": [ - "@napi-rs/wasm-runtime", - "@emnapi/core", - "@emnapi/runtime", - "@tybys/wasm-util", - "@emnapi/wasi-threads", - "tslib" - ], - "cpu": [ - "wasm32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@emnapi/core": "^1.10.0", - "@emnapi/runtime": "^1.10.0", - "@emnapi/wasi-threads": "^1.2.1", - "@napi-rs/wasm-runtime": "^1.1.4", - "@tybys/wasm-util": "^0.10.1", - "tslib": "^2.8.1" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@tailwindcss/oxide-win32-arm64-msvc": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.3.0.tgz", - "integrity": "sha512-Pe+RPVTi1T+qymuuRpcdvwSVZjnll/f7n8gBxMMh3xLTctMDKqpdfGimbMyioqtLhUYZxdJ9wGNhV7MKHvgZsQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 20" - } - }, - "node_modules/@tailwindcss/oxide-win32-x64-msvc": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.3.0.tgz", - "integrity": "sha512-Mvrf2kXW/yeW/OTezZlCGOirXRcUuLIBx/5Y12BaPM7wJoryG6dfS/NJL8aBPqtTEx/Vm4T4vKzFUcKDT+TKUA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 20" - } - }, - "node_modules/@tailwindcss/vite": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.3.0.tgz", - "integrity": "sha512-t6J3OrB5Fc0ExuhohouH0fWUGMYL6PTLhW+E7zIk/pdbnJARZDCwjBznFnkh5ynRnIRSI4YjtTH0t6USjJISrw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@tailwindcss/node": "4.3.0", - "@tailwindcss/oxide": "4.3.0", - "tailwindcss": "4.3.0" - }, - "peerDependencies": { - "vite": "^5.2.0 || ^6 || ^7 || ^8" - } - }, - "node_modules/@tybys/wasm-util": { - "version": "0.10.2", - "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.2.tgz", - "integrity": "sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, - "node_modules/@types/chai": { - "version": "5.2.3", - "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", - "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/deep-eql": "*", - "assertion-error": "^2.0.1" - } - }, - "node_modules/@types/deep-eql": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", - "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/estree": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", - "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/node": { - "version": "24.12.4", - "resolved": "https://registry.npmjs.org/@types/node/-/node-24.12.4.tgz", - "integrity": "sha512-GUUEShf+PBCGW2KaXwcIt3Yk+e3pkKwWKb9GSyM9WQVE+ep2jzmHdGsHzu4wgcZy5fN9FBdVzjpBQsYlpfpgLA==", - "dev": true, - "license": "MIT", - "dependencies": { - "undici-types": "~7.16.0" - } - }, - "node_modules/@vitejs/plugin-vue": { - "version": "6.0.7", - "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-6.0.7.tgz", - "integrity": "sha512-km+p+XdSz9Sxm5rqUbqcSfZYaAniKxWBj1KURl+Jr7UaPvvX7BmaWMdP69I5rrFDeQGyxAG7NXdc57vz+snhWg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@rolldown/pluginutils": "^1.0.1" - }, - "engines": { - "node": "^20.19.0 || >=22.12.0" - }, - "peerDependencies": { - "vite": "^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0", - "vue": "^3.2.25" - } - }, - "node_modules/@vitest/expect": { - "version": "4.1.7", - "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.7.tgz", - "integrity": "sha512-1R+tw0ortHEbZDGMymm+pN7/AFQ/RkFFdtd7EN+VBpynKmLbP8A3rpEXdshBJ7+8hQ9zBJh/i1s0yKNtxAnU7w==", - "dev": true, - "license": "MIT", - "dependencies": { - "@standard-schema/spec": "^1.1.0", - "@types/chai": "^5.2.2", - "@vitest/spy": "4.1.7", - "@vitest/utils": "4.1.7", - "chai": "^6.2.2", - "tinyrainbow": "^3.1.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/@vitest/mocker": { - "version": "4.1.7", - "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.7.tgz", - "integrity": "sha512-vY7nuamKgfvpA1Koa3oYIw/k7D6kZnpGyNMZW8loow2bsBYla1TFdqTaXncWdRn4pgwNs+90RhnXhJScDwQeJA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@vitest/spy": "4.1.7", - "estree-walker": "^3.0.3", - "magic-string": "^0.30.21" - }, - "funding": { - "url": "https://opencollective.com/vitest" - }, - "peerDependencies": { - "msw": "^2.4.9", - "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" - }, - "peerDependenciesMeta": { - "msw": { - "optional": true - }, - "vite": { - "optional": true - } - } - }, - "node_modules/@vitest/mocker/node_modules/estree-walker": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", - "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/estree": "^1.0.0" - } - }, - "node_modules/@vitest/pretty-format": { - "version": "4.1.7", - "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.7.tgz", - "integrity": "sha512-umgCarTOYQWIaDMvGDRZij+6b9oVeLIyJzfN+AS88e0ZOU3QTgNNSTtjQOpcvWr3np1N0j4WgZj+sb3oYBDscw==", - "dev": true, - "license": "MIT", - "dependencies": { - "tinyrainbow": "^3.1.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/@vitest/runner": { - "version": "4.1.7", - "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.7.tgz", - "integrity": "sha512-BapjmAQ2aI78WdMEfeUWivnfVzB+VPGwWRQcJE0OUq7qEeEcBsCSf+0T5iREBNE5nBb4wA5Ya0W6IA+sghdEFw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@vitest/utils": "4.1.7", - "pathe": "^2.0.3" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/@vitest/snapshot": { - "version": "4.1.7", - "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.7.tgz", - "integrity": "sha512-ZacLzja+TmJeZ1h14xW2FB/WpeimUD3haBXQPyJqxvo8jQTmfeA8zv58mtjN2C7EHXZDYVcVYdYmAxjkWVvKCw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@vitest/pretty-format": "4.1.7", - "@vitest/utils": "4.1.7", - "magic-string": "^0.30.21", - "pathe": "^2.0.3" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/@vitest/spy": { - "version": "4.1.7", - "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.7.tgz", - "integrity": "sha512-kbkI5LMWakyuTIvs6fUJ5qdIVb1XVKsYJAT4OJ938cHMROYMSfmoQdZy0aaAnjbbc8F61vkoTqz/Az+/HiIu5Q==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/@vitest/utils": { - "version": "4.1.7", - "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.7.tgz", - "integrity": "sha512-T532WBu791cBxJlCl6SO+J14l81DQx6uQHm1bQbmCDY7nqlEIgkza/UFnSBNaUtSf41unldDFjdOBYEQC4b5Hw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@vitest/pretty-format": "4.1.7", - "convert-source-map": "^2.0.0", - "tinyrainbow": "^3.1.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/@volar/language-core": { - "version": "2.4.28", - "resolved": "https://registry.npmjs.org/@volar/language-core/-/language-core-2.4.28.tgz", - "integrity": "sha512-w4qhIJ8ZSitgLAkVay6AbcnC7gP3glYM3fYwKV3srj8m494E3xtrCv6E+bWviiK/8hs6e6t1ij1s2Endql7vzQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@volar/source-map": "2.4.28" - } - }, - "node_modules/@volar/source-map": { - "version": "2.4.28", - "resolved": "https://registry.npmjs.org/@volar/source-map/-/source-map-2.4.28.tgz", - "integrity": "sha512-yX2BDBqJkRXfKw8my8VarTyjv48QwxdJtvRgUpNE5erCsgEUdI2DsLbpa+rOQVAJYshY99szEcRDmyHbF10ggQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/@volar/typescript": { - "version": "2.4.28", - "resolved": "https://registry.npmjs.org/@volar/typescript/-/typescript-2.4.28.tgz", - "integrity": "sha512-Ja6yvWrbis2QtN4ClAKreeUZPVYMARDYZl9LMEv1iQ1QdepB6wn0jTRxA9MftYmYa4DQ4k/DaSZpFPUfxl8giw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@volar/language-core": "2.4.28", - "path-browserify": "^1.0.1", - "vscode-uri": "^3.0.8" - } - }, - "node_modules/@vue/compiler-core": { - "version": "3.5.34", - "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.34.tgz", - "integrity": "sha512-s9cLyK5mLcvZ4Agva5QgRsQyLKvts9WbU9DB6NqiZkkGEdwmcEiylj5Jbwkp680drF/NNCV8OlAJSe+yMLxaJw==", - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.29.3", - "@vue/shared": "3.5.34", - "entities": "^7.0.1", - "estree-walker": "^2.0.2", - "source-map-js": "^1.2.1" - } - }, - "node_modules/@vue/compiler-dom": { - "version": "3.5.34", - "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.34.tgz", - "integrity": "sha512-EbF/T++k0e2MMZlJsBhzK8Sgwt0HcIPOhzn1CTB/lv6sQcyk+OWf8YeiLxZp3ro7MbbLcAfAJ6sEvjFWuNgUCw==", - "license": "MIT", - "dependencies": { - "@vue/compiler-core": "3.5.34", - "@vue/shared": "3.5.34" - } - }, - "node_modules/@vue/compiler-sfc": { - "version": "3.5.34", - "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.34.tgz", - "integrity": "sha512-D/ihr6uZeIt6r+pVZf46RWT1fAsLFMbUP7k8G1VkiiWexriED9GrX3echHd4Abbt17zjlfiFJ8z7a3BxZOPNjg==", - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.29.3", - "@vue/compiler-core": "3.5.34", - "@vue/compiler-dom": "3.5.34", - "@vue/compiler-ssr": "3.5.34", - "@vue/shared": "3.5.34", - "estree-walker": "^2.0.2", - "magic-string": "^0.30.21", - "postcss": "^8.5.14", - "source-map-js": "^1.2.1" - } - }, - "node_modules/@vue/compiler-ssr": { - "version": "3.5.34", - "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.34.tgz", - "integrity": "sha512-cDtTHKibkThKGHH1SP+WdccquNRYQDFH6rRjQCqT9G2ltFAfoR5pUftpab/z+aM5mW9HLLVQW7hfKKQe/1GBeQ==", - "license": "MIT", - "dependencies": { - "@vue/compiler-dom": "3.5.34", - "@vue/shared": "3.5.34" - } - }, - "node_modules/@vue/language-core": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/@vue/language-core/-/language-core-3.3.1.tgz", - "integrity": "sha512-NP8g6V7x81NVOXbLupUvYY6i6LqUkjkVowe2epRedmpgaFCOdjgWHE/rQBvEJ4r7koAYODIjGeBWEdt6n7jYXQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@volar/language-core": "2.4.28", - "@vue/compiler-dom": "^3.5.0", - "@vue/shared": "^3.5.0", - "alien-signals": "^3.2.0", - "muggle-string": "^0.4.1", - "path-browserify": "^1.0.1", - "picomatch": "^4.0.4" - } - }, - "node_modules/@vue/reactivity": { - "version": "3.5.34", - "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.34.tgz", - "integrity": "sha512-y9XDjCEuBp+98k+UL5dbYkh57AHU4o6cxZedOPXw3bmrZZYLQsVHguGurq7hVrPCSrQtrnz1f9dssyFr+dMXfQ==", - "license": "MIT", - "dependencies": { - "@vue/shared": "3.5.34" - } - }, - "node_modules/@vue/runtime-core": { - "version": "3.5.34", - "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.34.tgz", - "integrity": "sha512-mKeBYvu8tcMSLhypAHBmriUFfWXKTCF/23Z4jiCoYK3UtWepkliViNLuR90V9XOyD62mUxs9p1jsrpK3CCGIzw==", - "license": "MIT", - "dependencies": { - "@vue/reactivity": "3.5.34", - "@vue/shared": "3.5.34" - } - }, - "node_modules/@vue/runtime-dom": { - "version": "3.5.34", - "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.34.tgz", - "integrity": "sha512-e8kZzERmCwUnBRVsgSQlAfrfU2rGoy0FFKPBXSlfEjc/O3KfA7QP0t1/2ZylrbchjmIKB4dPTd07A6WPr0eOrg==", - "license": "MIT", - "dependencies": { - "@vue/reactivity": "3.5.34", - "@vue/runtime-core": "3.5.34", - "@vue/shared": "3.5.34", - "csstype": "^3.2.3" - } - }, - "node_modules/@vue/server-renderer": { - "version": "3.5.34", - "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.34.tgz", - "integrity": "sha512-nHxmJoTrKsmrkbILRhkC9gY1G3moZbJTqCzDd7DOOzG5KH9oeJ0Unqrff5f9v0pW//jES05ZkJcNtfE8JjOIew==", - "license": "MIT", - "dependencies": { - "@vue/compiler-ssr": "3.5.34", - "@vue/shared": "3.5.34" - }, - "peerDependencies": { - "vue": "3.5.34" - } - }, - "node_modules/@vue/shared": { - "version": "3.5.34", - "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.34.tgz", - "integrity": "sha512-24uqU4OIiX29ryC3MeWid/Xf2fa2EFRUVLb77nRhk+UrTVrh/XiGtFAFmJBAtBRbjwNdsPRP+jj/OL27Eg1NDA==", - "license": "MIT" - }, - "node_modules/@vue/test-utils": { - "version": "2.4.10", - "resolved": "https://registry.npmjs.org/@vue/test-utils/-/test-utils-2.4.10.tgz", - "integrity": "sha512-SmoZ5EA1kYiAFs9NkYdiFFQF+cSnUwnvlYEbY+DogWQZUiqOm/Y29eSbc5T6yi75SgSF9863SBeXniIEoPajCA==", - "dev": true, - "license": "MIT", - "dependencies": { - "js-beautify": "^1.14.9", - "vue-component-type-helpers": "^3.0.0" - }, - "peerDependencies": { - "@vue/compiler-dom": "3.x", - "@vue/server-renderer": "3.x", - "vue": "3.x" - }, - "peerDependenciesMeta": { - "@vue/server-renderer": { - "optional": true - } - } - }, - "node_modules/@vue/tsconfig": { - "version": "0.9.1", - "resolved": "https://registry.npmjs.org/@vue/tsconfig/-/tsconfig-0.9.1.tgz", - "integrity": "sha512-buvjm+9NzLCJL29KY1j1991YYJ5e6275OiK+G4jtmfIb+z4POywbdm0wXusT9adVWqe0xqg70TbI7+mRx4uU9w==", - "dev": true, - "license": "MIT", - "peerDependencies": { - "typescript": ">= 5.8", - "vue": "^3.4.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - }, - "vue": { - "optional": true - } - } - }, - "node_modules/abbrev": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-2.0.0.tgz", - "integrity": "sha512-6/mh1E2u2YgEsCHdY0Yx5oW+61gZU+1vXaoiHHrpKeuRNNgFvS+/jrwHiQhB5apAf5oB7UB7E19ol2R2LKH8hQ==", - "dev": true, - "license": "ISC", - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "node_modules/alien-signals": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/alien-signals/-/alien-signals-3.2.1.tgz", - "integrity": "sha512-I8FjmltrfnDFoZedi5CG8DghVYNhzb/Ijluz7tCSJH0xpd0484Kowhbb1XDYOxfJpU1p5wnM2X54dA+IfGyD1g==", - "dev": true, - "license": "MIT" - }, - "node_modules/ansi-regex": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", - "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" - } - }, - "node_modules/ansi-styles": { - "version": "6.2.3", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", - "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/assertion-error": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", - "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - } - }, - "node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true, - "license": "MIT" - }, - "node_modules/bidi-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/bidi-js/-/bidi-js-1.0.3.tgz", - "integrity": "sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw==", - "dev": true, - "license": "MIT", - "dependencies": { - "require-from-string": "^2.0.2" - } - }, - "node_modules/brace-expansion": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.0.tgz", - "integrity": "sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/chai": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz", - "integrity": "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - } - }, - "node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true, - "license": "MIT" - }, - "node_modules/commander": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz", - "integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=14" - } - }, - "node_modules/config-chain": { - "version": "1.1.13", - "resolved": "https://registry.npmjs.org/config-chain/-/config-chain-1.1.13.tgz", - "integrity": "sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ini": "^1.3.4", - "proto-list": "~1.2.1" - } - }, - "node_modules/convert-source-map": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", - "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", - "dev": true, - "license": "MIT" - }, - "node_modules/cross-spawn": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", - "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", - "dev": true, - "license": "MIT", - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/css-tree": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.2.1.tgz", - "integrity": "sha512-X7sjQzceUhu1u7Y/ylrRZFU2FS6LRiFVp6rKLPg23y3x3c3DOKAwuXGDp+PAGjh6CSnCjYeAul8pcT8bAl+lSA==", - "dev": true, - "license": "MIT", - "dependencies": { - "mdn-data": "2.27.1", - "source-map-js": "^1.2.1" - }, - "engines": { - "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0" - } - }, - "node_modules/csstype": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", - "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", - "license": "MIT" - }, - "node_modules/data-urls": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-7.0.0.tgz", - "integrity": "sha512-23XHcCF+coGYevirZceTVD7NdJOqVn+49IHyxgszm+JIiHLoB2TkmPtsYkNWT1pvRSGkc35L6NHs0yHkN2SumA==", - "dev": true, - "license": "MIT", - "dependencies": { - "whatwg-mimetype": "^5.0.0", - "whatwg-url": "^16.0.0" - }, - "engines": { - "node": "^20.19.0 || ^22.12.0 || >=24.0.0" - } - }, - "node_modules/decimal.js": { - "version": "10.6.0", - "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.6.0.tgz", - "integrity": "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==", - "dev": true, - "license": "MIT" - }, - "node_modules/detect-libc": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", - "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=8" - } - }, - "node_modules/eastasianwidth": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", - "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", - "dev": true, - "license": "MIT" - }, - "node_modules/editorconfig": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/editorconfig/-/editorconfig-1.0.7.tgz", - "integrity": "sha512-e0GOtq/aTQhVdNyDU9e02+wz9oDDM+SIOQxWME2QRjzRX5yyLAuHDE+0aE8vHb9XRC8XD37eO2u57+F09JqFhw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@one-ini/wasm": "0.1.1", - "commander": "^10.0.0", - "minimatch": "^9.0.1", - "semver": "^7.5.3" - }, - "bin": { - "editorconfig": "bin/editorconfig" - }, - "engines": { - "node": ">=14" - } - }, - "node_modules/emoji-regex": { - "version": "9.2.2", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", - "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", - "dev": true, - "license": "MIT" - }, - "node_modules/enhanced-resolve": { - "version": "5.22.0", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.22.0.tgz", - "integrity": "sha512-xYcDWrpELkFzz9SpZ3PlI6Eu6eD93Yf0WLDRxikGhWJ3MAir2SNZTIVCVZqZ/NUyx8AdMc2gT9C0gPiw18kG+A==", - "dev": true, - "license": "MIT", - "dependencies": { - "graceful-fs": "^4.2.4", - "tapable": "^2.3.3" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/entities": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/entities/-/entities-7.0.1.tgz", - "integrity": "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==", - "license": "BSD-2-Clause", - "engines": { - "node": ">=0.12" - }, - "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" - } - }, - "node_modules/es-module-lexer": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.1.0.tgz", - "integrity": "sha512-n27zTYMjYu1aj4MjCWzSP7G9r75utsaoc8m61weK+W8JMBGGQybd43GstCXZ3WNmSFtGT9wi59qQTW6mhTR5LQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/estree-walker": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", - "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", - "license": "MIT" - }, - "node_modules/expect-type": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.3.0.tgz", - "integrity": "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=12.0.0" - } - }, - "node_modules/fdir": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", - "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12.0.0" - }, - "peerDependencies": { - "picomatch": "^3 || ^4" - }, - "peerDependenciesMeta": { - "picomatch": { - "optional": true - } - } - }, - "node_modules/foreground-child": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", - "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", - "dev": true, - "license": "ISC", - "dependencies": { - "cross-spawn": "^7.0.6", - "signal-exit": "^4.0.1" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/glob": { - "version": "10.5.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", - "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", - "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", - "dev": true, - "license": "ISC", - "dependencies": { - "foreground-child": "^3.1.0", - "jackspeak": "^3.1.2", - "minimatch": "^9.0.4", - "minipass": "^7.1.2", - "package-json-from-dist": "^1.0.0", - "path-scurry": "^1.11.1" - }, - "bin": { - "glob": "dist/esm/bin.mjs" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/graceful-fs": { - "version": "4.2.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", - "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/html-encoding-sniffer": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-6.0.0.tgz", - "integrity": "sha512-CV9TW3Y3f8/wT0BRFc1/KAVQ3TUHiXmaAb6VW9vtiMFf7SLoMd1PdAc4W3KFOFETBJUb90KatHqlsZMWV+R9Gg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@exodus/bytes": "^1.6.0" - }, - "engines": { - "node": "^20.19.0 || ^22.12.0 || >=24.0.0" - } - }, - "node_modules/ini": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", - "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", - "dev": true, - "license": "ISC" - }, - "node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/is-potential-custom-element-name": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", - "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true, - "license": "ISC" - }, - "node_modules/jackspeak": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", - "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", - "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "@isaacs/cliui": "^8.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - }, - "optionalDependencies": { - "@pkgjs/parseargs": "^0.11.0" - } - }, - "node_modules/jiti": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.7.0.tgz", - "integrity": "sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==", - "dev": true, - "license": "MIT", - "bin": { - "jiti": "lib/jiti-cli.mjs" - } - }, - "node_modules/js-beautify": { - "version": "1.15.4", - "resolved": "https://registry.npmjs.org/js-beautify/-/js-beautify-1.15.4.tgz", - "integrity": "sha512-9/KXeZUKKJwqCXUdBxFJ3vPh467OCckSBmYDwSK/EtV090K+iMJ7zx2S3HLVDIWFQdqMIsZWbnaGiba18aWhaA==", - "dev": true, - "license": "MIT", - "dependencies": { - "config-chain": "^1.1.13", - "editorconfig": "^1.0.4", - "glob": "^10.4.2", - "js-cookie": "^3.0.5", - "nopt": "^7.2.1" - }, - "bin": { - "css-beautify": "js/bin/css-beautify.js", - "html-beautify": "js/bin/html-beautify.js", - "js-beautify": "js/bin/js-beautify.js" - }, - "engines": { - "node": ">=14" - } - }, - "node_modules/js-cookie": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-3.0.7.tgz", - "integrity": "sha512-z/wZZgDrkNV1eA0ULjM/F9/50Ya8fbzgKneSpoPsXSGd0KnpdtHfOZWK+GcwLk+EZbS4F9RBhU+K2RgzuDaItw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=20" - } - }, - "node_modules/jsdom": { - "version": "29.1.1", - "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-29.1.1.tgz", - "integrity": "sha512-ECi4Fi2f7BdJtUKTflYRTiaMxIB0O6zfR1fX0GXpUrf6flp8QIYn1UT20YQqdSOfk2dfkCwS8LAFoJDEppNK5Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "@asamuzakjp/css-color": "^5.1.11", - "@asamuzakjp/dom-selector": "^7.1.1", - "@bramus/specificity": "^2.4.2", - "@csstools/css-syntax-patches-for-csstree": "^1.1.3", - "@exodus/bytes": "^1.15.0", - "css-tree": "^3.2.1", - "data-urls": "^7.0.0", - "decimal.js": "^10.6.0", - "html-encoding-sniffer": "^6.0.0", - "is-potential-custom-element-name": "^1.0.1", - "lru-cache": "^11.3.5", - "parse5": "^8.0.1", - "saxes": "^6.0.0", - "symbol-tree": "^3.2.4", - "tough-cookie": "^6.0.1", - "undici": "^7.25.0", - "w3c-xmlserializer": "^5.0.0", - "webidl-conversions": "^8.0.1", - "whatwg-mimetype": "^5.0.0", - "whatwg-url": "^16.0.1", - "xml-name-validator": "^5.0.0" - }, - "engines": { - "node": "^20.19.0 || ^22.13.0 || >=24.0.0" - }, - "peerDependencies": { - "canvas": "^3.0.0" - }, - "peerDependenciesMeta": { - "canvas": { - "optional": true - } - } - }, - "node_modules/lightningcss": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz", - "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==", - "dev": true, - "license": "MPL-2.0", - "dependencies": { - "detect-libc": "^2.0.3" - }, - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - }, - "optionalDependencies": { - "lightningcss-android-arm64": "1.32.0", - "lightningcss-darwin-arm64": "1.32.0", - "lightningcss-darwin-x64": "1.32.0", - "lightningcss-freebsd-x64": "1.32.0", - "lightningcss-linux-arm-gnueabihf": "1.32.0", - "lightningcss-linux-arm64-gnu": "1.32.0", - "lightningcss-linux-arm64-musl": "1.32.0", - "lightningcss-linux-x64-gnu": "1.32.0", - "lightningcss-linux-x64-musl": "1.32.0", - "lightningcss-win32-arm64-msvc": "1.32.0", - "lightningcss-win32-x64-msvc": "1.32.0" - } - }, - "node_modules/lightningcss-android-arm64": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz", - "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-darwin-arm64": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz", - "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-darwin-x64": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz", - "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-freebsd-x64": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz", - "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-arm-gnueabihf": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz", - "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-arm64-gnu": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz", - "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "libc": [ - "glibc" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-arm64-musl": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz", - "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==", - "cpu": [ - "arm64" - ], - "dev": true, - "libc": [ - "musl" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-x64-gnu": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz", - "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==", - "cpu": [ - "x64" - ], - "dev": true, - "libc": [ - "glibc" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-x64-musl": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz", - "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==", - "cpu": [ - "x64" - ], - "dev": true, - "libc": [ - "musl" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-win32-arm64-msvc": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz", - "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-win32-x64-msvc": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz", - "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lru-cache": { - "version": "11.5.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.5.0.tgz", - "integrity": "sha512-5YgH9UJd7wVb9hIouI2adWpgqrrICkt070Dnj8EUY1+B4B2P9eRLPAkAAo6NICA7CEhOIeBHl46u9zSNpNu7zA==", - "dev": true, - "license": "BlueOak-1.0.0", - "engines": { - "node": "20 || >=22" - } - }, - "node_modules/magic-string": { - "version": "0.30.21", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", - "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", - "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.5" - } - }, - "node_modules/mdn-data": { - "version": "2.27.1", - "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.27.1.tgz", - "integrity": "sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ==", - "dev": true, - "license": "CC0-1.0" - }, - "node_modules/minimatch": { - "version": "9.0.9", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", - "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^2.0.2" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/minipass": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", - "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", - "dev": true, - "license": "BlueOak-1.0.0", - "engines": { - "node": ">=16 || 14 >=14.17" - } - }, - "node_modules/muggle-string": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/muggle-string/-/muggle-string-0.4.1.tgz", - "integrity": "sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/nanoid": { - "version": "3.3.12", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.12.tgz", - "integrity": "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "bin": { - "nanoid": "bin/nanoid.cjs" - }, - "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" - } - }, - "node_modules/nopt": { - "version": "7.2.1", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-7.2.1.tgz", - "integrity": "sha512-taM24ViiimT/XntxbPyJQzCG+p4EKOpgD3mxFwW38mGjVUrfERQOeY4EDHjdnptttfHuHQXFx+lTP08Q+mLa/w==", - "dev": true, - "license": "ISC", - "dependencies": { - "abbrev": "^2.0.0" - }, - "bin": { - "nopt": "bin/nopt.js" - }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "node_modules/obug": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.1.tgz", - "integrity": "sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==", - "dev": true, - "funding": [ - "https://github.com/sponsors/sxzz", - "https://opencollective.com/debug" - ], - "license": "MIT" - }, - "node_modules/package-json-from-dist": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", - "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", - "dev": true, - "license": "BlueOak-1.0.0" - }, - "node_modules/parse5": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-8.0.1.tgz", - "integrity": "sha512-z1e/HMG90obSGeidlli3hj7cbocou0/wa5HacvI3ASx34PecNjNQeaHNo5WIZpWofN9kgkqV1q5YvXe3F0FoPw==", - "dev": true, - "license": "MIT", - "dependencies": { - "entities": "^8.0.0" - }, - "funding": { - "url": "https://github.com/inikulin/parse5?sponsor=1" - } - }, - "node_modules/parse5/node_modules/entities": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-8.0.0.tgz", - "integrity": "sha512-zwfzJecQ/Uej6tusMqwAqU/6KL2XaB2VZ2Jg54Je6ahNBGNH6Ek6g3jjNCF0fG9EWQKGZNddNjU5F1ZQn/sBnA==", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=20.19.0" - }, - "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" - } - }, - "node_modules/path-browserify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-1.0.1.tgz", - "integrity": "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==", - "dev": true, - "license": "MIT" - }, - "node_modules/path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/path-scurry": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", - "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", - "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "lru-cache": "^10.2.0", - "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" - }, - "engines": { - "node": ">=16 || 14 >=14.18" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/path-scurry/node_modules/lru-cache": { - "version": "10.4.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", - "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/pathe": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", - "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", - "dev": true, - "license": "MIT" - }, - "node_modules/picocolors": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", - "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", - "license": "ISC" - }, - "node_modules/picomatch": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", - "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/postcss": { - "version": "8.5.15", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.15.tgz", - "integrity": "sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/postcss" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "nanoid": "^3.3.12", - "picocolors": "^1.1.1", - "source-map-js": "^1.2.1" - }, - "engines": { - "node": "^10 || ^12 || >=14" - } - }, - "node_modules/proto-list": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz", - "integrity": "sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==", - "dev": true, - "license": "ISC" - }, - "node_modules/punycode": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", - "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/require-from-string": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", - "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/rolldown": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.2.tgz", - "integrity": "sha512-oZx5zVDtVB44AW3eaifgDml1gWRDZGvjcfdxonE4swNPG98PrrXjaO/KrnUjzlMnztCCRVlUueA1kCXhARGk6g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@oxc-project/types": "=0.132.0", - "@rolldown/pluginutils": "^1.0.0" - }, - "bin": { - "rolldown": "bin/cli.mjs" - }, - "engines": { - "node": "^20.19.0 || >=22.12.0" - }, - "optionalDependencies": { - "@rolldown/binding-android-arm64": "1.0.2", - "@rolldown/binding-darwin-arm64": "1.0.2", - "@rolldown/binding-darwin-x64": "1.0.2", - "@rolldown/binding-freebsd-x64": "1.0.2", - "@rolldown/binding-linux-arm-gnueabihf": "1.0.2", - "@rolldown/binding-linux-arm64-gnu": "1.0.2", - "@rolldown/binding-linux-arm64-musl": "1.0.2", - "@rolldown/binding-linux-ppc64-gnu": "1.0.2", - "@rolldown/binding-linux-s390x-gnu": "1.0.2", - "@rolldown/binding-linux-x64-gnu": "1.0.2", - "@rolldown/binding-linux-x64-musl": "1.0.2", - "@rolldown/binding-openharmony-arm64": "1.0.2", - "@rolldown/binding-wasm32-wasi": "1.0.2", - "@rolldown/binding-win32-arm64-msvc": "1.0.2", - "@rolldown/binding-win32-x64-msvc": "1.0.2" - } - }, - "node_modules/saxes": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", - "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==", - "dev": true, - "license": "ISC", - "dependencies": { - "xmlchars": "^2.2.0" - }, - "engines": { - "node": ">=v12.22.7" - } - }, - "node_modules/semver": { - "version": "7.8.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.1.tgz", - "integrity": "sha512-rkVq3IXh+4FDGch+KwzX3aV9W3kO54GyEgpvBzSyctDA6Xtd7RJQV1xmXbeQp5v7+VzLOfVqiutSE6GICgPFvg==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "license": "MIT", - "dependencies": { - "shebang-regex": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/siginfo": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", - "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", - "dev": true, - "license": "ISC" - }, - "node_modules/signal-exit": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", - "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/smol-toml": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/smol-toml/-/smol-toml-1.6.1.tgz", - "integrity": "sha512-dWUG8F5sIIARXih1DTaQAX4SsiTXhInKf1buxdY9DIg4ZYPZK5nGM1VRIYmEbDbsHt7USo99xSLFu5Q1IqTmsg==", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">= 18" - }, - "funding": { - "url": "https://github.com/sponsors/cyyynthia" - } - }, - "node_modules/source-map-js": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", - "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/stackback": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", - "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", - "dev": true, - "license": "MIT" - }, - "node_modules/std-env": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/std-env/-/std-env-4.1.0.tgz", - "integrity": "sha512-Rq7ybcX2RuC55r9oaPVEW7/xu3tj8u4GeBYHBWCychFtzMIr86A7e3PPEBPT37sHStKX3+TiX/Fr/ACmJLVlLQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/string-width": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", - "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", - "dev": true, - "license": "MIT", - "dependencies": { - "eastasianwidth": "^0.2.0", - "emoji-regex": "^9.2.2", - "strip-ansi": "^7.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/string-width-cjs": { - "name": "string-width", - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/string-width-cjs/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/string-width-cjs/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true, - "license": "MIT" - }, - "node_modules/string-width-cjs/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-ansi": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", - "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^6.2.2" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" - } - }, - "node_modules/strip-ansi-cjs": { - "name": "strip-ansi", - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-ansi-cjs/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/symbol-tree": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", - "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", - "dev": true, - "license": "MIT" - }, - "node_modules/tailwindcss": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.3.0.tgz", - "integrity": "sha512-y6nxMGB1nMW9R6k96e5gdIFzcfL/gTJRNaqGes1YvkLnPVXzWgbqFF2yLC0T8G774n24cx3Pe8XrKoniCOAH+Q==", - "dev": true, - "license": "MIT" - }, - "node_modules/tapable": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.3.tgz", - "integrity": "sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - } - }, - "node_modules/tinybench": { - "version": "2.9.0", - "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", - "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", - "dev": true, - "license": "MIT" - }, - "node_modules/tinyexec": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.1.2.tgz", - "integrity": "sha512-dAqSqE/RabpBKI8+h26GfLq6Vb3JVXs30XYQjdMjaj/c2tS8IYYMbIzP599KtRj7c57/wYApb3QjgRgXmrCukA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - } - }, - "node_modules/tinyglobby": { - "version": "0.2.16", - "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.16.tgz", - "integrity": "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==", - "dev": true, - "license": "MIT", - "dependencies": { - "fdir": "^6.5.0", - "picomatch": "^4.0.4" - }, - "engines": { - "node": ">=12.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/SuperchupuDev" - } - }, - "node_modules/tinyrainbow": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.1.0.tgz", - "integrity": "sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/tldts": { - "version": "7.0.30", - "resolved": "https://registry.npmjs.org/tldts/-/tldts-7.0.30.tgz", - "integrity": "sha512-ELrFxuqsDdHUwoh0XxDbxuLD3Wnz49Z57IFvTtvWy1hJdcMZjXLIuonjilCiWHlT2GbE4Wlv1wKVTzDFnXH1aw==", - "dev": true, - "license": "MIT", - "dependencies": { - "tldts-core": "^7.0.30" - }, - "bin": { - "tldts": "bin/cli.js" - } - }, - "node_modules/tldts-core": { - "version": "7.0.30", - "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-7.0.30.tgz", - "integrity": "sha512-uiHN8PIB1VmWyS98eZYja4xzlYqeFZVjb4OuYlJQnZAuJhMw4PbKQOKgHKhBdJR3FE/t5mUQ1Kd80++B+qhD1Q==", - "dev": true, - "license": "MIT" - }, - "node_modules/tough-cookie": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-6.0.1.tgz", - "integrity": "sha512-LktZQb3IeoUWB9lqR5EWTHgW/VTITCXg4D21M+lvybRVdylLrRMnqaIONLVb5mav8vM19m44HIcGq4qASeu2Qw==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "tldts": "^7.0.5" - }, - "engines": { - "node": ">=16" - } - }, - "node_modules/tr46": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-6.0.0.tgz", - "integrity": "sha512-bLVMLPtstlZ4iMQHpFHTR7GAGj2jxi8Dg0s2h2MafAE4uSWF98FC/3MomU51iQAMf8/qDUbKWf5GxuvvVcXEhw==", - "dev": true, - "license": "MIT", - "dependencies": { - "punycode": "^2.3.1" - }, - "engines": { - "node": ">=20" - } - }, - "node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "dev": true, - "license": "0BSD", - "optional": true - }, - "node_modules/typescript": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.3.tgz", - "integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==", - "devOptional": true, - "license": "Apache-2.0", - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=14.17" - } - }, - "node_modules/undici": { - "version": "7.25.0", - "resolved": "https://registry.npmjs.org/undici/-/undici-7.25.0.tgz", - "integrity": "sha512-xXnp4kTyor2Zq+J1FfPI6Eq3ew5h6Vl0F/8d9XU5zZQf1tX9s2Su1/3PiMmUANFULpmksxkClamIZcaUqryHsQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=20.18.1" - } - }, - "node_modules/undici-types": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", - "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", - "dev": true, - "license": "MIT" - }, - "node_modules/vite": { - "version": "8.0.14", - "resolved": "https://registry.npmjs.org/vite/-/vite-8.0.14.tgz", - "integrity": "sha512-s4BJJ+5y1pYL6Otw51FHhVJQhPnuRinKig64g/1+EUNaJsd3gCKdD31IPFvswUgW9/60QT9oFHbZHbQK5imcxw==", - "dev": true, - "license": "MIT", - "dependencies": { - "lightningcss": "^1.32.0", - "picomatch": "^4.0.4", - "postcss": "^8.5.15", - "rolldown": "1.0.2", - "tinyglobby": "^0.2.16" - }, - "bin": { - "vite": "bin/vite.js" - }, - "engines": { - "node": "^20.19.0 || >=22.12.0" - }, - "funding": { - "url": "https://github.com/vitejs/vite?sponsor=1" - }, - "optionalDependencies": { - "fsevents": "~2.3.3" - }, - "peerDependencies": { - "@types/node": "^20.19.0 || >=22.12.0", - "@vitejs/devtools": "^0.1.18", - "esbuild": "^0.27.0 || ^0.28.0", - "jiti": ">=1.21.0", - "less": "^4.0.0", - "sass": "^1.70.0", - "sass-embedded": "^1.70.0", - "stylus": ">=0.54.8", - "sugarss": "^5.0.0", - "terser": "^5.16.0", - "tsx": "^4.8.1", - "yaml": "^2.4.2" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - }, - "@vitejs/devtools": { - "optional": true - }, - "esbuild": { - "optional": true - }, - "jiti": { - "optional": true - }, - "less": { - "optional": true - }, - "sass": { - "optional": true - }, - "sass-embedded": { - "optional": true - }, - "stylus": { - "optional": true - }, - "sugarss": { - "optional": true - }, - "terser": { - "optional": true - }, - "tsx": { - "optional": true - }, - "yaml": { - "optional": true - } - } - }, - "node_modules/vitest": { - "version": "4.1.7", - "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.7.tgz", - "integrity": "sha512-flYyaFd2CgoCoU+0UKt3pxksgC+S02iTDN0n3LtqaMeXsI9SBcdNujc2k0DeFLzUn/0k538yNjOSdwgCqcrwJA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@vitest/expect": "4.1.7", - "@vitest/mocker": "4.1.7", - "@vitest/pretty-format": "4.1.7", - "@vitest/runner": "4.1.7", - "@vitest/snapshot": "4.1.7", - "@vitest/spy": "4.1.7", - "@vitest/utils": "4.1.7", - "es-module-lexer": "^2.0.0", - "expect-type": "^1.3.0", - "magic-string": "^0.30.21", - "obug": "^2.1.1", - "pathe": "^2.0.3", - "picomatch": "^4.0.3", - "std-env": "^4.0.0-rc.1", - "tinybench": "^2.9.0", - "tinyexec": "^1.0.2", - "tinyglobby": "^0.2.15", - "tinyrainbow": "^3.1.0", - "vite": "^6.0.0 || ^7.0.0 || ^8.0.0", - "why-is-node-running": "^2.3.0" - }, - "bin": { - "vitest": "vitest.mjs" - }, - "engines": { - "node": "^20.0.0 || ^22.0.0 || >=24.0.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" - }, - "peerDependencies": { - "@edge-runtime/vm": "*", - "@opentelemetry/api": "^1.9.0", - "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", - "@vitest/browser-playwright": "4.1.7", - "@vitest/browser-preview": "4.1.7", - "@vitest/browser-webdriverio": "4.1.7", - "@vitest/coverage-istanbul": "4.1.7", - "@vitest/coverage-v8": "4.1.7", - "@vitest/ui": "4.1.7", - "happy-dom": "*", - "jsdom": "*", - "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" - }, - "peerDependenciesMeta": { - "@edge-runtime/vm": { - "optional": true - }, - "@opentelemetry/api": { - "optional": true - }, - "@types/node": { - "optional": true - }, - "@vitest/browser-playwright": { - "optional": true - }, - "@vitest/browser-preview": { - "optional": true - }, - "@vitest/browser-webdriverio": { - "optional": true - }, - "@vitest/coverage-istanbul": { - "optional": true - }, - "@vitest/coverage-v8": { - "optional": true - }, - "@vitest/ui": { - "optional": true - }, - "happy-dom": { - "optional": true - }, - "jsdom": { - "optional": true - }, - "vite": { - "optional": false - } - } - }, - "node_modules/vscode-uri": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.1.0.tgz", - "integrity": "sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/vue": { - "version": "3.5.34", - "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.34.tgz", - "integrity": "sha512-WdLBG9gm02OgJIG9axd5Hpx0TFLdzVgfG2evFFu8Rur5O/IoGc5cMjnjh3tPL6GnRGsYvUhBSKVPYVcxRKpMCA==", - "license": "MIT", - "dependencies": { - "@vue/compiler-dom": "3.5.34", - "@vue/compiler-sfc": "3.5.34", - "@vue/runtime-dom": "3.5.34", - "@vue/server-renderer": "3.5.34", - "@vue/shared": "3.5.34" - }, - "peerDependencies": { - "typescript": "*" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/vue-component-type-helpers": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/vue-component-type-helpers/-/vue-component-type-helpers-3.3.1.tgz", - "integrity": "sha512-pu58kqxmVyEH6VfNYW1UyEfR3XAnJ27ZXT3yzXxxpjLxVzAbyC35Zk/nm/RMs7ijWnJNSd9fWkeex2OhUsx3MA==", - "dev": true, - "license": "MIT" - }, - "node_modules/vue-tsc": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/vue-tsc/-/vue-tsc-3.3.1.tgz", - "integrity": "sha512-webBP3jhlxzhELZ2g+11KJ6pg5OVY1xWhWrj7N/yQMi1CrtxJnW+tUACyRVeDK0cQNLP2Va5HNYK8pe+7c+msw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@volar/typescript": "2.4.28", - "@vue/language-core": "3.3.1" - }, - "bin": { - "vue-tsc": "bin/vue-tsc.js" - }, - "peerDependencies": { - "typescript": ">=5.0.0" - } - }, - "node_modules/w3c-xmlserializer": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz", - "integrity": "sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==", - "dev": true, - "license": "MIT", - "dependencies": { - "xml-name-validator": "^5.0.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/webidl-conversions": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-8.0.1.tgz", - "integrity": "sha512-BMhLD/Sw+GbJC21C/UgyaZX41nPt8bUTg+jWyDeg7e7YN4xOM05YPSIXceACnXVtqyEw/LMClUQMtMZ+PGGpqQ==", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=20" - } - }, - "node_modules/whatwg-mimetype": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-5.0.0.tgz", - "integrity": "sha512-sXcNcHOC51uPGF0P/D4NVtrkjSU2fNsm9iog4ZvZJsL3rjoDAzXZhkm2MWt1y+PUdggKAYVoMAIYcs78wJ51Cw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=20" - } - }, - "node_modules/whatwg-url": { - "version": "16.0.1", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-16.0.1.tgz", - "integrity": "sha512-1to4zXBxmXHV3IiSSEInrreIlu02vUOvrhxJJH5vcxYTBDAx51cqZiKdyTxlecdKNSjj8EcxGBxNf6Vg+945gw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@exodus/bytes": "^1.11.0", - "tr46": "^6.0.0", - "webidl-conversions": "^8.0.1" - }, - "engines": { - "node": "^20.19.0 || ^22.12.0 || >=24.0.0" - } - }, - "node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "license": "ISC", - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/why-is-node-running": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", - "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", - "dev": true, - "license": "MIT", - "dependencies": { - "siginfo": "^2.0.0", - "stackback": "0.0.2" - }, - "bin": { - "why-is-node-running": "cli.js" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/wrap-ansi": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", - "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^6.1.0", - "string-width": "^5.0.1", - "strip-ansi": "^7.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/wrap-ansi-cjs": { - "name": "wrap-ansi", - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/wrap-ansi-cjs/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/wrap-ansi-cjs/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true, - "license": "MIT" - }, - "node_modules/wrap-ansi-cjs/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/wrap-ansi-cjs/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/xml-name-validator": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz", - "integrity": "sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=18" - } - }, - "node_modules/xmlchars": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", - "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", - "dev": true, - "license": "MIT" - } - } -} diff --git a/quiz-app2/package.json b/quiz-app2/package.json deleted file mode 100644 index 74bd72d..0000000 --- a/quiz-app2/package.json +++ /dev/null @@ -1,30 +0,0 @@ -{ - "name": "quiz-app2", - "private": true, - "version": "0.0.0", - "type": "module", - "scripts": { - "dev": "vite", - "build": "vue-tsc -b && vite build", - "preview": "vite preview", - "test": "vitest run", - "test:build": "node ./test/build-check.mjs" - }, - "dependencies": { - "vue": "^3.5.34" - }, - "devDependencies": { - "@tailwindcss/vite": "^4.3.0", - "@types/node": "^24.12.3", - "@vitejs/plugin-vue": "^6.0.6", - "@vue/test-utils": "^2.4.10", - "@vue/tsconfig": "^0.9.1", - "jsdom": "^29.1.1", - "smol-toml": "^1.6.1", - "tailwindcss": "^4.3.0", - "typescript": "~6.0.2", - "vite": "^8.0.12", - "vitest": "^4.1.7", - "vue-tsc": "^3.2.8" - } -} diff --git a/quiz-app2/public/favicon.jpg b/quiz-app2/public/favicon.jpg deleted file mode 100644 index 932ce34782bc1e32e02b9b36ff574099b3fa678a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 15945 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 PCI4HS{69JXPN)7aw7LtM diff --git a/quiz-app2/public/icons.svg b/quiz-app2/public/icons.svg deleted file mode 100644 index e952219..0000000 --- a/quiz-app2/public/icons.svg +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/quiz-app2/src/App.vue b/quiz-app2/src/App.vue deleted file mode 100644 index 50316ac..0000000 --- a/quiz-app2/src/App.vue +++ /dev/null @@ -1,1443 +0,0 @@ - - - diff --git a/quiz-app2/src/assets/bluesky-logo.svg b/quiz-app2/src/assets/bluesky-logo.svg deleted file mode 100755 index 199dd9e..0000000 --- a/quiz-app2/src/assets/bluesky-logo.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/quiz-app2/src/assets/go-conference-2026-logo.svg b/quiz-app2/src/assets/go-conference-2026-logo.svg deleted file mode 100644 index eef0823..0000000 --- a/quiz-app2/src/assets/go-conference-2026-logo.svg +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - - - - - - - - - - - - - - diff --git a/quiz-app2/src/assets/x-logo.svg b/quiz-app2/src/assets/x-logo.svg deleted file mode 100755 index 56368dd..0000000 --- a/quiz-app2/src/assets/x-logo.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/quiz-app2/src/components/games/ChoiceSelectGame.vue b/quiz-app2/src/components/games/ChoiceSelectGame.vue deleted file mode 100644 index f9c4922..0000000 --- a/quiz-app2/src/components/games/ChoiceSelectGame.vue +++ /dev/null @@ -1,181 +0,0 @@ - - - diff --git a/quiz-app2/src/components/games/FillBlankTapGame.vue b/quiz-app2/src/components/games/FillBlankTapGame.vue deleted file mode 100644 index 560c549..0000000 --- a/quiz-app2/src/components/games/FillBlankTapGame.vue +++ /dev/null @@ -1,205 +0,0 @@ - - - diff --git a/quiz-app2/src/components/ui/PressButton.vue b/quiz-app2/src/components/ui/PressButton.vue deleted file mode 100644 index 8850f3b..0000000 --- a/quiz-app2/src/components/ui/PressButton.vue +++ /dev/null @@ -1,38 +0,0 @@ - - - diff --git a/quiz-app2/src/components/ui/StageOutputPanel.vue b/quiz-app2/src/components/ui/StageOutputPanel.vue deleted file mode 100644 index 1c5fdce..0000000 --- a/quiz-app2/src/components/ui/StageOutputPanel.vue +++ /dev/null @@ -1,72 +0,0 @@ - - - diff --git a/quiz-app2/src/data/stages.toml b/quiz-app2/src/data/stages.toml deleted file mode 100644 index e58b964..0000000 --- a/quiz-app2/src/data/stages.toml +++ /dev/null @@ -1,243 +0,0 @@ -[[campaignTiers]] -id = "" -title = { ja = "", en = "" } -difficultyLabel = { ja = "", en = "" } -description = { ja = "Go Conference 2026 CodeLab", en = "Go Conference 2026 CodeLab" } -unlocksSpecial = true - -[[campaignTiers.stages]] -id = "fill-builtin-infinite-loop" -kind = "fill" -label = { ja = "builtin", en = "builtin" } -title = { ja = "無限ループの作成", en = "Build an infinite loop" } -prompt = { ja = "Go で無限ループを作るためのキーワードを埋めてください。", en = "Fill in the keyword to create an infinite loop in Go." } -outputLines = """ -""" -why = { ja = "Go には `while` や `loop` といったキーワードは存在せず、繰り返し構文はすべて `for` に統一されています。", en = "Go does not have `while` or `loop` keywords; instead, it uses only `for` for all loops." } -takeaway = { ja = "`for` の条件式を省略して `for {}` と書くだけで、もっともシンプルでスッキリとした無限ループを作成できます。", en = "By omitting the condition from a `for` statement and simply writing `for {}`, you can create an infinite loop more simply and clearly." } -templateLines = """ -[1] { - -} -""" -pool = [ "for", "while", "loop", "(true)", "True:" ] -correctAnswers = [ "for" ] - -[[campaignTiers.stages]] -id = "select-result-error-shortdecl" -kind = "fill" -label = { ja = "error", en = "error" } -title = { ja = "result, err :=", en = "result, err :=" } -prompt = { ja = "calc(a, b) の結果と error を同時に受ける 3 か所。", en = "Three places for receiving the result and error from calc(a, b)." } -outputLines = """ -""" -playgroundUrl = "https://go.dev/play/p/bGgF19hsfbC" -why = { ja = "複数戻り値は左辺を並べ、初回代入なら `:=` を使います。", en = "We can receive multiple return values by listing variables on the left side. We use `:=` for declaration and assignment at once." } -takeaway = { ja = "Go は複数の戻り値を返すことが出来ます。 err を最後に返すのが一般的です。", en = "Functions in Go can return multiple values, and it is common practice to return `err` as the last value." } -templateLines = """ -[1][2][3]calc(a, b) -if err != nil { - fmt.Println(err) -} -fmt.Println(result) -""" -pool = [ "result,", "err", ":=", "=", "panic(err)" ] -correctAnswers = [ "result,", "err", ":=" ] - -[[campaignTiers.stages]] -id = "select-blank-import-pprof" -kind = "fill" -label = { ja = "import", en = "import" } -title = { ja = "pprof をブランク import", en = "pprof blank import" } -prompt = { ja = "pprof を blank import する 2 か所。", en = "Two places for pprof blank import." } -outputLines = """ -""" -playgroundUrl = "https://go.dev/play/p/oIKQdxUcgjq" -why = { ja = "blank import の `_` は名前を使わず、package の `init` だけを有効にします。", en = "A blank import `_` does not use the package name; it only enables the package's `init` function." } -takeaway = { ja = "`import _ \"net/http/pprof\"` は副作用だけを欲しいときの定番です。", en = "`import _ \"net/http/pprof\"` is a standard way to include a package for its side effects only." } -templateLines = """ -import [1] [2] -""" -pool = [ "_", "\"net/http/pprof\"", "\"runtime/pprof\"", "." ] -correctAnswers = [ "_", "\"net/http/pprof\"" ] - -[[campaignTiers.stages]] -id = "select-struct-plusv" -kind = "fill" -label = { ja = "fmt", en = "fmt" } -title = { ja = "構造体の field 名", en = "The field name of a structure" } -prompt = { ja = "{Name:Gopher Age:10} を field 名つきで表示する 2 か所。", en = "Two places for printing {Name:Gopher Age:10} with the names of its fields." } -outputLines = """ -{Name:Gopher Age:10} -""" -playgroundUrl = "https://go.dev/play/p/bUZNxIr7BYy" -why = { ja = "`fmt.Printf` と `%+v` を組み合わせると、struct の field 名も含めて表示できます。", en = "By using `fmt.Printf` and `%+v`, we can print the value of a struct along with its field names." } -takeaway = { ja = "`fmt.Print` や `fmt.Println` ではなく、`fmt.Printf(\"%+v\", gopher)` を使うと field 名つきで表示できます。", en = "`fmt.Printf(\"%+v\", gopher)` prints the value of a struct including its field names, unlike `fmt.Print` or `fmt.Println`." } -templateLines = """ -gopher := Gopher{Name: "Gopher", Age: 10} -[1]([2], gopher) -""" -pool = [ "fmt.Printf", "fmt.Print", "fmt.Println", "\"%v\"", "\"%T\"", "\"%+v\"" ] -correctAnswers = [ "fmt.Printf", "\"%+v\"" ] - -[[campaignTiers.stages]] -id = "fill-fmt-hex-output" -kind = "fill" -label = { ja = "fmt", en = "fmt" } -title = { ja = "10進と16進", en = "Decimal and hexadecimal" } -prompt = { ja = "Value: 255, Hex: ff を出す 2 か所。", en = "Two places for printing Value: 255, Hex: ff." } -outputLines = """ -Value: 255, Hex: ff -""" -playgroundUrl = "https://go.dev/play/p/gKHeYb66kh3" -why = { ja = "`%d` は 10 進、`%x` は小文字の 16 進です。", en = "`%d` prints a value in decimal and `%x` prints it in hexadecimal." } -takeaway = { ja = "同じ数値でも verb を変えるだけで表示形式を切り替えられます。", en = "We can change the printing format for the same value by changing the verb." } -templateLines = """ -num := 255 -fmt.Printf( - "Value: [1], Hex: [2]", - num, - num, -) -""" -pool = [ "%d", "%b", "%o", "%x", "%X" ] -correctAnswers = [ "%d", "%x" ] - -[[campaignTiers.stages]] -id = "fill-fmt-indexed-order" -kind = "fill" -label = { ja = "fmt", en = "fmt" } -title = { ja = "引数番号で並べ替え", en = "Order by argument number" } -prompt = { ja = "Age: 20, Name: Alice を出す 2 か所。", en = "Two places for printing Age: 20, Name: Alice." } -outputLines = """ -Age: 20, Name: Alice -""" -playgroundUrl = "https://go.dev/play/p/qL2BSv7XrwK" -why = { ja = "`%[n]` を使うと、引数の順番を変えずに参照先だけを入れ替えられます。", en = "`%[n]` refers to the n-th argument without changing the order of arguments." } -takeaway = { ja = "`%[2]d` は 2 番目の引数、`%[1]s` は 1 番目の引数を使う指定です。", en = "`%[2]d` refers to the second argument, and `%[1]s` refers to the first argument." } -templateLines = """ -name := "Alice" -age := 20 -fmt.Printf( - "Age: [1], Name: [2]", - name, - age, -) -""" -pool = [ "%s", "%d", "%[1]s", "%[2]d", "%[1]d", "%[2]s" ] -correctAnswers = [ "%[2]d", "%[1]s" ] - -[[campaignTiers.stages]] -id = "fill-fmt-pi-report" -kind = "fill" -label = { ja = "fmt", en = "fmt" } -title = { ja = "fmt.Printf", en = "fmt.Printf" } -prompt = { ja = "Name: \"pi\", Value: 3.14, Type: float64 を出す 3 か所。", en = "Three places for printing Name: \"pi\", Value: 3.14, Type: float64." } -outputLines = """ -Name: "pi", Value: 3.14, Type: float64 -""" -playgroundUrl = "https://go.dev/play/p/ZtSoKQnvkam" -why = { ja = "`%q` は引用符つき文字列、`%.2f` は小数第 2 位まで、`%T` は値の型名を出します。", en = "`%q` prints a string with quotes, `%.2f` prints to two decimal places, and `%T` prints the type of the value." } -takeaway = { ja = "`%q` / `%.2f` / `%T` を使い分けると、文字列・丸めた小数・型名を一度に確認できます。", en = "By using `%q`, `%.2f`, and `%T`, we can see a quoted string, a rounded decimal, and the type of a value all at once." } -templateLines = """ -pi := 3.14159 -name := "pi" -fmt.Printf( - "Name: [1], ", - "Value: [2], ", - "Type: [3]", - name, - pi, - pi, -) -""" -pool = [ "%s", "%q", "%.2f", "%2f", "%f", "%v", "%T", "%t" ] -correctAnswers = [ "%q", "%.2f", "%T" ] - -[[campaignTiers.stages]] -id = "fill-defer-output-order" -kind = "fill" -label = { ja = "defer", en = "defer" } -title = { ja = "defer の実行順序", en = "Execution order of defer" } -prompt = { ja = "コードの実行結果(出力)の順番を上から完成させてください。", en = "Complete the execution order (output) from top to bottom." } -outputLines = """ -[1] -[2] -[3] -""" -playgroundUrl = "https://go.dev/play/p/p9uTA5pzU1G" -why = { ja = "`defer` に登録された関数は、関数が終了する際に「最後に追加されたものから順に(LIFO: Last-In, First-Out)」逆順で実行されます。", en = "`defer` statements are executed in reverse order (LIFO: Last-In, First-Out) when the function returns." } -takeaway = { ja = "通常の文(C)が先に実行され、その後に `defer` が下から上の順(B -> A)で実行されるため、出力は C、B、A の順になります。", en = "Since regular statements (C) execute first, followed by `defer` statements in reverse order (B -> A), the output will be C, B, then A." } -templateLines = """ -defer fmt.Println("A") -defer fmt.Println("B") -fmt.Println("C") -""" -pool = [ "A", "B", "C" ] -correctAnswers = [ "C", "B", "A" ] - -[[campaignTiers.stages]] -id = "fill-channel-recv-only" -kind = "fill" -label = { ja = "channel", en = "channel" } -title = { ja = "受信専用チャネル", en = "Receive-only channel" } -prompt = { ja = "受信専用チャネルから値を受け取って表示する 2 か所。", en = "Two places for printing a value from a receive-only channel." } -outputLines = """ -42 -""" -playgroundUrl = "https://go.dev/play/p/ZrQFACUb0_1" -why = { ja = "型の位置では `<-chan int`、値を読む式の位置では `<-ch` を使います。", en = "`<-chan int` indicates a receive-only channel with type, and `<-ch` receives the value from the channel." } -takeaway = { ja = "`<-chan` は型、`<-ch` は受信式です。", en = "`<-chan` is a type, while `<-ch` is an expression." } -templateLines = """ -func show_ch(ch [1] int) { - println([2]ch) -} - -func main() { - ch := make(chan int, 1) - ch <- 42 - show_ch(ch) -} -""" -pool = [ "<-chan", "<-", "chan<-", "&" ] -correctAnswers = [ "<-chan", "<-" ] - -[[campaignTiers.stages]] -id = "fill-unsafe-sizeof-struct" -kind = "fill" -label = { ja = "unsafe", en = "unsafe" } -title = { ja = "0バイトの構造体", en = "0-byte structure" } -prompt = { ja = "配列全体の占めるバイト数(出力)が 0 になるように型を埋めてください。", en = "Fill in the type so that the total size of the array (output) becomes 0." } -outputLines = """ -0 -""" -playgroundUrl = "https://go.dev/play/p/QZNiYpYMAMt" -why = { ja = "`struct{}`(空の構造体)はサイズが 0 バイトの特殊な型です。そのため、要素数が1億個あっても配列全体のサイズは 0 バイトになります。", en = "`struct{}` (an empty struct) is a special type with a size of 0 bytes. Therefore, even an array with 100 million elements will have a total size of 0 bytes." } -takeaway = { ja = "`unsafe.Sizeof` で確認すると、`struct{}` の配列はメモリを一切消費しないことが分かります。値を持たない「セット(集合)」を実装する際によく使われます。", en = "Using `unsafe.Sizeof`, you can see that an array of `struct{}` consumes no memory at all. It is often used to implement a 'set' (collection) that holds no values." } -templateLines = """ -var arr [100_000_000][1] -fmt.Println(unsafe.Sizeof(arr)) -""" -pool = [ "struct{}", "any", "bool", "byte", "0" ] -correctAnswers = [ "struct{}" ] - -[[campaignTiers.stages]] -id = "fill-unsafe-pointer-conversion" -kind = "fill" -label = { ja = "unsafe", en = "unsafe" } -title = { ja = "ポインタの型変換", en = "Type conversion of a pointer" } -prompt = { ja = "int型のポインタをfloat64型のポインタに変換する 2 か所。", en = "Two places for converting an int pointer to a float64 pointer." } -outputLines = """ -""" -playgroundUrl = "https://go.dev/play/p/oXl7EN0oW5T" -why = { ja = "Goでは異なるポインタ型同士を直接キャストできませんが、`unsafe.Pointer` を経由することで任意のポインタ型に無理やり変換できます。", en = "In Go, you cannot directly cast between different pointer types, but by going through `unsafe.Pointer`, you can convert to any pointer type." } -takeaway = { ja = "安全性を犠牲にして型を変換する際は、`unsafe.Pointer` と目的のポインタ型(`*float64` など)を組み合わせます。", en = "When converting types at the expense of safety, combine `unsafe.Pointer` with the target pointer type (e.g., `*float64`)." } -templateLines = """ -x := 42 -int_ptr := &x -float_ptr := ([1])( - [2](int_ptr), -) -""" -pool = [ "*float64", "&float64", "float64", "unsafe.Pointer", "itof64" ] -correctAnswers = [ "*float64", "unsafe.Pointer" ] diff --git a/quiz-app2/src/data/stages.ts b/quiz-app2/src/data/stages.ts deleted file mode 100644 index 8c8190b..0000000 --- a/quiz-app2/src/data/stages.ts +++ /dev/null @@ -1,304 +0,0 @@ -import type { - CampaignTier, - FillStage, - SelectStage, - Stage, - StageKind, - I18nText, -} from "../types"; -import stagesDocument from "./stages.toml"; - -export const STAGE_TIME_LIMIT_MS = 30_000; -export const PREVIEW_UNLOCK_KEYWORD = "gofar,gotogether"; -export const FOOTER_TAP_THRESHOLD = 10; -export const SPECIAL_PAGE_URL = "https://gocon.jp/2026/"; - -type TomlRecord = Record; - -const stageKinds = ["fill", "select"] as const satisfies readonly StageKind[]; - -const isRecord = (value: unknown): value is TomlRecord => - typeof value === "object" && value !== null && !Array.isArray(value); - -const expectRecord = (value: unknown, path: string): TomlRecord => { - if (!isRecord(value)) { - throw new Error(`${path} must be an object.`); - } - - return value; -}; - -const expectArray = (value: unknown, path: string): unknown[] => { - if (!Array.isArray(value)) { - throw new Error(`${path} must be an array.`); - } - - return value; -}; - -const expectString = (value: unknown, path: string): string => { - if (typeof value !== "string") { - throw new Error(`${path} must be a string.`); - } - - return value; -}; - -const expectI18nText = (value: unknown, path: string): I18nText => { - const record = expectRecord(value, path); - return { - ja: expectString(record.ja, `${path}.ja`), - en: expectString(record.en, `${path}.en`), - }; -}; - -const expectStringArray = (value: unknown, path: string): string[] => - expectArray(value, path).map((item, index) => - expectString(item, `${path}[${index}]`), - ); - -const expectOptionalBoolean = ( - value: unknown, - path: string, -): boolean | undefined => { - if (value === undefined) { - return undefined; - } - - if (typeof value !== "boolean") { - throw new Error(`${path} must be a boolean when present.`); - } - - return value; -}; - -const expectOptionalPlaygroundUrl = ( - value: unknown, - path: string, -): string | undefined => { - if (value === undefined) { - return undefined; - } - - const text = expectString(value, path); - let parsed: URL; - try { - parsed = new URL(text); - } catch { - throw new Error(`${path} must be a valid absolute URL when present.`); - } - - if ( - parsed.protocol !== "https:" || - parsed.hostname !== "go.dev" || - !parsed.pathname.startsWith("/play/") - ) { - throw new Error( - `${path} must be an https://go.dev/play/ URL when present.`, - ); - } - - return parsed.toString(); -}; - -const expectOneOf = ( - value: unknown, - allowed: readonly T[], - path: string, -): T => { - const text = expectString(value, path); - - if (!allowed.includes(text as T)) { - throw new Error(`${path} must be one of: ${allowed.join(", ")}.`); - } - - return text as T; -}; - -const expectUniqueStrings = (values: string[], path: string) => { - const seen = new Set(); - - for (const value of values) { - if (seen.has(value)) { - throw new Error(`${path} contains duplicate value "${value}".`); - } - - seen.add(value); - } -}; - -const parseStageBase = ( - record: TomlRecord, - kind: K, - path: string, -) => { - const outputLinesRaw = expectString( - record.outputLines, - `${path}.outputLines`, - ); - const outputLines = - outputLinesRaw === "" - ? [] - : outputLinesRaw.replace(/\r?\n$/, "").split(/\r?\n/); - - const playgroundUrl = expectOptionalPlaygroundUrl( - record.playgroundUrl, - `${path}.playgroundUrl`, - ); - - return { - id: expectString(record.id, `${path}.id`), - kind, - label: expectI18nText(record.label, `${path}.label`), - title: expectI18nText(record.title, `${path}.title`), - prompt: expectI18nText(record.prompt, `${path}.prompt`), - outputLines, - ...(playgroundUrl === undefined ? {} : { playgroundUrl }), - why: expectI18nText(record.why, `${path}.why`), - takeaway: expectI18nText(record.takeaway, `${path}.takeaway`), - }; -}; - -const validateFillTemplate = (stage: FillStage, path: string) => { - const allLines = [...stage.templateLines, ...stage.outputLines]; - - const placeholderIndexes = allLines.flatMap((line) => - Array.from(line.matchAll(/\[(\d+)\]/g), (match) => Number(match[1])), - ); - - if (placeholderIndexes.length !== stage.correctAnswers.length) { - throw new Error( - `${path} must contain exactly ${stage.correctAnswers.length} placeholders across templateLines and outputLines.`, - ); - } - - const expectedIndexes = Array.from( - { length: stage.correctAnswers.length }, - (_, index) => index + 1, - ); - - for (const [index, value] of placeholderIndexes.entries()) { - if (value !== expectedIndexes[index]) { - throw new Error( - `${path} placeholders must be numbered [1]...[${stage.correctAnswers.length}] in order across templateLines and outputLines.`, - ); - } - } -}; - -const parseFillStage = (record: TomlRecord, path: string): FillStage => { - const templateLinesRaw = expectString( - record.templateLines, - `${path}.templateLines`, - ); - const templateLines = templateLinesRaw.replace(/\r?\n$/, "").split(/\r?\n/); - - const stage = { - ...parseStageBase(record, "fill", path), - templateLines, - pool: expectStringArray(record.pool, `${path}.pool`), - correctAnswers: expectStringArray( - record.correctAnswers, - `${path}.correctAnswers`, - ), - }; - - validateFillTemplate(stage, path); - - for (const answer of stage.correctAnswers) { - if (!stage.pool.includes(answer)) { - throw new Error( - `${path}.correctAnswers contains "${answer}" that is not in ${path}.pool.`, - ); - } - } - - return stage; -}; - -const parseSelectStage = (record: TomlRecord, path: string): SelectStage => { - const stage = { - ...parseStageBase(record, "select", path), - snippetLines: expectStringArray( - record.snippetLines, - `${path}.snippetLines`, - ), - options: expectStringArray(record.options, `${path}.options`), - correctAnswers: expectStringArray( - record.correctAnswers, - `${path}.correctAnswers`, - ), - }; - - expectUniqueStrings(stage.options, `${path}.options`); - expectUniqueStrings(stage.correctAnswers, `${path}.correctAnswers`); - - if (stage.correctAnswers.length === 0) { - throw new Error(`${path}.correctAnswers must contain at least one item.`); - } - - for (const answer of stage.correctAnswers) { - if (!stage.options.includes(answer)) { - throw new Error( - `${path}.correctAnswers contains "${answer}" that is not in ${path}.options.`, - ); - } - } - - return stage; -}; - -const parseStage = (value: unknown, path: string): Stage => { - const record = expectRecord(value, path); - const kind = expectOneOf(record.kind, stageKinds, `${path}.kind`); - - switch (kind) { - case "fill": - return parseFillStage(record, path); - case "select": - return parseSelectStage(record, path); - } -}; - -const parseCampaignTier = (value: unknown, path: string): CampaignTier => { - const record = expectRecord(value, path); - const stages = expectArray(record.stages, `${path}.stages`).map( - (stage, index) => parseStage(stage, `${path}.stages[${index}]`), - ); - const unlocksSpecial = expectOptionalBoolean( - record.unlocksSpecial, - `${path}.unlocksSpecial`, - ); - - return { - id: expectString(record.id, `${path}.id`), - title: expectI18nText(record.title, `${path}.title`), - difficultyLabel: expectI18nText( - record.difficultyLabel, - `${path}.difficultyLabel`, - ), - description: expectI18nText(record.description, `${path}.description`), - ...(unlocksSpecial === undefined ? {} : { unlocksSpecial }), - stages, - }; -}; - -const loadCampaignTiers = (): CampaignTier[] => { - const root = expectRecord(stagesDocument, "src/data/stages.toml"); - const tiers = expectArray( - root.campaignTiers, - "src/data/stages.toml.campaignTiers", - ).map((tier, index) => parseCampaignTier(tier, `campaignTiers[${index}]`)); - const tierIds = tiers.map((tier) => tier.id); - const stageIds = tiers.flatMap((tier) => - tier.stages.map((stage) => stage.id), - ); - - expectUniqueStrings(tierIds, "campaignTiers"); - expectUniqueStrings(stageIds, "campaignTiers[].stages"); - - return tiers; -}; - -export const campaignTiers: CampaignTier[] = loadCampaignTiers(); -export const stages: Stage[] = campaignTiers.flatMap((tier) => tier.stages); diff --git a/quiz-app2/src/main.ts b/quiz-app2/src/main.ts deleted file mode 100644 index 3c9bfeb..0000000 --- a/quiz-app2/src/main.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { createApp } from "vue"; -import "./style.css"; -import App from "./App.vue"; - -createApp(App).mount("#app"); diff --git a/quiz-app2/src/style.css b/quiz-app2/src/style.css deleted file mode 100644 index ddcba56..0000000 --- a/quiz-app2/src/style.css +++ /dev/null @@ -1,1068 +0,0 @@ -@import "tailwindcss"; - -:root { - color-scheme: light; - font-family: - "Noto Sans JP", - ui-sans-serif, - system-ui, - -apple-system, - BlinkMacSystemFont, - "Segoe UI", - sans-serif; - --quiz-black: #18232d; - --quiz-light-gray: #d5e1e8; - --quiz-dark-gray: #314455; - --quiz-yellow: #f4c928; - --quiz-cyan: #55d3c2; - --quiz-blue: #00c1e5; - --quiz-navy: #082a59; - --quiz-text-strong: #082a59; - --quiz-text-body: #18232d; - --quiz-text-muted: #314455; - --quiz-header-height: 81px; - --quiz-white: #ffffff; - --quiz-light-blue: #edfcff; - --quiz-light-cyan: #eefffd; - --quiz-red: #fd7045; - --quiz-shadow: 0 2px 12px rgba(16, 57, 114, 0.06); - --quiz-shadow-soft: 0 1px 0 rgba(16, 57, 114, 0.08); - background: var(--quiz-light-blue); - color: var(--quiz-text-body); - font-synthesis: none; - text-rendering: optimizeLegibility; - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; -} - -@layer base { - * { - @apply box-border; - } - - html, - body, - #app { - @apply min-h-full; - } - - body { - @apply m-0 min-h-[100svh]; - background: linear-gradient( - 180deg, - #f7feff 0%, - var(--quiz-light-blue) 100% - ); - color: var(--quiz-text-body); - } - - button { - @apply touch-manipulation; - } - - a { - color: inherit; - } -} - -@layer utilities { - .text-quiz-strong { - color: var(--quiz-text-strong); - } - - .text-quiz-body { - color: var(--quiz-text-body); - } - - .text-quiz-muted { - color: var(--quiz-text-muted); - } - - .text-quiz-success { - color: #156d61; - } - - .text-quiz-danger { - color: #c94f2b; - } - - .quiz-theme .text-slate-50, - .quiz-theme .text-slate-100, - .quiz-theme .text-slate-200 { - color: var(--quiz-text-strong); - } - - .quiz-theme .text-slate-300 { - color: var(--quiz-text-body); - } - - .quiz-theme .text-slate-400, - .quiz-theme .text-slate-500 { - color: var(--quiz-text-muted); - } - - .quiz-theme .text-emerald-300 { - color: #156d61; - } - - .quiz-theme .text-rose-300 { - color: #c94f2b; - } -} - -@layer components { - .quiz-theme { - color: var(--quiz-text-body); - } - - .quiz-theme .bg-white\/10 { - background-color: rgba(213, 225, 232, 0.85); - } - - .quiz-theme .bg-white\/5 { - background-color: rgba(237, 252, 255, 0.92); - } - - .quiz-theme .bg-white\/60 { - background-color: rgba(16, 57, 114, 0.24); - } - - .quiz-theme .border-white\/10 { - border-color: var(--quiz-light-gray); - } - - .surface-card { - background: var(--quiz-white); - border: 1px solid var(--quiz-light-gray); - border-radius: 16px; - box-shadow: var(--quiz-shadow); - } - - .surface-subpanel { - background: var(--quiz-light-blue); - border: 1px solid var(--quiz-light-gray); - border-radius: 16px; - } - - .info-pill { - display: inline-flex; - align-items: center; - justify-content: center; - border-radius: 16px; - background: var(--quiz-navy); - color: var(--quiz-white); - font-size: 0.75rem; - font-weight: 700; - letter-spacing: 0.05em; - padding: 4px 16px; - } - - .top-page-main { - width: 100%; - margin: 0 auto; - padding: 16px 8px; - } - - .card { - background: var(--quiz-white); - border-radius: 16px; - padding: 16px; - box-shadow: 0 2px 12px rgba(16, 57, 114, 0.06); - } - - .chip { - display: inline-block; - background: var(--quiz-navy); - color: var(--quiz-white); - font-size: 0.75rem; - font-weight: 700; - padding: 4px 16px; - border-radius: 16px; - margin-bottom: 8px; - letter-spacing: 0.05em; - } - - .home-title { - font-family: - "Barlow", - ui-sans-serif, - system-ui, - -apple-system, - BlinkMacSystemFont, - "Segoe UI", - sans-serif; - font-size: 2rem; - line-height: 1.2; - color: var(--quiz-navy); - margin: 0 0 16px; - } - - .home-lead { - color: var(--quiz-text-body); - margin: 0 0 16px; - line-height: 1.75; - } - - .home-actions { - display: flex; - flex-direction: column; - gap: 8px; - margin-top: 16px; - } - - #preview-entry { - margin-top: 16px; - } - - .unlock-note { - margin: 8px 0 0; - color: var(--quiz-text-muted); - font-size: 0.9rem; - } - - .c-button { - appearance: none; - width: 100%; - border: 2px solid transparent; - border-radius: 999px; - padding: 12px 0; - background: var(--quiz-red); - color: var(--quiz-white); - font-family: inherit; - font-size: 1rem; - font-weight: 700; - line-height: 1.75; - cursor: pointer; - display: block; - text-align: center; - transition: - background-color 160ms ease, - color 160ms ease, - border-color 160ms ease, - opacity 160ms ease; - } - - .c-button[data-variant="tertiary"] { - background: var(--quiz-white); - color: var(--quiz-navy); - border-color: var(--quiz-navy); - } - - .c-button:hover { - opacity: 0.88; - } - - .pressable { - @apply inline-flex items-center justify-center gap-2 rounded-full border-2 font-bold transition-all duration-200 ease-out active:translate-y-[2px] disabled:pointer-events-none disabled:translate-y-0 disabled:opacity-45; - } - - .code-surface { - background: var(--quiz-light-blue); - border: 1px solid var(--quiz-light-gray); - border-left: 4px solid var(--quiz-blue); - border-radius: 8px; - padding: 12px 10px 12px 12px; - color: var(--quiz-black); - font-family: - ui-monospace, SFMono-Regular, SFMono-Regular, Menlo, Monaco, - Consolas, "Liberation Mono", monospace; - font-size: 13px; - line-height: 1.7; - } - - .code-slot { - @apply inline-flex min-h-11 min-w-[60px] items-center justify-center px-3 py-2 text-[13px] font-semibold transition-all duration-200; - border: 2px dashed var(--quiz-blue); - border-radius: 12px; - background: var(--quiz-white); - color: var(--quiz-navy); - } - - .code-slot.is-filled { - border-style: solid; - border-color: var(--quiz-cyan); - background: var(--quiz-light-cyan); - color: #1a7a6e; - } - - .timeline-card { - background: var(--quiz-white); - border: 1px solid var(--quiz-light-gray); - border-radius: 16px; - box-shadow: var(--quiz-shadow-soft); - } - - .timeline-card.is-dragging { - border-color: var(--quiz-blue); - background: var(--quiz-light-blue); - box-shadow: 0 8px 24px rgba(0, 193, 229, 0.16); - } - - .timeline-node { - @apply flex h-11 w-11 items-center justify-center text-sm font-semibold; - border-radius: 14px; - background: var(--quiz-navy); - color: var(--quiz-white); - } - - .timeline-rail { - background: linear-gradient( - 180deg, - rgba(0, 193, 229, 0.24), - rgba(85, 211, 194, 0.7), - transparent - ); - } - - .timeline-drag-handle { - display: inline-flex; - align-items: center; - justify-content: center; - min-width: 44px; - min-height: 44px; - border: 2px solid var(--quiz-light-gray); - border-radius: 12px; - background: var(--quiz-light-blue); - color: var(--quiz-navy); - font-size: 1rem; - font-weight: 700; - line-height: 1; - touch-action: none; - cursor: grab; - transition: all 160ms ease; - } - - .timeline-drag-handle:active { - cursor: grabbing; - transform: translateY(2px); - } - - .timeline-drag-handle:disabled { - cursor: default; - opacity: 0.45; - transform: translateY(0); - } - - .word-circle-answer { - display: flex; - flex-direction: column; - gap: 6px; - } - - .word-circle-current { - margin: 0; - font-family: - "Barlow", - ui-sans-serif, - system-ui, - -apple-system, - BlinkMacSystemFont, - "Segoe UI", - sans-serif; - font-size: 1.75rem; - font-weight: 700; - letter-spacing: 0.18em; - color: var(--quiz-navy); - } - - .word-circle-helper { - margin: 0; - font-size: 0.85rem; - color: var(--quiz-text-muted); - } - - .word-circle-board { - position: relative; - width: min(280px, 72vw); - aspect-ratio: 1; - margin: 0 auto; - } - - .word-circle-lines { - position: absolute; - inset: 0; - width: 100%; - height: 100%; - overflow: visible; - } - - .word-circle-ring { - fill: rgba(237, 252, 255, 0.92); - stroke: rgba(0, 193, 229, 0.26); - stroke-width: 1.4; - } - - .word-circle-path { - fill: none; - stroke: var(--quiz-blue); - stroke-width: 3; - stroke-linecap: round; - stroke-linejoin: round; - } - - .word-circle-node { - position: absolute; - transform: translate(-50%, -50%); - display: inline-flex; - align-items: center; - justify-content: center; - width: 56px; - height: 56px; - border: 2px solid var(--quiz-light-gray); - border-radius: 999px; - background: var(--quiz-white); - color: var(--quiz-navy); - font-family: - "Barlow", - ui-sans-serif, - system-ui, - -apple-system, - BlinkMacSystemFont, - "Segoe UI", - sans-serif; - font-size: 1.3rem; - font-weight: 700; - box-shadow: var(--quiz-shadow-soft); - cursor: pointer; - touch-action: none; - transition: - transform 160ms ease, - border-color 160ms ease, - background-color 160ms ease, - color 160ms ease; - } - - .word-circle-node:hover:not(:disabled), - .word-circle-node.is-selected { - border-color: var(--quiz-blue); - background: var(--quiz-light-blue); - color: var(--quiz-navy); - } - - .word-circle-node:active:not(:disabled) { - transform: translate(-50%, -50%) scale(0.96); - } - - .feedback-chip { - @apply inline-flex rounded-full px-3 py-1 text-xs font-semibold uppercase tracking-[0.24em]; - } - - .feedback-chip-success { - background: var(--quiz-light-cyan); - border: 1px solid var(--quiz-cyan); - color: #1a7a6e; - } - - .feedback-chip-danger { - background: #fff4f1; - border: 1px solid var(--quiz-red); - color: var(--quiz-red); - } - - .slot-shell { - background: var(--quiz-white); - border: 2px dashed var(--quiz-light-gray); - border-radius: 16px; - } - - .magnet-piece { - background: var(--quiz-light-cyan); - border: 1px solid var(--quiz-cyan); - border-radius: 16px; - box-shadow: var(--quiz-shadow-soft); - } - - .choice-card { - display: inline-flex; - align-items: flex-start; - justify-content: center; - gap: 0.5rem; - width: 100%; - min-height: 44px; - flex-direction: column; - padding: 12px 16px; - text-align: left; - border-width: 2px; - border-style: solid; - border-radius: 16px; - font-weight: 700; - transition: all 200ms ease-out; - border-color: var(--quiz-light-gray); - background: var(--quiz-white); - color: var(--quiz-black); - box-shadow: var(--quiz-shadow-soft); - } - - .choice-card:active { - transform: translateY(2px); - } - - .choice-card:disabled { - pointer-events: none; - opacity: 0.45; - transform: translateY(0); - } - - .choice-card:hover:not(:disabled) { - border-color: var(--quiz-blue); - background: var(--quiz-light-blue); - } - - .choice-card.is-active { - border-color: var(--quiz-blue); - background: var(--quiz-light-blue); - color: var(--quiz-navy); - } - - .choice-card.is-linked, - .choice-card.is-correct { - border-color: var(--quiz-cyan); - background: var(--quiz-light-cyan); - color: #1a7a6e; - } - - .choice-card.is-wrong { - border-color: var(--quiz-red); - background: #fff4f1; - color: var(--quiz-red); - } - - .choice-card.is-muted { - color: var(--quiz-dark-gray); - opacity: 0.88; - } - - .choice-code { - margin-top: 16px; - overflow-x: auto; - background: var(--quiz-light-blue); - border: 1px solid var(--quiz-light-gray); - border-left: 4px solid var(--quiz-blue); - border-radius: 8px; - padding: 16px; - color: var(--quiz-black); - font-size: 13px; - line-height: 1.6; - } - - .problem-copy { - color: var(--quiz-text-strong); - font-size: 0.96rem; - font-weight: 700; - line-height: 1.75; - } - - .display-title { - font-family: - "Barlow", - ui-sans-serif, - system-ui, - -apple-system, - BlinkMacSystemFont, - "Segoe UI", - sans-serif; - font-weight: 700; - letter-spacing: 0.01em; - color: var(--quiz-navy); - } - - .quiz-shell { - padding: calc( - env(safe-area-inset-top) + var(--quiz-header-height) + 11px - ) - 16px calc(env(safe-area-inset-bottom) + 20px); - } - - .quiz-shell-wide-panels { - padding-inline: 10px; - } - - .header { - position: fixed; - top: 0; - width: 100%; - z-index: 1000; - display: flex; - align-items: center; - justify-content: space-between; - gap: 16px; - padding: 4px 40px; - height: var(--quiz-header-height); - background: var(--quiz-white); - box-shadow: 0 1px 0 var(--quiz-light-gray); - } - - .header-logo { - display: flex; - align-items: center; - gap: 16px; - min-width: 0; - } - - .header-brand { - display: flex; - align-items: center; - gap: 10px; - flex-shrink: 0; - min-width: 0; - } - - .header-conference-logo { - display: block; - width: min(136px, 42vw); - height: auto; - flex-shrink: 0; - } - - .header-product-mark { - font-family: - "Barlow", - ui-sans-serif, - system-ui, - -apple-system, - BlinkMacSystemFont, - "Segoe UI", - sans-serif; - font-size: 1.5rem; - font-weight: 700; - color: var(--quiz-navy); - letter-spacing: 0.02em; - white-space: nowrap; - } - - .header-tagline { - font-size: 0.8rem; - font-weight: 400; - color: var(--quiz-text-muted); - white-space: nowrap; - } - - .header-actions { - display: flex; - align-items: center; - gap: 16px; - margin-left: auto; - flex-shrink: 0; - } - - .language-switch { - position: relative; - display: inline-flex; - align-items: center; - justify-content: center; - } - - .language-toggle { - appearance: none; - display: inline-flex; - align-items: center; - justify-content: center; - width: 44px; - height: 44px; - border: 1px solid var(--quiz-light-gray); - border-radius: 999px; - background: var(--quiz-light-blue); - color: var(--quiz-text-muted); - cursor: pointer; - transition: - background-color 160ms ease, - color 160ms ease, - box-shadow 160ms ease, - border-color 160ms ease; - } - - .language-toggle:hover, - .language-switch.open .language-toggle { - background: var(--quiz-white); - color: var(--quiz-navy); - box-shadow: 0 1px 4px rgba(16, 57, 114, 0.12); - } - - .language-toggle:focus-visible { - outline: 2px solid var(--quiz-blue); - outline-offset: 2px; - } - - .language-toggle-icon { - width: 20px; - height: 20px; - flex-shrink: 0; - } - - .language-menu { - position: absolute; - top: calc(100% + 4px); - right: 0; - display: none; - flex-direction: column; - gap: 4px; - min-width: 148px; - padding: 8px; - border: 1px solid var(--quiz-light-gray); - border-radius: 16px; - background: var(--quiz-white); - box-shadow: 0 14px 32px rgba(16, 57, 114, 0.14); - } - - .language-menu.open { - display: flex; - } - - .language-menu-item { - appearance: none; - width: 100%; - border: none; - border-radius: 12px; - background: transparent; - color: var(--quiz-text-muted); - cursor: pointer; - font-size: 0.9rem; - font-weight: 700; - line-height: 1.4; - padding: 10px 12px; - text-align: left; - transition: - background-color 160ms ease, - color 160ms ease; - } - - .language-menu-item:hover, - .language-menu-item.active { - background: var(--quiz-light-blue); - color: var(--quiz-navy); - } - - .language-menu-item:focus-visible { - outline: 2px solid var(--quiz-blue); - outline-offset: 2px; - } - - .sr-only { - position: absolute; - width: 1px; - height: 1px; - padding: 0; - margin: -1px; - overflow: hidden; - clip: rect(0, 0, 0, 0); - white-space: nowrap; - border: 0; - } - - .progress-panel { - background: linear-gradient( - 135deg, - rgba(237, 252, 255, 0.98), - rgba(238, 255, 253, 0.98) - ); - border: 1px solid rgba(0, 193, 229, 0.16); - border-radius: 20px; - box-shadow: 0 10px 24px rgba(16, 57, 114, 0.08); - } - - .progress-metric { - background: var(--quiz-white); - border: 1px solid var(--quiz-light-gray); - border-radius: 14px; - box-shadow: var(--quiz-shadow-soft); - } - - .progress-fill { - background: linear-gradient(90deg, var(--quiz-blue), var(--quiz-cyan)); - } - - .explanation-inline-code { - display: inline-block; - margin: 0 0.08em; - padding: 0.08em 0.45em; - border: 1px solid rgba(0, 193, 229, 0.22); - border-radius: 8px; - background: rgba(0, 193, 229, 0.12); - color: var(--quiz-navy); - font-family: - ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, - "Liberation Mono", monospace; - font-size: 0.92em; - font-weight: 700; - white-space: break-spaces; - } - - .primary-link-button { - display: inline-flex; - align-items: center; - justify-content: center; - gap: 0.5rem; - width: 100%; - min-height: 48px; - padding: 12px 16px; - border-width: 2px; - border-style: solid; - border-radius: 999px; - font-size: 0.875rem; - font-weight: 700; - transition: all 200ms ease-out; - border-color: transparent; - background: var(--quiz-red); - color: var(--quiz-white); - box-shadow: var(--quiz-shadow-soft); - text-decoration: none; - } - - .primary-link-button:active { - transform: translateY(2px); - } - - .primary-link-button:hover { - opacity: 0.88; - } - - .playground-link-button { - background: var(--quiz-navy); - } - - footer { - background: var(--quiz-light-blue); - border-top: 1px solid var(--quiz-light-gray); - padding: 16px 24px calc(env(safe-area-inset-bottom) + 16px); - } - - .footer-gradient { - width: 100%; - height: 8px; - background: linear-gradient(90deg, var(--quiz-blue), var(--quiz-cyan)); - } - - .footer-info { - width: min(760px, 100%); - margin: 0 auto; - display: flex; - align-items: center; - justify-content: space-between; - gap: 16px; - } - - .logo-container { - display: flex; - flex-direction: column; - align-items: flex-start; - gap: 10px; - color: var(--quiz-navy); - font-family: - "Barlow", - ui-sans-serif, - system-ui, - -apple-system, - BlinkMacSystemFont, - "Segoe UI", - sans-serif; - font-size: 1rem; - font-weight: 700; - letter-spacing: 0.02em; - flex-shrink: 0; - } - - .footer-logo { - display: block; - width: min(196px, 100%); - height: auto; - } - - .copyright { - margin: 0; - font-size: 0.875rem; - color: var(--quiz-text-body); - text-align: right; - white-space: nowrap; - } - - .copyright a { - color: var(--quiz-blue); - font-weight: 700; - text-decoration: none; - } - - .copyright a:hover { - text-decoration: underline; - } - - .quiz-input { - width: 100%; - min-height: 48px; - padding: 12px 16px; - border: 2px solid var(--quiz-light-gray); - border-radius: 14px; - background: var(--quiz-white); - color: var(--quiz-black); - font: inherit; - line-height: 1.4; - appearance: none; - transition: - border-color 160ms ease, - box-shadow 160ms ease; - } - - .quiz-input::placeholder { - color: rgba(24, 35, 45, 0.72); - } - - .quiz-input:focus { - outline: none; - border-color: var(--quiz-blue); - box-shadow: 0 0 0 4px rgba(0, 193, 229, 0.12); - } - - .app-modal-card { - background: var(--quiz-white); - border: 1px solid var(--quiz-light-gray); - box-shadow: 0 14px 32px rgba(16, 57, 114, 0.14); - } -} - -@media (max-width: 768px) { - :root { - --quiz-header-height: 56px; - } - - .quiz-shell { - padding-top: calc( - env(safe-area-inset-top) + var(--quiz-header-height) + 12px - ); - } - - .quiz-shell-wide-panels { - padding-inline: 8px; - } - - .header { - padding: 4px 16px; - } - - .header-conference-logo { - width: 112px; - } - - .header-product-mark { - font-size: 1.25rem; - } - - .header-actions { - gap: 8px; - } - - .header-tagline { - display: none; - } - - .home-title { - font-size: 1.6rem; - } - - .word-circle-board { - width: min(240px, 74vw); - } - - .word-circle-node { - width: 52px; - height: 52px; - font-size: 1.2rem; - } - - .language-toggle { - width: 40px; - height: 40px; - } - - .language-menu { - min-width: 136px; - } - - footer { - padding: 16px 16px calc(env(safe-area-inset-bottom) + 16px); - } - - .footer-info { - flex-direction: column; - align-items: center; - text-align: center; - } - - .logo-container { - align-items: center; - text-align: center; - } - - .copyright { - text-align: center; - white-space: normal; - } -} - -.stage-enter-active, -.stage-leave-active, -.drawer-enter-active, -.drawer-leave-active, -.snap-enter-active, -.snap-leave-active { - transition: all 240ms ease; -} - -.stage-enter-from, -.stage-leave-to { - opacity: 0; - transform: translateY(12px) scale(0.99); -} - -.drawer-enter-from, -.drawer-leave-to { - opacity: 0; - transform: translateY(18px); -} - -.snap-enter-from, -.snap-leave-to { - opacity: 0; - transform: scale(0.92); -} - -.reorder-move { - transition: transform 240ms ease; -} - -@keyframes shake { - 0%, - 100% { - transform: translateX(0); - } - 20% { - transform: translateX(-4px); - } - 40% { - transform: translateX(4px); - } - 60% { - transform: translateX(-3px); - } - 80% { - transform: translateX(3px); - } -} - -@keyframes pop { - 0% { - transform: scale(0.96); - } - 70% { - transform: scale(1.02); - } - 100% { - transform: scale(1); - } -} - -.option-shake { - animation: shake 320ms ease; -} - -.pop-in { - animation: pop 240ms ease; -} diff --git a/quiz-app2/src/types.ts b/quiz-app2/src/types.ts deleted file mode 100644 index a9bda30..0000000 --- a/quiz-app2/src/types.ts +++ /dev/null @@ -1,50 +0,0 @@ -export type Locale = "ja" | "en"; - -export interface I18nText { - ja: string; - en: string; -} - -export type StageKind = "fill" | "select"; - -export interface StageBase { - id: string; - kind: StageKind; - label: I18nText; - title: I18nText; - prompt: I18nText; - outputLines: string[]; - playgroundUrl?: string; - why: I18nText; - takeaway: I18nText; -} - -export interface FillStage extends StageBase { - kind: "fill"; - templateLines: string[]; - pool: string[]; - correctAnswers: string[]; -} - -export interface SelectStage extends StageBase { - kind: "select"; - snippetLines: string[]; - options: string[]; - correctAnswers: string[]; -} - -export type Stage = FillStage | SelectStage; - -export interface CampaignTier { - id: string; - title: I18nText; - difficultyLabel: I18nText; - description: I18nText; - unlocksSpecial?: boolean; - stages: Stage[]; -} - -export interface StageSubmission { - correct: boolean; - selectionSummary?: string; -} diff --git a/quiz-app2/src/vite-env.d.ts b/quiz-app2/src/vite-env.d.ts deleted file mode 100644 index 0df6bc0..0000000 --- a/quiz-app2/src/vite-env.d.ts +++ /dev/null @@ -1,6 +0,0 @@ -/// - -declare module "*.toml" { - const value: unknown; - export default value; -} diff --git a/quiz-app2/test/app.ui.test.ts b/quiz-app2/test/app.ui.test.ts deleted file mode 100644 index 39c4300..0000000 --- a/quiz-app2/test/app.ui.test.ts +++ /dev/null @@ -1,227 +0,0 @@ -import { flushPromises, mount } from "@vue/test-utils"; -import { afterEach, describe, expect, it, vi } from "vitest"; -import { nextTick } from "vue"; -import App from "../src/App.vue"; -import { - STAGE_TIME_LIMIT_MS, - stages, - campaignTiers, - PREVIEW_UNLOCK_KEYWORD, -} from "../src/data/stages"; - -const settle = async () => { - await nextTick(); - await flushPromises(); - await nextTick(); -}; - -const normalizeText = (value: string) => value.replace(/\s+/g, " ").trim(); - -const findEnabledButton = ( - wrapper: ReturnType, - label: string, -) => { - const button = wrapper - .findAll("button") - .find( - (candidate) => - candidate.attributes("disabled") === undefined && - normalizeText(candidate.text()) === label, - ); - - expect(button, `missing button: ${label}`).toBeTruthy(); - return button!; -}; - -const hasEnabledButton = (wrapper: ReturnType, label: string) => - wrapper - .findAll("button") - .some( - (candidate) => - candidate.attributes("disabled") === undefined && - normalizeText(candidate.text()) === label, - ); - -const clickButton = async ( - wrapper: ReturnType, - label: string, -) => { - await findEnabledButton(wrapper, label).trigger("click"); - await settle(); -}; - -const findButton = (wrapper: ReturnType, label: string) => { - const button = wrapper - .findAll("button") - .find((candidate) => normalizeText(candidate.text()) === label); - - expect(button, `missing button: ${label}`).toBeTruthy(); - return button!; -}; - -const unlockPreview = async (wrapper: ReturnType) => { - const footer = wrapper.find("footer"); - expect(footer.exists()).toBe(true); - - for (let i = 0; i < 10; i += 1) { - await footer.trigger("click"); - } - await settle(); - - const keywordInput = wrapper.find('input[placeholder="合言葉を入力"]'); - expect(keywordInput.exists()).toBe(true); - // ハードコードされていたキーワードをデータから参照 - await keywordInput.setValue(PREVIEW_UNLOCK_KEYWORD); - await settle(); - await clickButton(wrapper, "開く"); -}; - -describe("quiz-app2 campaign flow", () => { - afterEach(() => { - vi.clearAllTimers(); - vi.useRealTimers(); - }); - - it("keeps preview gated while a perfect Tier 1 clear unlocks the special page", async () => { - vi.useFakeTimers(); - - const wrapper = mount(App); - await settle(); - const shell = () => wrapper.find("[data-screen]"); - - expect(shell().attributes("data-screen")).toBe("home"); - expect(hasEnabledButton(wrapper, "問題一覧を見る")).toBe(false); - - await clickButton(wrapper, "クイズを始める"); - expect(shell().attributes("data-screen")).toBe("question"); - expect(findButton(wrapper, "リセット").classes()).toContain("bg-white"); - - for (let i = 0; i < stages.length; i++) { - const stage = stages[i]; - if (!stage) continue; - - if (i === 0) { - await clickButton(wrapper, stage.correctAnswers[0]!); - await clickButton(wrapper, "リセット"); - } - - for (const answer of stage.correctAnswers) { - await clickButton(wrapper, answer); - } - await clickButton(wrapper, "回答する"); - expect(shell().attributes("data-screen")).toBe("result"); - expect(wrapper.text()).toContain("◯ 正解"); - - if (i === stages.length - 1) { - await clickButton(wrapper, "結果を見る"); - } else { - await clickButton(wrapper, "つぎへ"); - } - } - - expect(shell().attributes("data-screen")).toBe("score"); - expect(wrapper.text()).toContain(`${stages.length}/${stages.length}`); - expect(wrapper.text()).toContain("おみやげをプレゼント"); - - // Tierが1つだけの場合はボタン名が「もう一度挑戦する」に変化する対応 - const isSingleTier = campaignTiers.length === 1; - const retryButtonLabel = isSingleTier - ? "もう一度挑戦する" - : "同じレベルでもう一度"; - await clickButton(wrapper, retryButtonLabel); - expect(shell().attributes("data-screen")).toBe("question"); - }); - - it("unlocks the preview list only through the hidden keyword flow", async () => { - const wrapper = mount(App); - await settle(); - const shell = () => wrapper.find("[data-screen]"); - - expect(hasEnabledButton(wrapper, "問題一覧を見る")).toBe(false); - await unlockPreview(wrapper); - expect(hasEnabledButton(wrapper, "問題一覧を見る")).toBe(true); - - await clickButton(wrapper, "問題一覧を見る"); - expect(shell().attributes("data-screen")).toBe("preview"); - - if (stages[0]) { - // prompt が多言語オブジェクト化されたため .ja を参照する - expect(wrapper.text()).toContain(stages[0].prompt.ja); - } - - const closeBtn = wrapper - .findAll("button") - .find((c) => c.text().includes("閉じる")); - if (closeBtn) { - await closeBtn.trigger("click"); - await settle(); - } - expect(shell().attributes("data-screen")).toBe("home"); - }); - - it("shows the retry headline without the tier subtitle on an imperfect clear", async () => { - const wrapper = mount(App); - await settle(); - const shell = () => wrapper.find("[data-screen]"); - - await clickButton(wrapper, "クイズを始める"); - - const firstStage = stages[0]; - if (firstStage) { - const wrongTokens = firstStage.pool.filter( - (token) => !firstStage.correctAnswers.includes(token), - ); - let tokensToClick = [...wrongTokens]; - - while (tokensToClick.length < firstStage.correctAnswers.length) { - tokensToClick.push(firstStage.pool[0]!); - } - tokensToClick = tokensToClick.slice(0, firstStage.correctAnswers.length); - - for (const token of tokensToClick) { - await clickButton(wrapper, token); - } - } - - await clickButton(wrapper, "回答する"); - expect(wrapper.text()).toContain("× 不正解"); - - for (let i = 0; i < stages.length; i++) { - if (i === 0) { - await clickButton(wrapper, "つぎへ"); - continue; - } - - const stage = stages[i]; - if (!stage) continue; - - for (const answer of stage.correctAnswers) { - await clickButton(wrapper, answer); - } - await clickButton(wrapper, "回答する"); - - if (i === stages.length - 1) { - await clickButton(wrapper, "結果を見る"); - } else { - await clickButton(wrapper, "つぎへ"); - } - } - - expect(shell().attributes("data-screen")).toBe("score"); - - const isSingleTier = campaignTiers.length === 1; - if (isSingleTier) { - expect(wrapper.text()).toContain("再挑戦"); - } else { - // title が多言語オブジェクト化されたため .ja を参照する - const tierTitle = campaignTiers[0]?.title.ja || ""; - const compactTitle = tierTitle.split(" / ")[0]?.trim() || ""; - - expect(wrapper.text()).toContain(`${compactTitle} を再挑戦`); - - if (tierTitle.includes("/")) { - expect(wrapper.text()).not.toContain(`${tierTitle} を再挑戦`); - } - } - }); -}); diff --git a/quiz-app2/test/build-check.mjs b/quiz-app2/test/build-check.mjs deleted file mode 100644 index b7bd3de..0000000 --- a/quiz-app2/test/build-check.mjs +++ /dev/null @@ -1,54 +0,0 @@ -import assert from "node:assert/strict"; -import { spawnSync } from "node:child_process"; -import { existsSync, readFileSync, rmSync } from "node:fs"; -import { join } from "node:path"; -import { fileURLToPath } from "node:url"; - -const rootDir = fileURLToPath(new URL("..", import.meta.url)); -const npmCommand = process.platform === "win32" ? "npm.cmd" : "npm"; -const pagesRepository = "GoCon/2026-codelab"; -const pagesBasePath = `/${pagesRepository.split("/")[1]}/`; -const pagesOutputDir = "dist-pages-check"; - -function run(args, env = {}) { - const result = spawnSync(npmCommand, args, { - cwd: rootDir, - env: { ...process.env, ...env }, - stdio: "inherit", - }); - - if (result.status !== 0) { - throw new Error(`Command failed: ${npmCommand} ${args.join(" ")}`); - } -} - -function readHtml(directory) { - const indexPath = join(rootDir, directory, "index.html"); - assert.ok(existsSync(indexPath), `Missing build output: ${indexPath}`); - return readFileSync(indexPath, "utf8"); -} - -rmSync(join(rootDir, pagesOutputDir), { force: true, recursive: true }); - -try { - run(["run", "build"]); - - const localHtml = readHtml("dist"); - assert.match(localHtml, /href="\/favicon\.jpg"/); - assert.match(localHtml, /src="\/assets\//); - assert.match(localHtml, /href="\/assets\//); - assert.doesNotMatch(localHtml, /\/2026-codelab\//); - - run(["exec", "--", "vite", "build", "--outDir", pagesOutputDir], { - VITE_BASE_PATH: pagesBasePath, - }); - - const pagesHtml = readHtml(pagesOutputDir); - assert.match(pagesHtml, new RegExp(`href="${pagesBasePath}favicon\\.jpg"`)); - assert.match(pagesHtml, new RegExp(`src="${pagesBasePath}assets/`)); - assert.match(pagesHtml, new RegExp(`href="${pagesBasePath}assets/`)); - - console.log("Build checks passed."); -} finally { - rmSync(join(rootDir, pagesOutputDir), { force: true, recursive: true }); -} diff --git a/quiz-app2/test/game-edge-cases.test.ts b/quiz-app2/test/game-edge-cases.test.ts deleted file mode 100644 index 3494193..0000000 --- a/quiz-app2/test/game-edge-cases.test.ts +++ /dev/null @@ -1,286 +0,0 @@ -import { flushPromises, mount } from "@vue/test-utils"; -import { afterEach, describe, expect, it, vi } from "vitest"; -import { nextTick } from "vue"; -import App from "../src/App.vue"; -import ChoiceSelectGame from "../src/components/games/ChoiceSelectGame.vue"; -import FillBlankTapGame from "../src/components/games/FillBlankTapGame.vue"; -import { STAGE_TIME_LIMIT_MS, campaignTiers, stages } from "../src/data/stages"; -import type { FillStage, SelectStage } from "../src/types"; - -const settle = async () => { - await nextTick(); - await flushPromises(); - await nextTick(); -}; - -const findButtonContaining = ( - wrapper: ReturnType, - snippet: string, -) => { - const button = wrapper - .findAll("button") - .find((candidate) => candidate.text().includes(snippet)); - - expect(button, `missing button containing: ${snippet}`).toBeTruthy(); - return button!; -}; - -const clickExactButton = async ( - wrapper: ReturnType, - label: string, -) => { - const button = wrapper - .findAll("button") - .find( - (candidate) => candidate.text().replace(/\s+/g, " ").trim() === label, - ); - - expect(button, `missing button: ${label}`).toBeTruthy(); - await button!.trigger("click"); - await settle(); -}; - -describe("quiz-app2 edge cases", () => { - afterEach(() => { - vi.restoreAllMocks(); - vi.clearAllTimers(); - vi.useRealTimers(); - }); - - it("shows timeout feedback and still lets the user move to the next stage", async () => { - vi.useFakeTimers(); - vi.spyOn(Math, "random").mockReturnValue(0); - - let now = 0; - vi.spyOn(performance, "now").mockImplementation(() => now); - - const wrapper = mount(App); - await settle(); - await clickExactButton(wrapper, "クイズを始める"); - - now = STAGE_TIME_LIMIT_MS + 1; - vi.advanceTimersByTime(100); - await settle(); - - // 多言語対応済みの文字列で検証 - expect(wrapper.text()).toContain("タイムアップ"); - - await findButtonContaining(wrapper, "つぎへ").trigger("click"); - await settle(); - - const nextStage = stages[1]; - if (nextStage) { - expect(wrapper.text()).toContain(nextStage.correctAnswers[0]); - } - wrapper.unmount(); - }); - - it("keeps only one tier and all stages use the fill format", () => { - expect(campaignTiers).toHaveLength(1); - - const expectedLength = stages.length; - expect(campaignTiers[0]?.stages).toHaveLength(expectedLength); - expect(stages).toHaveLength(expectedLength); - - const printfStage = stages.find( - (stage) => stage.id === "fill-fmt-pi-report", - ); - if (printfStage && printfStage.kind === "fill") { - expect(printfStage.playgroundUrl).toMatch(/^https:\/\/go\.dev\/play\//); - } - - const structStage = stages.find( - (stage) => stage.id === "select-struct-plusv", - ); - if (structStage && structStage.kind === "fill") { - expect(structStage.playgroundUrl).toMatch(/^https:\/\/go\.dev\/play\//); - expect(structStage.correctAnswers).toEqual(["fmt.Printf", '"%+v"']); - } - }); - - it("shows a random praise line when the answer is correct", async () => { - vi.spyOn(Math, "random").mockReturnValue(0); - - const wrapper = mount(App); - await settle(); - await clickExactButton(wrapper, "クイズを始める"); - - const stage = stages[0]; - if (!stage) return; - - for (const answer of stage.correctAnswers) { - await clickExactButton(wrapper, answer); - } - await clickExactButton(wrapper, "回答する"); - - expect(wrapper.text()).toContain("正解"); - expect(wrapper.text()).toContain("その調子!"); - wrapper.unmount(); - }); - - it("shows a random miss line when the answer is incorrect", async () => { - vi.spyOn(Math, "random").mockReturnValue(0); - - const wrapper = mount(App); - await settle(); - await clickExactButton(wrapper, "クイズを始める"); - - const stage = stages[0]; - if (!stage) return; - - if (stage.kind === "fill") { - const wrongTokens = stage.pool.filter( - (t) => !stage.correctAnswers.includes(t), - ); - let tokensToClick = [...wrongTokens]; - while (tokensToClick.length < stage.correctAnswers.length) { - tokensToClick.push(stage.pool[0]!); - } - for (let i = 0; i < stage.correctAnswers.length; i++) { - await clickExactButton(wrapper, tokensToClick[i]!); - } - } else if (stage.kind === "select") { - const wrongOptions = stage.options.filter( - (o) => !stage.correctAnswers.includes(o), - ); - let optionsToClick = [...wrongOptions]; - while (optionsToClick.length < stage.correctAnswers.length) { - optionsToClick.push(stage.options[0]!); - } - for (let i = 0; i < stage.correctAnswers.length; i++) { - await clickExactButton(wrapper, optionsToClick[i]!); - } - } - - await clickExactButton(wrapper, "回答する"); - - expect(wrapper.text()).toContain("不正解"); - expect(wrapper.text()).toContain("おっと、違うよ!"); - wrapper.unmount(); - }); - - it("allows duplicate fill tokens to be used in separate slots", async () => { - const duplicateStage: FillStage = { - id: "fill-duplicate-recv", - kind: "fill", - label: { ja: "channel", en: "channel" }, - title: { ja: "重複トークン", en: "Duplicate Tokens" }, - prompt: { - ja: "同じ token を 2 回使う。", - en: "Use the same token twice.", - }, - outputLines: ["println(<-ch, <-ch)"], - why: { - ja: "受信演算子は複数回出てきても別 token として扱います。", - en: "Each token is separate.", - }, - takeaway: { - ja: "pool に同じ token が複数あっても順に選べます。", - en: "Select in order.", - }, - templateLines: ["println([1]ch, [2]ch)"], - pool: ["<-", "<-", "&"], - correctAnswers: ["<-", "<-"], - }; - - const wrapper = mount(FillBlankTapGame, { - props: { - stage: duplicateStage, - locked: false, - resetSignal: 0, - submitSignal: 0, - }, - }); - await settle(); - - expect(wrapper.emitted("ready-change")?.[0]).toEqual([false]); - expect(wrapper.find(".stage-output-panel").text()).toContain( - "println(<-ch, <-ch)", - ); - - const arrowButtons = () => - wrapper - .findAll("button") - .filter( - (candidate) => - candidate.text().trim() === "<-" && - candidate.classes().includes("pressable"), - ); - - expect(arrowButtons()).toHaveLength(2); - await arrowButtons()[0]!.trigger("click"); - await settle(); - await arrowButtons()[1]!.trigger("click"); - await settle(); - - expect(wrapper.emitted("ready-change")?.at(-1)).toEqual([true]); - - await wrapper.setProps({ submitSignal: 1 }); - await settle(); - - expect(wrapper.emitted("submit")).toEqual([ - [{ correct: true, selectionSummary: "<- <-" }], - ]); - wrapper.unmount(); - }); - - it("lets the multi-select format toggle choices and reports a wrong submit", async () => { - const selectStage: SelectStage = { - id: "select-import-edge", - kind: "select", - label: { ja: "import", en: "import" }, - title: { ja: "blank import", en: "blank import" }, - prompt: { ja: "必要な 3 つを選ぶ。", en: "Choose 3 options." }, - outputLines: ['import _ "net/http/pprof"'], - why: { - ja: "副作用だけ欲しいときは blank import を使います。", - en: "Use blank import for side effects.", - }, - takeaway: { - ja: "import / _ / package path を揃えます。", - en: "Align imports.", - }, - snippetLines: ["?, ?, ?"], - options: ["import", "_", '"net/http/pprof"', '"runtime/pprof"'], - correctAnswers: ["import", "_", '"net/http/pprof"'], - }; - - const wrapper = mount(ChoiceSelectGame, { - props: { - stage: selectStage, - locked: false, - resetSignal: 0, - submitSignal: 0, - }, - }); - await settle(); - - expect(wrapper.emitted("ready-change")?.[0]).toEqual([false]); - expect(wrapper.text()).toContain("コード表示エリア"); - expect(wrapper.find(".stage-output-panel").text()).toContain( - 'import _ "net/http/pprof"', - ); - - await findButtonContaining(wrapper, "import").trigger("click"); - await settle(); - await findButtonContaining(wrapper, "_").trigger("click"); - await settle(); - await findButtonContaining(wrapper, '"runtime/pprof"').trigger("click"); - await settle(); - - expect(wrapper.emitted("ready-change")?.at(-1)).toEqual([true]); - - await wrapper.setProps({ submitSignal: 1 }); - await settle(); - - expect(wrapper.emitted("submit")).toEqual([ - [{ correct: false, selectionSummary: expect.stringContaining("import") }], - ]); - - await findButtonContaining(wrapper, '"runtime/pprof"').trigger("click"); - await settle(); - expect(wrapper.emitted("ready-change")?.at(-1)).toEqual([false]); - - wrapper.unmount(); - }); -}); diff --git a/quiz-app2/test/layout.ui.test.ts b/quiz-app2/test/layout.ui.test.ts deleted file mode 100644 index 6c8256d..0000000 --- a/quiz-app2/test/layout.ui.test.ts +++ /dev/null @@ -1,99 +0,0 @@ -import { flushPromises, mount } from "@vue/test-utils"; -import { describe, expect, it } from "vitest"; -import { nextTick } from "vue"; -import App from "../src/App.vue"; -import { PREVIEW_UNLOCK_KEYWORD } from "../src/data/stages"; - -const settle = async () => { - await nextTick(); - await flushPromises(); - await nextTick(); -}; - -const setViewport = (width: number) => { - Object.defineProperty(window, "innerWidth", { - configurable: true, - writable: true, - value: width, - }); - window.dispatchEvent(new Event("resize")); -}; - -const clickExactButton = async ( - wrapper: ReturnType, - label: string, -) => { - const button = wrapper - .findAll("button") - .find( - (candidate) => candidate.text().replace(/\s+/g, " ").trim() === label, - ); - - expect(button, `missing button: ${label}`).toBeTruthy(); - await button!.trigger("click"); - await settle(); -}; - -const unlockPreview = async (wrapper: ReturnType) => { - const footer = wrapper.find("footer"); - for (let i = 0; i < 10; i += 1) { - await footer.trigger("click"); - } - await settle(); - - const keywordInput = wrapper.find('input[placeholder="合言葉を入力"]'); - expect(keywordInput.exists()).toBe(true); - // ハードコードされていたキーワードをデータから参照 - await keywordInput.setValue(PREVIEW_UNLOCK_KEYWORD); - await settle(); - await clickExactButton(wrapper, "開く"); -}; - -describe("quiz-app2 layout shell", () => { - it("keeps header and footer intact on smartphone screens", async () => { - setViewport(390); - - const wrapper = mount(App); - await settle(); - const shell = wrapper.find("[data-screen]"); - - expect(shell.exists()).toBe(true); - expect(shell.classes()).toContain("mx-auto"); - expect(shell.classes()).toContain("max-w-[760px]"); - expect(wrapper.find("header.header").exists()).toBe(true); - expect(wrapper.find("#language-switch").exists()).toBe(true); - expect(wrapper.find("#footer-copyright").exists()).toBe(true); - expect(wrapper.find("#home-card").exists()).toBe(true); - expect(wrapper.find("#home-chip").text()).toBe("WELCOME"); - - await clickExactButton(wrapper, "クイズを始める"); - - expect(shell.attributes("data-screen")).toBe("question"); - expect(shell.classes()).toContain("max-w-[448px]"); - expect(shell.classes()).toContain("quiz-shell-wide-panels"); - expect(wrapper.find("header.header").text()).toContain("CodeLab"); - expect(wrapper.find("#header-tagline").text()).toBe( - "Go の知識を試してみよう!", - ); - expect(wrapper.find("footer").text()).toContain("Go Conference 2026"); - expect(wrapper.find("footer").text()).toContain("Renée French"); - }); - - it("keeps the same bounded shell on desktop screens and preview view", async () => { - setViewport(1280); - - const wrapper = mount(App); - await settle(); - const shell = wrapper.find("[data-screen]"); - await unlockPreview(wrapper); - await clickExactButton(wrapper, "問題一覧を見る"); - - expect(shell.attributes("data-screen")).toBe("preview"); - expect(shell.classes()).toContain("max-w-[430px]"); - expect(shell.classes()).toContain("mx-auto"); - expect(wrapper.find("header.header").exists()).toBe(true); - expect(wrapper.find("#language-switch").exists()).toBe(true); - expect(wrapper.find("#footer-copyright").exists()).toBe(true); - expect(wrapper.find("header.header").text()).toContain("CodeLab"); - }); -}); diff --git a/quiz-app2/tsconfig.app.json b/quiz-app2/tsconfig.app.json deleted file mode 100644 index 32d8ab4..0000000 --- a/quiz-app2/tsconfig.app.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "extends": "@vue/tsconfig/tsconfig.dom.json", - "compilerOptions": { - "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", - "types": ["vite/client"], - - /* Linting */ - "noUnusedLocals": true, - "noUnusedParameters": true, - "erasableSyntaxOnly": true, - "noFallthroughCasesInSwitch": true, - }, - "include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.vue"], -} diff --git a/quiz-app2/tsconfig.json b/quiz-app2/tsconfig.json deleted file mode 100644 index b1ec43d..0000000 --- a/quiz-app2/tsconfig.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "files": [], - "references": [{ "path": "./tsconfig.app.json" }, { "path": "./tsconfig.node.json" }], -} diff --git a/quiz-app2/tsconfig.node.json b/quiz-app2/tsconfig.node.json deleted file mode 100644 index 0a089c8..0000000 --- a/quiz-app2/tsconfig.node.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "compilerOptions": { - "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo", - "target": "es2023", - "lib": ["ES2023"], - "module": "esnext", - "types": ["node"], - "skipLibCheck": true, - - /* Bundler mode */ - "moduleResolution": "bundler", - "allowImportingTsExtensions": true, - "verbatimModuleSyntax": true, - "moduleDetection": "force", - "noEmit": true, - - /* Linting */ - "noUnusedLocals": true, - "noUnusedParameters": true, - "erasableSyntaxOnly": true, - "noFallthroughCasesInSwitch": true, - }, - "include": ["vite.config.ts"], -} diff --git a/quiz-app2/vite.config.ts b/quiz-app2/vite.config.ts deleted file mode 100644 index 641f54e..0000000 --- a/quiz-app2/vite.config.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { defineConfig } from "vitest/config"; -import type { Plugin } from "vite"; -import vue from "@vitejs/plugin-vue"; -import tailwindcss from "@tailwindcss/vite"; -import { parse } from "smol-toml"; - -const basePath = process.env.VITE_BASE_PATH ?? "/"; - -const tomlData = (): Plugin => ({ - name: "quiz-app2-toml-data", - transform(source, id) { - if (!id.endsWith(".toml")) { - return null; - } - - const parsed = parse(source); - - return { - code: `export default ${JSON.stringify(parsed)};`, - map: null, - }; - }, -}); - -// https://vite.dev/config/ -export default defineConfig({ - base: basePath, - plugins: [tomlData(), vue(), tailwindcss()], - test: { - environment: "jsdom", - }, -});