From 6cba1f13519af6c407ddd26e415268a32376493d Mon Sep 17 00:00:00 2001 From: snc Date: Thu, 30 Jul 2026 21:22:21 +0900 Subject: [PATCH] initial commit Co-authored-by: Cursor --- .../creating-visual-explainers/SKILL.md | 245 + .../references/base.html | 83 + .../references/model-answer.html | 968 ++ .../references/node-install-guide.md | 68 + .../scripts/deploy-diagram.sh | 125 + .../skills/setting-up-comment-tool/SKILL.md | 294 + .env.example | 19 + .gitignore | 42 + README.md | 259 + eslint.config.mjs | 20 + next.config.ts | 7 + package-lock.json | 8225 +++++++++++++++++ package.json | 36 + public/feedback-demo.html | 111 + public/widget.js | 2 + scripts/migrate.ts | 53 + src/app/api/comments/route.ts | 157 + src/app/api/voice-analyze/route.ts | 163 + src/app/layout.tsx | 11 + src/lib/db.ts | 7 + src/shared/api-client.ts | 5 + src/shared/constants.ts | 61 + src/shared/slug.ts | 10 + src/shared/time.ts | 12 + src/shared/types.ts | 27 + src/widget/api.ts | 53 + src/widget/dom.ts | 24 + src/widget/highlight.ts | 110 + src/widget/icons.ts | 16 + src/widget/index.ts | 286 + src/widget/pin.ts | 513 + src/widget/render/card.ts | 83 + src/widget/render/index.ts | 22 + src/widget/render/name-dialog.ts | 26 + src/widget/render/popup.ts | 54 + src/widget/render/sidebar.ts | 201 + src/widget/render/toggle.ts | 53 + src/widget/scroll.ts | 40 + src/widget/selection.ts | 36 + src/widget/state.ts | 85 + src/widget/styles.ts | 202 + src/widget/voice.ts | 326 + tests/shared.unit.test.ts | 129 + tests/widget.integration.test.ts | 168 + tsconfig.json | 34 + vercel.json | 3 + vitest.config.ts | 8 + 47 files changed, 13482 insertions(+) create mode 100644 .claude/skills/creating-visual-explainers/SKILL.md create mode 100644 .claude/skills/creating-visual-explainers/references/base.html create mode 100644 .claude/skills/creating-visual-explainers/references/model-answer.html create mode 100644 .claude/skills/creating-visual-explainers/references/node-install-guide.md create mode 100755 .claude/skills/creating-visual-explainers/scripts/deploy-diagram.sh create mode 100644 .claude/skills/setting-up-comment-tool/SKILL.md create mode 100644 .env.example create mode 100644 .gitignore create mode 100644 README.md create mode 100644 eslint.config.mjs create mode 100644 next.config.ts create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 public/feedback-demo.html create mode 100644 public/widget.js create mode 100644 scripts/migrate.ts create mode 100644 src/app/api/comments/route.ts create mode 100644 src/app/api/voice-analyze/route.ts create mode 100644 src/app/layout.tsx create mode 100644 src/lib/db.ts create mode 100644 src/shared/api-client.ts create mode 100644 src/shared/constants.ts create mode 100644 src/shared/slug.ts create mode 100644 src/shared/time.ts create mode 100644 src/shared/types.ts create mode 100644 src/widget/api.ts create mode 100644 src/widget/dom.ts create mode 100644 src/widget/highlight.ts create mode 100644 src/widget/icons.ts create mode 100644 src/widget/index.ts create mode 100644 src/widget/pin.ts create mode 100644 src/widget/render/card.ts create mode 100644 src/widget/render/index.ts create mode 100644 src/widget/render/name-dialog.ts create mode 100644 src/widget/render/popup.ts create mode 100644 src/widget/render/sidebar.ts create mode 100644 src/widget/render/toggle.ts create mode 100644 src/widget/scroll.ts create mode 100644 src/widget/selection.ts create mode 100644 src/widget/state.ts create mode 100644 src/widget/styles.ts create mode 100644 src/widget/voice.ts create mode 100644 tests/shared.unit.test.ts create mode 100644 tests/widget.integration.test.ts create mode 100644 tsconfig.json create mode 100644 vercel.json create mode 100644 vitest.config.ts diff --git a/.claude/skills/creating-visual-explainers/SKILL.md b/.claude/skills/creating-visual-explainers/SKILL.md new file mode 100644 index 0000000..3ab50f6 --- /dev/null +++ b/.claude/skills/creating-visual-explainers/SKILL.md @@ -0,0 +1,245 @@ +--- +name: creating-visual-explainers +description: Generates an illustrated HTML page about any topic and deploys it to surge.sh with a comment widget (text/pin/voice feedback). Triggered by requests like "図解を作って", "図解を生成して", "このトピックを図解して", "図解してデプロイして", "この図解を再デプロイして", or "この図解を削除して". +--- + +# Creating Visual Explainers + +任意のトピックについて、前提知識がなくても理解できる図解HTMLを生成し、コメント機能(テキスト引用・ピン・音声)付きで surge.sh に公開する。品質基準は「入社したての新卒社会人が読んでも腹落ちする明快さ」だが、この基準は出力には表示しない。 + +## 依存 + +- `references/base.html` — 図解テンプレート(Tailwind CSS CDN・Lucide Icons CDN・ADS配色を含む「額縁」) +- `references/model-answer.html` — 模範回答(品質基準・デザインパターンの実例)。base.htmlと同一の額縁を含む完全なHTMLファイル。widget 未含(デプロイ時に自動注入されるため) +- リポジトリルートの `fb-tool-url.txt` / `fb-api-token.txt` — セットアップスキル(`setting-up-comment-tool`)が生成する + +## ワークフロー + +### Step 0: 前提確認 + +1. `references/base.html` が存在するか確認する。存在しない場合、以下を伝えて終了: + +> テンプレートファイルが見つかりません。スキルのフォルダ構成が壊れている可能性があります。運営に連絡してください。 + +2. リポジトリルート(`.claude` フォルダがある階層)に `fb-tool-url.txt` が存在するか確認する。存在しない場合、以下を伝えて終了: + +> コメントツールのセットアップがまだ完了していません。 +> 先にチャット欄で「セットアップして」と伝えてください。 + +### Step 1: 模範回答の読み込み + +`references/model-answer.html` を読み、以下を把握する: + +- 完成品の品質水準 +- デザインパターン(色使い・余白・カード・フロー図などの視覚表現) +- Tailwind CSSクラスの使い方 +- Lucide Iconsの使い方 +- コンテンツの構成・情報量・説明の深さ + +模範回答がデザインガイドラインの代わりになる。パーツ一覧やルールではなく、実物から読み取る。 + +### Step 2: テンプレートの読み込み + +`references/base.html` を読み、額縁の構造を把握する: + +- `` 〜 `` のプレースホルダー位置 +- ``, `` のプレースホルダー +- ADS配色のTailwind設定 +- 読み込み済みのCDN(Tailwind CSS・Lucide Icons) + +### Step 3: ウェブで情報収集 + +トピックについてウェブ検索を行い、正確かつ最新の情報を収集する。 + +検索は **2〜3回** に絞る。以下の観点でクエリを組み立てる: + +1. **正確な定義**: トピックの公式な定義、公式ドキュメントの説明 +2. **最新動向**: 直近の変更点、アップデート、現在のベストプラクティス +3. **具体例**: 実際の使われ方、初心者に伝わるたとえに使える事例 + +検索結果から以下を整理し、Step 4 の参考情報とする: + +- トピックの正確な定義(検索結果を優先。AIの学習データだけに頼らない) +- 最近変わった点があれば、それを明記する +- たとえ話に使えそうな事例や数字 +- **出典URL**: 図解に採用した情報のソースURLを控えておく(Step 4 でインライン出典に使う) + +### Step 4: コンテンツ生成 + +Step 3 で収集した情報をもとに、図解HTMLを生成する。検索結果で得た定義・事実・具体例を優先的に採用する。 + +模範回答のデザインを参考にしつつ、Tailwindの語彙で自由にレイアウトを組む。模範回答のパターンに合うものはそのまま使い、合わないものはTailwindクラスでその場で作る。テンプレートの定義済みパーツに縛られない。 + +### Step 5: ファイル作成 + +1. `output/` ディレクトリがなければ作成する +2. トピックに関連する短い英単語のスラッグを決める(例: `api-basics`, `git-rebase`) +3. `references/base.html` を `output/{スラッグ}.html` にコピーする +4. コピーしたファイル内のプレースホルダーをすべて置換する: + - `` → 図解のタイトル + - `` → 内容を要約した1文 + - `` 〜 `` → Step 4で生成したコンテンツ +5. ファイルを保存する(ブラウザで開くのは Step 6 のデプロイ後に行う。ローカルでは開かない) + +### Step 6: 公開 + +ファイル保存後、公開する前にまず Node.js の有無を確認する。 + +```bash +node --version +``` + +バージョン番号が表示された → そのまま「公開の実行」に進む。 + +`command not found` と表示された → `references/node-install-guide.md` の手順に従ってNode.jsのインストールを案内する。 + +#### 公開の実行 + +以下のスクリプトを**実行する**(中身を読む必要はない)。コメントウィジェットの埋め込みはこのスクリプトが自動で行う。 + +**macOS / Git Bash(Windows)の場合:** + +```bash +bash .claude/skills/creating-visual-explainers/scripts/deploy-diagram.sh output/{スラッグ}.html [スラッグ] +``` + +**Windows(PowerShell)で bash が使えない場合:** + +```powershell +$fbUrl = (Get-Content fb-tool-url.txt).Trim() +$apiToken = (Get-Content fb-api-token.txt).Trim() +$snippet = "" +$dir = "$env:TEMP\diagram-deploy" +New-Item -ItemType Directory -Force $dir | Out-Null +# (?s)^(.*) は貪欲マッチのため「最後の 」だけを置換する +# (本文中にコード例として があっても多重注入しない。bash 版と同じ挙動) +(Get-Content output/{スラッグ}.html -Raw) -replace '(?s)^(.*)', ('$1' + $snippet + '') | Set-Content "$dir\index.html" +Set-Content "$dir\robots.txt" "User-agent: *`nDisallow: /" +npx --yes surge $dir --domain diagram-[スラッグ].surge.sh +Add-Content deploy-history.log "$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss') | https://diagram-[スラッグ].surge.sh" +``` + +スラッグにはトピックに関連する短い英単語を指定する(例: `git-rebase`, `api-basics`)。 + +#### 初回の場合(Surge未登録) + +ターミナルにメールアドレスとパスワードの入力を求められる。以下を伝える: + +> 初回のみアカウント登録が必要です。 +> メールアドレスを入力して Enter → パスワードを決めて入力して Enter。 +> 確認メールが届いたらリンクをクリックすれば登録完了です。 +> 次回以降はこの手順は不要です。 + +#### エラーが出た場合 + +エラーメッセージをそのまま見せず、**何が起きていて何をすれば解決するか**を、専門用語を避けて平易に説明する。 + +よくあるエラーと対応: + +- **`npx: command not found`** — Node.js がまだ入っていない。`references/node-install-guide.md` の手順を案内する +- **`surge: not found` / surge関連エラー** — `npm install -g surge` を実行してから再度試す +- **認証エラー / `Login required`** — `npx surge login` を実行してメールアドレスとパスワードを入力する +- **`fb-tool-url.txt が見つかりません`** — セットアップが未完了。チャット欄で「セットアップして」と伝えるよう案内する +- **その他** — エラーの内容を読み、「何が問題で」「次に何をすればいいか」を平易に説明する + +### 図解の再デプロイ + +ユーザーが「この図解を再デプロイして」と依頼した場合(内容の修正後や、公開済み図解にコメント機能を付け直すとき): + +1. 対象の HTML を特定する(直近に編集した `output/{スラッグ}.html`。不明ならリポジトリルートの `deploy-history.log` から URL を確認し、ドメインの `diagram-` 以降がスラッグ) +2. 「公開の実行」と同じコマンドを、**同じスラッグ**で再実行する(同じ URL に上書き公開される) + +### 図解の削除 + +ユーザーが「この図解を削除して」と依頼した場合: + +1. リポジトリルートの `deploy-history.log` を読み、直近のデプロイURLを特定する + - ログが存在しない場合 → ユーザーに削除したいURLを聞く +2. `npx surge teardown [ドメイン]` を実行する +3. 削除完了をユーザーに伝える + +### Step 7: 完了報告 + +#### 公開に成功した場合 + +``` +完成・公開完了: 【図解のタイトル】 + +(図解の内容を1〜2文で要約) + +公開URL: +https://diagram-スラッグ.surge.sh + +図解の主なポイント: +- (主要トピックを3〜5個) + +コメントの付け方: +ページ右端のボタンから、テキスト選択・ピン・音声(マイクに向かって喋る)の +3通りでフィードバックできます。 + +この図解を削除したいとき: +チャット欄で「この図解を削除して」と伝えてください。 +``` + +#### 公開できなかった場合 + +``` +完成: 【図解のタイトル】 + +(図解の内容を1〜2文で要約) + +ファイルの保存先: +output/{スラッグ}.html(ブラウザにドラッグ&ドロップすると表示できます) + +図解の主なポイント: +- (主要トピックを3〜5個) + +URLで共有したいとき: +チャット欄で「この図解を公開して」と伝えてください。 +``` + +## 守ること(禁止事項) + +- **見出し(`h1`〜`h4`)を必ず入れる** — 音声コメントはページ内の見出しを手がかりにピンを配置する。見出しが1つも無いHTMLでは音声フィードバックがエラーになる +- **React・shadcn/ui を使わない** — 静的な図解にJSフレームワークは不要。AIの出力を制限し、モデルによる品質差も限定的にしてしまう +- **絵文字を使わない** — OS依存で表示が変わる。アイコンはLucide Iconsを使う +- **インタラクティブ要素を入れない** — トグル、フェードイン、アニメーション、フォーム、クリックで開閉する要素は一切禁止 +- **` + + +
+ +
+
+ + + +
+
+

AI-Driven School の図解ツールで作成

+
+ + + + diff --git a/.claude/skills/creating-visual-explainers/references/model-answer.html b/.claude/skills/creating-visual-explainers/references/model-answer.html new file mode 100644 index 0000000..ce24d75 --- /dev/null +++ b/.claude/skills/creating-visual-explainers/references/model-answer.html @@ -0,0 +1,968 @@ + + + + + + + + + + + + APIの仕組み + + + + + + + + + +
+ +
+
+ + + + + +
+
+ + テクノロジー +
+

+ APIの仕組み +

+

+ 「APIって何?」と聞かれて、うまく答えられない。
+ ひとことで言うと ── +

+
+ + + + + +
+
+

+ API = ソフトウェアの「注文窓口」 +

+

+ 中身を知らなくても、決まった形で頼めば結果が届く +

+
+ + +
+ +
+
+ +
+
あなたのアプリ
+
「天気を教えて」
+
+ + +
+
+ リクエスト + + +
+
+ + +
+
+ +
+
API
+
注文を届け、結果を返す
+
レストランのウェイター役
+
+ + +
+
+ 依頼 + + +
+
+ + +
+
+ +
+
サービス
+
処理して結果を返す
+
+
+ + +
+
+ + 結果(レスポンス)があなたのアプリに届く +
+
+ + +
+
+
あなたも毎日使っている
+
+
+ + +
+
+ + 天気予報 +
+
+ + Googleログイン +
+
+ + オンライン決済 +
+
+ + 地図・ナビ +
+
+ +
+ +

+ ここから先で、この仕組みをひとつずつ丁寧に解説していきます。 +

+ + + + + +
+
+
+ +
+

そもそもAPIって何?

+
+ +

+ API(エーピーアイ)は Application Programming Interface(アプリケーション・プログラミング・インターフェース)の略称です。正式名称を聞いても「何のこと?」と思いますよね。 +

+ +

+ まずは日常のたとえで考えてみましょう。レストランに行った場面を想像してください。 +

+ +

+ あなた(お客さん)は、厨房に直接入って料理を作ることはできません。厨房のルールも、調理器具の使い方も知りません。でも、ウェイターに「パスタをください」と注文すれば、厨房で作られた料理があなたのテーブルに届きます。厨房の中で何が起きているかを知る必要はありません。 +

+ + +
+

レストランで考えるAPIの役割

+ +
+ +
+
+ +
+
あなた
+
お客さん
+
「パスタください」
+
+ + +
+
+ 注文 + + +
+
+ + +
+
+ +
+
API
+
ウェイター
+
注文を伝え、料理を届ける
+
+ + +
+
+ 依頼 + + +
+
+ + +
+
+ +
+
サーバー
+
厨房
+
パスタを作って渡す
+
+
+ +
+
+ + 料理(レスポンス)があなたのテーブルに届く +
+
+
+ +

+ この比喩がAPIの本質をほぼ言い当てています。あなた(アプリ)は、厨房(サーバー)の中で何が起きているかを知る必要がありません。ウェイター(API)に決まった形式で注文を伝えれば、結果が返ってくる。これがAPIです。 +

+ + +
+
+
+ +
+
+

ここがポイント

+

+ APIは「仲介役」です。相手の内部構造を知らなくても、決まったルールで話しかければ結果が返ってくる。これがAPIの本質です。この「決まったルール」のことを、エンジニアは「インターフェース(Interface)」と呼びます。 +

+
+
+
+
+ + + + + +
+
+
+ +
+

もう少し正確に言うと

+
+ +

+ レストランのたとえで、ざっくりとしたイメージはつかめましたか? ここからもう少しだけ正確に説明します。 +

+ +

+ APIとは、ひとことで言えば「ソフトウェア同士が会話するための窓口」です。あなたが使っているアプリの裏側で、別のサービスのデータや機能を借りてくるための「取り決め」と考えてください。 +

+ +
+
技術的な定義
+

+ API = あるソフトウェアの機能を、 + 別のソフトウェアから使えるようにする仕組み +

+

出典: MDN Web Docs — Web API の紹介

+
+ +

+ これだけだとまだ抽象的に感じるかもしれません。では、APIがある世界とない世界を比べてみましょう。 +

+ + +
+ +
+
+ + BEFORE — APIがない世界 +
+
    +
  • + + 天気情報が欲しければ、自分で気象観測の仕組みを構築する +
  • +
  • + + 決済機能が欲しければ、クレジットカード処理を自前で開発する +
  • +
  • + + 地図を表示したければ、地図データを自分で作成・更新する +
  • +
  • + + ユーザー認証はパスワード管理からセキュリティ対策まで全部自前 +
  • +
+
+ + 膨大な開発コストと時間。バグのリスクも高い。 +
+
+ + +
+
+ + AFTER — APIがある世界 +
+
    +
  • + + 天気情報は天気予報APIに問い合わせるだけで取得できる +
  • +
  • + + 決済はStripe APIに任せれば数行のコードで完成 +
  • +
  • + + 地図はGoogle Maps APIで高品質な地図を即表示できる +
  • +
  • + + ログインはGoogleやAppleのAPIで安全に認証できる +
  • +
+
+ + 「自分が本当に作るべきもの」に集中できる。 +
+
+
+
+ + + + + +
+
+
+ +
+

APIの仕組み — リクエストとレスポンス

+
+ +

+ APIでのやり取りは、実はとてもシンプルです。基本は「聞く(リクエスト)」「答える(レスポンス)」の2つだけ。 +

+ +

+ リクエスト(Request)とは、「こういう情報をください」「この処理をしてください」とAPIに送るメッセージのことです。レスポンス(Response)は、APIがその要求に対して返す結果です。この2つのやり取りを分解すると、4つのステップになります。 +

+ + +
+

APIリクエスト〜レスポンスの流れ

+ +
+
+
1
+ +
リクエスト送信
+
あなたのアプリが
「こういう情報ください」
とAPIに送る
+
+ +
+ + +
+ +
+
2
+ +
APIが受け取る
+
リクエストの内容を
チェック・認証する
(門番の役割)
+
+ +
+ + +
+ +
+
3
+ +
サーバーが処理
+
データベース検索や
計算など、実際の
処理を実行する
+
+ +
+ + +
+ +
+
4
+ +
レスポンス返却
+
処理結果をあなたの
アプリに返す
(料理が届く瞬間)
+
+
+
+ +

+ 言葉だけだとまだピンとこないかもしれません。では、実際のコードで見てみましょう。たとえば、天気予報APIから東京の天気を取得するコードは、たったこれだけです。 +

+ + +
+
+ + JavaScript — 天気予報APIの呼び出し例 +
+
// 1. APIにリクエストを送る(「東京の天気を教えて」と聞く)
+const response = await fetch("https://api.weather.example.com/current?city=tokyo");
+
+// 2. レスポンスをJSON形式(データの構造)に変換する
+const data = await response.json();
+
+// 3. 必要なデータを取り出して使う
+console.log(data.temperature); // → "22°C"
+console.log(data.condition);   // → "晴れ"
+console.log(data.humidity);    // → "65%"
+
+ + +
+

+ + コードの解説(1行ずつ読み解く) +

+
+
+ 1 + fetch() は「指定したURLに問い合わせる」命令。URLの末尾にある ?city=tokyo が「東京の情報が欲しい」というリクエストの中身です。レストランのたとえで言えば「パスタください」にあたる部分。 +
+
+ 2 + 返ってきたデータは機械向けの生データなので、.json() で人間が読みやすい形(JSON = JavaScript Object Notation)に変換します。JSONは「名前: 値」の組み合わせでデータを表現する書式で、Web業界で最も広く使われています。 +
+
+ 3 + 変換したデータから data.temperature のように、ドット(.)で区切って欲しい情報を名前で取り出します。辞書で単語を引くのに似ています。 +
+
+
+ +

+ ターミナル(コマンドを入力する黒い画面)からもAPIを試せます。curl(カール)というコマンドを使うと、たった1行でAPIにリクエストを送れます。 +

+ + +
+
+
+
+
+
+
+
+ + ターミナル — curlコマンドでAPIを叩く +
+
+
$ curl https://api.weather.example.com/current?city=tokyo
+
+# 返ってくるレスポンス(JSON形式)
+{
+  "city": "東京",
+  "temperature": "22°C",
+  "condition": "晴れ",
+  "humidity": "65%"
+}
+
+ +
+
+
+ +
+
+

ちょっと補足: URLの構造

+

+ https://api.weather.example.com/current?city=tokyo のURLは、大きく3つの部分に分かれます。api.weather.example.com がAPIの住所(ベースURL)、/current が「何を」(現在の天気)、?city=tokyo が「どこの」(東京)というパラメータです。レストランで例えると「〇〇レストランの(住所)、メインメニューから(何を)、パスタを(詳細)」に対応します。 +

+
+
+
+ +

+ では、このAPIのレスポンスが実際のアプリではどう表示されるのでしょうか? あなたが見ている天気アプリの画面を覗いてみましょう。 +

+ + +
+
+
+
+
+
+
+
+ + weather-app.example.com +
+
+
+
+
現在地: 東京
+
+ + 22°C +
+
晴れ
+
+
65%
+
3m/s
+
+
+
+
+ +
+
+
+ +
+
+

APIの結果 → アプリの画面

+

+ 上の天気アプリは、裏側で temperature: "22°C"condition: "晴れ" というAPIレスポンスを受け取り、見やすいデザインに変換して表示しています。あなたが普段見ているきれいな画面の裏側では、こうしたAPIのやり取りが行われているのです。 +

+
+
+
+
+ + + + + +
+
+
+ +
+

身近なAPIの例 — 実はあなたも毎日使っている

+
+ +

+ 「API」と聞くとプログラマーの専門用語に聞こえるかもしれません。しかし、あなたがスマホで何気なくやっている日常の操作の裏側では、たくさんのAPIが動いています。「あなたが見ている画面」の裏側で、APIが何をしているのかを図解します。 +

+ +
+ +
+
+
+
東京
+
+ + 22°C +
+
晴れ
+
+
65%
+
3m/s
+
+
+
+
+

+ 天気予報アプリ +

+
裏側でAPIがやっていること
+

気象庁のサーバーに「東京の最新天気データをください」とリクエストを送り、気温・天候・湿度・風速などのデータをJSON形式で受け取っている

+
+
+ + +
+
+
アカウントにログイン
+
+
+ G +
+ Google でログイン +
+
+
+ または +
+
+
メールアドレスで登録
+
+
+

+ 「Googleでログイン」ボタン +

+
裏側でAPIがやっていること
+

GoogleのOAuth API(オーオース = 認可の仕組み)に「このユーザーの身元を確認してください」と問い合わせ、認証トークン(本人確認済みの証)を受け取っている

+
+
+ + +
+
+
+ +
+
お支払い完了
+
¥1,980
+
VISA **** 4242
+
+
+

+ オンライン決済 +

+
裏側でAPIがやっていること
+

Stripe等の決済APIが、クレジットカード会社のサーバーと暗号化通信を行い、与信確認(この人は支払える?)→ 決済処理 → 結果通知を実行している

+

出典: Stripe API 公式ドキュメント

+
+
+ + +
+
+
+
+
+
+
+ 現在地 +
+
+
+
+ + 東京駅 +
+
12分
+
+
+
+
+

+ 地図・ナビアプリ +

+
裏側でAPIがやっていること
+

Google Maps APIが地図画像の取得、現在の交通情報の取得、経路計算をそれぞれ別のAPIに問い合わせ、統合して表示している

+
+
+
+ +
+
+
+ +
+
+

気づきましたか?

+

+ 上の4つの例に共通しているのは、あなたがAPIの存在を意識していないということです。天気を確認するとき「今からAPIを呼ぶぞ」とは思いませんよね。優れたAPIは、ユーザーにその存在を感じさせません。まるで空気のように、裏側で静かに仕事をしているのです。 +

+
+
+
+
+ + + + + +
+
+
+ +
+

APIを使うとどう嬉しいか

+
+ +

+ ここまで読んで「APIは便利そうだ」と感じてもらえたと思います。では、開発者の視点から見たとき、APIを使うことで具体的にどのくらいの効果があるのか。数字と一緒に見てみましょう。 +

+ +
+
+
50回+
+
あなたが1日に
APIを使っている回数
+
+
+
24,000+
+
世界で公開されている
APIの数
+

出典: ProgrammableWeb

+
+
+
0.2秒
+
多くのAPIの
平均応答時間
+
+
+ +
+
+
+ +
+
+

開発スピードが上がる

+

決済、認証、地図、翻訳...。これらをゼロから作ると何ヶ月もかかりますが、APIを使えば数日〜数時間で実装できます。車を作りたいとき、エンジンから設計する必要はないのです。

+
+
+ +
+
+ +
+
+

品質が担保される

+

Google Maps、Stripe、AWSなど、各分野の専門企業が何千人体制で開発・運用しているAPIの品質は、個人や小さなチームで再現できるレベルではありません。その品質を「借りる」ことができます。

+
+
+ +
+
+ +
+
+

保守の手間が減る

+

API提供元がバグ修正・機能改善・セキュリティ更新を継続的に行ってくれます。あなたはAPIを「使うだけ」。自分でゼロから作った機能は、自分でずっと面倒を見続ける必要があります。

+
+
+ +
+
+ +
+
+

レゴのように拡張できる

+

APIはレゴブロックのように組み合わせられます。たとえば「翻訳API + 音声合成API」を組み合わせれば、多言語音声読み上げ機能が作れます。1つのAPIだけでは実現できない価値が、組み合わせで生まれるのです。

+
+
+
+
+ + + + + +
+
+
+ +
+

よくある誤解

+
+ +

+ APIについて学び始めると、多くの人が同じところでつまずきます。ここでは、初学者が陥りがちな3つの誤解を取り上げて、正しい理解に修正します。 +

+ +
+
+
+
+ +
+

誤解: 「APIはプログラマーだけが使うもの」

+
+
+
+
+ +
+
+

実際は:

+

+ あなたも毎日APIを使っています。朝、天気アプリを開く。SNSにログインする。電子マネーで買い物する。これらの操作はすべて、裏側でAPIが動いています。プログラマーが「APIを使う」のは、この仕組みのコードを書いている側にいるだけの話。気づかないうちにAPIの恩恵を毎日受けているのです。 +

+
+
+
+
+ +
+
+
+ +
+

誤解: 「APIって難しい技術でしょ?」

+
+
+
+
+ +
+
+

実際は:

+

+ APIの概念自体は「注文して結果を受け取る」というシンプルな仕組みです。レストランで注文できるなら、APIの概念は理解できます。先ほどのコード例のように、実際のプログラムも数行で書けることがほとんどです。難しいのはAPIそのものではなく、「APIで何を作るか」を考える部分。道具はシンプル、使いこなすセンスが問われるということです。 +

+
+
+
+
+ +
+
+
+ +
+

誤解: 「APIを使うと個人情報が漏れそうで怖い」

+
+
+
+
+ +
+
+

実際は:

+

+ 適切に設計されたAPIは、必要最小限の情報だけをやり取りします。たとえば銀行のAPIが口座残高を返す際、パスワードや暗証番号は一切含まれません。APIはデータの「窓口」であり、「何の情報を公開し、何を隠すか」を厳密に制御できます。むしろ、データベースに直接触るよりもAPIを介した方が安全なのです。レストランのたとえで言えば、お客さんが直接厨房に入るより、ウェイターを通した方が厨房の秩序が保たれるのと同じです。 +

+
+
+
+
+
+
+ + + + + +
+
+
+ +
+

まとめ — 覚えておきたい3つのこと

+
+ +

+ 長い図解を読んでいただきありがとうございます。最後に、この記事で伝えたかったことを3つに絞ってまとめます。 +

+ +
+
+
+
01
+
+

APIは「ソフトウェアの窓口」

+

+ レストランのウェイターのように、あなた(アプリ)とサーバーの間を取り持つ仲介役。相手の内部構造を知らなくても、決まったルール(インターフェース)で話しかければ結果が返ってきます。 +

+
+
+
+ +
+
+
02
+
+

あなたはすでにAPIユーザー

+

+ 天気予報、SNSログイン、地図検索、オンライン決済。気づかないうちに、あなたの日常はAPIに支えられています。APIは特別な人だけのものではなく、全員の生活を支える仕組みです。 +

+
+
+
+ +
+
+
03
+
+

APIで「車輪の再発明」がなくなる

+

+ すでにある優れた機能をAPIで借りることで、自分は「自分にしか作れない部分」に集中できます。開発スピードが上がり、品質も上がり、保守の手間も減る。これがAPIの最大の恩恵です。 +

+
+
+
+
+ +
+ +

APIは「知っている」だけで世界が広がる概念です。

+

+ 次にアプリを使うとき、「この裏側でどんなAPIが動いているんだろう?」と想像してみてください。天気予報の数字も、ログインボタンも、決済完了の画面も、すべてAPIが繋いでいます。テクノロジーの見え方が、少しだけ変わるはずです。 +

+
+
+ + +
+
+

AI-Driven School の図解ツールで作成

+
+ + + + diff --git a/.claude/skills/creating-visual-explainers/references/node-install-guide.md b/.claude/skills/creating-visual-explainers/references/node-install-guide.md new file mode 100644 index 0000000..f90974f --- /dev/null +++ b/.claude/skills/creating-visual-explainers/references/node-install-guide.md @@ -0,0 +1,68 @@ +# Node.js インストールガイド + +図解の公開ステップやセットアップの前提確認で Node.js が未インストールだった場合に参照する。 + +## 説明と許可 + +以下をユーザーに伝え、インストールの許可を求める: + +> 図解の作成は完了しました。`output/` フォルダ内の HTML ファイルをブラウザにドラッグ&ドロップすれば、今すぐ確認できます。 +> +> URLで公開するには「Node.js」のインストールが必要です。 +> Node.js は、パソコン上でプログラムを動かすための土台で、世界中で使われている安全な道具です。 +> **README にも記載の通り、AI-Driven School 運営が確認済みですので安心してください。** +> +> 今からインストールしてもよいですか? + +ユーザーが許可しなかった場合 → ファイルの確認方法(生成された HTML ファイルをブラウザで開く)を伝えて終了。 + +## macOS の場合 + +インストーラーをダウンロードする: + +```bash +PKG_NAME=$(curl -sL https://nodejs.org/dist/latest-lts/ | grep -o 'node-v[0-9.]*\.pkg' | head -1) && curl -fsSL "https://nodejs.org/dist/latest-lts/${PKG_NAME}" -o /tmp/node-install.pkg && echo "ダウンロード完了: ${PKG_NAME}" +``` + +ダウンロード完了後、インストールを実行する**前に**以下を伝える: + +> インストールのために、パソコンのパスワードの入力が必要です。 +> これはパソコンにログインするときに使っているパスワードです。 +> 画面下のターミナル欄にパスワードを入力して Enter を押してください。 +> 入力中の文字は画面に表示されませんが、正常な動作です。 + +```bash +sudo installer -pkg /tmp/node-install.pkg -target / && rm /tmp/node-install.pkg +``` + +## Windows の場合 + +インストールを実行する**前に**以下を伝える: + +> インストール中に「このアプリがデバイスに変更を加えることを許可しますか?」という確認画面が表示されることがあります。 +> 「はい」を押してください。 + +```powershell +winget install OpenJS.NodeJS.LTS --accept-package-agreements --accept-source-agreements +``` + +インストール完了後、現在のターミナルで Node.js を使えるようにする: + +```powershell +$env:Path = [System.Environment]::GetEnvironmentVariable("Path","Machine") + ";" + [System.Environment]::GetEnvironmentVariable("Path","User") +``` + +winget が使えない場合(「winget は認識されていません」と表示された場合): + +```powershell +$msi = (Invoke-WebRequest -Uri "https://nodejs.org/dist/latest-lts/" -UseBasicParsing).Links.href | Where-Object { $_ -match "x64\.msi$" } | Select-Object -First 1; Invoke-WebRequest -Uri "https://nodejs.org/dist/latest-lts/$msi" -OutFile "$env:TEMP\node-install.msi" -UseBasicParsing; Start-Process msiexec.exe -ArgumentList "/i `"$env:TEMP\node-install.msi`"" -Verb RunAs -Wait; Remove-Item "$env:TEMP\node-install.msi" +``` + +## インストール完了の確認 + +```bash +node --version +``` + +バージョン番号が表示された → インストール成功。 +エラーが出た → Cursor を再起動してからもう一度試すよう案内する。 diff --git a/.claude/skills/creating-visual-explainers/scripts/deploy-diagram.sh b/.claude/skills/creating-visual-explainers/scripts/deploy-diagram.sh new file mode 100755 index 0000000..eb9d33a --- /dev/null +++ b/.claude/skills/creating-visual-explainers/scripts/deploy-diagram.sh @@ -0,0 +1,125 @@ +#!/bin/bash +# 図解 HTML にコメントウィジェットの script タグを注入して surge.sh に公開する。 +# +# 使い方: deploy-diagram.sh [スラッグ] +# +# ウィジェットの配信元 URL とトークンは、セットアップスキルが +# リポジトリルートに生成する fb-tool-url.txt / fb-api-token.txt から読む。 +set -e + +HTML_FILE="${1:?使い方: deploy-diagram.sh [スラッグ]}" +SLUG="${2:-}" + +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +RED='\033[0;31m' +NC='\033[0m' + +if ! command -v node &>/dev/null; then + echo -e "${RED}エラー: Node.js がインストールされていません${NC}" >&2 + echo "Node.js をインストールしてから、もう一度試してください。" >&2 + exit 1 +fi + +if [ ! -f "$HTML_FILE" ]; then + echo -e "${RED}エラー: $HTML_FILE が見つかりません${NC}" >&2 + echo "先に図解を生成してください。" >&2 + exit 1 +fi + +if [ -n "$SLUG" ]; then + DOMAIN="diagram-${SLUG}.surge.sh" +else + DOMAIN="diagram-$(date +%y%m%d%H%M).surge.sh" +fi + +# このスクリプトは <ルート>/.claude/skills/creating-visual-explainers/scripts/ に置かれる前提。 +# 4階層上 = このスキル一式が置かれているリポジトリのルート +ROOT_DIR="$(cd "$(dirname "$0")/../../../.." && pwd)" + +FB_URL_FILE="$ROOT_DIR/fb-tool-url.txt" +if [ ! -f "$FB_URL_FILE" ]; then + echo -e "${RED}エラー: fb-tool-url.txt が見つかりません${NC}" >&2 + echo "チャット欄で「セットアップして」と伝えてください。" >&2 + exit 1 +fi +FB_URL=$(cat "$FB_URL_FILE") + +FB_TOKEN_FILE="$ROOT_DIR/fb-api-token.txt" +if [ ! -f "$FB_TOKEN_FILE" ]; then + echo -e "${RED}エラー: fb-api-token.txt が見つかりません${NC}" >&2 + echo "セットアップが完了していない可能性があります。チャット欄で「セットアップして」と伝えてください。" >&2 + exit 1 +fi +API_TOKEN=$(cat "$FB_TOKEN_FILE") + +if [[ ! "$FB_URL" =~ ^https:// ]]; then + echo -e "${RED}エラー: fb-tool-url.txt の URL が https:// で始まっていません${NC}" >&2 + exit 1 +fi +if [[ "$FB_URL" =~ [\\|\&$'\n'] ]]; then + echo -e "${RED}エラー: fb-tool-url.txt に不正な文字が含まれています${NC}" >&2 + exit 1 +fi +if [[ "$API_TOKEN" =~ [\\|\&$'\n'\ ] ]]; then + echo -e "${RED}エラー: fb-api-token.txt に不正な文字が含まれています${NC}" >&2 + exit 1 +fi + +if ! grep -q '' "$HTML_FILE"; then + echo -e "${RED}エラー: $HTML_FILE に タグが見つかりません${NC}" >&2 + echo "HTML ファイルの構造が壊れている可能性があります。" >&2 + exit 1 +fi + +TEMP_DIR=$(mktemp -d) +trap 'rm -rf "$TEMP_DIR"' EXIT + +SNIPPET="" + +# 最後の だけを置換する。図解の題材が HTML やコードだと本文中にも +# が現れうるため、一括置換すると script タグが複数挿し込まれて壊れる。 +awk -v snippet="$SNIPPET" ' + { line[NR] = $0; if (index($0, "")) last = NR } + END { + for (i = 1; i <= NR; i++) { + if (i == last) { + # 該当行の最後の を置換する(sub は先頭一致なので位置を自力で求める) + s = line[i]; pos = 0; from = 1 + while ((p = index(substr(s, from), "")) > 0) { + pos = from + p - 1 + from = pos + 7 + } + printf "%s%s%s\n", substr(s, 1, pos - 1), snippet, substr(s, pos + 7) + } else { + print line[i] + } + } + } +' "$HTML_FILE" > "$TEMP_DIR/index.html" + +printf "User-agent: *\nDisallow: /\n" > "$TEMP_DIR/robots.txt" + +echo -e "${YELLOW}公開中...${NC}" +npx --yes surge "$TEMP_DIR" --domain "$DOMAIN" + +touch "$ROOT_DIR/deploy-history.log" +echo "$(date '+%Y-%m-%d %H:%M:%S') | https://${DOMAIN}" >> "$ROOT_DIR/deploy-history.log" + +echo "" +echo -e "${GREEN}完了!${NC}" +echo "URL: https://${DOMAIN}" + +if [[ "$OSTYPE" == "darwin"* ]]; then + echo "https://${DOMAIN}" | pbcopy + echo -e "${GREEN}URLをクリップボードにコピーしました${NC}" + open "https://${DOMAIN}" +elif command -v clip.exe &>/dev/null; then + echo -n "https://${DOMAIN}" | clip.exe + echo -e "${GREEN}URLをクリップボードにコピーしました${NC}" + start "https://${DOMAIN}" 2>/dev/null || true +elif command -v xdg-open &>/dev/null; then + xdg-open "https://${DOMAIN}" +fi + +echo -e "${YELLOW}削除するとき: npx surge teardown ${DOMAIN}${NC}" diff --git a/.claude/skills/setting-up-comment-tool/SKILL.md b/.claude/skills/setting-up-comment-tool/SKILL.md new file mode 100644 index 0000000..7dd1167 --- /dev/null +++ b/.claude/skills/setting-up-comment-tool/SKILL.md @@ -0,0 +1,294 @@ +--- +name: setting-up-comment-tool +description: 音声図解コメントツールの初期セットアップ(Vercel・Neon・Gemini APIキー)を対話的にガイドするスキル。「セットアップして」「セットアップの続きをして」「コメントツールをセットアップして」「フィードバック機能を設定して」「音声図解コメントツールを使えるようにして」と依頼された際、および「コメントが保存されません」などセットアップ起因のトラブル調査を求められた際に使用する。 +--- + +# Setting Up Comment Tool + +図解に対するコメント機能(テキスト引用・ピン・音声)を使えるようにする初期セットアップ。Vercel(ホスティング)・Neon Postgres(データベース)・Gemini APIキー(音声解析)の設定を対話的にガイドする。 + +**実行するのは1回だけ。** セットアップ完了後は、図解を作ってsurge.shにデプロイするだけで自動的にコメント機能が付く。 + +## 用語の定義 + +- **ツールフォルダ**: このツール本体(`package.json` の name が `voice-diagram-comment-tool` のフォルダ)。`npm` / `vercel` コマンドはすべてここで実行する +- **リポジトリルート**: `.claude` フォルダがある階層。`fb-tool-url.txt` / `fb-api-token.txt` はここに保存する(デプロイスクリプトがここから読むため) + +## Step 0: ツールフォルダの特定 + +1. ワークスペースのルートに `package.json` があり、name が `voice-diagram-comment-tool` なら、ルートがツールフォルダ(単体利用) +2. なければ、ワークスペース内から name が `voice-diagram-comment-tool` の `package.json` を探す(別リポジトリに組み込んだ利用。フォルダ名は通常 `voice-diagram-comment`) +3. 見つからない場合、以下を伝えて終了: + +> ツール本体のフォルダが見つかりません。「voice-diagram-comment」(音声図解コメントツール)フォルダがこのリポジトリ内にあるか確認してください。 + +## 前提確認 + +### Node.js + +```bash +node --version +``` + +バージョン番号が表示された → 次に進む。 +`command not found` → `.claude/skills/creating-visual-explainers/references/node-install-guide.md` の手順でインストールを案内する。 + +### Vercel CLI + +```bash +vercel --version +``` + +バージョン番号が表示された → 次に進む。 +`command not found` → 以下を実行: + +```bash +npm install -g vercel +``` + +## ワークフロー + +以降のコマンドは、明記がない限り**ツールフォルダで実行する**。 + +### Step 1: 依存関係のインストール + +```bash +npm install +``` + +### Step 2: Vercelにログイン + +```bash +vercel login +``` + +ブラウザが開く。以下を伝える: + +> ブラウザでVercelのログイン画面が開きます。 +> アカウントを持っていない場合は「Sign Up」から無料アカウントを作成してください。 +> メールアドレスまたはGitHubアカウントで登録できます。 +> ログインが完了したら、ターミナルに戻ってください。 + +### Step 3: Vercelに初回デプロイ + +先にデプロイしてVercelプロジェクトを作成する(環境変数の設定にはプロジェクトが必要なため)。 + +自動命名に任せると名前の衝突などで不正になることがある。最初にプロジェクト名を明示してリンクする: + +```bash +vercel link --yes --project voice-diagram-comment-tool +``` + +(同名プロジェクトが既にある等でエラーになったら、`voice-comment-tool-2` のように名前を変えて再実行する) + +続いて本番デプロイ: + +```bash +vercel --yes --prod +``` + +デプロイが完了すると以下の2つのURLが出力される。両方を控えておく: + +- **Inspect URL**: `https://vercel.com/{slug}/{project}/...` 形式(Step 6 で使う) +- **Production URL**: `https://xxx.vercel.app` 形式(最終的なツールURL) + +この時点では環境変数が未設定なのでツールはまだ動かない。そのまま次に進む。 + +### Step 4: APIトークンの生成と設定 + +APIを保護するためのトークンを生成し、Vercelの環境変数に設定する。 + +```bash +openssl rand -hex 16 +``` + +**Windows(PowerShell)の場合:** + +```powershell +[System.Guid]::NewGuid().ToString("N") +``` + +表示された文字列がトークン。これを環境変数として設定する: + +```bash +echo "生成したトークン" | vercel env add API_TOKEN production +``` + +「生成したトークン」は実際に生成した値に置き換える。`vercel env add` が対話式プロンプトを出さずに値を受け取るよう、パイプで渡す。 + +同じトークンを**リポジトリルート**の `fb-api-token.txt` に保存する(1行、トークンのみ)。 + +### Step 5: Gemini APIキーの設定 + +音声フィードバック(喋った内容をAIが聞き取って図解にピンを配置する機能)に使うAPIキーを設定する。 + +ユーザーに以下を案内する: + +> 音声フィードバックには Google の Gemini というAIを使います。無料のAPIキーを発行しましょう。 +> +> 1. ブラウザで https://aistudio.google.com/apikey を開く +> 2. Googleアカウントでログイン +> 3. 「APIキーを作成」(Create API key)をクリック +> 4. 表示されたキー(`AIza...` で始まる文字列)をコピー +> 5. コピーしたキーをこのチャット欄に貼り付けてください +> +> 無料枠の範囲で使えます。クレジットカードの登録は不要です。 + +キーを受け取ったら設定する: + +```bash +echo "受け取ったAPIキー" | vercel env add GEMINI_API_KEY production +``` + +**注意**: 受け取ったAPIキーは環境変数の設定にだけ使う。ファイルに書き出したり、チャットの返答で復唱したりしない。 + +### Step 6: データベースの追加 + +Step 3 の Inspect URL からデプロイID部分(末尾のランダム文字列)を削り、`/stores` を付けて Storage ページの直接URLを組み立てる。 + +例: Inspect URL が `https://vercel.com/your-projects/voice-diagram-comment-tool/abc123xyz` なら +→ `https://vercel.com/your-projects/voice-diagram-comment-tool/stores` + +ユーザーにブラウザでの操作を案内する。以下の `{StorageページURL}` を組み立てたURLに置き換えて伝える: + +> コメントを保存するデータベースを追加します。ブラウザで以下のURLを開いてください。 +> +> {StorageページURL} +> +> ページが開いたら、以下の操作をしてください。 +> +> 1. 「Create Database」をクリック +> 2. 「Neon Postgres」を選択 +> 3. プランは「Free」を選択(無料、クレジットカード不要) +> 4. 「Create」をクリック +> 5. 次の画面で: +> - 「Search Projects」からプロジェクトを選択 +> - 「Custom Prefix」の欄を `DATABASE` に変更 +> - 「Connect」をクリック +> +> 完了したら教えてください。 + +### Step 7: 環境変数の取得とマイグレーション + +```bash +vercel env pull .env.local +``` + +テーブルを作成: + +```bash +npm run db:migrate +``` + +`Migration complete.` と表示されれば成功。 + +### Step 8: 再デプロイ + +環境変数を反映するため、もう一度デプロイする。 + +```bash +vercel --prod +``` + +### Step 9: URLを保存する + +ツールの**固定URL**(`https://プロジェクト名.vercel.app` 形式。通常は `https://voice-diagram-comment-tool.vercel.app`)を、**リポジトリルート**の `fb-tool-url.txt` に書き出す。URLのみを1行で保存する。 + +**注意**: `vercel --prod` の出力に表示されるランダム文字列入りのURL(例: `https://voice-diagram-comment-tool-a1b2c3-xxx.vercel.app`)は保存しない。デプロイ固有URLはVercelの保護機能により外部から読めないことがある。 + +保存したら、公開ページから widget.js に到達できることを確認する: + +```bash +curl -s -o /dev/null -w "%{http_code}" "$(cat fb-tool-url.txt)/widget.js" +``` + +**Windows(PowerShell)の場合:** + +```powershell +(Invoke-WebRequest "$((Get-Content fb-tool-url.txt).Trim())/widget.js").StatusCode +``` + +`200` ならOK。`404` や `401` が返る場合は、Vercelダッシュボードのプロジェクト → Settings → Domains に表示されているドメインを確認し、そのURLで `fb-tool-url.txt` を保存し直して再確認する。 + +### Step 10: 音声解析APIの通し検証 + +widget.js は静的ファイルなので、これだけではAPI(サーバーレス関数)が動いている保証にならない。無音の音声データを実際に `/api/voice-analyze` へ送り、Gemini まで通ることを確認する。 + +まず、16kHz・モノラル・1秒の無音WAVを生成してbase64化し、リクエストボディを組み立てる(**リポジトリルート**で実行): + +```bash +node -e ' +const sr = 16000, dataLen = sr * 2; // 16bit mono 1秒 +const b = Buffer.alloc(44 + dataLen); +b.write("RIFF", 0); b.writeUInt32LE(36 + dataLen, 4); b.write("WAVE", 8); +b.write("fmt ", 12); b.writeUInt32LE(16, 16); b.writeUInt16LE(1, 20); b.writeUInt16LE(1, 22); +b.writeUInt32LE(sr, 24); b.writeUInt32LE(sr * 2, 28); b.writeUInt16LE(2, 32); b.writeUInt16LE(16, 34); +b.write("data", 36); b.writeUInt32LE(dataLen, 40); +const body = { + audioBase64: b.toString("base64"), + mimeType: "audio/wav", + targets: [{ index: 0, text: "テスト", heading: "テスト", isHeading: true }], +}; +require("fs").writeFileSync("/tmp/fb-voice-check.json", JSON.stringify(body)); +' +``` + +**Windows(PowerShell)の場合**: 上と同じ `node -e` スクリプトを実行し、出力先だけ `$env:TEMP + "\\fb-voice-check.json"` に読み替える(`node -e` はWindowsでも動く)。 + +続いて、APIトークンを付けてPOSTする: + +```bash +curl -s -w "\n%{http_code}" -X POST "$(cat fb-tool-url.txt)/api/voice-analyze" \ + -H "Authorization: Bearer $(cat fb-api-token.txt)" \ + -H "Content-Type: application/json" \ + --data @/tmp/fb-voice-check.json +``` + +結果の判定: + +- **HTTP 200 で `{"results":[]}`**(無音なので空配列)→ 音声解析まで通しで動いている。セットアップ成功 +- **HTTP 404** → APIルートがデプロイされていない。Vercelのビルド設定の問題(エラー対応の「音声の解析に失敗しました」を参照) +- **`Gemini エラー` を含む応答** → Geminiのモデル名またはAPIキーの問題(エラー対応の「Gemini エラー (404)」を参照) +- **HTTP 403** → `fb-api-token.txt` の値と Vercel の `API_TOKEN` が食い違っている。Step 4 をやり直す + +### Step 11: 完了報告 + +リポジトリルートに `fb-tool-url.txt` と `fb-api-token.txt` があることを確認し、以下を伝える: + +``` +セットアップ完了 + +あなたのコメントツール URL: +https://xxx.vercel.app + +以降「図解を作って」と伝えるだけで、コメント機能付きの図解が公開されます。 + +公開した図解では、3通りの方法でフィードバックできます: +- テキストをマウスで選択してコメント +- 好きな場所にピンを刺してコメント +- マイクに向かって喋るだけ(AIが該当箇所にピンを自動配置) + +APIはトークンで保護されています。図解のデプロイ時に自動で埋め込まれるため、 +追加の操作は不要です。 +``` + +`fb-tool-url.txt` / `fb-api-token.txt` / `.env.local` はGitにコミットしない(このフォルダの `.gitignore` で除外済み。別リポジトリに組み込んでいる場合は、そのリポジトリの `.gitignore` にも追加するよう案内する)。 + +## エラー対応 + +エラーメッセージをそのまま見せず、何が起きていて何をすれば解決するかを平易に説明する。 + +- **`vercel: command not found`** → `npm install -g vercel` を実行 +- **`DATABASE_URL is not set`** → Step 6のデータベース追加が完了しているか確認。完了していれば `vercel env pull .env.local` を再実行 +- **マイグレーション失敗** → ツールフォルダの `.env.local` に `DATABASE_URL` が含まれているか確認 +- **音声フィードバックで「GEMINI_API_KEY が未設定です」** → Step 5が完了しているか `vercel env ls` で確認し、設定後に `vercel --prod` で再デプロイ +- **「音声の解析に失敗しました」** → まず `/api/voice-analyze` が404を返していないか確認する(`curl -s -o /dev/null -w "%{http_code}" -X POST "$(cat fb-tool-url.txt)/api/voice-analyze"` — 404以外=APIは存在する)。404ならAPIルートがデプロイされていない。原因はVercelのビルド設定: `vercel.json` に `buildCommand` を直接書くとVercelがNext.jsを認識できず、APIが生成されない。ツールフォルダの `vercel.json` が `{"framework": "nextjs"}` のみで、`package.json` に `vercel-build` スクリプトがあることを確認し、`vercel --prod` で再デプロイする +- **「Gemini エラー (404)」** → 指定中のGeminiモデルが新規APIキーでは利用不可になっている(例: `gemini-2.5-flash` は新規ユーザーに提供終了)。`echo "gemini-flash-latest" | vercel env add GEMINI_MODEL production` で環境変数を設定し、`vercel --prod` で再デプロイする(ツールが最新版なら既定値が `gemini-flash-latest` のためこのエラーは起きない) +- **「コメントが保存されません」と相談された** → 順に切り分ける: ①Step 9の到達確認(widget.js が200か)②`vercel env ls` で `DATABASE_URL` と `API_TOKEN` が production にあるか ③マイグレーション済みか(`vercel env pull .env.local` → `npm run db:migrate`)④環境変数を後から足した場合は `vercel --prod` で再デプロイしたか + +## 依存 + +- ツールフォルダの `package.json` — 依存関係と `db:migrate` スクリプト +- ツールフォルダの `scripts/migrate.ts` — DBマイグレーションスクリプト +- `.claude/skills/creating-visual-explainers/references/node-install-guide.md` — Node.jsインストール手順 +- `.claude/skills/creating-visual-explainers/scripts/deploy-diagram.sh` — `fb-tool-url.txt` / `fb-api-token.txt` の読み取り側 diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..a503284 --- /dev/null +++ b/.env.example @@ -0,0 +1,19 @@ +# ローカルで API(npm run dev)を動かすときに使う環境変数のテンプレート。 +# このファイルを .env.local にコピーして、各値を自分のものに置き換える。 +# 通常は `vercel env pull .env.local` で Vercel から取得すればよく、手で書く必要はない。 +# +# 本番(Vercel)には同じ変数を Vercel プロジェクトの環境変数として設定する。 +# 設定手順はセットアップスキル(「セットアップして」)が案内する。 + +# Neon Postgres への接続文字列。Vercel の Storage 連携で自動的に設定される +DATABASE_URL= + +# API を保護する合言葉。セットアップ時に自動生成される(fb-api-token.txt と同じ値) +# ローカル開発では未設定でも動く +API_TOKEN= + +# 音声解析に使う Google AI Studio の API キー(https://aistudio.google.com/apikey で無料発行) +GEMINI_API_KEY= + +# 任意。未指定なら gemini-flash-latest(常に最新のflash系を指す公式エイリアス)が使われる +# GEMINI_MODEL= diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b240077 --- /dev/null +++ b/.gitignore @@ -0,0 +1,42 @@ +# dependencies +/node_modules +/.pnp +.pnp.* + +# testing +/coverage + +# next.js +/.next/ +/out/ + +# production +/build + +# misc +.DS_Store +*.pem + +# debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* +.pnpm-debug.log* + +# env files(シークレットは絶対にコミットしない。テンプレートの .env.example だけは配布に含める) +.env* +!.env.example + +# vercel +.vercel + +# typescript +*.tsbuildinfo +next-env.d.ts + +# 図解の生成物・セットアップで生成されるファイル +output/*.html +deploy-history.log +fb-tool-url.txt +fb-api-token.txt +.env*.local diff --git a/README.md b/README.md new file mode 100644 index 0000000..59539e1 --- /dev/null +++ b/README.md @@ -0,0 +1,259 @@ +# 音声図解コメントツール + +わからない言葉やしくみを AI に伝えるだけで、噛み砕いた図解ページを自動で作って公開できるツールです。公開した図解には、見た人がコメントを付けられます。コメントの付け方は3通りあります。 + +- **テキストを選んでコメント** — 気になる箇所をマウスで選択すると、引用付きでコメントできます +- **ピンを刺してコメント** — 図の好きな場所をクリックしてピンを立て、コメントできます +- **マイクに向かって喋るだけ** — 「上の説明はもっとシンプルに。真ん中の図は矢印が逆」と喋ると、AI(Gemini)が聞き取って、図解の該当箇所にピンを自動で配置します + +さらに、こんな特徴があります。 + +- **優先度が色でわかる** — Must(赤)・Better(黄)・Want(緑)の3段階で重要度を伝えられます +- **セットアップは1回だけ** — 一度設定すれば、以降は図解を公開するたびにコメント機能が自動で付きます +- **無料で使える** — Vercel・Neon・Surge・Gemini API のすべて無料プランで動きます。クレジットカードの登録は不要です + +## しくみ + +このツールは2つの部品でできています。 + +``` + 「○○を図解して」と AI に依頼 + │ + ▼ + ① 図解 HTML(output/ に生成 → Surge に公開される静的ページ) + │ デプロイ時に の直前へ1行だけ自動注入される + │ + ▼ + ② バックエンド(あなたの Vercel で常時動く Next.js アプリ)= このフォルダ + ├─ widget.js … コメント UI 本体(画面右端に出るパネル) + ├─ /api/comments … コメントの保存・取得・更新・削除 + └─ /api/voice-analyze … 音声を Gemini に渡してピン配置情報に変換 + │ + ▼ + Neon Postgres(comments テーブル) +``` + +②は1つ動いていればOKです。図解を作るたびに何かをセットアップする必要はありません。 + +## セットアップ + +初回だけ必要な設定です。AI が順番に案内してくれます。 + +``` + ① Node.js / Vercel CLI を確認 + ↓ + ② Vercel にログイン・デプロイ + ↓ + ③ API トークンを自動生成 + ↓ + ④ Gemini API キーを発行(音声機能用・ブラウザで操作) + ↓ + ⑤ データベースを追加(ブラウザで操作) + ↓ + ⑥ マイグレーション実行・再デプロイ + ↓ + ⑦ 完了(fb-tool-url.txt と fb-api-token.txt が生成される) +``` + + + +### やり方 + +このフォルダを Cursor で開き、チャット欄で次のように伝えてください。 + +``` +セットアップして +``` + +AI がステップごとに案内してくれます。 + +### 必要なもの + + +| 必要なもの | 説明 | 費用 | +| ------------- | ---------------------------------------- | ------------- | +| Node.js | パソコン上でプログラムを動かすための土台。AI が自動でインストールを案内します | 無料 | +| Vercel アカウント | コメント機能を動かすサーバー。メールアドレスで登録できます | 無料(Hobby プラン) | +| Neon Postgres | コメントデータを保存するデータベース。Vercel の画面から追加します | 無料 | +| Google アカウント | 音声を聞き取る AI(Gemini)の API キー発行に使います | 無料 | +| Surge アカウント | 図解を公開するサービス。初回にメールアドレスとパスワードで登録します | 無料 | + +アカウントを持っていなくても大丈夫です。セットアップの途中で AI が「今このアカウントを作ってください」と、必要になったタイミングで案内します。 + +### Gemini API キーの発行手順(音声機能に必要) + +API キーとは、AI サービスを利用するための「あなた専用の合鍵」です。発行したことがなくても、以下の4ステップ・3分程度で作れます。セットアップ中に AI がこの手順を案内するので、事前にやっておく必要はありません。 + +1. ブラウザで [Google AI Studio](https://aistudio.google.com/apikey) を開く +2. Google アカウントでログインする +3. 「API キーを作成」(Create API key)ボタンを押す +4. 表示された `AIza...` で始まる文字列がキーです。セットアップ中に AI から求められたら、チャット欄に貼り付けてください + +無料枠の範囲で使えます。クレジットカードの登録は不要です。 + +### 大事な注意 + +- **API キーやトークンは自分専用です。** 他の人と共有したり、チャットや SNS に貼ったりしないでください +- セットアップで生成される `fb-tool-url.txt`・`fb-api-token.txt`・`.env.local` は Git にコミットされない設定になっています(`.gitignore` で除外済み) +- 図解は URL を知っていれば誰でも見られます。**機密情報を図解に載せない**前提で使ってください + + + +## 使い方 + + + +### 図解を作る + +Cursor のチャット欄に、知りたいことを書いて送信します。 + +``` +APIについて図解して +``` + +AI が図解を作り、コメント機能付きで自動デプロイします。 + +### コメントを付ける(見る側) + +公開された図解を開くと、画面右端にボタンが出ます。ボタンには3つの機能が並んでいます。 + + +| アイコン | 機能 | +| ---- | -------------------- | +| パネル | サイドバーの開閉。コメント一覧を見る | +| ピン | フィードバックモードの ON / OFF | +| マイク | 音声フィードバックの録音開始 / 停止 | + + +**音声で伝える**: マイクアイコンを押して喋り、もう一度押して停止します。数秒後、AI が「どの見出しへの指摘か」を判定して、該当箇所にピンを自動で配置します。初回はブラウザがマイクの使用許可を求めるので「許可」を選んでください。 + +**ピンで伝える**: ピンアイコンで フィードバックモードを ON にすると、クリックでピン、ドラッグでテキスト引用コメントになります。`Esc` でモードを抜けます。 + +初回だけ名前の入力を求められます(次回以降は自動で記憶されます)。 + +### 優先度の意味 + + +| 優先度 | 色 | 使いどころ | +| ------ | --- | -------------------------- | +| Must | 赤 | 必ず直すべき箇所(事実の間違い、重大なわかりにくさ) | +| Better | 黄 | 直すとよくなる箇所(表現の改善、構成の入れ替え) | +| Want | 緑 | 余裕があれば対応(細かい表現、好みの範囲) | + + + + +### コメントを確認・管理する + +画面右端のパネルボタンでサイドバーが開きます。 + +- **未解決 / 解決済 / すべて** のフィルタで絞り込めます +- コメントカードと本文の該当箇所は相互にジャンプできます +- 返信・解決・編集・削除・優先度変更ができます +- サイドバーのボタンから **JSON / Markdown** でエクスポートできます + + + +### 図解の削除 + +チャット欄で「この図解を削除して」と伝えてください。AI がデプロイ履歴(`deploy-history.log`。公開のたびに自動で記録されます)から URL を特定し、削除を実行します。 + +## 普段使っているリポジトリに組み込む + +このフォルダを単体で使うだけでなく、**普段開発しているリポジトリの中にこのツールを入れて使う**ことができます。ファイルを手で動かす必要はありません。組み込み作業は AI に任せます。 + +### 手順 + +1. このツールのフォルダ(`voice-diagram-comment`)を、パソコンの好きな場所に置きます(クローンまたはダウンロード。すでにあるならそのままでOK) +2. 組み込みたいリポジトリを Cursor で開きます +3. 下のプロンプトをチャット欄に貼り付け、**1行目のパスだけ自分の環境に書き換えて**送信します + +``` +「音声図解コメントツール」をこのリポジトリに組み込んでください。 + +ツールの場所: (ここにフォルダのパスを書く。 + Mac の例: /Users/あなたの名前/Downloads/voice-diagram-comment + Windows の例: C:\Users\あなたの名前\Downloads\voice-diagram-comment) + +やってほしいこと: +1. 上記フォルダを、このリポジトリ内の tools/voice-diagram-comment/ にコピーする + (node_modules・.next・.vercel・.env.local はコピーしない。.env.example はコピーしてよい) +2. コピーしたフォルダ内の .claude/skills/ にある2つのスキルフォルダを、 + このリポジトリ直下の .claude/skills/ に移動する + (スキルはリポジトリのルートにないと Cursor に認識されないため) +3. このリポジトリの .gitignore に、トークンファイルと生成物の除外設定を追加する + (除外すべきものは、コピーしたフォルダ内の .gitignore に書いてある。 + パスは実際の配置に合わせて調整する) +4. コピー元のフォルダ直下に fb-tool-url.txt と fb-api-token.txt があれば、 + このリポジトリのルートにコピーする(セットアップ済み環境の引き継ぎ) +5. 終わったら、組み込んだ内容と「次に何をすればいいか」を教えてください + (fb-tool-url.txt が無い場合は、次に「セットアップして」と伝えれば + 初期セットアップが始まる、と案内してください) +``` + +4. AI の作業が終わったら、案内に従って「セットアップして」と伝えます(すでに単体でセットアップ済みの場合は不要です。Vercel の再デプロイも不要です) + +### 組み込むとこうなる + +``` + あなたのリポジトリ/ + ├── src/ ... ← 既存のファイルはそのまま + ├── .claude/skills/ ← スキルはルートに置かれる + │ ├── setting-up-comment-tool/ + │ └── creating-visual-explainers/ + ├── tools/ + │ └── voice-diagram-comment/ ← ツール本体 + ├── output/ ← 図解の生成先(自動作成) + ├── fb-tool-url.txt ← セットアップで生成 + └── fb-api-token.txt ← セットアップで生成 +``` + +以降は、そのリポジトリのチャット欄で「図解を作って」と伝えるだけで、コメント機能付きの図解が公開できます。 + +## フォルダの中身 + +``` +voice-diagram-comment/ +├── .claude/skills/ +│ ├── creating-visual-explainers/ ← 図解生成スキル +│ │ ├── SKILL.md +│ │ ├── references/ ← 図解テンプレート・模範回答・Nodeインストールガイド +│ │ └── scripts/ +│ │ └── deploy-diagram.sh ← デプロイ+widget 自動注入 +│ └── setting-up-comment-tool/ ← セットアップスキル +│ └── SKILL.md +├── output/ ← 図解の保存先 +├── public/ +│ ├── widget.js ← コメント UI 本体(ビルド成果物) +│ └── feedback-demo.html ← ローカル動作確認用デモ +├── src/ +│ ├── app/api/comments/ ← コメント CRUD API +│ ├── app/api/voice-analyze/ ← 音声解析 API(Gemini) +│ └── lib/ shared/ widget/ ← DB接続・共通処理・コメント UI のソースコード +├── tests/ ← 自動テスト +├── scripts/ +│ └── migrate.ts ← DB テーブル作成(セットアップ時に1回だけ実行) +├── .env.example ← 環境変数のテンプレート(実際の値は書かない) +├── package.json +└── README.md ← この説明書 +``` + +セットアップ完了後に `fb-tool-url.txt`(ツールの URL)と `fb-api-token.txt`(API トークン)がリポジトリのルートに自動生成されます。あなたが手で編集する必要のあるファイルはありません。 + +## 困ったとき + + +| 症状 | 原因と対処 | +| ----------------------------- | ---------------------------------------------------------------------------------------------- | +| 「セットアップして」と言っても反応しない | スキルが認識されていません。このフォルダ自体(または組み込み先リポジトリのルート)を Cursor で開いているか、`.claude/skills/` がルート直下にあるか確認してください | +| マイクを押すと「マイクへのアクセスが許可されませんでした」 | ブラウザのマイク権限を確認。`https://` のページでないと許可されません(Surge の URL は https なので通常OK) | +| 音声で「ピンを置ける要素が見つかりませんでした」 | 図解 HTML に見出しがありません。このツールのスキルで作った図解なら自動で入ります | +| 音声で「GEMINI_API_KEY が未設定です」 | Gemini API キーの設定が漏れています。チャット欄で「セットアップの続きをして」と伝えてください | +| コメントが保存されない | データベースの設定が未完了の可能性。チャット欄で「コメントが保存されません」と伝えると AI が原因を調べます | +| 公開済みの図解にコメント機能が付かない | コメント機能はデプロイ時に注入されます。「この図解を再デプロイして」と伝えてください | +| その他 | 何が起きても、まずはチャット欄で AI に状況を伝えてください。エラーの内容を読み取って、次にやるべきことを案内してくれます | + + +--- + +Mac / Windows どちらでも使えます。AI-Driven School 運営が動作確認済みです。 \ No newline at end of file diff --git a/eslint.config.mjs b/eslint.config.mjs new file mode 100644 index 0000000..01e434c --- /dev/null +++ b/eslint.config.mjs @@ -0,0 +1,20 @@ +import { defineConfig, globalIgnores } from "eslint/config"; +import nextVitals from "eslint-config-next/core-web-vitals"; +import nextTs from "eslint-config-next/typescript"; + +const eslintConfig = defineConfig([ + ...nextVitals, + ...nextTs, + // Override default ignores of eslint-config-next. + globalIgnores([ + // Default ignores of eslint-config-next: + ".next/**", + "out/**", + "build/**", + "next-env.d.ts", + // esbuild が生成するバンドル。lint 対象にすると実コードの警告が埋もれる + "public/widget.js", + ]), +]); + +export default eslintConfig; diff --git a/next.config.ts b/next.config.ts new file mode 100644 index 0000000..e9ffa30 --- /dev/null +++ b/next.config.ts @@ -0,0 +1,7 @@ +import type { NextConfig } from "next"; + +const nextConfig: NextConfig = { + /* config options here */ +}; + +export default nextConfig; diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..0a34450 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,8225 @@ +{ + "name": "voice-diagram-comment-tool", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "voice-diagram-comment-tool", + "version": "0.1.0", + "dependencies": { + "@neondatabase/serverless": "^1.0.2", + "next": "16.1.6", + "react": "19.2.3", + "react-dom": "19.2.3" + }, + "devDependencies": { + "@types/jsdom": "^28.0.1", + "@types/node": "^20", + "@types/react": "^19", + "@types/react-dom": "^19", + "dotenv": "^17.3.1", + "esbuild": "^0.27.4", + "eslint": "^9", + "eslint-config-next": "16.1.6", + "jsdom": "^27.0.1", + "typescript": "5.9.3", + "vitest": "^3.2.4" + } + }, + "node_modules/@asamuzakjp/css-color": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-4.1.2.tgz", + "integrity": "sha512-NfBUvBaYgKIuq6E/RBLY1m0IohzNHAYyaJGuTK79Z23uNwmz2jl1mPsC5ZxCCxylinKhT1Amn5oNTlx1wN8cQg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@csstools/css-calc": "^3.0.0", + "@csstools/css-color-parser": "^4.0.1", + "@csstools/css-parser-algorithms": "^4.0.0", + "@csstools/css-tokenizer": "^4.0.0", + "lru-cache": "^11.2.5" + } + }, + "node_modules/@asamuzakjp/css-color/node_modules/lru-cache": { + "version": "11.2.7", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.7.tgz", + "integrity": "sha512-aY/R+aEsRelme17KGQa/1ZSIpLpNYYrhcrepKTZgE+W3WM16YMCaPwOHLHsmopZHELU0Ojin1lPVxKR0MihncA==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/@asamuzakjp/dom-selector": { + "version": "6.8.1", + "resolved": "https://registry.npmjs.org/@asamuzakjp/dom-selector/-/dom-selector-6.8.1.tgz", + "integrity": "sha512-MvRz1nCqW0fsy8Qz4dnLIvhOlMzqDVBabZx6lH+YywFDdjXhMY37SmpV1XFX3JzG5GWHn63j6HX6QPr3lZXHvQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@asamuzakjp/nwsapi": "^2.3.9", + "bidi-js": "^1.0.3", + "css-tree": "^3.1.0", + "is-potential-custom-element-name": "^1.0.1", + "lru-cache": "^11.2.6" + } + }, + "node_modules/@asamuzakjp/dom-selector/node_modules/lru-cache": { + "version": "11.2.7", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.7.tgz", + "integrity": "sha512-aY/R+aEsRelme17KGQa/1ZSIpLpNYYrhcrepKTZgE+W3WM16YMCaPwOHLHsmopZHELU0Ojin1lPVxKR0MihncA==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } + }, + "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/code-frame": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", + "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.28.5", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.0.tgz", + "integrity": "sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz", + "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", + "@babel/helper-compilation-targets": "^7.28.6", + "@babel/helper-module-transforms": "^7.28.6", + "@babel/helpers": "^7.28.6", + "@babel/parser": "^7.29.0", + "@babel/template": "^7.28.6", + "@babel/traverse": "^7.29.0", + "@babel/types": "^7.29.0", + "@jridgewell/remapping": "^2.3.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/generator": { + "version": "7.29.1", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.1.tgz", + "integrity": "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.0", + "@babel/types": "^7.29.0", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz", + "integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.28.6", + "@babel/helper-validator-option": "^7.27.1", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", + "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz", + "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz", + "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.28.6", + "@babel/helper-validator-identifier": "^7.28.5", + "@babel/traverse": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "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==", + "dev": true, + "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==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", + "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.6.tgz", + "integrity": "sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.0.tgz", + "integrity": "sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.0" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/template": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz", + "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.28.6", + "@babel/parser": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz", + "integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", + "@babel/helper-globals": "^7.28.0", + "@babel/parser": "^7.29.0", + "@babel/template": "^7.28.6", + "@babel/types": "^7.29.0", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.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==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "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.1.1", + "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-3.1.1.tgz", + "integrity": "sha512-HJ26Z/vmsZQqs/o3a6bgKslXGFAungXGbinULZO3eMsOyNJHeBBZfup5FiZInOghgoM4Hwnmw+OgbJCNg1wwUQ==", + "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.0.2", + "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-4.0.2.tgz", + "integrity": "sha512-0GEfbBLmTFf0dJlpsNU7zwxRIH0/BGEMuXLTCvFYxuL1tNhqzTbtnFICyJLTNK4a+RechKP75e7w42ClXSnJQw==", + "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.1.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.1", + "resolved": "https://registry.npmjs.org/@csstools/css-syntax-patches-for-csstree/-/css-syntax-patches-for-csstree-1.1.1.tgz", + "integrity": "sha512-BvqN0AMWNAnLk9G8jnUT77D+mUbY/H2b3uDTvg2isJkHaOufUE2R3AOwxWo7VBQKT1lOdwdvorddo2B/lk64+w==", + "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.9.0", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.9.0.tgz", + "integrity": "sha512-0DQ98G9ZQZOxfUcQn1waV2yS8aWdZ6kJMbYCJB3oUBecjWYO1fqJ+a1DRfPF3O5JEkwqwP1A9QEN/9mYm2Yd0w==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/wasi-threads": "1.2.0", + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.9.0.tgz", + "integrity": "sha512-QN75eB0IH2ywSpRpNddCRfQIhmJYBCJ1x5Lb3IscKAL8bMnVAKnRg8dCoXbHzVLLH7P38N2Z3mtulB7W0J0FKw==", + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/wasi-threads": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.0.tgz", + "integrity": "sha512-N10dEJNSsUx41Z6pZsXU8FjPjpBEplgH24sfkmITrBED1/U2Esum9F3lfLrMjKHHjmi557zQn7kR9R+XWXu5Rg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.4.tgz", + "integrity": "sha512-cQPwL2mp2nSmHHJlCyoXgHGhbEPMrEEU5xhkcy3Hs/O7nGZqEpZ2sUtLaL9MORLtDfRvVl2/3PAuEkYZH0Ty8Q==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.4.tgz", + "integrity": "sha512-X9bUgvxiC8CHAGKYufLIHGXPJWnr0OCdR0anD2e21vdvgCI8lIfqFbnoeOz7lBjdrAGUhqLZLcQo6MLhTO2DKQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.4.tgz", + "integrity": "sha512-gdLscB7v75wRfu7QSm/zg6Rx29VLdy9eTr2t44sfTW7CxwAtQghZ4ZnqHk3/ogz7xao0QAgrkradbBzcqFPasw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.4.tgz", + "integrity": "sha512-PzPFnBNVF292sfpfhiyiXCGSn9HZg5BcAz+ivBuSsl6Rk4ga1oEXAamhOXRFyMcjwr2DVtm40G65N3GLeH1Lvw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.4.tgz", + "integrity": "sha512-b7xaGIwdJlht8ZFCvMkpDN6uiSmnxxK56N2GDTMYPr2/gzvfdQN8rTfBsvVKmIVY/X7EM+/hJKEIbbHs9oA4tQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.4.tgz", + "integrity": "sha512-sR+OiKLwd15nmCdqpXMnuJ9W2kpy0KigzqScqHI3Hqwr7IXxBp3Yva+yJwoqh7rE8V77tdoheRYataNKL4QrPw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.4.tgz", + "integrity": "sha512-jnfpKe+p79tCnm4GVav68A7tUFeKQwQyLgESwEAUzyxk/TJr4QdGog9sqWNcUbr/bZt/O/HXouspuQDd9JxFSw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.4.tgz", + "integrity": "sha512-2kb4ceA/CpfUrIcTUl1wrP/9ad9Atrp5J94Lq69w7UwOMolPIGrfLSvAKJp0RTvkPPyn6CIWrNy13kyLikZRZQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.4.tgz", + "integrity": "sha512-aBYgcIxX/wd5n2ys0yESGeYMGF+pv6g0DhZr3G1ZG4jMfruU9Tl1i2Z+Wnj9/KjGz1lTLCcorqE2viePZqj4Eg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.4.tgz", + "integrity": "sha512-7nQOttdzVGth1iz57kxg9uCz57dxQLHWxopL6mYuYthohPKEK0vU0C3O21CcBK6KDlkYVcnDXY099HcCDXd9dA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.4.tgz", + "integrity": "sha512-oPtixtAIzgvzYcKBQM/qZ3R+9TEUd1aNJQu0HhGyqtx6oS7qTpvjheIWBbes4+qu1bNlo2V4cbkISr8q6gRBFA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.4.tgz", + "integrity": "sha512-8mL/vh8qeCoRcFH2nM8wm5uJP+ZcVYGGayMavi8GmRJjuI3g1v6Z7Ni0JJKAJW+m0EtUuARb6Lmp4hMjzCBWzA==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.4.tgz", + "integrity": "sha512-1RdrWFFiiLIW7LQq9Q2NES+HiD4NyT8Itj9AUeCl0IVCA459WnPhREKgwrpaIfTOe+/2rdntisegiPWn/r/aAw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.4.tgz", + "integrity": "sha512-tLCwNG47l3sd9lpfyx9LAGEGItCUeRCWeAx6x2Jmbav65nAwoPXfewtAdtbtit/pJFLUWOhpv0FpS6GQAmPrHA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.4.tgz", + "integrity": "sha512-BnASypppbUWyqjd1KIpU4AUBiIhVr6YlHx/cnPgqEkNoVOhHg+YiSVxM1RLfiy4t9cAulbRGTNCKOcqHrEQLIw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.4.tgz", + "integrity": "sha512-+eUqgb/Z7vxVLezG8bVB9SfBie89gMueS+I0xYh2tJdw3vqA/0ImZJ2ROeWwVJN59ihBeZ7Tu92dF/5dy5FttA==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.4.tgz", + "integrity": "sha512-S5qOXrKV8BQEzJPVxAwnryi2+Iq5pB40gTEIT69BQONqR7JH1EPIcQ/Uiv9mCnn05jff9umq/5nqzxlqTOg9NA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.4.tgz", + "integrity": "sha512-xHT8X4sb0GS8qTqiwzHqpY00C95DPAq7nAwX35Ie/s+LO9830hrMd3oX0ZMKLvy7vsonee73x0lmcdOVXFzd6Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.4.tgz", + "integrity": "sha512-RugOvOdXfdyi5Tyv40kgQnI0byv66BFgAqjdgtAKqHoZTbTF2QqfQrFwa7cHEORJf6X2ht+l9ABLMP0dnKYsgg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.4.tgz", + "integrity": "sha512-2MyL3IAaTX+1/qP0O1SwskwcwCoOI4kV2IBX1xYnDDqthmq5ArrW94qSIKCAuRraMgPOmG0RDTA74mzYNQA9ow==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.4.tgz", + "integrity": "sha512-u8fg/jQ5aQDfsnIV6+KwLOf1CmJnfu1ShpwqdwC0uA7ZPwFws55Ngc12vBdeUdnuWoQYx/SOQLGDcdlfXhYmXQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.4.tgz", + "integrity": "sha512-JkTZrl6VbyO8lDQO3yv26nNr2RM2yZzNrNHEsj9bm6dOwwu9OYN28CjzZkH57bh4w0I2F7IodpQvUAEd1mbWXg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.4.tgz", + "integrity": "sha512-/gOzgaewZJfeJTlsWhvUEmUG4tWEY2Spp5M20INYRg2ZKl9QPO3QEEgPeRtLjEWSW8FilRNacPOg8R1uaYkA6g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.4.tgz", + "integrity": "sha512-Z9SExBg2y32smoDQdf1HRwHRt6vAHLXcxD2uGgO/v2jK7Y718Ix4ndsbNMU/+1Qiem9OiOdaqitioZwxivhXYg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.4.tgz", + "integrity": "sha512-DAyGLS0Jz5G5iixEbMHi5KdiApqHBWMGzTtMiJ72ZOLhbu/bzxgAe8Ue8CTS3n3HbIUHQz/L51yMdGMeoxXNJw==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.4.tgz", + "integrity": "sha512-+knoa0BDoeXgkNvvV1vvbZX4+hizelrkwmGJBdT17t8FNPwG2lKemmuMZlmaNQ3ws3DKKCxpb4zRZEIp3UxFCg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", + "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", + "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/config-array": { + "version": "0.21.2", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.2.tgz", + "integrity": "sha512-nJl2KGTlrf9GjLimgIru+V/mzgSK0ABCDQRvxw5BjURL7WfH5uoWmizbH7QB6MmnMBd8cIC9uceWnezL1VZWWw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/object-schema": "^2.1.7", + "debug": "^4.3.1", + "minimatch": "^3.1.5" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/config-helpers": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.2.tgz", + "integrity": "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.17.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/core": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz", + "integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "3.3.5", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.5.tgz", + "integrity": "sha512-4IlJx0X0qftVsN5E+/vGujTRIFtwuLbNsVUe7TO6zYPDR1O6nFwvwhIKEKSrl6dZchmYBITazxKoUYOjdtjlRg==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^6.14.0", + "debug": "^4.3.2", + "espree": "^10.0.1", + "globals": "^14.0.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.1", + "minimatch": "^3.1.5", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/js": { + "version": "9.39.4", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.4.tgz", + "integrity": "sha512-nE7DEIchvtiFTwBw4Lfbu59PG+kCofhjsKaCWzxTpt4lfRjRMqG6uMBzKXuEcyXhOHoUp9riAm7/aWYGhXZ9cw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + } + }, + "node_modules/@eslint/object-schema": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.7.tgz", + "integrity": "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.1.tgz", + "integrity": "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.17.0", + "levn": "^0.4.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@humanfs/core": { + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", + "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node": { + "version": "0.16.7", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.7.tgz", + "integrity": "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/core": "^0.19.1", + "@humanwhocodes/retry": "^0.4.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/retry": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@img/colour": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@img/colour/-/colour-1.1.0.tgz", + "integrity": "sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@img/sharp-darwin-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.34.5.tgz", + "integrity": "sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-arm64": "1.2.4" + } + }, + "node_modules/@img/sharp-darwin-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.34.5.tgz", + "integrity": "sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-x64": "1.2.4" + } + }, + "node_modules/@img/sharp-libvips-darwin-arm64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.2.4.tgz", + "integrity": "sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-darwin-x64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.2.4.tgz", + "integrity": "sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.2.4.tgz", + "integrity": "sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==", + "cpu": [ + "arm" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.2.4.tgz", + "integrity": "sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-ppc64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.2.4.tgz", + "integrity": "sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==", + "cpu": [ + "ppc64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-riscv64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-riscv64/-/sharp-libvips-linux-riscv64-1.2.4.tgz", + "integrity": "sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==", + "cpu": [ + "riscv64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-s390x": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.2.4.tgz", + "integrity": "sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==", + "cpu": [ + "s390x" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-x64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.2.4.tgz", + "integrity": "sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-arm64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.2.4.tgz", + "integrity": "sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-x64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.2.4.tgz", + "integrity": "sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-linux-arm": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.34.5.tgz", + "integrity": "sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==", + "cpu": [ + "arm" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.34.5.tgz", + "integrity": "sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm64": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-ppc64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.34.5.tgz", + "integrity": "sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==", + "cpu": [ + "ppc64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-ppc64": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-riscv64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-riscv64/-/sharp-linux-riscv64-0.34.5.tgz", + "integrity": "sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==", + "cpu": [ + "riscv64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-riscv64": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-s390x": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.34.5.tgz", + "integrity": "sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==", + "cpu": [ + "s390x" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-s390x": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.34.5.tgz", + "integrity": "sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-x64": "1.2.4" + } + }, + "node_modules/@img/sharp-linuxmusl-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.34.5.tgz", + "integrity": "sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-arm64": "1.2.4" + } + }, + "node_modules/@img/sharp-linuxmusl-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.34.5.tgz", + "integrity": "sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-x64": "1.2.4" + } + }, + "node_modules/@img/sharp-wasm32": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.34.5.tgz", + "integrity": "sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==", + "cpu": [ + "wasm32" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT", + "optional": true, + "dependencies": { + "@emnapi/runtime": "^1.7.0" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.34.5.tgz", + "integrity": "sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-ia32": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.34.5.tgz", + "integrity": "sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg==", + "cpu": [ + "ia32" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.34.5.tgz", + "integrity": "sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "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==", + "dev": true, + "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": "0.2.12", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.12.tgz", + "integrity": "sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "^1.4.3", + "@emnapi/runtime": "^1.4.3", + "@tybys/wasm-util": "^0.10.0" + } + }, + "node_modules/@neondatabase/serverless": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@neondatabase/serverless/-/serverless-1.0.2.tgz", + "integrity": "sha512-I5sbpSIAHiB+b6UttofhrN/UJXII+4tZPAq1qugzwCwLIL8EZLV7F/JyHUrEIiGgQpEXzpnjlJ+zwcEhheGvCw==", + "license": "MIT", + "dependencies": { + "@types/node": "^22.15.30", + "@types/pg": "^8.8.0" + }, + "engines": { + "node": ">=19.0.0" + } + }, + "node_modules/@neondatabase/serverless/node_modules/@types/node": { + "version": "22.19.15", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.15.tgz", + "integrity": "sha512-F0R/h2+dsy5wJAUe3tAU6oqa2qbWY5TpNfL/RGmo1y38hiyO1w3x2jPtt76wmuaJI4DQnOBu21cNXQ2STIUUWg==", + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/@next/env": { + "version": "16.1.6", + "resolved": "https://registry.npmjs.org/@next/env/-/env-16.1.6.tgz", + "integrity": "sha512-N1ySLuZjnAtN3kFnwhAwPvZah8RJxKasD7x1f8shFqhncnWZn4JMfg37diLNuoHsLAlrDfM3g4mawVdtAG8XLQ==", + "license": "MIT" + }, + "node_modules/@next/eslint-plugin-next": { + "version": "16.1.6", + "resolved": "https://registry.npmjs.org/@next/eslint-plugin-next/-/eslint-plugin-next-16.1.6.tgz", + "integrity": "sha512-/Qq3PTagA6+nYVfryAtQ7/9FEr/6YVyvOtl6rZnGsbReGLf0jZU6gkpr1FuChAQpvV46a78p4cmHOVP8mbfSMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-glob": "3.3.1" + } + }, + "node_modules/@next/swc-darwin-arm64": { + "version": "16.1.6", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-16.1.6.tgz", + "integrity": "sha512-wTzYulosJr/6nFnqGW7FrG3jfUUlEf8UjGA0/pyypJl42ExdVgC6xJgcXQ+V8QFn6niSG2Pb8+MIG1mZr2vczw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-darwin-x64": { + "version": "16.1.6", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-16.1.6.tgz", + "integrity": "sha512-BLFPYPDO+MNJsiDWbeVzqvYd4NyuRrEYVB5k2N3JfWncuHAy2IVwMAOlVQDFjj+krkWzhY2apvmekMkfQR0CUQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-arm64-gnu": { + "version": "16.1.6", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-16.1.6.tgz", + "integrity": "sha512-OJYkCd5pj/QloBvoEcJ2XiMnlJkRv9idWA/j0ugSuA34gMT6f5b7vOiCQHVRpvStoZUknhl6/UxOXL4OwtdaBw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-arm64-musl": { + "version": "16.1.6", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-16.1.6.tgz", + "integrity": "sha512-S4J2v+8tT3NIO9u2q+S0G5KdvNDjXfAv06OhfOzNDaBn5rw84DGXWndOEB7d5/x852A20sW1M56vhC/tRVbccQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-x64-gnu": { + "version": "16.1.6", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-16.1.6.tgz", + "integrity": "sha512-2eEBDkFlMMNQnkTyPBhQOAyn2qMxyG2eE7GPH2WIDGEpEILcBPI/jdSv4t6xupSP+ot/jkfrCShLAa7+ZUPcJQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-x64-musl": { + "version": "16.1.6", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-16.1.6.tgz", + "integrity": "sha512-oicJwRlyOoZXVlxmIMaTq7f8pN9QNbdes0q2FXfRsPhfCi8n8JmOZJm5oo1pwDaFbnnD421rVU409M3evFbIqg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-win32-arm64-msvc": { + "version": "16.1.6", + "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-16.1.6.tgz", + "integrity": "sha512-gQmm8izDTPgs+DCWH22kcDmuUp7NyiJgEl18bcr8irXA5N2m2O+JQIr6f3ct42GOs9c0h8QF3L5SzIxcYAAXXw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-win32-x64-msvc": { + "version": "16.1.6", + "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-16.1.6.tgz", + "integrity": "sha512-NRfO39AIrzBnixKbjuo2YiYhB6o9d8v/ymU9m/Xk8cyVk+k7XylniXkHwjs4s70wedVffc6bQNbufk5v0xEm0A==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nolyfill/is-core-module": { + "version": "1.0.39", + "resolved": "https://registry.npmjs.org/@nolyfill/is-core-module/-/is-core-module-1.0.39.tgz", + "integrity": "sha512-nn5ozdjYQpUCZlWGuxcJY/KpxkWQs4DcbMCmKojjyrYDEAGy4Ce19NN4v5MduafTwJlbKc99UA8YhSVqq9yPZA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.4.0" + } + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.59.0.tgz", + "integrity": "sha512-upnNBkA6ZH2VKGcBj9Fyl9IGNPULcjXRlg0LLeaioQWueH30p6IXtJEbKAgvyv+mJaMxSm1l6xwDXYjpEMiLMg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.59.0.tgz", + "integrity": "sha512-hZ+Zxj3SySm4A/DylsDKZAeVg0mvi++0PYVceVyX7hemkw7OreKdCvW2oQ3T1FMZvCaQXqOTHb8qmBShoqk69Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.59.0.tgz", + "integrity": "sha512-W2Psnbh1J8ZJw0xKAd8zdNgF9HRLkdWwwdWqubSVk0pUuQkoHnv7rx4GiF9rT4t5DIZGAsConRE3AxCdJ4m8rg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.59.0.tgz", + "integrity": "sha512-ZW2KkwlS4lwTv7ZVsYDiARfFCnSGhzYPdiOU4IM2fDbL+QGlyAbjgSFuqNRbSthybLbIJ915UtZBtmuLrQAT/w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.59.0.tgz", + "integrity": "sha512-EsKaJ5ytAu9jI3lonzn3BgG8iRBjV4LxZexygcQbpiU0wU0ATxhNVEpXKfUa0pS05gTcSDMKpn3Sx+QB9RlTTA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.59.0.tgz", + "integrity": "sha512-d3DuZi2KzTMjImrxoHIAODUZYoUUMsuUiY4SRRcJy6NJoZ6iIqWnJu9IScV9jXysyGMVuW+KNzZvBLOcpdl3Vg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.59.0.tgz", + "integrity": "sha512-t4ONHboXi/3E0rT6OZl1pKbl2Vgxf9vJfWgmUoCEVQVxhW6Cw/c8I6hbbu7DAvgp82RKiH7TpLwxnJeKv2pbsw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.59.0.tgz", + "integrity": "sha512-CikFT7aYPA2ufMD086cVORBYGHffBo4K8MQ4uPS/ZnY54GKj36i196u8U+aDVT2LX4eSMbyHtyOh7D7Zvk2VvA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.59.0.tgz", + "integrity": "sha512-jYgUGk5aLd1nUb1CtQ8E+t5JhLc9x5WdBKew9ZgAXg7DBk0ZHErLHdXM24rfX+bKrFe+Xp5YuJo54I5HFjGDAA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.59.0.tgz", + "integrity": "sha512-peZRVEdnFWZ5Bh2KeumKG9ty7aCXzzEsHShOZEFiCQlDEepP1dpUl/SrUNXNg13UmZl+gzVDPsiCwnV1uI0RUA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.59.0.tgz", + "integrity": "sha512-gbUSW/97f7+r4gHy3Jlup8zDG190AuodsWnNiXErp9mT90iCy9NKKU0Xwx5k8VlRAIV2uU9CsMnEFg/xXaOfXg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.59.0.tgz", + "integrity": "sha512-yTRONe79E+o0FWFijasoTjtzG9EBedFXJMl888NBEDCDV9I2wGbFFfJQQe63OijbFCUZqxpHz1GzpbtSFikJ4Q==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.59.0.tgz", + "integrity": "sha512-sw1o3tfyk12k3OEpRddF68a1unZ5VCN7zoTNtSn2KndUE+ea3m3ROOKRCZxEpmT9nsGnogpFP9x6mnLTCaoLkA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.59.0.tgz", + "integrity": "sha512-+2kLtQ4xT3AiIxkzFVFXfsmlZiG5FXYW7ZyIIvGA7Bdeuh9Z0aN4hVyXS/G1E9bTP/vqszNIN/pUKCk/BTHsKA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.59.0.tgz", + "integrity": "sha512-NDYMpsXYJJaj+I7UdwIuHHNxXZ/b/N2hR15NyH3m2qAtb/hHPA4g4SuuvrdxetTdndfj9b1WOmy73kcPRoERUg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.59.0.tgz", + "integrity": "sha512-nLckB8WOqHIf1bhymk+oHxvM9D3tyPndZH8i8+35p/1YiVoVswPid2yLzgX7ZJP0KQvnkhM4H6QZ5m0LzbyIAg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.59.0.tgz", + "integrity": "sha512-oF87Ie3uAIvORFBpwnCvUzdeYUqi2wY6jRFWJAy1qus/udHFYIkplYRW+wo+GRUP4sKzYdmE1Y3+rY5Gc4ZO+w==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.59.0.tgz", + "integrity": "sha512-3AHmtQq/ppNuUspKAlvA8HtLybkDflkMuLK4DPo77DfthRb71V84/c4MlWJXixZz4uruIH4uaa07IqoAkG64fg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.59.0.tgz", + "integrity": "sha512-2UdiwS/9cTAx7qIUZB/fWtToJwvt0Vbo0zmnYt7ED35KPg13Q0ym1g442THLC7VyI6JfYTP4PiSOWyoMdV2/xg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.59.0.tgz", + "integrity": "sha512-M3bLRAVk6GOwFlPTIxVBSYKUaqfLrn8l0psKinkCFxl4lQvOSz8ZrKDz2gxcBwHFpci0B6rttydI4IpS4IS/jQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.59.0.tgz", + "integrity": "sha512-tt9KBJqaqp5i5HUZzoafHZX8b5Q2Fe7UjYERADll83O4fGqJ49O1FsL6LpdzVFQcpwvnyd0i+K/VSwu/o/nWlA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.59.0.tgz", + "integrity": "sha512-V5B6mG7OrGTwnxaNUzZTDTjDS7F75PO1ae6MJYdiMu60sq0CqN5CVeVsbhPxalupvTX8gXVSU9gq+Rx1/hvu6A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.59.0.tgz", + "integrity": "sha512-UKFMHPuM9R0iBegwzKF4y0C4J9u8C6MEJgFuXTBerMk7EJ92GFVFYBfOZaSGLu6COf7FxpQNqhNS4c4icUPqxA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.59.0.tgz", + "integrity": "sha512-laBkYlSS1n2L8fSo1thDNGrCTQMmxjYY5G0WFWjFFYZkKPjsMBsgJfGf4TLxXrF6RyhI60L8TMOjBMvXiTcxeA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.59.0.tgz", + "integrity": "sha512-2HRCml6OztYXyJXAvdDXPKcawukWY2GpR5/nxKp4iBgiO3wcoEGkAaqctIbZcNB6KlUQBIqt8VYkNSj2397EfA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rtsao/scc": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz", + "integrity": "sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==", + "dev": true, + "license": "MIT" + }, + "node_modules/@swc/helpers": { + "version": "0.5.15", + "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.15.tgz", + "integrity": "sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.8.0" + } + }, + "node_modules/@tybys/wasm-util": { + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz", + "integrity": "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==", + "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.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/jsdom": { + "version": "28.0.1", + "resolved": "https://registry.npmjs.org/@types/jsdom/-/jsdom-28.0.1.tgz", + "integrity": "sha512-GJq2QE4TAZ5ajSoCasn5DOFm8u1mI3tIFvM5tIq3W5U/RTB6gsHwc6Yhpl91X9VSDOUVblgXmG+2+sSvFQrdlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "@types/tough-cookie": "*", + "parse5": "^7.0.0", + "undici-types": "^7.21.0" + } + }, + "node_modules/@types/jsdom/node_modules/undici-types": { + "version": "7.24.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.24.5.tgz", + "integrity": "sha512-kNh333UBSbgK35OIW7FwJTr9tTfVIG51Fm1tSVT7m8foPHfDVjsb7OIee/q/rs3bB2aV/3qOPgG5mHNWl1odiA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/json5": { + "version": "0.0.29", + "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", + "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "20.19.37", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.37.tgz", + "integrity": "sha512-8kzdPJ3FsNsVIurqBs7oodNnCEVbni9yUEkaHbgptDACOPW04jimGagZ51E6+lXUwJjgnBw+hyko/lkFWCldqw==", + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/@types/pg": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/@types/pg/-/pg-8.18.0.tgz", + "integrity": "sha512-gT+oueVQkqnj6ajGJXblFR4iavIXWsGAFCk3dP4Kki5+a9R4NMt0JARdk6s8cUKcfUoqP5dAtDSLU8xYUTFV+Q==", + "license": "MIT", + "dependencies": { + "@types/node": "*", + "pg-protocol": "*", + "pg-types": "^2.2.0" + } + }, + "node_modules/@types/react": { + "version": "19.2.14", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.14.tgz", + "integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==", + "dev": true, + "license": "MIT", + "dependencies": { + "csstype": "^3.2.2" + } + }, + "node_modules/@types/react-dom": { + "version": "19.2.3", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz", + "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "^19.2.0" + } + }, + "node_modules/@types/tough-cookie": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.5.tgz", + "integrity": "sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "8.57.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.57.0.tgz", + "integrity": "sha512-qeu4rTHR3/IaFORbD16gmjq9+rEs9fGKdX0kF6BKSfi+gCuG3RCKLlSBYzn/bGsY9Tj7KE/DAQStbp8AHJGHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.12.2", + "@typescript-eslint/scope-manager": "8.57.0", + "@typescript-eslint/type-utils": "8.57.0", + "@typescript-eslint/utils": "8.57.0", + "@typescript-eslint/visitor-keys": "8.57.0", + "ignore": "^7.0.5", + "natural-compare": "^1.4.0", + "ts-api-utils": "^2.4.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^8.57.0", + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", + "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "8.57.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.57.0.tgz", + "integrity": "sha512-XZzOmihLIr8AD1b9hL9ccNMzEMWt/dE2u7NyTY9jJG6YNiNthaD5XtUHVF2uCXZ15ng+z2hT3MVuxnUYhq6k1g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/scope-manager": "8.57.0", + "@typescript-eslint/types": "8.57.0", + "@typescript-eslint/typescript-estree": "8.57.0", + "@typescript-eslint/visitor-keys": "8.57.0", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/project-service": { + "version": "8.57.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.57.0.tgz", + "integrity": "sha512-pR+dK0BlxCLxtWfaKQWtYr7MhKmzqZxuii+ZjuFlZlIGRZm22HnXFqa2eY+90MUz8/i80YJmzFGDUsi8dMOV5w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/tsconfig-utils": "^8.57.0", + "@typescript-eslint/types": "^8.57.0", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "8.57.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.57.0.tgz", + "integrity": "sha512-nvExQqAHF01lUM66MskSaZulpPL5pgy5hI5RfrxviLgzZVffB5yYzw27uK/ft8QnKXI2X0LBrHJFr1TaZtAibw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.57.0", + "@typescript-eslint/visitor-keys": "8.57.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/tsconfig-utils": { + "version": "8.57.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.57.0.tgz", + "integrity": "sha512-LtXRihc5ytjJIQEH+xqjB0+YgsV4/tW35XKX3GTZHpWtcC8SPkT/d4tqdf1cKtesryHm2bgp6l555NYcT2NLvA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "8.57.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.57.0.tgz", + "integrity": "sha512-yjgh7gmDcJ1+TcEg8x3uWQmn8ifvSupnPfjP21twPKrDP/pTHlEQgmKcitzF/rzPSmv7QjJ90vRpN4U+zoUjwQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.57.0", + "@typescript-eslint/typescript-estree": "8.57.0", + "@typescript-eslint/utils": "8.57.0", + "debug": "^4.4.3", + "ts-api-utils": "^2.4.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/types": { + "version": "8.57.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.57.0.tgz", + "integrity": "sha512-dTLI8PEXhjUC7B9Kre+u0XznO696BhXcTlOn0/6kf1fHaQW8+VjJAVHJ3eTI14ZapTxdkOmc80HblPQLaEeJdg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "8.57.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.57.0.tgz", + "integrity": "sha512-m7faHcyVg0BT3VdYTlX8GdJEM7COexXxS6KqGopxdtkQRvBanK377QDHr4W/vIPAR+ah9+B/RclSW5ldVniO1Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/project-service": "8.57.0", + "@typescript-eslint/tsconfig-utils": "8.57.0", + "@typescript-eslint/types": "8.57.0", + "@typescript-eslint/visitor-keys": "8.57.0", + "debug": "^4.4.3", + "minimatch": "^10.2.2", + "semver": "^7.7.3", + "tinyglobby": "^0.2.15", + "ts-api-utils": "^2.4.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.4.tgz", + "integrity": "sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { + "version": "10.2.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.4.tgz", + "integrity": "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "8.57.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.57.0.tgz", + "integrity": "sha512-5iIHvpD3CZe06riAsbNxxreP+MuYgVUsV0n4bwLH//VJmgtt54sQeY2GszntJ4BjYCpMzrfVh2SBnUQTtys2lQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.9.1", + "@typescript-eslint/scope-manager": "8.57.0", + "@typescript-eslint/types": "8.57.0", + "@typescript-eslint/typescript-estree": "8.57.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "8.57.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.57.0.tgz", + "integrity": "sha512-zm6xx8UT/Xy2oSr2ZXD0pZo7Jx2XsCoID2IUh9YSTFRu7z+WdwYTRk6LhUftm1crwqbuoF6I8zAFeCMw0YjwDg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.57.0", + "eslint-visitor-keys": "^5.0.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", + "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@unrs/resolver-binding-android-arm-eabi": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm-eabi/-/resolver-binding-android-arm-eabi-1.11.1.tgz", + "integrity": "sha512-ppLRUgHVaGRWUx0R0Ut06Mjo9gBaBkg3v/8AxusGLhsIotbBLuRk51rAzqLC8gq6NyyAojEXglNjzf6R948DNw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@unrs/resolver-binding-android-arm64": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm64/-/resolver-binding-android-arm64-1.11.1.tgz", + "integrity": "sha512-lCxkVtb4wp1v+EoN+HjIG9cIIzPkX5OtM03pQYkG+U5O/wL53LC4QbIeazgiKqluGeVEeBlZahHalCaBvU1a2g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@unrs/resolver-binding-darwin-arm64": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-arm64/-/resolver-binding-darwin-arm64-1.11.1.tgz", + "integrity": "sha512-gPVA1UjRu1Y/IsB/dQEsp2V1pm44Of6+LWvbLc9SDk1c2KhhDRDBUkQCYVWe6f26uJb3fOK8saWMgtX8IrMk3g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@unrs/resolver-binding-darwin-x64": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-x64/-/resolver-binding-darwin-x64-1.11.1.tgz", + "integrity": "sha512-cFzP7rWKd3lZaCsDze07QX1SC24lO8mPty9vdP+YVa3MGdVgPmFc59317b2ioXtgCMKGiCLxJ4HQs62oz6GfRQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@unrs/resolver-binding-freebsd-x64": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-freebsd-x64/-/resolver-binding-freebsd-x64-1.11.1.tgz", + "integrity": "sha512-fqtGgak3zX4DCB6PFpsH5+Kmt/8CIi4Bry4rb1ho6Av2QHTREM+47y282Uqiu3ZRF5IQioJQ5qWRV6jduA+iGw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm-gnueabihf": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-gnueabihf/-/resolver-binding-linux-arm-gnueabihf-1.11.1.tgz", + "integrity": "sha512-u92mvlcYtp9MRKmP+ZvMmtPN34+/3lMHlyMj7wXJDeXxuM0Vgzz0+PPJNsro1m3IZPYChIkn944wW8TYgGKFHw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm-musleabihf": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-musleabihf/-/resolver-binding-linux-arm-musleabihf-1.11.1.tgz", + "integrity": "sha512-cINaoY2z7LVCrfHkIcmvj7osTOtm6VVT16b5oQdS4beibX2SYBwgYLmqhBjA1t51CarSaBuX5YNsWLjsqfW5Cw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm64-gnu": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-gnu/-/resolver-binding-linux-arm64-gnu-1.11.1.tgz", + "integrity": "sha512-34gw7PjDGB9JgePJEmhEqBhWvCiiWCuXsL9hYphDF7crW7UgI05gyBAi6MF58uGcMOiOqSJ2ybEeCvHcq0BCmQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm64-musl": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-musl/-/resolver-binding-linux-arm64-musl-1.11.1.tgz", + "integrity": "sha512-RyMIx6Uf53hhOtJDIamSbTskA99sPHS96wxVE/bJtePJJtpdKGXO1wY90oRdXuYOGOTuqjT8ACccMc4K6QmT3w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-ppc64-gnu": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-ppc64-gnu/-/resolver-binding-linux-ppc64-gnu-1.11.1.tgz", + "integrity": "sha512-D8Vae74A4/a+mZH0FbOkFJL9DSK2R6TFPC9M+jCWYia/q2einCubX10pecpDiTmkJVUH+y8K3BZClycD8nCShA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-riscv64-gnu": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-gnu/-/resolver-binding-linux-riscv64-gnu-1.11.1.tgz", + "integrity": "sha512-frxL4OrzOWVVsOc96+V3aqTIQl1O2TjgExV4EKgRY09AJ9leZpEg8Ak9phadbuX0BA4k8U5qtvMSQQGGmaJqcQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-riscv64-musl": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-musl/-/resolver-binding-linux-riscv64-musl-1.11.1.tgz", + "integrity": "sha512-mJ5vuDaIZ+l/acv01sHoXfpnyrNKOk/3aDoEdLO/Xtn9HuZlDD6jKxHlkN8ZhWyLJsRBxfv9GYM2utQ1SChKew==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-s390x-gnu": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-s390x-gnu/-/resolver-binding-linux-s390x-gnu-1.11.1.tgz", + "integrity": "sha512-kELo8ebBVtb9sA7rMe1Cph4QHreByhaZ2QEADd9NzIQsYNQpt9UkM9iqr2lhGr5afh885d/cB5QeTXSbZHTYPg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-x64-gnu": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-gnu/-/resolver-binding-linux-x64-gnu-1.11.1.tgz", + "integrity": "sha512-C3ZAHugKgovV5YvAMsxhq0gtXuwESUKc5MhEtjBpLoHPLYM+iuwSj3lflFwK3DPm68660rZ7G8BMcwSro7hD5w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-x64-musl": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-musl/-/resolver-binding-linux-x64-musl-1.11.1.tgz", + "integrity": "sha512-rV0YSoyhK2nZ4vEswT/QwqzqQXw5I6CjoaYMOX0TqBlWhojUf8P94mvI7nuJTeaCkkds3QE4+zS8Ko+GdXuZtA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-wasm32-wasi": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-wasm32-wasi/-/resolver-binding-wasm32-wasi-1.11.1.tgz", + "integrity": "sha512-5u4RkfxJm+Ng7IWgkzi3qrFOvLvQYnPBmjmZQ8+szTK/b31fQCnleNl1GgEt7nIsZRIf5PLhPwT0WM+q45x/UQ==", + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@napi-rs/wasm-runtime": "^0.2.11" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@unrs/resolver-binding-win32-arm64-msvc": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-arm64-msvc/-/resolver-binding-win32-arm64-msvc-1.11.1.tgz", + "integrity": "sha512-nRcz5Il4ln0kMhfL8S3hLkxI85BXs3o8EYoattsJNdsX4YUU89iOkVn7g0VHSRxFuVMdM4Q1jEpIId1Ihim/Uw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@unrs/resolver-binding-win32-ia32-msvc": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-ia32-msvc/-/resolver-binding-win32-ia32-msvc-1.11.1.tgz", + "integrity": "sha512-DCEI6t5i1NmAZp6pFonpD5m7i6aFrpofcp4LA2i8IIq60Jyo28hamKBxNrZcyOwVOZkgsRp9O2sXWBWP8MnvIQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@unrs/resolver-binding-win32-x64-msvc": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-x64-msvc/-/resolver-binding-win32-x64-msvc-1.11.1.tgz", + "integrity": "sha512-lrW200hZdbfRtztbygyaq/6jP6AKE8qQN2KvPcJ+x7wiD038YtnYtZ82IMNJ69GJibV7bwL3y9FgK+5w/pYt6g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@vitest/expect": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.2.4.tgz", + "integrity": "sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/chai": "^5.2.2", + "@vitest/spy": "3.2.4", + "@vitest/utils": "3.2.4", + "chai": "^5.2.0", + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/mocker": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-3.2.4.tgz", + "integrity": "sha512-46ryTE9RZO/rfDd7pEqFl7etuyzekzEhUbTW3BvmeO/BcCMEgq59BKhek3dXDWgAj4oMK6OZi+vRr1wPW6qjEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "3.2.4", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.17" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "msw": "^2.4.9", + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, + "node_modules/@vitest/pretty-format": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-3.2.4.tgz", + "integrity": "sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-3.2.4.tgz", + "integrity": "sha512-oukfKT9Mk41LreEW09vt45f8wx7DordoWUZMYdY/cyAk7w5TWkTRCNZYF7sX7n2wB7jyGAl74OxgwhPgKaqDMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/utils": "3.2.4", + "pathe": "^2.0.3", + "strip-literal": "^3.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/snapshot": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-3.2.4.tgz", + "integrity": "sha512-dEYtS7qQP2CjU27QBC5oUOxLE/v5eLkGqPE0ZKEIDGMs4vKWe7IjgLOeauHsR0D5YuuycGRO5oSRXnwnmA78fQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "3.2.4", + "magic-string": "^0.30.17", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/spy": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-3.2.4.tgz", + "integrity": "sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyspy": "^4.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/utils": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-3.2.4.tgz", + "integrity": "sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "3.2.4", + "loupe": "^3.1.4", + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/acorn": { + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", + "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/agent-base": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", + "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, + "node_modules/ajv": { + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz", + "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "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/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/aria-query": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.2.tgz", + "integrity": "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/array-buffer-byte-length": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.2.tgz", + "integrity": "sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "is-array-buffer": "^3.0.5" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array-includes": { + "version": "3.1.9", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.9.tgz", + "integrity": "sha512-FmeCCAenzH0KH381SPT5FZmiA/TmpndpcaShhfgEN9eCVjnFBqq3l1xrI42y8+PPLI6hypzou4GXw00WHmPBLQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.24.0", + "es-object-atoms": "^1.1.1", + "get-intrinsic": "^1.3.0", + "is-string": "^1.1.1", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.findlast": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/array.prototype.findlast/-/array.prototype.findlast-1.2.5.tgz", + "integrity": "sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.findlastindex": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.6.tgz", + "integrity": "sha512-F/TKATkzseUExPlfvmwQKGITM3DGTK+vkAsCZoDc5daVygbJBnjEUCbgkAvVFsgfXfX4YIqZ/27G3k3tdXrTxQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.9", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "es-shim-unscopables": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.flat": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.3.tgz", + "integrity": "sha512-rwG/ja1neyLqCuGZ5YYrznA62D4mZXg0i1cIskIUKSiqF3Cje9/wXAls9B9s1Wa2fomMsIv8czB8jZcPmxCXFg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.flatmap": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.3.tgz", + "integrity": "sha512-Y7Wt51eKJSyi80hFrJCePGGNo5ktJCslFuboqJsbf57CCPcm5zztluPlc4/aD8sWsKvlwatezpV4U1efk8kpjg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.tosorted": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/array.prototype.tosorted/-/array.prototype.tosorted-1.1.4.tgz", + "integrity": "sha512-p6Fx8B7b7ZhL/gmUsAy0D15WhvDccw3mnGNbZpi3pmeJdxtWsj2jEaI4Y6oo3XiHfzuSgPwKc04MYt6KgvC/wA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.3", + "es-errors": "^1.3.0", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/arraybuffer.prototype.slice": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.4.tgz", + "integrity": "sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-buffer-byte-length": "^1.0.1", + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "is-array-buffer": "^3.0.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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/ast-types-flow": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.8.tgz", + "integrity": "sha512-OH/2E5Fg20h2aPrbe+QL8JZQFko0YZaF+j4mnQ7BGhfavO7OpSLa8a0y9sBwomHdSbkhTS8TQNayBfnW5DwbvQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/async-function": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/async-function/-/async-function-1.0.0.tgz", + "integrity": "sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/available-typed-arrays": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", + "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "possible-typed-array-names": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/axe-core": { + "version": "4.11.1", + "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.11.1.tgz", + "integrity": "sha512-BASOg+YwO2C+346x3LZOeoovTIoTrRqEsqMa6fmfAV0P+U9mFr9NsyOEpiYvFjbc64NMrSswhV50WdXzdb/Z5A==", + "dev": true, + "license": "MPL-2.0", + "engines": { + "node": ">=4" + } + }, + "node_modules/axobject-query": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz", + "integrity": "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">= 0.4" + } + }, + "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/baseline-browser-mapping": { + "version": "2.10.7", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.7.tgz", + "integrity": "sha512-1ghYO3HnxGec0TCGBXiDLVns4eCSx4zJpxnHrlqFQajmhfKMQBzUGDdkMK7fUW7PTHTeLf+j87aTuKuuwWzMGw==", + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "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": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz", + "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "baseline-browser-mapping": "^2.9.0", + "caniuse-lite": "^1.0.30001759", + "electron-to-chromium": "^1.5.263", + "node-releases": "^2.0.27", + "update-browserslist-db": "^1.2.0" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/cac": { + "version": "6.7.14", + "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", + "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/call-bind": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", + "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.0", + "es-define-property": "^1.0.0", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001778", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001778.tgz", + "integrity": "sha512-PN7uxFL+ExFJO61aVmP1aIEG4i9whQd4eoSCebav62UwDyp5OHh06zN4jqKSMePVgxHifCw1QJxdRkA1Pisekg==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/chai": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/chai/-/chai-5.3.3.tgz", + "integrity": "sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "assertion-error": "^2.0.1", + "check-error": "^2.1.1", + "deep-eql": "^5.0.1", + "loupe": "^3.1.0", + "pathval": "^2.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/check-error": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.3.tgz", + "integrity": "sha512-PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 16" + } + }, + "node_modules/client-only": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz", + "integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==", + "license": "MIT" + }, + "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/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, + "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/cssstyle": { + "version": "5.3.7", + "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-5.3.7.tgz", + "integrity": "sha512-7D2EPVltRrsTkhpQmksIu+LxeWAIEk6wRDMJ1qljlv+CKHJM+cJLlfhWIzNA44eAsHXSNe3+vO6DW1yCYx8SuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@asamuzakjp/css-color": "^4.1.1", + "@csstools/css-syntax-patches-for-csstree": "^1.0.21", + "css-tree": "^3.1.0", + "lru-cache": "^11.2.4" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/cssstyle/node_modules/lru-cache": { + "version": "11.2.7", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.7.tgz", + "integrity": "sha512-aY/R+aEsRelme17KGQa/1ZSIpLpNYYrhcrepKTZgE+W3WM16YMCaPwOHLHsmopZHELU0Ojin1lPVxKR0MihncA==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } + }, + "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==", + "dev": true, + "license": "MIT" + }, + "node_modules/damerau-levenshtein": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz", + "integrity": "sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==", + "dev": true, + "license": "BSD-2-Clause" + }, + "node_modules/data-urls": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-6.0.1.tgz", + "integrity": "sha512-euIQENZg6x8mj3fO6o9+fOW8MimUI4PpD/fZBhJfeioZVy9TUpM4UY7KjQNVZFlqwJ0UdzRDzkycB997HEq1BQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "whatwg-mimetype": "^5.0.0", + "whatwg-url": "^15.1.0" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/data-urls/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/data-view-buffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.2.tgz", + "integrity": "sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/data-view-byte-length": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/data-view-byte-length/-/data-view-byte-length-1.0.2.tgz", + "integrity": "sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/inspect-js" + } + }, + "node_modules/data-view-byte-offset": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/data-view-byte-offset/-/data-view-byte-offset-1.0.1.tgz", + "integrity": "sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "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/deep-eql": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz", + "integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/define-properties": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", + "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.0.1", + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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==", + "license": "Apache-2.0", + "optional": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/dotenv": { + "version": "17.3.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.3.1.tgz", + "integrity": "sha512-IO8C/dzEb6O3F9/twg6ZLXz164a2fhTnEWb95H23Dm4OuN+92NmEAlTrupP9VW6Jm3sO26tQlqyvyi4CsnY9GA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.313", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.313.tgz", + "integrity": "sha512-QBMrTWEf00GXZmJyx2lbYD45jpI3TUFnNIzJ5BBc8piGUDwMPa1GV6HJWTZVvY/eiN3fSopl7NRbgGp9sZ9LTA==", + "dev": true, + "license": "ISC" + }, + "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/entities": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", + "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/es-abstract": { + "version": "1.24.1", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.1.tgz", + "integrity": "sha512-zHXBLhP+QehSSbsS9Pt23Gg964240DPd6QCf8WpkqEXxQ7fhdZzYsocOr5u7apWonsS5EjZDmTF+/slGMyasvw==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-buffer-byte-length": "^1.0.2", + "arraybuffer.prototype.slice": "^1.0.4", + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "data-view-buffer": "^1.0.2", + "data-view-byte-length": "^1.0.2", + "data-view-byte-offset": "^1.0.1", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "es-set-tostringtag": "^2.1.0", + "es-to-primitive": "^1.3.0", + "function.prototype.name": "^1.1.8", + "get-intrinsic": "^1.3.0", + "get-proto": "^1.0.1", + "get-symbol-description": "^1.1.0", + "globalthis": "^1.0.4", + "gopd": "^1.2.0", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "internal-slot": "^1.1.0", + "is-array-buffer": "^3.0.5", + "is-callable": "^1.2.7", + "is-data-view": "^1.0.2", + "is-negative-zero": "^2.0.3", + "is-regex": "^1.2.1", + "is-set": "^2.0.3", + "is-shared-array-buffer": "^1.0.4", + "is-string": "^1.1.1", + "is-typed-array": "^1.1.15", + "is-weakref": "^1.1.1", + "math-intrinsics": "^1.1.0", + "object-inspect": "^1.13.4", + "object-keys": "^1.1.1", + "object.assign": "^4.1.7", + "own-keys": "^1.0.1", + "regexp.prototype.flags": "^1.5.4", + "safe-array-concat": "^1.1.3", + "safe-push-apply": "^1.0.0", + "safe-regex-test": "^1.1.0", + "set-proto": "^1.0.0", + "stop-iteration-iterator": "^1.1.0", + "string.prototype.trim": "^1.2.10", + "string.prototype.trimend": "^1.0.9", + "string.prototype.trimstart": "^1.0.8", + "typed-array-buffer": "^1.0.3", + "typed-array-byte-length": "^1.0.3", + "typed-array-byte-offset": "^1.0.4", + "typed-array-length": "^1.0.7", + "unbox-primitive": "^1.1.0", + "which-typed-array": "^1.1.19" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-iterator-helpers": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/es-iterator-helpers/-/es-iterator-helpers-1.3.1.tgz", + "integrity": "sha512-zWwRvqWiuBPr0muUG/78cW3aHROFCNIQ3zpmYDpwdbnt2m+xlNyRWpHBpa2lJjSBit7BQ+RXA1iwbSmu5yJ/EQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.24.1", + "es-errors": "^1.3.0", + "es-set-tostringtag": "^2.1.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.3.0", + "globalthis": "^1.0.4", + "gopd": "^1.2.0", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.2.0", + "has-symbols": "^1.1.0", + "internal-slot": "^1.1.0", + "iterator.prototype": "^1.1.5", + "math-intrinsics": "^1.1.0", + "safe-array-concat": "^1.1.3" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-module-lexer": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", + "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==", + "dev": true, + "license": "MIT" + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-shim-unscopables": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.1.0.tgz", + "integrity": "sha512-d9T8ucsEhh8Bi1woXCf+TIKDIROLG5WCkxg8geBCbvk22kzwC5G2OnXVMO6FUsvQlgUUXQ2itephWDLqDzbeCw==", + "dev": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-to-primitive": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.3.0.tgz", + "integrity": "sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-callable": "^1.2.7", + "is-date-object": "^1.0.5", + "is-symbol": "^1.0.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/esbuild": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.4.tgz", + "integrity": "sha512-Rq4vbHnYkK5fws5NF7MYTU68FPRE1ajX7heQ/8QXXWqNgqqJ/GkmmyxIzUnf2Sr/bakf8l54716CcMGHYhMrrQ==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.27.4", + "@esbuild/android-arm": "0.27.4", + "@esbuild/android-arm64": "0.27.4", + "@esbuild/android-x64": "0.27.4", + "@esbuild/darwin-arm64": "0.27.4", + "@esbuild/darwin-x64": "0.27.4", + "@esbuild/freebsd-arm64": "0.27.4", + "@esbuild/freebsd-x64": "0.27.4", + "@esbuild/linux-arm": "0.27.4", + "@esbuild/linux-arm64": "0.27.4", + "@esbuild/linux-ia32": "0.27.4", + "@esbuild/linux-loong64": "0.27.4", + "@esbuild/linux-mips64el": "0.27.4", + "@esbuild/linux-ppc64": "0.27.4", + "@esbuild/linux-riscv64": "0.27.4", + "@esbuild/linux-s390x": "0.27.4", + "@esbuild/linux-x64": "0.27.4", + "@esbuild/netbsd-arm64": "0.27.4", + "@esbuild/netbsd-x64": "0.27.4", + "@esbuild/openbsd-arm64": "0.27.4", + "@esbuild/openbsd-x64": "0.27.4", + "@esbuild/openharmony-arm64": "0.27.4", + "@esbuild/sunos-x64": "0.27.4", + "@esbuild/win32-arm64": "0.27.4", + "@esbuild/win32-ia32": "0.27.4", + "@esbuild/win32-x64": "0.27.4" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "9.39.4", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.4.tgz", + "integrity": "sha512-XoMjdBOwe/esVgEvLmNsD3IRHkm7fbKIUGvrleloJXUZgDHig2IPWNniv+GwjyJXzuNqVjlr5+4yVUZjycJwfQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.8.0", + "@eslint-community/regexpp": "^4.12.1", + "@eslint/config-array": "^0.21.2", + "@eslint/config-helpers": "^0.4.2", + "@eslint/core": "^0.17.0", + "@eslint/eslintrc": "^3.3.5", + "@eslint/js": "9.39.4", + "@eslint/plugin-kit": "^0.4.1", + "@humanfs/node": "^0.16.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.4.2", + "@types/estree": "^1.0.6", + "ajv": "^6.14.0", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.6", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^8.4.0", + "eslint-visitor-keys": "^4.2.1", + "espree": "^10.4.0", + "esquery": "^1.5.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.5", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } + } + }, + "node_modules/eslint-config-next": { + "version": "16.1.6", + "resolved": "https://registry.npmjs.org/eslint-config-next/-/eslint-config-next-16.1.6.tgz", + "integrity": "sha512-vKq40io2B0XtkkNDYyleATwblNt8xuh3FWp8SpSz3pt7P01OkBFlKsJZ2mWt5WsCySlDQLckb1zMY9yE9Qy0LA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@next/eslint-plugin-next": "16.1.6", + "eslint-import-resolver-node": "^0.3.6", + "eslint-import-resolver-typescript": "^3.5.2", + "eslint-plugin-import": "^2.32.0", + "eslint-plugin-jsx-a11y": "^6.10.0", + "eslint-plugin-react": "^7.37.0", + "eslint-plugin-react-hooks": "^7.0.0", + "globals": "16.4.0", + "typescript-eslint": "^8.46.0" + }, + "peerDependencies": { + "eslint": ">=9.0.0", + "typescript": ">=3.3.1" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/eslint-config-next/node_modules/globals": { + "version": "16.4.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-16.4.0.tgz", + "integrity": "sha512-ob/2LcVVaVGCYN+r14cnwnoDPUufjiYgSqRhiFD0Q1iI4Odora5RE8Iv1D24hAz5oMophRGkGz+yuvQmmUMnMw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint-import-resolver-node": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.9.tgz", + "integrity": "sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^3.2.7", + "is-core-module": "^2.13.0", + "resolve": "^1.22.4" + } + }, + "node_modules/eslint-import-resolver-node/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-import-resolver-typescript": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-typescript/-/eslint-import-resolver-typescript-3.10.1.tgz", + "integrity": "sha512-A1rHYb06zjMGAxdLSkN2fXPBwuSaQ0iO5M/hdyS0Ajj1VBaRp0sPD3dn1FhME3c/JluGFbwSxyCfqdSbtQLAHQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "@nolyfill/is-core-module": "1.0.39", + "debug": "^4.4.0", + "get-tsconfig": "^4.10.0", + "is-bun-module": "^2.0.0", + "stable-hash": "^0.0.5", + "tinyglobby": "^0.2.13", + "unrs-resolver": "^1.6.2" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint-import-resolver-typescript" + }, + "peerDependencies": { + "eslint": "*", + "eslint-plugin-import": "*", + "eslint-plugin-import-x": "*" + }, + "peerDependenciesMeta": { + "eslint-plugin-import": { + "optional": true + }, + "eslint-plugin-import-x": { + "optional": true + } + } + }, + "node_modules/eslint-module-utils": { + "version": "2.12.1", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.12.1.tgz", + "integrity": "sha512-L8jSWTze7K2mTg0vos/RuLRS5soomksDPoJLXIslC7c8Wmut3bx7CPpJijDcBZtxQ5lrbUdM+s0OlNbz0DCDNw==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^3.2.7" + }, + "engines": { + "node": ">=4" + }, + "peerDependenciesMeta": { + "eslint": { + "optional": true + } + } + }, + "node_modules/eslint-module-utils/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-plugin-import": { + "version": "2.32.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.32.0.tgz", + "integrity": "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@rtsao/scc": "^1.1.0", + "array-includes": "^3.1.9", + "array.prototype.findlastindex": "^1.2.6", + "array.prototype.flat": "^1.3.3", + "array.prototype.flatmap": "^1.3.3", + "debug": "^3.2.7", + "doctrine": "^2.1.0", + "eslint-import-resolver-node": "^0.3.9", + "eslint-module-utils": "^2.12.1", + "hasown": "^2.0.2", + "is-core-module": "^2.16.1", + "is-glob": "^4.0.3", + "minimatch": "^3.1.2", + "object.fromentries": "^2.0.8", + "object.groupby": "^1.0.3", + "object.values": "^1.2.1", + "semver": "^6.3.1", + "string.prototype.trimend": "^1.0.9", + "tsconfig-paths": "^3.15.0" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 || ^9" + } + }, + "node_modules/eslint-plugin-import/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-plugin-jsx-a11y": { + "version": "6.10.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.10.2.tgz", + "integrity": "sha512-scB3nz4WmG75pV8+3eRUQOHZlNSUhFNq37xnpgRkCCELU3XMvXAxLk1eqWWyE22Ki4Q01Fnsw9BA3cJHDPgn2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "aria-query": "^5.3.2", + "array-includes": "^3.1.8", + "array.prototype.flatmap": "^1.3.2", + "ast-types-flow": "^0.0.8", + "axe-core": "^4.10.0", + "axobject-query": "^4.1.0", + "damerau-levenshtein": "^1.0.8", + "emoji-regex": "^9.2.2", + "hasown": "^2.0.2", + "jsx-ast-utils": "^3.3.5", + "language-tags": "^1.0.9", + "minimatch": "^3.1.2", + "object.fromentries": "^2.0.8", + "safe-regex-test": "^1.0.3", + "string.prototype.includes": "^2.0.1" + }, + "engines": { + "node": ">=4.0" + }, + "peerDependencies": { + "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9" + } + }, + "node_modules/eslint-plugin-react": { + "version": "7.37.5", + "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.37.5.tgz", + "integrity": "sha512-Qteup0SqU15kdocexFNAJMvCJEfa2xUKNV4CC1xsVMrIIqEy3SQ/rqyxCWNzfrd3/ldy6HMlD2e0JDVpDg2qIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-includes": "^3.1.8", + "array.prototype.findlast": "^1.2.5", + "array.prototype.flatmap": "^1.3.3", + "array.prototype.tosorted": "^1.1.4", + "doctrine": "^2.1.0", + "es-iterator-helpers": "^1.2.1", + "estraverse": "^5.3.0", + "hasown": "^2.0.2", + "jsx-ast-utils": "^2.4.1 || ^3.0.0", + "minimatch": "^3.1.2", + "object.entries": "^1.1.9", + "object.fromentries": "^2.0.8", + "object.values": "^1.2.1", + "prop-types": "^15.8.1", + "resolve": "^2.0.0-next.5", + "semver": "^6.3.1", + "string.prototype.matchall": "^4.0.12", + "string.prototype.repeat": "^1.0.0" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7" + } + }, + "node_modules/eslint-plugin-react-hooks": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-7.0.1.tgz", + "integrity": "sha512-O0d0m04evaNzEPoSW+59Mezf8Qt0InfgGIBJnpC0h3NH/WjUAR7BIKUfysC6todmtiZ/A0oUVS8Gce0WhBrHsA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.24.4", + "@babel/parser": "^7.24.4", + "hermes-parser": "^0.25.1", + "zod": "^3.25.0 || ^4.0.0", + "zod-validation-error": "^3.5.0 || ^4.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0" + } + }, + "node_modules/eslint-plugin-react/node_modules/resolve": { + "version": "2.0.0-next.6", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.6.tgz", + "integrity": "sha512-3JmVl5hMGtJ3kMmB3zi3DL25KfkCEyy3Tw7Gmw7z5w8M9WlwoPFnIvwChzu1+cF3iaK3sp18hhPz8ANeimdJfA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "is-core-module": "^2.16.1", + "node-exports-info": "^1.6.0", + "object-keys": "^1.1.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/eslint-scope": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", + "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/espree": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", + "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.15.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^4.2.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz", + "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "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/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "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/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-glob": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.1.tgz", + "integrity": "sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fastq": { + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz", + "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==", + "dev": true, + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/file-entry-cache": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "flat-cache": "^4.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.4" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/flatted": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.1.tgz", + "integrity": "sha512-IxfVbRFVlV8V/yRaGzk0UVIcsKKHMSfYw66T/u4nTwlWteQePsxe//LjudR1AMX4tZW3WFCh3Zqa/sjlqpbURQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/for-each": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz", + "integrity": "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-callable": "^1.2.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/function.prototype.name": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.8.tgz", + "integrity": "sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "functions-have-names": "^1.2.3", + "hasown": "^2.0.2", + "is-callable": "^1.2.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/functions-have-names": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/generator-function": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/generator-function/-/generator-function-2.0.1.tgz", + "integrity": "sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/get-symbol-description": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.1.0.tgz", + "integrity": "sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-tsconfig": { + "version": "4.13.6", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.13.6.tgz", + "integrity": "sha512-shZT/QMiSHc/YBLxxOkMtgSid5HFoauqCE3/exfsEcwg1WkeqjG+V40yBbBrsD+jW2HDXcs28xOfcbm2jI8Ddw==", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/globals": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globalthis": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz", + "integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-properties": "^1.2.1", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-bigints": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.1.0.tgz", + "integrity": "sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-proto": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.2.0.tgz", + "integrity": "sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/hermes-estree": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.25.1.tgz", + "integrity": "sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw==", + "dev": true, + "license": "MIT" + }, + "node_modules/hermes-parser": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.25.1.tgz", + "integrity": "sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "hermes-estree": "0.25.1" + } + }, + "node_modules/html-encoding-sniffer": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-4.0.0.tgz", + "integrity": "sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "whatwg-encoding": "^3.1.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/http-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/https-proxy-agent": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/internal-slot": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.1.0.tgz", + "integrity": "sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "hasown": "^2.0.2", + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/is-array-buffer": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz", + "integrity": "sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-async-function": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-async-function/-/is-async-function-2.1.1.tgz", + "integrity": "sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "async-function": "^1.0.0", + "call-bound": "^1.0.3", + "get-proto": "^1.0.1", + "has-tostringtag": "^1.0.2", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-bigint": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.1.0.tgz", + "integrity": "sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-bigints": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-boolean-object": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.2.2.tgz", + "integrity": "sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-bun-module": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-bun-module/-/is-bun-module-2.0.0.tgz", + "integrity": "sha512-gNCGbnnnnFAUGKeZ9PdbyeGYJqewpmc2aKHUEMO5nQPWU9lOmv7jcmQIv+qHD8fXW6W7qfuCwX4rY9LNRjXrkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.7.1" + } + }, + "node_modules/is-bun-module/node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-core-module": { + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", + "dev": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-data-view": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-data-view/-/is-data-view-1.0.2.tgz", + "integrity": "sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "get-intrinsic": "^1.2.6", + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-date-object": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.1.0.tgz", + "integrity": "sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-finalizationregistry": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-finalizationregistry/-/is-finalizationregistry-1.1.1.tgz", + "integrity": "sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-generator-function": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.1.2.tgz", + "integrity": "sha512-upqt1SkGkODW9tsGNG5mtXTXtECizwtS2kA161M+gJPc1xdb/Ax629af6YrTwcOeQHbewrPNlE5Dx7kzvXTizA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.4", + "generator-function": "^2.0.0", + "get-proto": "^1.0.1", + "has-tostringtag": "^1.0.2", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-map": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz", + "integrity": "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-negative-zero": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.3.tgz", + "integrity": "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-number-object": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.1.1.tgz", + "integrity": "sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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/is-regex": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", + "integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-set": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.3.tgz", + "integrity": "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-shared-array-buffer": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.4.tgz", + "integrity": "sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-string": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.1.1.tgz", + "integrity": "sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-symbol": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.1.1.tgz", + "integrity": "sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "has-symbols": "^1.1.0", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-typed-array": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.15.tgz", + "integrity": "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "which-typed-array": "^1.1.16" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakmap": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz", + "integrity": "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakref": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.1.1.tgz", + "integrity": "sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakset": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.4.tgz", + "integrity": "sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "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/iterator.prototype": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/iterator.prototype/-/iterator.prototype-1.1.5.tgz", + "integrity": "sha512-H0dkQoCa3b2VEeKQBOxFph+JAbcrQdE7KC0UkqwpLmv2EC4P41QXP+rqo9wYodACiG5/WM5s9oDApTU8utwj9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.6", + "get-proto": "^1.0.0", + "has-symbols": "^1.1.0", + "set-function-name": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/js-yaml": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsdom": { + "version": "27.0.1", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-27.0.1.tgz", + "integrity": "sha512-SNSQteBL1IlV2zqhwwolaG9CwhIhTvVHWg3kTss/cLE7H/X4644mtPQqYvCfsSrGQWt9hSZcgOXX8bOZaMN+kA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@asamuzakjp/dom-selector": "^6.7.2", + "cssstyle": "^5.3.1", + "data-urls": "^6.0.0", + "decimal.js": "^10.6.0", + "html-encoding-sniffer": "^4.0.0", + "http-proxy-agent": "^7.0.2", + "https-proxy-agent": "^7.0.6", + "is-potential-custom-element-name": "^1.0.1", + "parse5": "^8.0.0", + "rrweb-cssom": "^0.8.0", + "saxes": "^6.0.0", + "symbol-tree": "^3.2.4", + "tough-cookie": "^6.0.0", + "w3c-xmlserializer": "^5.0.0", + "webidl-conversions": "^8.0.0", + "whatwg-encoding": "^3.1.1", + "whatwg-mimetype": "^4.0.0", + "whatwg-url": "^15.1.0", + "ws": "^8.18.3", + "xml-name-validator": "^5.0.0" + }, + "engines": { + "node": ">=20" + }, + "peerDependencies": { + "canvas": "^3.0.0" + }, + "peerDependenciesMeta": { + "canvas": { + "optional": true + } + } + }, + "node_modules/jsdom/node_modules/parse5": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-8.0.0.tgz", + "integrity": "sha512-9m4m5GSgXjL4AjumKzq1Fgfp3Z8rsvjRNbnkVwfu2ImRqE5D0LnY2QfDen18FSY9C573YU5XxSapdHZTZ2WolA==", + "dev": true, + "license": "MIT", + "dependencies": { + "entities": "^6.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/jsx-ast-utils": { + "version": "3.3.5", + "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz", + "integrity": "sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-includes": "^3.1.6", + "array.prototype.flat": "^1.3.1", + "object.assign": "^4.1.4", + "object.values": "^1.1.6" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/language-subtag-registry": { + "version": "0.3.23", + "resolved": "https://registry.npmjs.org/language-subtag-registry/-/language-subtag-registry-0.3.23.tgz", + "integrity": "sha512-0K65Lea881pHotoGEa5gDlMxt3pctLi2RplBb7Ezh4rRdLEOtgi7n4EwK9lamnUCkKBqaeKRVebTq6BAxSkpXQ==", + "dev": true, + "license": "CC0-1.0" + }, + "node_modules/language-tags": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/language-tags/-/language-tags-1.0.9.tgz", + "integrity": "sha512-MbjN408fEndfiQXbFQ1vnd+1NoLDsnQW41410oQBXiyXDMYH5z505juWa4KUE1LqxRC7DgOgZDbKLxHIwm27hA==", + "dev": true, + "license": "MIT", + "dependencies": { + "language-subtag-registry": "^0.3.20" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/loupe": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.2.1.tgz", + "integrity": "sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "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==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "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/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "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/napi-postinstall": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/napi-postinstall/-/napi-postinstall-0.3.4.tgz", + "integrity": "sha512-PHI5f1O0EP5xJ9gQmFGMS6IZcrVvTjpXjz7Na41gTE7eE2hK11lg04CECCYEEjdc17EV4DO+fkGEtt7TpTaTiQ==", + "dev": true, + "license": "MIT", + "bin": { + "napi-postinstall": "lib/cli.js" + }, + "engines": { + "node": "^12.20.0 || ^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/napi-postinstall" + } + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "license": "MIT" + }, + "node_modules/next": { + "version": "16.1.6", + "resolved": "https://registry.npmjs.org/next/-/next-16.1.6.tgz", + "integrity": "sha512-hkyRkcu5x/41KoqnROkfTm2pZVbKxvbZRuNvKXLRXxs3VfyO0WhY50TQS40EuKO9SW3rBj/sF3WbVwDACeMZyw==", + "license": "MIT", + "dependencies": { + "@next/env": "16.1.6", + "@swc/helpers": "0.5.15", + "baseline-browser-mapping": "^2.8.3", + "caniuse-lite": "^1.0.30001579", + "postcss": "8.4.31", + "styled-jsx": "5.1.6" + }, + "bin": { + "next": "dist/bin/next" + }, + "engines": { + "node": ">=20.9.0" + }, + "optionalDependencies": { + "@next/swc-darwin-arm64": "16.1.6", + "@next/swc-darwin-x64": "16.1.6", + "@next/swc-linux-arm64-gnu": "16.1.6", + "@next/swc-linux-arm64-musl": "16.1.6", + "@next/swc-linux-x64-gnu": "16.1.6", + "@next/swc-linux-x64-musl": "16.1.6", + "@next/swc-win32-arm64-msvc": "16.1.6", + "@next/swc-win32-x64-msvc": "16.1.6", + "sharp": "^0.34.4" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.1.0", + "@playwright/test": "^1.51.1", + "babel-plugin-react-compiler": "*", + "react": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", + "react-dom": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", + "sass": "^1.3.0" + }, + "peerDependenciesMeta": { + "@opentelemetry/api": { + "optional": true + }, + "@playwright/test": { + "optional": true + }, + "babel-plugin-react-compiler": { + "optional": true + }, + "sass": { + "optional": true + } + } + }, + "node_modules/next/node_modules/postcss": { + "version": "8.4.31", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", + "integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==", + "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.6", + "picocolors": "^1.0.0", + "source-map-js": "^1.0.2" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/node-exports-info": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/node-exports-info/-/node-exports-info-1.6.0.tgz", + "integrity": "sha512-pyFS63ptit/P5WqUkt+UUfe+4oevH+bFeIiPPdfb0pFeYEu/1ELnJu5l+5EcTKYL5M7zaAa7S8ddywgXypqKCw==", + "dev": true, + "license": "MIT", + "dependencies": { + "array.prototype.flatmap": "^1.3.3", + "es-errors": "^1.3.0", + "object.entries": "^1.1.9", + "semver": "^6.3.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/node-releases": { + "version": "2.0.36", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.36.tgz", + "integrity": "sha512-TdC8FSgHz8Mwtw9g5L4gR/Sh9XhSP/0DEkQxfEFXOpiul5IiHgHan2VhYYb6agDSfp4KuvltmGApc8HMgUrIkA==", + "dev": true, + "license": "MIT" + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.assign": { + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.7.tgz", + "integrity": "sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0", + "has-symbols": "^1.1.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.entries": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.9.tgz", + "integrity": "sha512-8u/hfXFRBD1O0hPUjioLhoWFHRmt6tKA4/vZPyckBr18l1KE9uHrFaFaUi8MDRTpi4uak2goyPTSNJLXX2k2Hw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.fromentries": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.8.tgz", + "integrity": "sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.groupby": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/object.groupby/-/object.groupby-1.0.3.tgz", + "integrity": "sha512-+Lhy3TQTuzXI5hevh8sBGqbmurHbbIjAi0Z4S63nthVLmLxfbj4T54a4CfZrXIrt9iP4mVAPYMo/v99taj3wjQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.values": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.2.1.tgz", + "integrity": "sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/own-keys": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/own-keys/-/own-keys-1.0.1.tgz", + "integrity": "sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "get-intrinsic": "^1.2.6", + "object-keys": "^1.1.1", + "safe-push-apply": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse5": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz", + "integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "entities": "^6.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "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-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true, + "license": "MIT" + }, + "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/pathval": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.1.tgz", + "integrity": "sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14.16" + } + }, + "node_modules/pg-int8": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz", + "integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==", + "license": "ISC", + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/pg-protocol": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.13.0.tgz", + "integrity": "sha512-zzdvXfS6v89r6v7OcFCHfHlyG/wvry1ALxZo4LqgUoy7W9xhBDMaqOuMiF3qEV45VqsN6rdlcehHrfDtlCPc8w==", + "license": "MIT" + }, + "node_modules/pg-types": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/pg-types/-/pg-types-2.2.0.tgz", + "integrity": "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==", + "license": "MIT", + "dependencies": { + "pg-int8": "1.0.1", + "postgres-array": "~2.0.0", + "postgres-bytea": "~1.0.0", + "postgres-date": "~1.0.4", + "postgres-interval": "^1.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "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": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/possible-typed-array-names": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", + "integrity": "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/postcss": { + "version": "8.5.8", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.8.tgz", + "integrity": "sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==", + "dev": true, + "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.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postgres-array": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz", + "integrity": "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/postgres-bytea": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.1.tgz", + "integrity": "sha512-5+5HqXnsZPE65IJZSMkZtURARZelel2oXUEO8rH83VS/hxH5vv1uHquPg5wZs8yMAfdv971IU+kcPUczi7NVBQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/postgres-date": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.7.tgz", + "integrity": "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/postgres-interval": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.2.0.tgz", + "integrity": "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==", + "license": "MIT", + "dependencies": { + "xtend": "^4.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/prop-types": { + "version": "15.8.1", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "dev": true, + "license": "MIT", + "dependencies": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.13.1" + } + }, + "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/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/react": { + "version": "19.2.3", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.3.tgz", + "integrity": "sha512-Ku/hhYbVjOQnXDZFv2+RibmLFGwFdeeKHFcOTlrt7xplBnya5OGn/hIRDsqDiSUcfORsDC7MPxwork8jBwsIWA==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "19.2.3", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.3.tgz", + "integrity": "sha512-yELu4WmLPw5Mr/lmeEpox5rw3RETacE++JgHqQzd2dg+YbJuat3jH4ingc+WPZhxaoFzdv9y33G+F7Nl5O0GBg==", + "license": "MIT", + "dependencies": { + "scheduler": "^0.27.0" + }, + "peerDependencies": { + "react": "^19.2.3" + } + }, + "node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/reflect.getprototypeof": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz", + "integrity": "sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.9", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.7", + "get-proto": "^1.0.1", + "which-builtin-type": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/regexp.prototype.flags": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz", + "integrity": "sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-errors": "^1.3.0", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "set-function-name": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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/resolve": { + "version": "1.22.11", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz", + "integrity": "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-core-module": "^2.16.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/resolve-pkg-maps": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + } + }, + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rollup": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.59.0.tgz", + "integrity": "sha512-2oMpl67a3zCH9H79LeMcbDhXW/UmWG/y2zuqnF2jQq5uq9TbM9TVyXvA4+t+ne2IIkBdrLpAaRQAvo7YI/Yyeg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.59.0", + "@rollup/rollup-android-arm64": "4.59.0", + "@rollup/rollup-darwin-arm64": "4.59.0", + "@rollup/rollup-darwin-x64": "4.59.0", + "@rollup/rollup-freebsd-arm64": "4.59.0", + "@rollup/rollup-freebsd-x64": "4.59.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.59.0", + "@rollup/rollup-linux-arm-musleabihf": "4.59.0", + "@rollup/rollup-linux-arm64-gnu": "4.59.0", + "@rollup/rollup-linux-arm64-musl": "4.59.0", + "@rollup/rollup-linux-loong64-gnu": "4.59.0", + "@rollup/rollup-linux-loong64-musl": "4.59.0", + "@rollup/rollup-linux-ppc64-gnu": "4.59.0", + "@rollup/rollup-linux-ppc64-musl": "4.59.0", + "@rollup/rollup-linux-riscv64-gnu": "4.59.0", + "@rollup/rollup-linux-riscv64-musl": "4.59.0", + "@rollup/rollup-linux-s390x-gnu": "4.59.0", + "@rollup/rollup-linux-x64-gnu": "4.59.0", + "@rollup/rollup-linux-x64-musl": "4.59.0", + "@rollup/rollup-openbsd-x64": "4.59.0", + "@rollup/rollup-openharmony-arm64": "4.59.0", + "@rollup/rollup-win32-arm64-msvc": "4.59.0", + "@rollup/rollup-win32-ia32-msvc": "4.59.0", + "@rollup/rollup-win32-x64-gnu": "4.59.0", + "@rollup/rollup-win32-x64-msvc": "4.59.0", + "fsevents": "~2.3.2" + } + }, + "node_modules/rrweb-cssom": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.8.0.tgz", + "integrity": "sha512-guoltQEx+9aMf2gDZ0s62EcV8lsXR+0w8915TC3ITdn2YueuNjdAYh/levpU9nFaoChh9RUS5ZdQMrKfVEN9tw==", + "dev": true, + "license": "MIT" + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/safe-array-concat": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.3.tgz", + "integrity": "sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "get-intrinsic": "^1.2.6", + "has-symbols": "^1.1.0", + "isarray": "^2.0.5" + }, + "engines": { + "node": ">=0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safe-push-apply": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/safe-push-apply/-/safe-push-apply-1.0.0.tgz", + "integrity": "sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "isarray": "^2.0.5" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safe-regex-test": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.1.0.tgz", + "integrity": "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "is-regex": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true, + "license": "MIT" + }, + "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/scheduler": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", + "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", + "license": "MIT" + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/set-function-name": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz", + "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "functions-have-names": "^1.2.3", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/set-proto": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/set-proto/-/set-proto-1.0.0.tgz", + "integrity": "sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/sharp": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.34.5.tgz", + "integrity": "sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==", + "hasInstallScript": true, + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@img/colour": "^1.0.0", + "detect-libc": "^2.1.2", + "semver": "^7.7.3" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-darwin-arm64": "0.34.5", + "@img/sharp-darwin-x64": "0.34.5", + "@img/sharp-libvips-darwin-arm64": "1.2.4", + "@img/sharp-libvips-darwin-x64": "1.2.4", + "@img/sharp-libvips-linux-arm": "1.2.4", + "@img/sharp-libvips-linux-arm64": "1.2.4", + "@img/sharp-libvips-linux-ppc64": "1.2.4", + "@img/sharp-libvips-linux-riscv64": "1.2.4", + "@img/sharp-libvips-linux-s390x": "1.2.4", + "@img/sharp-libvips-linux-x64": "1.2.4", + "@img/sharp-libvips-linuxmusl-arm64": "1.2.4", + "@img/sharp-libvips-linuxmusl-x64": "1.2.4", + "@img/sharp-linux-arm": "0.34.5", + "@img/sharp-linux-arm64": "0.34.5", + "@img/sharp-linux-ppc64": "0.34.5", + "@img/sharp-linux-riscv64": "0.34.5", + "@img/sharp-linux-s390x": "0.34.5", + "@img/sharp-linux-x64": "0.34.5", + "@img/sharp-linuxmusl-arm64": "0.34.5", + "@img/sharp-linuxmusl-x64": "0.34.5", + "@img/sharp-wasm32": "0.34.5", + "@img/sharp-win32-arm64": "0.34.5", + "@img/sharp-win32-ia32": "0.34.5", + "@img/sharp-win32-x64": "0.34.5" + } + }, + "node_modules/sharp/node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "license": "ISC", + "optional": true, + "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/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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/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/stable-hash": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/stable-hash/-/stable-hash-0.0.5.tgz", + "integrity": "sha512-+L3ccpzibovGXFK+Ap/f8LOS0ahMrHTf3xu7mMLSpEGU0EO9ucaysSylKo9eRDFNhWve/y275iPmIZ4z39a9iA==", + "dev": true, + "license": "MIT" + }, + "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": "3.10.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.10.0.tgz", + "integrity": "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==", + "dev": true, + "license": "MIT" + }, + "node_modules/stop-iteration-iterator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.1.0.tgz", + "integrity": "sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "internal-slot": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/string.prototype.includes": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/string.prototype.includes/-/string.prototype.includes-2.0.1.tgz", + "integrity": "sha512-o7+c9bW6zpAdJHTtujeePODAhkuicdAryFsfVKwA+wGw89wJ4GTY484WTucM9hLtDEOpOvI+aHnzqnC5lHp4Rg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.3" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/string.prototype.matchall": { + "version": "4.0.12", + "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.12.tgz", + "integrity": "sha512-6CC9uyBL+/48dYizRf7H7VAYCMCNTBeM78x/VTUe9bFEaxBepPJDa1Ow99LqI/1yF7kuy7Q3cQsYMrcjGUcskA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.6", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.6", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "internal-slot": "^1.1.0", + "regexp.prototype.flags": "^1.5.3", + "set-function-name": "^2.0.2", + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.repeat": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/string.prototype.repeat/-/string.prototype.repeat-1.0.0.tgz", + "integrity": "sha512-0u/TldDbKD8bFCQ/4f5+mNRrXwZ8hg2w7ZR8wa16e8z9XpePWl3eGEcUD0OXpEH/VJH/2G3gjUtR3ZOiBe2S/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.5" + } + }, + "node_modules/string.prototype.trim": { + "version": "1.2.10", + "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.10.tgz", + "integrity": "sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "define-data-property": "^1.1.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-object-atoms": "^1.0.0", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimend": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.9.tgz", + "integrity": "sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimstart": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz", + "integrity": "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/strip-literal": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-3.1.0.tgz", + "integrity": "sha512-8r3mkIM/2+PpjHoOtiAW8Rg3jJLHaV7xPwG+YRGrv6FP0wwk/toTpATxWYOW0BKdWwl82VT2tFYi5DlROa0Mxg==", + "dev": true, + "license": "MIT", + "dependencies": { + "js-tokens": "^9.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/strip-literal/node_modules/js-tokens": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-9.0.1.tgz", + "integrity": "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/styled-jsx": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.1.6.tgz", + "integrity": "sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA==", + "license": "MIT", + "dependencies": { + "client-only": "0.0.1" + }, + "engines": { + "node": ">= 12.0.0" + }, + "peerDependencies": { + "react": ">= 16.8.0 || 17.x.x || ^18.0.0-0 || ^19.0.0-0" + }, + "peerDependenciesMeta": { + "@babel/core": { + "optional": true + }, + "babel-plugin-macros": { + "optional": true + } + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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/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/tinyglobby": { + "version": "0.2.15", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinyglobby/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/tinyglobby/node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/tinypool": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.1.1.tgz", + "integrity": "sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.0.0 || >=20.0.0" + } + }, + "node_modules/tinyrainbow": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-2.0.0.tgz", + "integrity": "sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tinyspy": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-4.0.4.tgz", + "integrity": "sha512-azl+t0z7pw/z958Gy9svOTuzqIk6xq+NSheJzn5MMWtWTFywIacg2wUlzKFGtt3cthx0r2SxMK0yzJOR0IES7Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tldts": { + "version": "7.0.25", + "resolved": "https://registry.npmjs.org/tldts/-/tldts-7.0.25.tgz", + "integrity": "sha512-keinCnPbwXEUG3ilrWQZU+CqcTTzHq9m2HhoUP2l7Xmi8l1LuijAXLpAJ5zRW+ifKTNscs4NdCkfkDCBYm352w==", + "dev": true, + "license": "MIT", + "dependencies": { + "tldts-core": "^7.0.25" + }, + "bin": { + "tldts": "bin/cli.js" + } + }, + "node_modules/tldts-core": { + "version": "7.0.25", + "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-7.0.25.tgz", + "integrity": "sha512-ZjCZK0rppSBu7rjHYDYsEaMOIbbT+nWF57hKkv4IUmZWBNrBWBOjIElc0mKRgLM8bm7x/BBlof6t2gi/Oq/Asw==", + "dev": true, + "license": "MIT" + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "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/ts-api-utils": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.4.0.tgz", + "integrity": "sha512-3TaVTaAv2gTiMB35i3FiGJaRfwb3Pyn/j3m/bfAvGe8FB7CF6u+LMYqYlDh7reQf7UNvoTvdfAqHGmPGOSsPmA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.12" + }, + "peerDependencies": { + "typescript": ">=4.8.4" + } + }, + "node_modules/tsconfig-paths": { + "version": "3.15.0", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz", + "integrity": "sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/json5": "^0.0.29", + "json5": "^1.0.2", + "minimist": "^1.2.6", + "strip-bom": "^3.0.0" + } + }, + "node_modules/tsconfig-paths/node_modules/json5": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", + "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "minimist": "^1.2.0" + }, + "bin": { + "json5": "lib/cli.js" + } + }, + "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==", + "license": "0BSD" + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/typed-array-buffer": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz", + "integrity": "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/typed-array-byte-length": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.3.tgz", + "integrity": "sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "for-each": "^0.3.3", + "gopd": "^1.2.0", + "has-proto": "^1.2.0", + "is-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-byte-offset": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.4.tgz", + "integrity": "sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "for-each": "^0.3.3", + "gopd": "^1.2.0", + "has-proto": "^1.2.0", + "is-typed-array": "^1.1.15", + "reflect.getprototypeof": "^1.0.9" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-length": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.7.tgz", + "integrity": "sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "is-typed-array": "^1.1.13", + "possible-typed-array-names": "^1.0.0", + "reflect.getprototypeof": "^1.0.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/typescript-eslint": { + "version": "8.57.0", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.57.0.tgz", + "integrity": "sha512-W8GcigEMEeB07xEZol8oJ26rigm3+bfPHxHvwbYUlu1fUDsGuQ7Hiskx5xGW/xM4USc9Ephe3jtv7ZYPQntHeA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/eslint-plugin": "8.57.0", + "@typescript-eslint/parser": "8.57.0", + "@typescript-eslint/typescript-estree": "8.57.0", + "@typescript-eslint/utils": "8.57.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/unbox-primitive": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.1.0.tgz", + "integrity": "sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-bigints": "^1.0.2", + "has-symbols": "^1.1.0", + "which-boxed-primitive": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "license": "MIT" + }, + "node_modules/unrs-resolver": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/unrs-resolver/-/unrs-resolver-1.11.1.tgz", + "integrity": "sha512-bSjt9pjaEBnNiGgc9rUiHGKv5l4/TGzDmYw3RhnkJGtLhbnnA/5qJj7x3dNDCRx/PJxu774LlH8lCOlB4hEfKg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "napi-postinstall": "^0.3.0" + }, + "funding": { + "url": "https://opencollective.com/unrs-resolver" + }, + "optionalDependencies": { + "@unrs/resolver-binding-android-arm-eabi": "1.11.1", + "@unrs/resolver-binding-android-arm64": "1.11.1", + "@unrs/resolver-binding-darwin-arm64": "1.11.1", + "@unrs/resolver-binding-darwin-x64": "1.11.1", + "@unrs/resolver-binding-freebsd-x64": "1.11.1", + "@unrs/resolver-binding-linux-arm-gnueabihf": "1.11.1", + "@unrs/resolver-binding-linux-arm-musleabihf": "1.11.1", + "@unrs/resolver-binding-linux-arm64-gnu": "1.11.1", + "@unrs/resolver-binding-linux-arm64-musl": "1.11.1", + "@unrs/resolver-binding-linux-ppc64-gnu": "1.11.1", + "@unrs/resolver-binding-linux-riscv64-gnu": "1.11.1", + "@unrs/resolver-binding-linux-riscv64-musl": "1.11.1", + "@unrs/resolver-binding-linux-s390x-gnu": "1.11.1", + "@unrs/resolver-binding-linux-x64-gnu": "1.11.1", + "@unrs/resolver-binding-linux-x64-musl": "1.11.1", + "@unrs/resolver-binding-wasm32-wasi": "1.11.1", + "@unrs/resolver-binding-win32-arm64-msvc": "1.11.1", + "@unrs/resolver-binding-win32-ia32-msvc": "1.11.1", + "@unrs/resolver-binding-win32-x64-msvc": "1.11.1" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/vite": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.1.tgz", + "integrity": "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.27.0", + "fdir": "^6.5.0", + "picomatch": "^4.0.3", + "postcss": "^8.5.6", + "rollup": "^4.43.0", + "tinyglobby": "^0.2.15" + }, + "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", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "lightningcss": "^1.21.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 + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "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/vite-node": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-3.2.4.tgz", + "integrity": "sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cac": "^6.7.14", + "debug": "^4.4.1", + "es-module-lexer": "^1.7.0", + "pathe": "^2.0.3", + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0" + }, + "bin": { + "vite-node": "vite-node.mjs" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/vite/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/vite/node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/vitest": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-3.2.4.tgz", + "integrity": "sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/chai": "^5.2.2", + "@vitest/expect": "3.2.4", + "@vitest/mocker": "3.2.4", + "@vitest/pretty-format": "^3.2.4", + "@vitest/runner": "3.2.4", + "@vitest/snapshot": "3.2.4", + "@vitest/spy": "3.2.4", + "@vitest/utils": "3.2.4", + "chai": "^5.2.0", + "debug": "^4.4.1", + "expect-type": "^1.2.1", + "magic-string": "^0.30.17", + "pathe": "^2.0.3", + "picomatch": "^4.0.2", + "std-env": "^3.9.0", + "tinybench": "^2.9.0", + "tinyexec": "^0.3.2", + "tinyglobby": "^0.2.14", + "tinypool": "^1.1.1", + "tinyrainbow": "^2.0.0", + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0", + "vite-node": "3.2.4", + "why-is-node-running": "^2.3.0" + }, + "bin": { + "vitest": "vitest.mjs" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@types/debug": "^4.1.12", + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "@vitest/browser": "3.2.4", + "@vitest/ui": "3.2.4", + "happy-dom": "*", + "jsdom": "*" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@types/debug": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + } + } + }, + "node_modules/vitest/node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/vitest/node_modules/tinyexec": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.2.tgz", + "integrity": "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==", + "dev": true, + "license": "MIT" + }, + "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-encoding": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz", + "integrity": "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==", + "deprecated": "Use @exodus/bytes instead for a more spec-conformant and faster implementation", + "dev": true, + "license": "MIT", + "dependencies": { + "iconv-lite": "0.6.3" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/whatwg-encoding/node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/whatwg-mimetype": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz", + "integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/whatwg-url": { + "version": "15.1.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-15.1.0.tgz", + "integrity": "sha512-2ytDk0kiEj/yu90JOAp44PVPUkO9+jVhyf+SybKlRHSDlvOOZhdPIrr7xTH64l4WixO2cP+wQIcgujkGBPPz6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "tr46": "^6.0.0", + "webidl-conversions": "^8.0.0" + }, + "engines": { + "node": ">=20" + } + }, + "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/which-boxed-primitive": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.1.1.tgz", + "integrity": "sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-bigint": "^1.1.0", + "is-boolean-object": "^1.2.1", + "is-number-object": "^1.1.1", + "is-string": "^1.1.1", + "is-symbol": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-builtin-type": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/which-builtin-type/-/which-builtin-type-1.2.1.tgz", + "integrity": "sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "function.prototype.name": "^1.1.6", + "has-tostringtag": "^1.0.2", + "is-async-function": "^2.0.0", + "is-date-object": "^1.1.0", + "is-finalizationregistry": "^1.1.0", + "is-generator-function": "^1.0.10", + "is-regex": "^1.2.1", + "is-weakref": "^1.0.2", + "isarray": "^2.0.5", + "which-boxed-primitive": "^1.1.0", + "which-collection": "^1.0.2", + "which-typed-array": "^1.1.16" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-collection": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.2.tgz", + "integrity": "sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-map": "^2.0.3", + "is-set": "^2.0.3", + "is-weakmap": "^2.0.2", + "is-weakset": "^2.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-typed-array": { + "version": "1.1.20", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.20.tgz", + "integrity": "sha512-LYfpUkmqwl0h9A2HL09Mms427Q1RZWuOHsukfVcKRq9q95iQxdw0ix1JQrqbcDR9PH1QDwf5Qo8OZb5lksZ8Xg==", + "dev": true, + "license": "MIT", + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "for-each": "^0.3.5", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ws": { + "version": "8.19.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.19.0.tgz", + "integrity": "sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "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" + }, + "node_modules/xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "license": "MIT", + "engines": { + "node": ">=0.4" + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/zod": { + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.3.6.tgz", + "integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/zod-validation-error": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/zod-validation-error/-/zod-validation-error-4.0.2.tgz", + "integrity": "sha512-Q6/nZLe6jxuU80qb/4uJ4t5v2VEZ44lzQjPDhYJNztRQ4wyWc6VF3D3Kb/fAuPetZQnhS3hnajCf9CsWesghLQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "zod": "^3.25.0 || ^4.0.0" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..01c85ba --- /dev/null +++ b/package.json @@ -0,0 +1,36 @@ +{ + "name": "voice-diagram-comment-tool", + "version": "0.1.0", + "private": true, + "scripts": { + "dev": "next dev", + "build": "next build", + "vercel-build": "npm run build:widget && next build", + "start": "next start", + "lint": "eslint", + "test": "vitest run", + "test:watch": "vitest", + "build:widget": "esbuild src/widget/index.ts --bundle --format=iife --outfile=public/widget.js --minify", + "dev:widget": "esbuild src/widget/index.ts --bundle --format=iife --outfile=public/widget.js --watch", + "db:migrate": "npx tsx scripts/migrate.ts" + }, + "dependencies": { + "@neondatabase/serverless": "^1.0.2", + "next": "16.1.6", + "react": "19.2.3", + "react-dom": "19.2.3" + }, + "devDependencies": { + "@types/jsdom": "^28.0.1", + "@types/node": "^20", + "@types/react": "^19", + "@types/react-dom": "^19", + "dotenv": "^17.3.1", + "esbuild": "^0.27.4", + "eslint": "^9", + "eslint-config-next": "16.1.6", + "jsdom": "^27.0.1", + "typescript": "5.9.3", + "vitest": "^3.2.4" + } +} diff --git a/public/feedback-demo.html b/public/feedback-demo.html new file mode 100644 index 0000000..ab2005a --- /dev/null +++ b/public/feedback-demo.html @@ -0,0 +1,111 @@ + + + + + + + HTTPリクエストの流れ(フィードバック確認用) + + + + + + + +
+
+
+ フィードバック確認用サンプル +
+

+ HTTPリクエストの流れ +

+

+ テキストを選択すると、右下のフィードバックボタンからコメントできます。 +

+
+ +
+

4ステップで理解する

+ +
+
+
1
+
+

ブラウザがリクエストを送る

+

ユーザーがURLを開くと、ブラウザはサーバーへ「このページをください」とHTTPリクエストを送ります。

+
+
+ +
+
2
+
+

サーバーが処理する

+

Webサーバーはリクエストを受け取り、必要なファイルやデータを探して応答内容を組み立てます。

+
+
+ +
+
3
+
+

レスポンスが返る

+

サーバーはHTTPレスポンスとしてHTMLやJSON、画像などをブラウザへ返します。ステータスコード200は成功を意味します。

+
+
+ +
+
4
+
+

ブラウザが表示する

+

ブラウザは受け取った内容を解釈し、画面上にページとして描画します。CSSやJavaScriptもここで適用されます。

+
+
+
+
+ +
+

このページの使い方

+
    +
  • 画面右端中央のボタン → フィードバックサイドバーを開く
  • +
  • 本文テキストをドラッグ選択 → 引用付きコメントを追加
  • +
  • サイドバーのピン・マイクボタン → ピン留めコメント/音声フィードバックを試せる
  • +
  • ローカルの /widget.js を読み込んでいます(npm run dev で起動して確認)
  • +
+
+ +
+ +
+

音声図解コメントツール / ローカル動作確認用デモ

+
+ + + + + diff --git a/public/widget.js b/public/widget.js new file mode 100644 index 0000000..e6b0f69 --- /dev/null +++ b/public/widget.js @@ -0,0 +1,2 @@ +"use strict";(()=>{var k={must:{bg:"#ef4444",text:"#fff",light:"rgba(239,68,68,0.1)",border:"rgba(239,68,68,0.3)"},better:{bg:"#f59e0b",text:"#fff",light:"rgba(245,158,11,0.1)",border:"rgba(245,158,11,0.3)"},want:{bg:"#22c55e",text:"#fff",light:"rgba(34,197,94,0.1)",border:"rgba(34,197,94,0.3)"}};var re={must:"better",better:"want",want:"must"};var H="fb-username",O="fb-sidebar-width";function ie(e){return e.replace(/^https?:\/\//,"").replace(/[^a-zA-Z0-9\u3040-\u309F\u30A0-\u30FF\u4E00-\u9FFF]/g,"_").substring(0,100)}function B(){return Date.now().toString(36)+Math.random().toString(36).substring(2,8)}function j(e){let t={"Content-Type":"application/json"};return e&&(t.Authorization="Bearer "+e),t}var Y="",U="";function ae(e,t){Y=e,U=t}function E(e,t){let r=Y+"/api/comments",n=j(U),i={method:e,headers:n};return e==="GET"?r+="?slug="+encodeURIComponent(t.slug):e==="DELETE"?r+="?id="+encodeURIComponent(t.id):i.body=JSON.stringify(t),fetch(r,i).then(a=>a.json())}function se(e,t,r){return fetch(Y+"/api/voice-analyze",{method:"POST",headers:j(U),body:JSON.stringify({audioBase64:e,mimeType:t,targets:r})}).then(n=>n.json())}var o={username:localStorage.getItem(H)||"",comments:[],filter:"unresolved",sidebarOpen:!1,selectedText:"",selectedQuoteContext:{beforeText:"",afterText:""},selectedRect:null,popupContent:"",editingId:null,editContent:"",editPriority:"want",replyingTo:null,replyText:"",editingName:!1,nameInput:"",popupPriority:"must",sidebarWidth:parseInt(localStorage.getItem(O)||"",10)||400,pinMode:!1,pinPopupPos:null,editingPinId:null,voiceRecording:!1,voiceProcessing:!1,voiceError:null},M=ie(window.location.href);function de(){let e=document.createElement("style");e.id="fb-widget-styles";let t=`url('data:image/svg+xml,%3Csvg%20xmlns="http://www.w3.org/2000/svg"%20width="28"%20height="28"%20viewBox="0%200%2028%2028"%3E%3Cpath%20d="M14%202a8%208%200%200%200-8%208c0%205.5%208%2016%208%2016s8-10.5%208-16a8%208%200%200%200-8-8Z"%20fill="%233b82f6"%20stroke="%23ffffff"%20stroke-width="1.6"%20stroke-linejoin="round"/%3E%3Ccircle%20cx="14"%20cy="10"%20r="3"%20fill="%23ffffff"/%3E%3C/svg%3E') 14 26, auto`;e.textContent=[':root{--fb-bg:#ffffff;--fb-fg:#0a0a0a;--fb-muted:#f5f5f5;--fb-muted-fg:#737373;--fb-border:#e5e5e5;--fb-primary:#171717;--fb-primary-fg:#fafafa;--fb-accent:#3b82f6;--fb-destructive:#ef4444;--fb-font:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Hiragino Sans",sans-serif}',"#fb-toggle{position:fixed;right:0;top:50%;transform:translateY(-50%);width:36px;background:var(--fb-bg);border:1px solid var(--fb-border);border-right:none;border-radius:8px 0 0 8px;cursor:pointer;z-index:99999;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:4px;font-family:var(--fb-font);transition:all .15s;box-shadow:-2px 0 8px rgba(0,0,0,0.06);padding:8px 0}","#fb-toggle:hover{background:var(--fb-muted);box-shadow:-2px 0 12px rgba(0,0,0,0.1)}","#fb-toggle .fb-toggle-icon{color:var(--fb-muted-fg);display:flex;align-items:center;justify-content:center;position:relative}","#fb-toggle .fb-toggle-label{font-size:10px;color:var(--fb-accent);font-weight:700;writing-mode:vertical-rl;letter-spacing:1px;line-height:1;white-space:nowrap}","#fb-toggle .fb-badge{position:absolute;top:-6px;left:-6px;background:var(--fb-destructive);color:#fff;font-size:9px;font-weight:700;min-width:16px;height:16px;border-radius:8px;display:flex;align-items:center;justify-content:center;padding:0 3px;line-height:1}","#fb-sidebar{position:fixed;top:0;height:100vh;background:rgba(245,245,245,0.5);border-left:1px solid var(--fb-border);z-index:99998;transition:right .3s ease;display:flex;flex-direction:column;font-family:var(--fb-font);font-size:14px;color:var(--fb-fg)}","#fb-sidebar *{box-sizing:border-box}",".fb-resize-handle{position:absolute;left:-3px;top:0;width:6px;height:100%;cursor:col-resize;z-index:1}",".fb-resize-handle:hover{background:rgba(59,130,246,0.15)}",".fb-resize-handle.active{background:rgba(59,130,246,0.3)}","body.fb-resizing{cursor:col-resize !important;-webkit-user-select:none !important;user-select:none !important}","body.fb-resizing *{cursor:col-resize !important}",".fb-header{display:flex;align-items:center;justify-content:space-between;padding:12px 16px;border-bottom:1px solid var(--fb-border)}",".fb-header-left{display:flex;align-items:center;gap:8px}",".fb-header-title{font-size:14px;font-weight:700;color:var(--fb-accent)}",".fb-header-count{background:var(--fb-muted);color:var(--fb-muted-fg);font-size:11px;padding:1px 7px;border-radius:10px}",".fb-header-actions{display:flex;align-items:center;gap:2px}",".fb-hdr-btn{background:none;border:none;cursor:pointer;padding:6px;border-radius:6px;color:var(--fb-muted-fg);transition:all .15s;display:inline-flex;align-items:center;gap:4px;font-family:var(--fb-font);font-size:12px}",".fb-hdr-btn:hover{background:var(--fb-muted);color:var(--fb-fg)}",".fb-user-row{display:flex;align-items:center;padding:8px 16px;border-bottom:1px solid var(--fb-border);font-size:13px;color:var(--fb-muted-fg);cursor:pointer;transition:color .15s;gap:6px}",".fb-user-row:hover{color:var(--fb-fg)}",".fb-filters{display:flex;border-bottom:1px solid var(--fb-border)}",".fb-filter{flex:1;padding:10px 0;text-align:center;font-size:13px;color:var(--fb-muted-fg);border:none;background:none;cursor:pointer;border-bottom:2px solid transparent;transition:all .15s;font-family:var(--fb-font)}",".fb-filter:hover{color:var(--fb-fg)}",".fb-filter.active{color:var(--fb-fg);border-bottom-color:var(--fb-accent)}",".fb-filter .cnt{font-size:11px;margin-left:4px;padding:1px 5px;border-radius:8px;background:var(--fb-muted);color:var(--fb-muted-fg)}",".fb-filter.active .cnt{background:rgba(59,130,246,0.1);color:var(--fb-accent)}",".fb-list{flex:1;overflow-y:auto;padding:8px}",".fb-empty{text-align:center;padding:60px 20px;color:var(--fb-muted-fg);font-size:13px}",".fb-empty svg{margin:0 auto 12px;display:block;color:var(--fb-border)}",".fb-card{background:var(--fb-bg);border:1px solid var(--fb-border);border-left:3px solid var(--fb-border);border-radius:12px;padding:14px;margin-bottom:6px;transition:box-shadow .3s,background .3s;cursor:pointer}",".fb-card:hover{box-shadow:0 1px 3px rgba(0,0,0,0.05)}",".fb-card.resolved{opacity:.5}",".fb-card-head{display:flex;align-items:center;justify-content:space-between;margin-bottom:4px}",".fb-card-head-left{display:flex;align-items:center;gap:6px}",".fb-avatar{width:22px;height:22px;border-radius:50%;background:var(--fb-primary);color:var(--fb-primary-fg);display:flex;align-items:center;justify-content:center;font-size:10px;font-weight:700;flex-shrink:0}",".fb-author{font-size:13px;font-weight:700;color:var(--fb-fg)}",".fb-time{font-size:11px;color:var(--fb-muted-fg)}",".fb-resolved-mark{font-size:11px;color:#22c55e;margin-left:4px;display:inline-flex;align-items:center;gap:2px}",".fb-badge-p{font-size:11px;font-weight:700;padding:2px 8px;border-radius:4px;color:#fff;cursor:default;transition:all .15s}",".fb-badge-p.own{cursor:pointer}",".fb-badge-p.own:hover{transform:scale(1.1);box-shadow:0 0 0 2px rgba(0,0,0,0.08)}",".fb-quote{font-size:12px;color:var(--fb-muted-fg);padding:6px 10px;background:var(--fb-muted);border-left:2px solid var(--fb-primary);border-radius:0 4px 4px 0;margin-bottom:8px;font-style:italic;line-height:1.5;cursor:pointer;transition:background .15s}",".fb-quote:hover{background:var(--fb-border)}",".fb-body{font-size:13px;color:var(--fb-muted-fg);line-height:1.6;margin-bottom:6px;white-space:pre-wrap}",".fb-actions{display:flex;gap:2px;flex-wrap:wrap}",".fb-act{font-size:12px;color:#a3a3a3;background:none;border:none;padding:4px 8px;border-radius:4px;cursor:pointer;transition:all .15s;display:inline-flex;align-items:center;gap:3px;font-family:var(--fb-font)}",".fb-act:hover{color:var(--fb-fg)}",".fb-act.del:hover{color:var(--fb-destructive)}",".fb-act.res:hover{color:#22c55e}",".fb-replies{margin-top:8px;padding-top:8px;border-top:1px solid var(--fb-muted)}",".fb-reply-item{display:flex;gap:8px;padding:6px 0}",".fb-reply-item+.fb-reply-item{border-top:1px solid var(--fb-muted)}",".fb-reply-avatar{width:20px;height:20px;border-radius:50%;background:var(--fb-primary);color:var(--fb-primary-fg);display:flex;align-items:center;justify-content:center;font-size:9px;font-weight:700;flex-shrink:0;margin-top:2px}",".fb-reply-meta{font-size:12px;color:var(--fb-muted-fg);margin-bottom:2px}",".fb-reply-meta strong{color:#525252;font-weight:700}",".fb-reply-text{font-size:13px;color:var(--fb-muted-fg);line-height:1.5}",".fb-reply-input{display:flex;gap:8px;margin-top:8px}",".fb-reply-input textarea{flex:1;padding:8px 10px;border:1px solid var(--fb-border);border-radius:6px;font-size:13px;font-family:var(--fb-font);resize:none;outline:none;min-height:36px;color:var(--fb-fg)}",".fb-reply-input textarea:focus{border-color:var(--fb-accent)}",".fb-reply-input button{padding:6px 14px;background:var(--fb-primary);color:var(--fb-primary-fg);border:none;border-radius:6px;font-size:12px;cursor:pointer;font-family:var(--fb-font);align-self:flex-end}",".fb-reply-input button:disabled{opacity:.4;cursor:default}",".fb-edit-area textarea{width:100%;padding:8px 10px;border:1px solid var(--fb-border);border-radius:6px;font-size:13px;font-family:var(--fb-font);resize:vertical;outline:none;min-height:50px;margin-bottom:6px;color:var(--fb-fg)}",".fb-edit-area textarea:focus{border-color:var(--fb-accent)}",".fb-edit-btns{display:flex;gap:6px}",".fb-edit-btns button{padding:4px 12px;border-radius:6px;font-size:12px;cursor:pointer;border:1px solid var(--fb-border);background:var(--fb-bg);color:var(--fb-muted-fg);font-family:var(--fb-font)}",".fb-edit-btns button.save{background:var(--fb-primary);color:var(--fb-primary-fg);border-color:var(--fb-primary)}",".fb-edit-pri{display:flex;gap:4px;margin-bottom:6px}",".fb-edit-pri button{padding:2px 10px;border-radius:4px;font-size:11px;font-weight:700;cursor:pointer;border:1px solid var(--fb-border);background:var(--fb-bg);color:var(--fb-muted-fg);font-family:var(--fb-font);transition:all .15s}",".fb-popup{position:fixed;z-index:100000;width:400px;background:var(--fb-bg);border:1px solid var(--fb-border);border-radius:12px;padding:16px;box-shadow:0 10px 25px rgba(0,0,0,0.1);font-family:var(--fb-font);display:none}",".fb-popup.show{display:block}",".fb-popup-head{margin-bottom:10px}",".fb-popup-head span{font-size:14px;font-weight:700;color:var(--fb-fg)}",".fb-popup-quote{font-size:13px;color:var(--fb-muted-fg);padding:8px 12px;background:var(--fb-muted);border-left:2px solid var(--fb-accent);border-radius:0 6px 6px 0;margin-bottom:10px;font-style:italic;line-height:1.5}",".fb-popup textarea{width:100%;min-height:70px;padding:10px 12px;border:1px solid var(--fb-border);border-radius:8px;font-size:14px;font-family:var(--fb-font);resize:vertical;outline:none;margin-bottom:10px;color:var(--fb-fg)}",".fb-popup textarea:focus{border-color:var(--fb-accent)}",".fb-popup textarea::placeholder{color:var(--fb-muted-fg)}",".fb-popup-pri{display:flex;gap:6px;margin-bottom:10px}",".fb-popup-pri button{flex:1;padding:7px 8px;border-radius:8px;font-size:13px;font-weight:600;cursor:pointer;border:2px solid transparent;transition:all .15s;font-family:var(--fb-font)}",".fb-popup-actions{display:flex;gap:8px;justify-content:flex-end}",".fb-popup-actions button{padding:8px 18px;border-radius:8px;font-size:13px;font-weight:600;cursor:pointer;transition:all .15s;font-family:var(--fb-font)}",".fb-popup-actions .cancel{background:none;border:1px solid var(--fb-border);color:var(--fb-muted-fg)}",".fb-popup-actions .cancel:hover{background:var(--fb-muted);color:var(--fb-fg)}",".fb-popup-actions .submit{border:none;color:#fff}",".fb-name-overlay{position:fixed;inset:0;background:rgba(0,0,0,0.4);display:flex;align-items:center;justify-content:center;z-index:100001}",".fb-name-box{background:var(--fb-bg);border-radius:12px;padding:28px;width:360px;box-shadow:0 20px 40px rgba(0,0,0,0.15)}",".fb-name-box h2{font-size:18px;font-weight:700;margin:0 0 6px;color:var(--fb-fg)}",".fb-name-box p{font-size:14px;color:var(--fb-muted-fg);margin:0 0 16px}",".fb-name-box input{width:100%;padding:10px 14px;border:1px solid var(--fb-border);border-radius:8px;font-size:15px;outline:none;margin-bottom:12px;font-family:var(--fb-font);color:var(--fb-fg)}",".fb-name-box input:focus{border-color:var(--fb-accent)}",".fb-name-box input::placeholder{color:var(--fb-muted-fg)}",".fb-name-box button{width:100%;padding:10px;background:var(--fb-primary);color:var(--fb-primary-fg);border:none;border-radius:8px;font-size:14px;font-weight:600;cursor:pointer;font-family:var(--fb-font)}",".fb-name-box button:disabled{opacity:.4;cursor:default}",".fb-name-input{width:100%;padding:4px 8px;border:1px solid var(--fb-border);border-radius:4px;font-size:12px;outline:none;font-family:var(--fb-font);color:var(--fb-fg)}",".fb-name-input:focus{border-color:var(--fb-accent)}",".fb-highlight{padding:1px 0;cursor:pointer;transition:background .15s}",".fb-highlight-must{background:rgba(239,68,68,0.15);border-bottom:2px solid #ef4444}",".fb-highlight-must:hover{background:rgba(239,68,68,0.25)}",".fb-highlight-better{background:rgba(245,158,11,0.15);border-bottom:2px solid #f59e0b}",".fb-highlight-better:hover{background:rgba(245,158,11,0.25)}",".fb-highlight-want{background:rgba(34,197,94,0.15);border-bottom:2px solid #22c55e}",".fb-highlight-want:hover{background:rgba(34,197,94,0.25)}","@keyframes fb-pulse{0%,100%{opacity:1}50%{opacity:0.4}}",".fb-highlight.fb-pulse{animation:fb-pulse 1s ease-in-out infinite}",".fb-card.fb-focused{box-shadow:0 0 0 2px var(--fb-accent);background:rgba(59,130,246,0.04)}","#fb-sidebar svg,#fb-toggle svg,.fb-popup svg{pointer-events:none}","body.fb-pin-mode{cursor:"+t+"}","body.fb-pin-mode *:not(#fb-toggle):not(#fb-toggle *):not(#fb-sidebar):not(#fb-sidebar *):not(#fb-pin-popup):not(#fb-pin-popup *):not(.fb-pin-marker):not(.fb-pin-marker *){cursor:"+t+"}",".fb-toggle-pin{display:flex;align-items:center;justify-content:center;padding:4px;cursor:pointer;transition:all .15s}",".fb-toggle-pin:hover{color:var(--fb-accent)}",".fb-toggle-voice{display:flex;align-items:center;justify-content:center;padding:4px;cursor:pointer;color:var(--fb-muted-fg);border-radius:4px;transition:all .15s}",".fb-toggle-voice:hover{color:var(--fb-accent)}",".fb-toggle-voice.recording{color:#ef4444;background:rgba(239,68,68,0.12);animation:fb-mic-pulse 1.1s ease-in-out infinite}",".fb-toggle-voice.processing{color:var(--fb-accent)}",".fb-toggle-voice.processing svg{animation:fb-mic-spin .8s linear infinite;transform-origin:center}","@keyframes fb-mic-pulse{0%,100%{box-shadow:0 0 0 0 rgba(239,68,68,0.5)}50%{box-shadow:0 0 0 5px rgba(239,68,68,0)}}","@keyframes fb-mic-spin{to{transform:rotate(360deg)}}",".fb-voice-toast{position:fixed;right:16px;bottom:70px;z-index:100001;max-width:320px;padding:10px 14px;border-radius:8px;background:var(--fb-fg);color:var(--fb-bg);font-family:var(--fb-font);font-size:13px;line-height:1.5;box-shadow:0 6px 20px rgba(0,0,0,0.2);opacity:0;transform:translateY(6px);transition:opacity .2s,transform .2s;pointer-events:none}",".fb-voice-toast.show{opacity:1;transform:translateY(0)}",".fb-pin-marker{position:absolute;transform:translate(-50%,-100%);cursor:grab;z-index:99990;transition:transform .15s}",".fb-pin-marker:hover{transform:translate(-50%,-100%) scale(1.15)}",".fb-pin-marker.resolved{opacity:.4}",".fb-pin-marker.fb-pin-flash .fb-pin-icon{animation:fb-pin-ring 1.4s ease}","@keyframes fb-pin-ring{0%,100%{box-shadow:0 2px 6px rgba(0,0,0,0.25)}30%,60%{box-shadow:0 0 0 8px rgba(59,130,246,0.45)}}",".fb-pin-ref{display:flex;align-items:center;gap:6px;font-size:12px;color:var(--fb-muted-fg);padding:6px 10px;background:var(--fb-muted);border-radius:6px;margin-bottom:8px;cursor:pointer;transition:background .15s}",".fb-pin-ref:hover{background:var(--fb-border)}",".fb-pin-ref-badge{display:inline-flex;align-items:center;justify-content:center;width:18px;height:18px;border-radius:50%;color:#fff;font-size:10px;font-weight:700;flex-shrink:0}","body.fb-dragging-pin,body.fb-dragging-pin *{cursor:grabbing !important;-webkit-user-select:none !important;user-select:none !important}","body.fb-dragging-pin .fb-pin-marker{transition:none}","body.fb-dragging-pin .fb-pin-tooltip{display:none !important}",".fb-pin-badge{cursor:pointer;transition:transform .1s}",".fb-pin-badge:hover{transform:scale(1.08)}",".fb-pin-badge.readonly{cursor:default}",".fb-pin-icon{width:24px;height:24px;border-radius:50%;color:#fff;font-size:11px;font-weight:700;display:flex;align-items:center;justify-content:center;box-shadow:0 2px 6px rgba(0,0,0,0.25);position:relative}",'.fb-pin-icon::after{content:"";position:absolute;bottom:-5px;left:50%;transform:translateX(-50%);width:0;height:0;border-left:5px solid transparent;border-right:5px solid transparent;border-top:6px solid var(--pin-color,#3b82f6)}',".fb-pin-tooltip{display:none;position:absolute;top:8px;left:28px;background:var(--fb-bg);border:1px solid var(--fb-border);border-radius:8px;padding:10px 12px;box-shadow:0 4px 12px rgba(0,0,0,0.1);min-width:180px;max-width:280px;z-index:99995;font-family:var(--fb-font)}",".fb-pin-marker:hover .fb-pin-tooltip{display:block}",".fb-pin-delete{position:absolute;top:4px;right:4px;width:18px;height:18px;border-radius:50%;background:rgba(0,0,0,0.5);color:#fff;border:none;font-size:13px;line-height:1;cursor:pointer;display:flex;align-items:center;justify-content:center;opacity:0;transition:opacity .15s}",".fb-pin-tooltip:hover .fb-pin-delete{opacity:1}",".fb-pin-popup{position:fixed;z-index:100000;width:300px;background:var(--fb-bg);border:1px solid var(--fb-border);border-radius:12px;padding:16px;box-shadow:0 10px 25px rgba(0,0,0,0.12);font-family:var(--fb-font);display:none}",".fb-pin-popup.show{display:block}",".fb-pin-popup-head{font-size:14px;font-weight:700;color:var(--fb-fg);margin-bottom:10px}",".fb-pin-popup textarea{width:100%;min-height:60px;padding:10px 12px;border:1px solid var(--fb-border);border-radius:8px;font-size:14px;font-family:var(--fb-font);resize:vertical;outline:none;margin-bottom:10px;color:var(--fb-fg);box-sizing:border-box}",".fb-pin-popup textarea:focus{border-color:var(--fb-accent)}",".fb-pin-popup textarea::placeholder{color:var(--fb-muted-fg)}",".fb-pin-popup-pri{display:flex;gap:6px;margin-bottom:10px}",".fb-pin-pri-btn{flex:1;padding:6px 8px;border-radius:6px;font-size:12px;font-weight:700;cursor:pointer;border:none;transition:opacity .15s}",".fb-pin-popup-actions{display:flex;gap:8px;justify-content:flex-end}",".fb-pin-popup-actions button{padding:8px 16px;border-radius:8px;font-size:13px;font-weight:600;cursor:pointer;transition:all .15s;font-family:var(--fb-font)}",".fb-pin-cancel{background:none;border:1px solid var(--fb-border);color:var(--fb-muted-fg)}",".fb-pin-cancel:hover{background:var(--fb-muted);color:var(--fb-fg)}",".fb-pin-submit{background:var(--fb-accent);border:none;color:#fff}",".fb-pin-submit:hover{background:#2563eb}"].join(` +`),document.head.appendChild(e)}function C(e,t,r){let n=document.createElement(e);return t&&Object.keys(t).forEach(i=>{let a=t[i];i==="className"?n.className=a:i==="innerHTML"?n.innerHTML=a:i.startsWith("on")&&typeof a=="function"?n.addEventListener(i.substring(2).toLowerCase(),a):n.setAttribute(i,String(a))}),r!=null&&(typeof r=="string"?n.textContent=r:Array.isArray(r)?r.forEach(i=>{i&&n.appendChild(i)}):n.appendChild(r)),n}function x(e){let t=document.createElement("div");return t.textContent=e,t.innerHTML}var Ze={message:'',check:'',rotateCcw:'',pencil:'',trash:'',user:'',x:'',panelRight:'',mic:'',loader:''};function h(e,t=14,r=2){return(Ze[e]||"").replace(/SIZE/g,String(t)).replace(/SW/g,String(r))}var le=null,ce=null;function pe(e){le=e}function fe(e){ce=e}function ue(e){let t=document.getElementById("fb-toggle");t||(t=C("button",{id:"fb-toggle"}),t.addEventListener("click",c=>{let f=c.target.closest("[data-action]");f?.dataset.action==="area-mode"?le?.():f?.dataset.action==="voice"?ce?.():e()}),document.body.appendChild(t));let r=o.comments.filter(c=>!c.parentId&&!c.resolved).length,n=o.comments.filter(c=>c.type==="pin"&&!c.parentId).length,i=''+h("panelRight",16);r>0&&(i+=''+r+""),i+="";let a=o.pinMode?"color:var(--fb-accent);background:rgba(59,130,246,0.1);border-radius:4px":"",d=o.pinMode?"\u30D5\u30A3\u30FC\u30C9\u30D0\u30C3\u30AF\u30E2\u30FC\u30C9 ON\uFF08\u30AF\u30EA\u30C3\u30AF\u3067\u7D42\u4E86\uFF09\uFF0F\u30AF\u30EA\u30C3\u30AF=\u30D4\u30F3\u30FB\u30C9\u30E9\u30C3\u30B0=\u5F15\u7528":"\u30D5\u30A3\u30FC\u30C9\u30D0\u30C3\u30AF\u30E2\u30FC\u30C9 OFF\uFF08\u30AF\u30EA\u30C3\u30AF\u3067\u958B\u59CB\uFF09";i+='',n>0&&(i+=''+n+""),i+="";let s=o.voiceRecording?" recording":o.voiceProcessing?" processing":"",l=o.voiceRecording?"\u9332\u97F3\u4E2D\uFF08\u30AF\u30EA\u30C3\u30AF\u3067\u505C\u6B62\u3057\u3066\u89E3\u6790\uFF09":o.voiceProcessing?"\u89E3\u6790\u4E2D...":"\u97F3\u58F0\u3067\u30D5\u30A3\u30FC\u30C9\u30D0\u30C3\u30AF",p=o.voiceProcessing?h("loader",14):h("mic",14);i+=''+p+"",i+='\u30B3\u30E1\u30F3\u30C8',t.innerHTML=i,t.style.display=o.sidebarOpen?"none":""}function X(e){let t=Date.now()-e;return t<6e4?"\u305F\u3063\u305F\u4ECA":t<36e5?Math.floor(t/6e4)+"\u5206\u524D":t<864e5?Math.floor(t/36e5)+"\u6642\u9593\u524D":new Date(e).toLocaleDateString("ja-JP",{month:"short",day:"numeric",hour:"2-digit",minute:"2-digit"})}function S(){return document.querySelector("main")||document.body}var Z=null;function be(e){let t=i=>window.CSS&&CSS.escape?CSS.escape(i):i,r=[],n=e;for(;n&&n.nodeType===1&&n!==document.body;){if(n.id){r.unshift("#"+t(n.id));break}let i=n.tagName.toLowerCase(),a=n.parentElement;if(a){let d=Array.from(a.children).filter(s=>s.tagName===n.tagName);d.length>1&&(i+=":nth-of-type("+(d.indexOf(n)+1)+")")}r.unshift(i),n=a}return r.join(">")}var Ve="#fb-sidebar,#fb-toggle,#fb-pin-popup,.fb-pin-marker,.fb-pin-tooltip";function ge(e,t){let r=S(),i=document.elementsFromPoint(e,t).find(d=>!d.closest(Ve)&&d!==r&&d!==document.body&&r.contains(d));if(!i)return null;let a=i.getBoundingClientRect();return{selector:be(i),dx:e-a.left,dy:t-a.top}}function Ke(e,t){return document.elementsFromPoint(e,t).some(r=>r.closest("#fb-sidebar,#fb-toggle,#fb-pin-popup"))}function Qe(e){try{let t=document.createRange();t.selectNodeContents(e);let r=t.getBoundingClientRect();if(r.width>=1&&r.height>=1)return r}catch{}return e.getBoundingClientRect()}function me(e){let t=Qe(e);return{x:t.left+t.width/2,y:t.top}}function Ge(e,t,r){let n=e.getBoundingClientRect();return{x:n.left+t,y:n.top+r}}function xe(e){return{selector:be(e),dx:null,dy:null}}function ve(e){let r=S().getBoundingClientRect();if(e.anchorSelector){let i=null;try{i=document.querySelector(e.anchorSelector)}catch{i=null}if(i){let a=e.anchorDx==null||e.anchorDy==null?me(i):Ge(i,e.anchorDx,e.anchorDy),d=(a.x-r.left)/r.width*100;return d<5&&(d=5),d>95&&(d=95),{xPct:d,yPx:a.y-r.top}}}let n=e.pinX??50;return n<5&&(n=5),n>95&&(n=95),{xPct:n,yPx:e.pinY??0}}function D(){return o.comments.filter(e=>e.type==="pin"&&!e.parentId)}function he(e){return D().findIndex(t=>t.id===e)+1}function ye(e){let r=S().getBoundingClientRect(),n=me(e),i=(n.x-r.left)/r.width*100;return i<5&&(i=5),i>95&&(i=95),{x:i,y:n.y-r.top}}function we(){o.pinMode=!0,o.pinPopupPos=null,document.body.classList.add("fb-pin-mode")}function V(){o.pinMode=!1,o.pinPopupPos=null,document.body.classList.remove("fb-pin-mode")}function Ee(e){if(e.target.closest("#fb-sidebar,#fb-toggle,#fb-pin-popup,.fb-pin-marker,.fb-pin-tooltip"))return;let r=S().getBoundingClientRect(),i=(e.clientX-r.left)/r.width*100,a=e.pageY-(r.top+window.scrollY);o.editingPinId=null,o.pinPopupPos={x:i,y:a},Z=ge(e.clientX,e.clientY)}function K(e,t,r,n,i){let a={id:B(),author:o.username||"\u533F\u540D",type:"pin",quote:"",quoteContext:{beforeText:"",afterText:""},content:r.trim()||"(\u30B3\u30E1\u30F3\u30C8\u306A\u3057)",priority:n,parentId:null,pageUrl:window.location.href,projectSlug:M,timestamp:Date.now(),resolved:!1,resolvedBy:null,resolvedAt:null,updatedAt:null,pinX:e,pinY:t,anchorSelector:i?.selector??null,anchorDx:i?.dx??null,anchorDy:i?.dy??null};return o.comments.push(a),E("POST",a),a}function Je(e,t){o.pinPopupPos&&(K(o.pinPopupPos.x,o.pinPopupPos.y,e,t,Z),Z=null,o.pinPopupPos=null,y())}function Te(){o.pinPopupPos=null}function $e(e){o.comments=o.comments.filter(t=>t.id!==e&&t.parentId!==e),E("DELETE",{id:e}),y()}function et(e,t,r,n){let i=o.comments.find(a=>a.id===e);i&&(i.pinX=t,i.pinY=r,i.anchorSelector=n?.selector??null,i.anchorDx=n?.dx??null,i.anchorDy=n?.dy??null,E("PUT",{id:e,action:"move",pinX:t,pinY:r,anchorSelector:i.anchorSelector,anchorDx:i.anchorDx,anchorDy:i.anchorDy}),y())}function tt(e,t,r){let n=o.comments.find(i=>i.id===e);n&&(n.content=t.trim()||"(\u30B3\u30E1\u30F3\u30C8\u306A\u3057)",n.priority=r,E("PUT",{id:e,action:"edit",content:n.content,priority:r}),y())}function nt(e,t){let r=D().find(i=>i.id===e);if(!r)return;o.editingPinId=e;let n=ve(r);o.pinPopupPos={x:n.xPct,y:n.yPx},Q(t)}function Pe(e){document.addEventListener("mousedown",t=>{let n=t.target.closest(".fb-pin-marker");if(!n)return;let i=n.dataset.pinId;if(!i)return;let a=t.clientX,d=t.clientY,s=!1;function l(u){if(!s&&Math.hypot(u.clientX-a,u.clientY-d)<6)return;s||(s=!0,document.body.classList.add("fb-dragging-pin")),u.preventDefault();let b=S().getBoundingClientRect(),g=(u.clientX-b.left)/b.width*100;g<5&&(g=5),g>95&&(g=95),n.style.left=g+"%",n.style.top=u.clientY-b.top+"px"}function p(){document.removeEventListener("mousemove",l),document.removeEventListener("mouseup",f),window.removeEventListener("blur",c),document.body.classList.remove("fb-dragging-pin")}function c(){let u=s;p(),u&&y()}function f(u){let w=s;if(p(),!w){nt(i,e);return}if(Ke(u.clientX,u.clientY)){y();return}let g=S().getBoundingClientRect(),P=(u.clientX-g.left)/g.width*100;P<5&&(P=5),P>95&&(P=95);let A=u.clientY-g.top,v=ge(u.clientX,u.clientY);et(i,P,A,v)}document.addEventListener("mousemove",l),document.addEventListener("mouseup",f),window.addEventListener("blur",c)})}function y(){document.querySelectorAll(".fb-pin-marker").forEach(l=>l.remove());let e=S();getComputedStyle(e).position==="static"&&(e.style.position="relative");let t=D(),r=t.map(l=>ve(l)),n=26,i=16,a=e.getBoundingClientRect().width,d=[];r.forEach((l,p)=>{let c=d.find(f=>Math.abs(f.x-l.xPct)<2&&Math.abs(f.y-l.yPx)<24);c||(c={x:l.xPct,y:l.yPx,items:[]},d.push(c)),c.items.push(p)});let s={};d.forEach(l=>{let p=l.items.length;l.items.forEach((c,f)=>{s[c]=(f-(p-1)/2)*n})}),t.forEach((l,p)=>{let c=document.createElement("div");c.className="fb-pin-marker"+(l.resolved?" resolved":""),c.dataset.pinId=l.id;let f=k[l.priority]||k.want,u=r[p].xPct,w=u/100*a,b=Math.min(Math.max(w+(s[p]||0),i),a-i),g=Math.round(b-w);c.style.left=g?"calc("+u+"% + "+g+"px)":u+"%",c.style.top=r[p].yPx+"px",c.style.setProperty("--pin-color",f.bg),c.innerHTML='
'+(p+1)+"
";let P=document.createElement("div");P.className="fb-pin-tooltip";let A=l.priority.charAt(0).toUpperCase()+l.priority.slice(1),v=''+A+"",W='
\u30AF\u30EA\u30C3\u30AF\u3067\u7DE8\u96C6 / \u30C9\u30E9\u30C3\u30B0\u3067\u79FB\u52D5
';P.innerHTML='
'+v+''+x(l.author)+'
'+x(l.content)+"
"+W,c.appendChild(P),e.appendChild(c)})}function Q(e){let t=document.getElementById("fb-pin-popup");if(t||(t=document.createElement("div"),t.id="fb-pin-popup",t.className="fb-pin-popup",document.body.appendChild(t)),!o.pinPopupPos){t.classList.remove("show");return}t.classList.add("show");let r=o.editingPinId&&D().find(v=>v.id===o.editingPinId)||null,i=S().getBoundingClientRect(),a=o.pinPopupPos.x/100*i.width+i.left,d=o.pinPopupPos.y+i.top+window.scrollY,s='
'+(r?"\u30B3\u30E1\u30F3\u30C8\u3092\u7DE8\u96C6":"\u30B3\u30E1\u30F3\u30C8\u3092\u8FFD\u52A0")+"
";s+='',s+='
',["must","better","want"].forEach(v=>{let W=k[v];s+='"}),s+="
",r&&(s+='
'),t.innerHTML=s;let l=300,p=8,c=d+24-window.scrollY;c+220>window.innerHeight&&(c=d-220-p-window.scrollY);let f=a-l/2;fwindow.innerWidth-p&&(f=window.innerWidth-l-p),t.style.position="fixed",t.style.top=c+"px",t.style.left=f+"px";let u=t.querySelector("#fb-pin-textarea");u.value=r?r.content:"",setTimeout(()=>u?.focus(),50);let w=t.querySelectorAll(".fb-pin-pri-btn"),b=r?r.priority:"better",g=()=>w.forEach(v=>{v.style.opacity=v.dataset.pri===b?"1":"0.4"});w.forEach(v=>{v.addEventListener("click",()=>{b=v.dataset.pri,A()})}),g();let P=()=>{o.editingPinId=null,o.pinPopupPos=null,t.classList.remove("show"),e()},A=()=>{r?tt(r.id,u.value,b):Je(u.value,b),P()};r&&t.querySelector(".fb-pin-delete-btn").addEventListener("click",()=>{$e(r.id),P()}),u.addEventListener("keydown",v=>{(v.metaKey||v.ctrlKey)&&v.key==="Enter"&&A()})}function ke(e){let t=e.author===o.username,r=k[e.priority]||k.want,n='
';if(n+='
',n+='
'+x(e.author.charAt(0))+"
",n+=''+x(e.author)+"",n+=''+X(e.timestamp)+"",e.resolved&&(n+=''+h("check",12)+" \u89E3\u6C7A\u6E08"),n+="
",n+=''+x(e.priority.charAt(0).toUpperCase()+e.priority.slice(1))+"",n+="
",e.type==="pin")n+='
'+he(e.id)+"\u30D4\u30F3\u3092\u8868\u793A
";else if(e.quote){let a=e.quote.length>100?e.quote.substring(0,100)+"...":e.quote;n+='
'+x(a)+"
"}o.editingId===e.id?(n+='
',n+='
',["must","better","want"].forEach(a=>{let d=o.editPriority===a,s=k[a];n+='"}),n+="
",n+='",n+='
',n+="
"):n+='
'+x(e.content)+"
",o.editingId!==e.id&&(n+='
',n+='",n+='",t&&(n+='"),t&&(n+='"),n+="
");let i=o.comments.filter(a=>a.parentId===e.id).sort((a,d)=>a.timestamp-d.timestamp);return i.length>0&&(n+='
',i.forEach(a=>{let d=a.author===o.username;n+='
'+x(a.author.charAt(0))+'
'+x(a.author)+" · "+X(a.timestamp)+"
",o.editingId===a.id?n+='
':(n+='
'+x(a.content)+"
",n+='
',n+='",d&&(n+='"),d&&(n+='"),n+="
"),n+="
"}),n+="
"),o.replyingTo===e.id&&(n+='
"),n+="
",n}function Ce(){let e=document.getElementById("fb-sidebar");if(!e)return;let t=o.sidebarWidth;e.style.width=t+"px",e.style.right=o.sidebarOpen?"0px":-(t+20)+"px",document.body.style.marginRight=o.sidebarOpen?t+"px":""}function N(){o.sidebarOpen=!o.sidebarOpen;let e=document.getElementById("fb-toggle");e&&(e.style.display=o.sidebarOpen?"none":""),document.body.style.transition="margin-right 0.3s ease",Ce()}function Se(){return o.comments.filter(e=>!e.parentId)}function ot(){let e=Se();return o.filter==="resolved"?e.filter(t=>t.resolved):o.filter==="all"?e:e.filter(t=>!t.resolved)}function Le(e){let t=document.getElementById("fb-sidebar"),r=!t;r&&(t=C("div",{id:"fb-sidebar"}),document.body.appendChild(t),t.addEventListener("mouseover",s=>{let l=s.target.closest(".fb-card");if(!l)return;let p=document.querySelector('.fb-highlight[data-comment-id="'+l.dataset.id+'"]');p&&p.classList.add("fb-pulse")}),t.addEventListener("mouseout",s=>{let l=s.target.closest(".fb-card");if(!l||s.relatedTarget&&l.contains(s.relatedTarget))return;let p=document.querySelector('.fb-highlight[data-comment-id="'+l.dataset.id+'"]');p&&p.classList.remove("fb-pulse")})),Ce();let n=Se(),i={unresolved:n.filter(s=>!s.resolved).length,resolved:n.filter(s=>s.resolved).length,all:n.length},a='
';a+='
',a+='
\u30B3\u30E1\u30F3\u30C8'+i.all+"
",a+='
',a+='",a+="
",o.username&&(o.editingName?a+='
':a+='
'+h("user",14)+x(o.username)+"
"),a+='
',["unresolved","resolved","all"].forEach(s=>{let l=s==="unresolved"?"\u672A\u89E3\u6C7A":s==="resolved"?"\u89E3\u6C7A\u6E08":"\u3059\u3079\u3066";a+='"}),a+="
";let d=ot().sort((s,l)=>l.timestamp-s.timestamp);a+='
',d.length===0?a+='
'+h("message",40)+'\u30B3\u30E1\u30F3\u30C8\u306F\u307E\u3060\u3042\u308A\u307E\u305B\u3093
\u30C6\u30AD\u30B9\u30C8\u3092\u9078\u629E\u3057\u3066\u30B3\u30E1\u30F3\u30C8\u3092\u8FFD\u52A0
':d.forEach(s=>{a+=ke(s)}),a+="
",t.innerHTML=a,r&&rt(t,e)}var T=null;function Me(e){T=e}function rt(e,t){e.addEventListener("click",r=>{let n=r.target.closest("[data-action]");if(n){let d=n.dataset.action,s=n.dataset.id;if(d==="close")T?.toggleSidebar();else if(d==="edit-name")o.editingName=!0,o.nameInput=o.username,t();else if(d==="cycle"&&s)T?.cyclePriority(s);else if(d==="scroll-quote"&&s)T?.scrollToQuote(s);else if(d==="scroll-pin"&&s)T?.scrollToPin(s);else if(d==="reply"&&s)o.replyingTo=o.replyingTo===s?null:s,o.replyText="",t();else if(d==="resolve"&&s)T?.resolveComment(s);else if(d==="edit"&&s){let l=o.comments.find(p=>p.id===s);l&&(o.editingId=s,o.editContent=l.content,o.editPriority=l.priority,t())}else d==="delete"&&s?T?.deleteComment(s):d==="delete-reply"&&s?T?.deleteReply(s):d==="cancel-edit"?(o.editingId=null,t()):d==="save-edit"&&s?T?.saveEdit(s):d==="submit-reply"&&s?T?.submitReply(s):d==="set-edit-pri"&&(o.editPriority=n.dataset.pri,t());return}let i=r.target.closest("[data-filter]");if(i){o.filter=i.dataset.filter,t();return}let a=r.target.closest(".fb-card");a&&a.dataset.id&&(o.comments.find(s=>s.id===a.dataset.id)?.type==="pin"?T?.scrollToPin(a.dataset.id):T?.scrollToQuote(a.dataset.id))}),e.addEventListener("mousedown",r=>{if(!r.target.closest(".fb-resize-handle"))return;r.preventDefault();let n=r.clientX,i=o.sidebarWidth,a=r.target.closest(".fb-resize-handle");document.body.classList.add("fb-resizing"),a.classList.add("active");function d(l){o.sidebarWidth=Math.min(800,Math.max(300,i+(n-l.clientX)));let p=document.getElementById("fb-sidebar");p&&(p.style.width=o.sidebarWidth+"px"),document.body.style.transition="none",document.body.style.marginRight=o.sidebarWidth+"px"}function s(){document.removeEventListener("mousemove",d),document.removeEventListener("mouseup",s),document.body.classList.remove("fb-resizing"),a.classList.remove("active"),document.body.style.transition="",localStorage.setItem(O,String(o.sidebarWidth))}document.addEventListener("mousemove",d),document.addEventListener("mouseup",s)}),e.addEventListener("input",r=>{let n=r.target;if(n.dataset.action==="name-input")o.nameInput=n.value;else if(n.dataset.action==="edit-textarea")o.editContent=n.value;else if(n.dataset.action==="reply-textarea"){o.replyText=n.value;let i=e.querySelector('[data-action="submit-reply"]');i&&(i.disabled=!o.replyText.trim())}}),e.addEventListener("keydown",r=>{let n=r.target;if(n.dataset.action==="name-input"&&(r.key==="Enter"?T?.finishNameEdit():r.key==="Escape"&&(o.editingName=!1,t())),n.dataset.action==="reply-textarea"&&(r.metaKey||r.ctrlKey)&&r.key==="Enter"){let i=e.querySelector('[data-action="submit-reply"]')?.dataset.id;i&&T?.submitReply(i)}}),e.addEventListener("blur",r=>{r.target.dataset?.action==="name-input"&&T?.finishNameEdit()},!0)}function Ie(e,t,r){let n=document.getElementById("fb-popup");if(!n&&(n=C("div",{id:"fb-popup",className:"fb-popup"}),document.body.appendChild(n),n.addEventListener("click",b=>{let g=b.target.closest("[data-action]");g&&(g.dataset.action==="cancel-popup"?t():g.dataset.action==="set-popup-pri"?(o.popupPriority=g.dataset.pri,r(o.popupPriority)):g.dataset.action==="submit-popup"&&r(o.popupPriority))}),n.addEventListener("input",b=>{b.target.dataset.action==="popup-textarea"&&(o.popupContent=b.target.value)}),n.addEventListener("keydown",b=>{b.target.dataset.action==="popup-textarea"&&(b.metaKey||b.ctrlKey)&&b.key==="Enter"&&r(o.popupPriority)})),!o.selectedRect){n.classList.remove("show");return}n.classList.add("show");let a=o.selectedText.length>120?o.selectedText.substring(0,120)+"...":o.selectedText,d='
\u30B3\u30E1\u30F3\u30C8\u3092\u8FFD\u52A0
';d+='
'+x(a)+"
",d+='",d+='
',["must","better","want"].forEach(b=>{let g=k[b];d+='"}),d+="
",n.innerHTML=d;let s=o.selectedRect,l=320,p=230,c=12,f=s.bottom+c;f+p>window.innerHeight&&(f=s.top-p-c),f

\u3088\u3046\u3053\u305D

\u30B3\u30E1\u30F3\u30C8\u306B\u8868\u793A\u3055\u308C\u308B\u540D\u524D\u3092\u5165\u529B\u3057\u3066\u304F\u3060\u3055\u3044\u3002

',document.body.appendChild(r);let n=document.getElementById("fb-name-field"),i=document.getElementById("fb-name-submit");n.addEventListener("input",()=>{i.disabled=!n.value.trim()}),n.addEventListener("keydown",a=>{a.key==="Enter"&&n.value.trim()&&Re(n.value.trim(),e)}),i.addEventListener("click",()=>{n.value.trim()&&Re(n.value.trim(),e)}),setTimeout(()=>{n.focus()},100)}function Re(e,t){o.username=e,localStorage.setItem(H,e);let r=document.getElementById("fb-name-overlay");r&&r.remove(),t()}var He=null,ze=null;function Be(e,t){He=e,ze=t}function m(){ue(N),Le(m),Ie(m,He,ze),Ae(m)}function it(){let e=[],t=document.createTreeWalker(document.body,NodeFilter.SHOW_TEXT,{acceptNode(n){let i=n.parentElement;return!i||i.tagName==="SCRIPT"||i.tagName==="STYLE"||i.closest("#fb-sidebar,#fb-toggle,#fb-popup,.fb-highlight")?NodeFilter.FILTER_REJECT:NodeFilter.FILTER_ACCEPT}}),r;for(;r=t.nextNode();)e.push(r);return e}function Ne(e,t,r){let n=e.replace(/[\s\u00A0]+/g," ");if(t>=n.length)return null;let i=0,a=0,d=-1,s=-1;for(;i=r&&(s=i),d===-1||s===-1||d>=s?null:[d,s]}function G(e,t,r,n,i){let a=e.textContent,d=document.createTextNode(a.substring(0,t)),s=document.createElement("mark");s.className="fb-highlight fb-highlight-"+n.priority,s.dataset.commentId=n.id,s.textContent=a.substring(t,r),s.addEventListener("click",()=>{i(n.id)});let l=document.createTextNode(a.substring(r));e.parentNode.insertBefore(d,e),e.parentNode.insertBefore(s,e),e.parentNode.insertBefore(l,e),e.parentNode.removeChild(e)}function L(e){document.querySelectorAll(".fb-highlight").forEach(t=>{let r=document.createTextNode(t.textContent||"");t.parentNode.replaceChild(r,t)}),document.body.normalize(),o.comments.filter(t=>!t.parentId&&!t.resolved&&t.quote&&t.quote.length>=2).forEach(t=>{let r=t.quote.replace(/[\s\u00A0]+/g," ").trim(),n=it(),i=!1;for(let a=0;a=0;u--){let w=d[u];if(w.end<=c||w.start>=f)continue;let b=Math.max(0,c-w.start),g=Math.min(w.node.textContent.length,f-w.start);try{G(w.node,b,g,t,e)}catch{}}}}}})}var J={must:"rgba(239,68,68,0.4)",better:"rgba(245,158,11,0.4)",want:"rgba(34,197,94,0.4)"};function Oe(e){let t=document.querySelector('.fb-highlight[data-comment-id="'+e+'"]');if(!t)return;t.scrollIntoView({behavior:"smooth",block:"center"});let r=o.comments.find(a=>a.id===e),n=r&&J[r.priority]||J.want,i=t.style.backgroundColor;t.style.backgroundColor=n,t.style.transition="background-color 0.3s",setTimeout(()=>{t.style.backgroundColor=i},1500)}function De(e){let t=document.querySelector('.fb-pin-marker[data-pin-id="'+e+'"]');t&&(t.scrollIntoView({behavior:"smooth",block:"center"}),t.classList.add("fb-pin-flash"),setTimeout(()=>{t.classList.remove("fb-pin-flash")},1500))}function _e(e,t){let r=!o.sidebarOpen;r&&t(),setTimeout(()=>{let n=document.querySelector('.fb-card[data-id="'+e+'"]');n&&(n.scrollIntoView({behavior:"smooth",block:"center"}),n.classList.add("fb-focused"),setTimeout(()=>{n.classList.remove("fb-focused")},1500))},r?350:0)}function at(e){let t="",r="";try{let n=document.createRange();n.setStart(document.body,0),n.setEnd(e.startContainer,e.startOffset),t=n.toString().slice(-50).replace(/[\s\u00A0]+/g," ").trim();let i=document.createRange();i.setStart(e.endContainer,e.endOffset),i.setEnd(document.body,document.body.childNodes.length),r=i.toString().slice(0,50).replace(/[\s\u00A0]+/g," ").trim()}catch{}return{beforeText:t,afterText:r}}function qe(e,t){document.addEventListener("mouseup",r=>{if(r.target.closest("#fb-sidebar,#fb-toggle,#fb-popup,#fb-name-overlay"))return;let n=window.getSelection(),i=n?.toString().trim();if(!i||!n||n.rangeCount===0)return;let a=n.getRangeAt(0);o.selectedText=i.replace(/[\s\u00A0]+/g," ").substring(0,200),o.selectedRect=a.getBoundingClientRect(),o.selectedQuoteContext=at(a),o.popupContent="",e()}),document.addEventListener("mousedown",r=>{r.target.closest("#fb-popup")||o.selectedRect&&t()})}var I=null,_=[],q=null,z=()=>{};function je(e){z=e}function st(){return document.querySelector("main")||document.body}var dt="div,p,li,td,th,dt,dd,figcaption,blockquote,h1,h2,h3,h4,h5,h6",lt=["H1","H2","H3","H4","H5","H6"],ct="#fb-sidebar,#fb-toggle,#fb-pin-popup,.fb-pin-marker,.fb-pin-tooltip,.fb-voice-toast",pt=2,ft=120,$=600;function ut(){let e=st(),t=Array.from(e.querySelectorAll(dt)).filter(a=>(a.textContent||"").trim()&&!a.closest(ct)),r=new Set(t.filter(a=>!t.some(d=>d!==a&&a.contains(d)))),n=[],i="";return Array.from(e.querySelectorAll("*")).forEach(a=>{let d=lt.includes(a.tagName);if(d){let f=Fe(a.textContent||"");f&&(i=f)}if(!r.has(a)||n.length>=$)return;let s=Fe(a.textContent||"");if(s.lengthft)return;let l=a.getBoundingClientRect();if(l.width<4||l.height<4)return;let{x:p,y:c}=ye(a);n.push({index:n.length,text:s,heading:d?"":i,isHeading:d,x:p,y:c,anchor:xe(a)})}),n}function Fe(e){return e.replace(/\s+/g," ").trim()}function Ye(){o.voiceProcessing||(o.voiceRecording?gt():bt())}async function bt(){if(o.voiceRecording||o.voiceProcessing)return;o.voiceError=null;try{q=await navigator.mediaDevices.getUserMedia({audio:!0})}catch{o.voiceError="\u30DE\u30A4\u30AF\u3078\u306E\u30A2\u30AF\u30BB\u30B9\u304C\u8A31\u53EF\u3055\u308C\u307E\u305B\u3093\u3067\u3057\u305F",z();return}_=[];let e=Tt();I=new MediaRecorder(q,e?{mimeType:e}:void 0),I.ondataavailable=t=>{t.data.size>0&&_.push(t.data)},I.onstop=()=>{mt()},I.start(),o.voiceRecording=!0,z()}function gt(){!o.voiceRecording||!I||(I.stop(),o.voiceRecording=!1,o.voiceProcessing=!0,z())}async function mt(){q?.getTracks().forEach(r=>r.stop()),q=null;let e=new Blob(_,{type:I?.mimeType||"audio/webm"});_=[];let t=ut();if(t.length===0){o.voiceError="\u30D4\u30F3\u3092\u7F6E\u3051\u308B\u8981\u7D20\u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093\u3067\u3057\u305F",o.voiceProcessing=!1,z();return}t.length>=$&&(o.voiceError=`\u30DA\u30FC\u30B8\u304C\u5927\u304D\u3044\u305F\u3081\u3001\u5148\u982D${$}\u7B87\u6240\u306E\u307F\u3092\u5BFE\u8C61\u306B\u3057\u3066\u3044\u307E\u3059`);try{let r=await Pt(e),n=await se(r,"audio/wav",t.map(i=>({index:i.index,text:i.text,heading:i.heading,isHeading:i.isHeading})));n.error?o.voiceError=n.error:xt(n.results||[],t)}catch{o.voiceError="\u97F3\u58F0\u306E\u89E3\u6790\u306B\u5931\u6557\u3057\u307E\u3057\u305F"}finally{o.voiceProcessing=!1,z()}}function xt(e,t){if(!Array.isArray(e)||e.length===0){o.voiceError="\u30D5\u30A3\u30FC\u30C9\u30D0\u30C3\u30AF\u3092\u805E\u304D\u53D6\u308C\u307E\u305B\u3093\u3067\u3057\u305F\u3002\u3082\u3046\u4E00\u5EA6\u304A\u8A66\u3057\u304F\u3060\u3055\u3044";return}let r=0;e.forEach(n=>{let i=t.find(a=>a.index===n.target_index);i||(i=yt(`${n.quote||""} ${n.comment||""}`,t)),i||(i=t[0],r++),i&&K(i.x,i.y,n.comment,Et(n.priority),i.anchor)}),r>0&&(o.voiceError=`${r}\u4EF6\u306F\u5834\u6240\u3092\u7279\u5B9A\u3067\u304D\u307E\u305B\u3093\u3067\u3057\u305F\uFF08\u5148\u982D\u306B\u7F6E\u3044\u3066\u3044\u307E\u3059\u3002\u30C9\u30E9\u30C3\u30B0\u3067\u79FB\u52D5\u3067\u304D\u307E\u3059\uFF09`),y()}var vt=.34,ht=4;function yt(e,t){let r=We(e);if(r.size===0)return;let n,i=vt;return t.forEach(a=>{if(a.text.lengthi||n&&d===i&&a.text.length>n.text.length)&&(i=d,n=a)}),n}function We(e){let t=e.replace(/\s+/g,"").toLowerCase(),r=new Set;for(let n=0;n{t.has(n)&&r++}),r/Math.min(e.size,t.size)}function Et(e){return e==="must"||e==="better"||e==="want"?e:"better"}function Tt(){let e=["audio/webm;codecs=opus","audio/webm","audio/ogg;codecs=opus","audio/ogg","audio/mp4"];for(let t of e)if(typeof MediaRecorder<"u"&&MediaRecorder.isTypeSupported?.(t))return t;return""}async function Pt(e){let t=await e.arrayBuffer(),r=window.AudioContext||window.webkitAudioContext,n=new r,i;try{i=await n.decodeAudioData(t)}finally{n.close()}let a=16e3,d=Math.max(1,Math.ceil(i.duration*a)),s=new OfflineAudioContext(1,d,a),l=s.createBufferSource();l.buffer=i,l.connect(s.destination),l.start();let p=await s.startRendering(),c=kt(p.getChannelData(0),a);return Ct(c)}function kt(e,t){let r=new ArrayBuffer(44+e.length*2),n=new DataView(r),i=(d,s)=>{for(let l=0;l{Array.isArray(e)&&(o.comments=e),m(),y(),L(R)}).catch(()=>{m(),y(),L(R)})}function oe(){o.selectedText="",o.selectedRect=null,o.popupContent="",o.popupPriority="must",m()}function Mt(e){if(!o.selectedText)return;let t={id:B(),author:o.username||"\u533F\u540D",type:"comment",quote:o.selectedText,quoteContext:o.selectedQuoteContext,content:o.popupContent.trim(),priority:e,parentId:null,pageUrl:window.location.href,projectSlug:M,timestamp:Date.now(),resolved:!1,resolvedBy:null,resolvedAt:null,updatedAt:null};o.comments.push(t),oe(),L(R),E("POST",t).then(ne)}function It(e){let t=o.comments.find(n=>n.id===e);if(!t)return;let r=!t.resolved;t.resolved=r,t.resolvedBy=r?o.username:null,t.resolvedAt=r?Date.now():null,m(),y(),L(R),E("PUT",{id:e,action:"resolve",resolved:r,resolvedBy:t.resolvedBy,resolvedAt:t.resolvedAt})}function Rt(e){let t=o.comments.find(r=>r.id===e);!t||t.author!==o.username||(t.priority=re[t.priority]||"must",m(),y(),L(R),E("PUT",{id:e,action:"cyclePriority",priority:t.priority}))}function At(e){o.comments=o.comments.filter(t=>t.id!==e&&t.parentId!==e),m(),y(),L(R),E("DELETE",{id:e})}function Ht(e){o.comments=o.comments.filter(t=>t.id!==e),m(),E("DELETE",{id:e})}function zt(e){let t=o.comments.find(r=>r.id===e);t&&(t.content=o.editContent,t.priority=o.editPriority,o.editingId=null,m(),y(),L(R),E("PUT",{id:e,action:"edit",content:t.content,priority:t.priority}))}function Bt(e){if(!o.replyText.trim()||!o.username)return;let t={id:B(),author:o.username,type:"comment",quote:"",quoteContext:{beforeText:"",afterText:""},content:o.replyText.trim(),priority:"want",parentId:e,pageUrl:window.location.href,projectSlug:M,timestamp:Date.now(),resolved:!1,resolvedBy:null,resolvedAt:null,updatedAt:null};o.comments.push(t),o.replyingTo=null,o.replyText="",m(),E("POST",t)}function Nt(){if(o.nameInput.trim()&&o.nameInput.trim()!==o.username){let e=o.username;o.username=o.nameInput.trim(),localStorage.setItem(H,o.username),E("PUT",{id:"_rename",action:"rename",author:o.username,oldAuthor:e,projectSlug:M}).then(ne)}o.editingName=!1,m()}function R(e){_e(e,N)}function Ot(){o.pinMode?V():we(),m(),y()}function Dt(){Ye()}var ee=null;function _t(e){let t=document.getElementById("fb-voice-toast");t||(t=document.createElement("div"),t.id="fb-voice-toast",t.className="fb-voice-toast",document.body.appendChild(t)),t.textContent=e,t.style.right=(o.sidebarOpen?o.sidebarWidth+16:16)+"px",requestAnimationFrame(()=>t.classList.add("show")),ee&&clearTimeout(ee),ee=setTimeout(()=>t.classList.remove("show"),6e3)}Be(oe,Mt);pe(Ot);fe(Dt);je(()=>{m(),o.voiceError&&(_t(o.voiceError),o.voiceError=null)});Me({toggleSidebar:N,cyclePriority:Rt,scrollToQuote:Oe,scrollToPin:De,resolveComment:It,deleteComment:At,deleteReply:Ht,saveEdit:zt,submitReply:Bt,finishNameEdit:Nt});var qt="#fb-sidebar,#fb-toggle,#fb-pin-popup,.fb-pin-marker,.fb-pin-tooltip,.fb-pin-delete,.fb-popup",Ft=6;function Ue(){Te(),o.editingPinId=null;let e=document.getElementById("fb-pin-popup");e&&e.classList.remove("show"),m()}function Wt(){let e=0,t=0;document.addEventListener("mousedown",r=>{e=r.clientX,t=r.clientY},!0),document.addEventListener("click",r=>{let n=!!r.target.closest(qt);if(!o.pinMode){o.pinPopupPos&&!n&&Ue();return}if(n)return;let i=window.getSelection();i&&i.toString().trim()||Math.hypot(r.clientX-e,r.clientY-t)>=Ft||(r.preventDefault(),r.stopPropagation(),Ee(r),o.pinPopupPos&&Q(m),m())},!0),document.addEventListener("keydown",r=>{r.key==="Escape"&&(o.pinPopupPos?Ue():o.pinMode&&(V(),m(),y()))})}var te=null;function jt(){window.addEventListener("resize",()=>{te&&clearTimeout(te),te=setTimeout(()=>{y()},100)})}function Xe(){de(),m(),qe(m,oe),Wt(),Pe(m),jt(),ne()}document.readyState==="loading"?document.addEventListener("DOMContentLoaded",Xe):Xe();})(); diff --git a/scripts/migrate.ts b/scripts/migrate.ts new file mode 100644 index 0000000..09cab4e --- /dev/null +++ b/scripts/migrate.ts @@ -0,0 +1,53 @@ +import { config } from 'dotenv'; +config({ path: '.env.local' }); +import { neon } from '@neondatabase/serverless'; + +async function migrate() { + const url = process.env.DATABASE_URL; + if (!url) { + console.error('DATABASE_URL is not set. Add it to .env.local'); + process.exit(1); + } + + const sql = neon(url); + + await sql` + CREATE TABLE IF NOT EXISTS comments ( + id TEXT PRIMARY KEY, + author TEXT NOT NULL, + type TEXT NOT NULL DEFAULT 'comment', + quote TEXT NOT NULL DEFAULT '', + quote_context_before TEXT NOT NULL DEFAULT '', + quote_context_after TEXT NOT NULL DEFAULT '', + content TEXT NOT NULL DEFAULT '', + priority TEXT NOT NULL DEFAULT 'want', + parent_id TEXT, + resolved BOOLEAN NOT NULL DEFAULT false, + resolved_by TEXT, + resolved_at BIGINT, + timestamp BIGINT NOT NULL, + updated_at BIGINT, + page_url TEXT NOT NULL, + project_slug TEXT NOT NULL + ) + `; + + // ピンコメント用の座標列(既存テーブルにも安全に追加できるよう冪等) + await sql`ALTER TABLE comments ADD COLUMN IF NOT EXISTS pin_x DOUBLE PRECISION`; + await sql`ALTER TABLE comments ADD COLUMN IF NOT EXISTS pin_y DOUBLE PRECISION`; + + // 要素アンカー方式(レスポンシブ追従): 最寄り要素のセレクタ+その要素からの相対オフセット + await sql`ALTER TABLE comments ADD COLUMN IF NOT EXISTS anchor_selector TEXT`; + await sql`ALTER TABLE comments ADD COLUMN IF NOT EXISTS anchor_dx DOUBLE PRECISION`; + await sql`ALTER TABLE comments ADD COLUMN IF NOT EXISTS anchor_dy DOUBLE PRECISION`; + + await sql`CREATE INDEX IF NOT EXISTS idx_comments_project ON comments (project_slug)`; + await sql`CREATE INDEX IF NOT EXISTS idx_comments_parent ON comments (parent_id)`; + + console.log('Migration complete.'); +} + +migrate().catch((e) => { + console.error('Migration failed:', e); + process.exit(1); +}); diff --git a/src/app/api/comments/route.ts b/src/app/api/comments/route.ts new file mode 100644 index 0000000..40d9684 --- /dev/null +++ b/src/app/api/comments/route.ts @@ -0,0 +1,157 @@ +import { NextRequest, NextResponse } from 'next/server'; +import { getDb } from '@/lib/db'; +import type { Comment } from '@/shared/types'; + +function corsHeaders() { + return { + 'Access-Control-Allow-Origin': '*', + 'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS', + 'Access-Control-Allow-Headers': 'Content-Type, Authorization', + }; +} + +export async function OPTIONS() { + return new NextResponse(null, { status: 204, headers: corsHeaders() }); +} + +function json(data: unknown, status = 200) { + return NextResponse.json(data, { status, headers: corsHeaders() }); +} + +function verifyToken(request: NextRequest): boolean { + const token = process.env.API_TOKEN; + // ローカル開発ではトークン無しで動かせるようにする。本番で未設定なら、 + // 設定ミスで API が誰でも書き込める状態になるより落ちた方が安全。 + if (!token) return process.env.NODE_ENV !== 'production'; + + const auth = request.headers.get('Authorization'); + if (!auth?.startsWith('Bearer ')) return false; + + return auth.slice(7) === token; +} + +export async function GET(request: NextRequest) { + if (!verifyToken(request)) return json({ error: 'Unauthorized' }, 403); + const slug = request.nextUrl.searchParams.get('slug'); + if (!slug) return json({ error: 'slug is required' }, 400); + + const sql = getDb(); + const rows = await sql` + SELECT * FROM comments WHERE project_slug = ${slug} ORDER BY timestamp ASC + `; + + return json(rows.map(toComment)); +} + +export async function POST(request: NextRequest) { + if (!verifyToken(request)) return json({ error: 'Unauthorized' }, 403); + const body = await request.json(); + const { id, author, type, quote, quoteContext, content, priority, parentId, pageUrl, projectSlug, timestamp, pinX, pinY, anchorSelector, anchorDx, anchorDy } = body; + + if (!id || !author || !projectSlug) return json({ error: 'Missing required fields' }, 400); + + const sql = getDb(); + await sql` + INSERT INTO comments (id, author, type, quote, quote_context_before, quote_context_after, content, priority, parent_id, page_url, project_slug, timestamp, pin_x, pin_y, anchor_selector, anchor_dx, anchor_dy) + VALUES (${id}, ${author}, ${type || 'comment'}, ${quote || ''}, ${quoteContext?.beforeText || ''}, ${quoteContext?.afterText || ''}, ${content || ''}, ${priority || 'want'}, ${parentId || null}, ${pageUrl || ''}, ${projectSlug}, ${timestamp || Date.now()}, ${pinX ?? null}, ${pinY ?? null}, ${anchorSelector ?? null}, ${anchorDx ?? null}, ${anchorDy ?? null}) + `; + + return json({ ok: true }); +} + +type PutAction = 'edit' | 'resolve' | 'cyclePriority' | 'rename' | 'move'; + +export async function PUT(request: NextRequest) { + if (!verifyToken(request)) return json({ error: 'Unauthorized' }, 403); + const body = await request.json(); + const { id, action } = body as { id: string; action: PutAction; [key: string]: unknown }; + + if (!id) return json({ error: 'id is required' }, 400); + if (!action) return json({ error: 'action is required' }, 400); + + const sql = getDb(); + + switch (action) { + case 'edit': { + const { content, priority } = body; + if (content === undefined || priority === undefined) return json({ error: 'content and priority are required for edit' }, 400); + await sql` + UPDATE comments SET content = ${content}, priority = ${priority}, updated_at = ${Date.now()} WHERE id = ${id} + `; + break; + } + case 'resolve': { + const { resolved, resolvedBy, resolvedAt } = body; + await sql` + UPDATE comments SET resolved = ${resolved}, resolved_by = ${resolvedBy || null}, resolved_at = ${resolvedAt || null} WHERE id = ${id} + `; + break; + } + case 'cyclePriority': { + const { priority } = body; + if (!priority) return json({ error: 'priority is required for cyclePriority' }, 400); + await sql` + UPDATE comments SET priority = ${priority} WHERE id = ${id} + `; + break; + } + case 'rename': { + const { author, oldAuthor, projectSlug } = body; + if (!author || !oldAuthor || !projectSlug) return json({ error: 'author, oldAuthor, projectSlug are required for rename' }, 400); + await sql` + UPDATE comments SET author = ${author} WHERE project_slug = ${projectSlug} AND author = ${oldAuthor} + `; + break; + } + case 'move': { + const { pinX, pinY, anchorSelector, anchorDx, anchorDy } = body; + if (pinX === undefined || pinY === undefined) return json({ error: 'pinX and pinY are required for move' }, 400); + await sql` + UPDATE comments SET pin_x = ${pinX}, pin_y = ${pinY}, anchor_selector = ${anchorSelector ?? null}, anchor_dx = ${anchorDx ?? null}, anchor_dy = ${anchorDy ?? null}, updated_at = ${Date.now()} WHERE id = ${id} + `; + break; + } + default: + return json({ error: `Unknown action: ${action}` }, 400); + } + + return json({ ok: true }); +} + +export async function DELETE(request: NextRequest) { + if (!verifyToken(request)) return json({ error: 'Unauthorized' }, 403); + const id = request.nextUrl.searchParams.get('id'); + if (!id) return json({ error: 'id is required' }, 400); + + const sql = getDb(); + await sql`DELETE FROM comments WHERE id = ${id} OR parent_id = ${id}`; + + return json({ ok: true }); +} + +function toComment(row: Record): Comment { + return { + id: row.id as string, + author: row.author as string, + type: (row.type as Comment['type']) || 'comment', + quote: (row.quote as string) || '', + quoteContext: { + beforeText: (row.quote_context_before as string) || '', + afterText: (row.quote_context_after as string) || '', + }, + content: (row.content as string) || '', + priority: (row.priority as Comment['priority']) || 'want', + parentId: (row.parent_id as string) || null, + resolved: (row.resolved as boolean) || false, + resolvedBy: (row.resolved_by as string) || null, + resolvedAt: row.resolved_at ? Number(row.resolved_at) : null, + timestamp: Number(row.timestamp), + updatedAt: row.updated_at ? Number(row.updated_at) : null, + pageUrl: (row.page_url as string) || '', + pinX: row.pin_x != null ? Number(row.pin_x) : null, + pinY: row.pin_y != null ? Number(row.pin_y) : null, + anchorSelector: (row.anchor_selector as string) || null, + anchorDx: row.anchor_dx != null ? Number(row.anchor_dx) : null, + anchorDy: row.anchor_dy != null ? Number(row.anchor_dy) : null, + }; +} diff --git a/src/app/api/voice-analyze/route.ts b/src/app/api/voice-analyze/route.ts new file mode 100644 index 0000000..4de7158 --- /dev/null +++ b/src/app/api/voice-analyze/route.ts @@ -0,0 +1,163 @@ +import { NextRequest, NextResponse } from 'next/server'; + +// 音声ファイルを base64 で受けるため body サイズ上限を緩める +export const maxDuration = 60; + +function corsHeaders() { + return { + 'Access-Control-Allow-Origin': '*', + 'Access-Control-Allow-Methods': 'POST, OPTIONS', + 'Access-Control-Allow-Headers': 'Content-Type, Authorization', + }; +} + +export async function OPTIONS() { + return new NextResponse(null, { status: 204, headers: corsHeaders() }); +} + +function json(data: unknown, status = 200) { + return NextResponse.json(data, { status, headers: corsHeaders() }); +} + +function verifyToken(request: NextRequest): boolean { + const token = process.env.API_TOKEN; + // ローカル開発ではトークン無しで動かせるようにする。本番で未設定なら、 + // 設定ミスで Gemini を誰でも叩ける状態になるより落ちた方が安全。 + if (!token) return process.env.NODE_ENV !== 'production'; + const auth = request.headers.get('Authorization'); + if (!auth?.startsWith('Bearer ')) return false; + return auth.slice(7) === token; +} + +interface Target { + index: number; + text: string; + heading: string; + isHeading?: boolean; +} + +// gemini-flash-latest は常に最新の flash 系モデルを指す公式エイリアス。 +// 固定バージョン名(例: gemini-2.5-flash)は新規APIキーで使えなくなることがある。 +const GEMINI_MODEL = process.env.GEMINI_MODEL || 'gemini-flash-latest'; + +// Gemini に JSON 配列で返させるためのスキーマ +const RESPONSE_SCHEMA = { + type: 'ARRAY', + items: { + type: 'OBJECT', + properties: { + target_index: { type: 'INTEGER' }, + quote: { type: 'STRING' }, + comment: { type: 'STRING' }, + priority: { type: 'STRING', enum: ['must', 'better', 'want'] }, + }, + required: ['target_index', 'quote', 'comment', 'priority'], + }, +}; + +function buildPrompt(targets: Target[]): string { + const list = targets + .map((t) => { + const context = t.isHeading ? '見出し' : t.heading || '(見出しなし)'; + return `${t.index}\t[${context}]\t${t.text}`; + }) + .join('\n'); + return [ + 'あなたは図解レビューのフィードバック解析AIです。', + 'ユーザーが音声で話したフィードバックを聞き取り、図解のどの部分に対する指摘かを1つずつ特定してください。', + '', + '# 図解の要素一覧', + '各行は「index[分類]その要素に表示されている文字」です。', + '[分類] が「見出し」の行はセクションの見出しそのもの、それ以外はその名前のセクションに属する本文・ラベルです。', + 'ピンはあなたが選んだ要素の真上に立ちます。', + list, + '', + '# 指示', + '- 音声を聞き、指摘・要望・感想を1件ずつ抽出する', + '- quote には、その指摘の根拠になった発言を音声のまま短く書き出す', + '- target_index は、話者が実際に言及した語句と最も一致する要素を選ぶ', + '- 話者が具体的な文言(カードのラベル・数字・固有名詞など)を口にしたら、見出しではなくその文言そのものの要素を選ぶ', + '- 具体的な文言がなく話題だけ分かる場合は、そのセクションの見出し要素を選ぶ', + '- どの要素にも結び付けられない場合だけ target_index に -1 を入れる', + '- comment はユーザーの意図を簡潔にまとめた日本語にする', + '- priority は次の基準で割り当てる: 修正が必須=must / 改善してほしい=better / 軽微な要望や肯定的な発言=want', + '- 「ここはOK」「良い」などの肯定的な発言も want として記録する', + '- 指摘が複数あれば配列で複数返す。何も聞き取れなければ空配列を返す', + ].join('\n'); +} + +export async function POST(request: NextRequest) { + if (!verifyToken(request)) return json({ error: 'Unauthorized' }, 403); + + const apiKey = process.env.GEMINI_API_KEY; + if (!apiKey) { + return json({ error: 'GEMINI_API_KEY が未設定です' }, 500); + } + + let body: { audioBase64?: string; mimeType?: string; targets?: Target[] }; + try { + body = await request.json(); + } catch { + return json({ error: 'リクエストボディが不正です' }, 400); + } + + const { audioBase64, mimeType, targets } = body; + if (!audioBase64) return json({ error: '音声データがありません' }, 400); + if (!Array.isArray(targets) || targets.length === 0) { + return json({ error: 'ページの要素情報がありません' }, 400); + } + + // Gemini は codecs パラメータ付き mimeType を嫌うことがあるためベース型に正規化 + const audioMime = (mimeType || 'audio/webm').split(';')[0]; + + const geminiBody = { + contents: [ + { + parts: [ + { text: buildPrompt(targets) }, + { inlineData: { mimeType: audioMime, data: audioBase64 } }, + ], + }, + ], + generationConfig: { + responseMimeType: 'application/json', + responseSchema: RESPONSE_SCHEMA, + }, + }; + + const url = `https://generativelanguage.googleapis.com/v1beta/models/${GEMINI_MODEL}:generateContent`; + + let geminiRes: Response; + try { + geminiRes = await fetch(url, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'x-goog-api-key': apiKey, + }, + body: JSON.stringify(geminiBody), + }); + } catch { + return json({ error: 'Gemini への接続に失敗しました' }, 502); + } + + if (!geminiRes.ok) { + const detail = await geminiRes.text().catch(() => ''); + return json({ error: `Gemini エラー (${geminiRes.status})`, detail }, 502); + } + + const data = await geminiRes.json(); + const text: string | undefined = data?.candidates?.[0]?.content?.parts?.[0]?.text; + if (!text) { + return json({ error: 'Gemini から有効な応答が得られませんでした' }, 502); + } + + let results: unknown; + try { + results = JSON.parse(text); + } catch { + return json({ error: 'Gemini 応答の JSON パースに失敗しました', detail: text }, 502); + } + + return json({ results }); +} diff --git a/src/app/layout.tsx b/src/app/layout.tsx new file mode 100644 index 0000000..233c549 --- /dev/null +++ b/src/app/layout.tsx @@ -0,0 +1,11 @@ +export default function RootLayout({ + children, +}: { + children: React.ReactNode; +}) { + return ( + + {children} + + ); +} diff --git a/src/lib/db.ts b/src/lib/db.ts new file mode 100644 index 0000000..d7ef3f7 --- /dev/null +++ b/src/lib/db.ts @@ -0,0 +1,7 @@ +import { neon } from '@neondatabase/serverless'; + +export function getDb() { + const url = process.env.DATABASE_URL; + if (!url) throw new Error('DATABASE_URL is not set'); + return neon(url); +} diff --git a/src/shared/api-client.ts b/src/shared/api-client.ts new file mode 100644 index 0000000..e551e32 --- /dev/null +++ b/src/shared/api-client.ts @@ -0,0 +1,5 @@ +export function authHeaders(token: string): Record { + const hdrs: Record = { 'Content-Type': 'application/json' }; + if (token) hdrs['Authorization'] = 'Bearer ' + token; + return hdrs; +} diff --git a/src/shared/constants.ts b/src/shared/constants.ts new file mode 100644 index 0000000..b2a130c --- /dev/null +++ b/src/shared/constants.ts @@ -0,0 +1,61 @@ +import type { Priority } from './types'; + +export const PRIORITY_COLORS: Record< + Priority, + { bg: string; text: string; light: string; border: string } +> = { + must: { + bg: '#ef4444', + text: '#fff', + light: 'rgba(239,68,68,0.1)', + border: 'rgba(239,68,68,0.3)', + }, + better: { + bg: '#f59e0b', + text: '#fff', + light: 'rgba(245,158,11,0.1)', + border: 'rgba(245,158,11,0.3)', + }, + want: { + bg: '#22c55e', + text: '#fff', + light: 'rgba(34,197,94,0.1)', + border: 'rgba(34,197,94,0.3)', + }, +}; + +export const PRIORITY_LABELS: Record = { + must: 'Must', + better: 'Better', + want: 'Want', +}; + +export const PRIORITY_CYCLE: Record = { + must: 'better', + better: 'want', + want: 'must', +}; + +export const HIGHLIGHT_COLORS: Record< + Priority, + { bg: string; hoverBg: string; border: string } +> = { + must: { + bg: 'rgba(239,68,68,0.15)', + hoverBg: 'rgba(239,68,68,0.25)', + border: '#ef4444', + }, + better: { + bg: 'rgba(245,158,11,0.15)', + hoverBg: 'rgba(245,158,11,0.25)', + border: '#f59e0b', + }, + want: { + bg: 'rgba(34,197,94,0.15)', + hoverBg: 'rgba(34,197,94,0.25)', + border: '#22c55e', + }, +}; + +export const USERNAME_KEY = 'fb-username'; +export const SIDEBAR_WIDTH_KEY = 'fb-sidebar-width'; diff --git a/src/shared/slug.ts b/src/shared/slug.ts new file mode 100644 index 0000000..3bf7a65 --- /dev/null +++ b/src/shared/slug.ts @@ -0,0 +1,10 @@ +export function slugify(url: string): string { + return url + .replace(/^https?:\/\//, '') + .replace(/[^a-zA-Z0-9\u3040-\u309F\u30A0-\u30FF\u4E00-\u9FFF]/g, '_') + .substring(0, 100); +} + +export function generateId(): string { + return Date.now().toString(36) + Math.random().toString(36).substring(2, 8); +} diff --git a/src/shared/time.ts b/src/shared/time.ts new file mode 100644 index 0000000..da10abb --- /dev/null +++ b/src/shared/time.ts @@ -0,0 +1,12 @@ +export function fmtTime(ts: number): string { + const d = Date.now() - ts; + if (d < 60000) return 'たった今'; + if (d < 3600000) return Math.floor(d / 60000) + '分前'; + if (d < 86400000) return Math.floor(d / 3600000) + '時間前'; + return new Date(ts).toLocaleDateString('ja-JP', { + month: 'short', + day: 'numeric', + hour: '2-digit', + minute: '2-digit', + }); +} diff --git a/src/shared/types.ts b/src/shared/types.ts new file mode 100644 index 0000000..5dd440e --- /dev/null +++ b/src/shared/types.ts @@ -0,0 +1,27 @@ +export type Priority = 'must' | 'better' | 'want'; + +export type CommentType = 'comment' | 'strikethrough' | 'pin'; + +export interface Comment { + id: string; + author: string; + type: CommentType; + quote: string; + quoteContext: { beforeText: string; afterText: string }; + content: string; + priority: Priority; + parentId: string | null; + resolved: boolean; + resolvedBy: string | null; + resolvedAt: number | null; + timestamp: number; + updatedAt: number | null; + pageUrl: string; + pinX: number | null; // type==='pin' のときコンテナ幅に対する%(フォールバック) + pinY: number | null; // type==='pin' のときコンテナ上端からのpx(フォールバック) + anchorSelector: string | null; // 追従の基準にする最寄り要素の CSS セレクタ + anchorDx: number | null; // 基準要素の左上からの相対px(横) + anchorDy: number | null; // 基準要素の左上からの相対px(縦) +} + +export type FilterMode = 'unresolved' | 'resolved' | 'all'; diff --git a/src/widget/api.ts b/src/widget/api.ts new file mode 100644 index 0000000..818be5f --- /dev/null +++ b/src/widget/api.ts @@ -0,0 +1,53 @@ +import { authHeaders } from '../shared/api-client'; + +let apiBase = ''; +let apiToken = ''; + +export function initApi(base: string, token: string): void { + apiBase = base; + apiToken = token; +} + +export function api(method: string, params: Record): Promise { + let url = apiBase + '/api/comments'; + const hdrs = authHeaders(apiToken); + const opts: RequestInit = { method, headers: hdrs }; + if (method === 'GET') { + url += '?slug=' + encodeURIComponent(params.slug as string); + } else if (method === 'DELETE') { + url += '?id=' + encodeURIComponent(params.id as string); + } else { + opts.body = JSON.stringify(params); + } + return fetch(url, opts).then((r) => r.json()); +} + +export interface VoiceTargetInput { + index: number; + text: string; // その要素に表示されている文字 + heading: string; // 直前の見出し(どのセクションの中か) + isHeading: boolean; // 見出し要素そのものか +} + +export interface VoiceResult { + target_index: number; // 一致する要素がなければ -1 + quote: string; // 判断の根拠になった発言 + comment: string; + priority: string; +} + +/** + * 録音した音声(base64) + ページ上の要素一覧を /api/voice-analyze に送り、 + * Gemini が「文字起こし+要素への振り分け」した結果を受け取る。 + */ +export function voiceApi( + audioBase64: string, + mimeType: string, + targets: VoiceTargetInput[], +): Promise<{ results?: VoiceResult[]; error?: string }> { + return fetch(apiBase + '/api/voice-analyze', { + method: 'POST', + headers: authHeaders(apiToken), + body: JSON.stringify({ audioBase64, mimeType, targets }), + }).then((r) => r.json()); +} diff --git a/src/widget/dom.ts b/src/widget/dom.ts new file mode 100644 index 0000000..109bf44 --- /dev/null +++ b/src/widget/dom.ts @@ -0,0 +1,24 @@ +type Attrs = Record; + +export function el(tag: string, attrs?: Attrs | null, children?: string | Node | (Node | null)[] | null): HTMLElement { + const e = document.createElement(tag); + if (attrs) Object.keys(attrs).forEach((k) => { + const v = attrs[k]; + if (k === 'className') e.className = v as string; + else if (k === 'innerHTML') e.innerHTML = v as string; + else if (k.startsWith('on') && typeof v === 'function') e.addEventListener(k.substring(2).toLowerCase(), v as EventListener); + else e.setAttribute(k, String(v)); + }); + if (children != null) { + if (typeof children === 'string') e.textContent = children; + else if (Array.isArray(children)) children.forEach((c) => { if (c) e.appendChild(c); }); + else e.appendChild(children); + } + return e; +} + +export function esc(s: string): string { + const d = document.createElement('div'); + d.textContent = s; + return d.innerHTML; +} diff --git a/src/widget/highlight.ts b/src/widget/highlight.ts new file mode 100644 index 0000000..756baba --- /dev/null +++ b/src/widget/highlight.ts @@ -0,0 +1,110 @@ +import { state, type FbComment } from './state'; + +function collectTextNodes(): Text[] { + const nodes: Text[] = []; + const tw = document.createTreeWalker(document.body, NodeFilter.SHOW_TEXT, { + acceptNode(n) { + const p = n.parentElement; + if (!p) return NodeFilter.FILTER_REJECT; + if (p.tagName === 'SCRIPT' || p.tagName === 'STYLE') return NodeFilter.FILTER_REJECT; + if (p.closest('#fb-sidebar,#fb-toggle,#fb-popup,.fb-highlight')) return NodeFilter.FILTER_REJECT; + return NodeFilter.FILTER_ACCEPT; + } + }); + let n: Node | null; + while ((n = tw.nextNode())) nodes.push(n as Text); + return nodes; +} + +export function mapNormToOrig(orig: string, normStart: number, normEnd: number): [number, number] | null { + const normalized = orig.replace(/[\s\u00A0]+/g, ' '); + if (normStart >= normalized.length) return null; + let origIdx = 0, normIdx = 0; + let origStart = -1, origEnd = -1; + while (origIdx < orig.length) { + if (normIdx === normStart && origStart === -1) origStart = origIdx; + if (normIdx === normEnd) { origEnd = origIdx; break; } + if (normIdx < normalized.length && orig[origIdx] === normalized[normIdx]) { + origIdx++; normIdx++; + } else { + origIdx++; + } + } + if (origEnd === -1 && normIdx >= normEnd) origEnd = origIdx; + if (origStart === -1 || origEnd === -1 || origStart >= origEnd) return null; + return [origStart, origEnd]; +} + +function wrapTextRange(node: Text, start: number, end: number, comment: FbComment, onClickHighlight: (id: string) => void): void { + const orig = node.textContent!; + const before = document.createTextNode(orig.substring(0, start)); + const mark = document.createElement('mark'); + mark.className = 'fb-highlight fb-highlight-' + comment.priority; + mark.dataset.commentId = comment.id; + mark.textContent = orig.substring(start, end); + mark.addEventListener('click', () => { onClickHighlight(comment.id); }); + const after = document.createTextNode(orig.substring(end)); + node.parentNode!.insertBefore(before, node); + node.parentNode!.insertBefore(mark, node); + node.parentNode!.insertBefore(after, node); + node.parentNode!.removeChild(node); +} + +export function applyHighlights(onClickHighlight: (id: string) => void): void { + document.querySelectorAll('.fb-highlight').forEach((el) => { + const t = document.createTextNode(el.textContent || ''); + el.parentNode!.replaceChild(t, el); + }); + document.body.normalize(); + + state.comments.filter((c) => !c.parentId && !c.resolved && c.quote && c.quote.length >= 2).forEach((c) => { + const search = c.quote.replace(/[\s\u00A0]+/g, ' ').trim(); + const textNodes = collectTextNodes(); + let found = false; + + for (let i = 0; i < textNodes.length; i++) { + const node = textNodes[i]; + const orig = node.textContent!; + + const di = orig.indexOf(search); + if (di !== -1) { + try { wrapTextRange(node, di, di + search.length, c, onClickHighlight); found = true; } catch (_) { /* ignore */ } + break; + } + + const norm = orig.replace(/[\s\u00A0]+/g, ' '); + const ni = norm.indexOf(search); + if (ni === -1) continue; + const range = mapNormToOrig(orig, ni, ni + search.length); + if (range) { + try { wrapTextRange(node, range[0], range[1], c, onClickHighlight); found = true; } catch (_) { /* ignore */ } + break; + } + } + + if (!found) { + let concat = ''; + const nodeMap: Array<{ node: Text; start: number; end: number }> = []; + for (let j = 0; j < textNodes.length; j++) { + const s = concat.length; + concat += textNodes[j].textContent; + nodeMap.push({ node: textNodes[j], start: s, end: concat.length }); + } + const concatNorm = concat.replace(/[\s\u00A0]+/g, ' '); + const ci = concatNorm.indexOf(search); + if (ci !== -1) { + const range = mapNormToOrig(concat, ci, ci + search.length); + if (range) { + const mStart = range[0], mEnd = range[1]; + for (let k = nodeMap.length - 1; k >= 0; k--) { + const nm = nodeMap[k]; + if (nm.end <= mStart || nm.start >= mEnd) continue; + const ls = Math.max(0, mStart - nm.start); + const le = Math.min(nm.node.textContent!.length, mEnd - nm.start); + try { wrapTextRange(nm.node, ls, le, c, onClickHighlight); } catch (_) { /* ignore */ } + } + } + } + } + }); +} diff --git a/src/widget/icons.ts b/src/widget/icons.ts new file mode 100644 index 0000000..81031fe --- /dev/null +++ b/src/widget/icons.ts @@ -0,0 +1,16 @@ +export const SVG: Record = { + message: '', + check: '', + rotateCcw: '', + pencil: '', + trash: '', + user: '', + x: '', + panelRight: '', + mic: '', + loader: '', +}; + +export function icon(name: string, size = 14, strokeWidth = 2): string { + return (SVG[name] || '').replace(/SIZE/g, String(size)).replace(/SW/g, String(strokeWidth)); +} diff --git a/src/widget/index.ts b/src/widget/index.ts new file mode 100644 index 0000000..b247ff8 --- /dev/null +++ b/src/widget/index.ts @@ -0,0 +1,286 @@ +/** + * Icons: Lucide (https://lucide.dev) + * ISC License - Copyright (c) Lucide Contributors 2026 + */ + +// document.currentScript は IIFE 先頭でキャプチャ必須(esbuild の import 巻き上げ前に実行される) +const SCRIPT = document.currentScript as HTMLScriptElement | null; +const API_BASE = SCRIPT ? SCRIPT.src.replace(/\/widget\.js.*$/, '') : ''; +const API_TOKEN = SCRIPT ? (SCRIPT.dataset.token || '') : ''; + +import type { Priority } from '../shared/types'; +import { USERNAME_KEY, PRIORITY_CYCLE } from '../shared/constants'; +import { generateId } from '../shared/slug'; +import { initApi, api } from './api'; +import { state, slug, type FbComment } from './state'; +import { injectStyles } from './styles'; +import { render, toggleSidebar, setRenderDeps } from './render/index'; +import { setToggleAreaHandler, setToggleVoiceHandler } from './render/toggle'; +import { setSidebarActions } from './render/sidebar'; +import { applyHighlights } from './highlight'; +import { scrollToQuote, scrollToCard, scrollToPin } from './scroll'; +import { setupTextSelection } from './selection'; +import { + enterPinMode, exitPinMode, + handlePinClick, renderPins, renderPinPopup, cancelPinPopup, + setupPinDrag, +} from './pin'; +import { toggleRecording, setVoiceOnChange } from './voice'; + +initApi(API_BASE, API_TOKEN); + +function loadComments(): Promise { + return api('GET', { slug }).then((c) => { + if (Array.isArray(c)) state.comments = c; + render(); + renderPins(); // ピンも state.comments に含まれるため取得後に描画 + applyHighlights(onClickHighlight); + }).catch(() => { + render(); + renderPins(); + applyHighlights(onClickHighlight); + }); +} + +function closePopup(): void { + state.selectedText = ''; + state.selectedRect = null; + state.popupContent = ''; + state.popupPriority = 'must'; + render(); +} + +function submitComment(priority: Priority): void { + if (!state.selectedText) return; + const c: FbComment = { + id: generateId(), author: state.username || '匿名', type: 'comment', + quote: state.selectedText, quoteContext: state.selectedQuoteContext, + content: state.popupContent.trim(), + priority, parentId: null, pageUrl: window.location.href, + projectSlug: slug, timestamp: Date.now(), + resolved: false, resolvedBy: null, resolvedAt: null, updatedAt: null, + }; + state.comments.push(c); + closePopup(); + applyHighlights(onClickHighlight); + api('POST', c as unknown as Record).then(loadComments); +} + +function resolveComment(id: string): void { + const c = state.comments.find((x) => x.id === id); + if (!c) return; + const now = !c.resolved; + c.resolved = now; + c.resolvedBy = now ? state.username : null; + c.resolvedAt = now ? Date.now() : null; + render(); renderPins(); applyHighlights(onClickHighlight); + api('PUT', { id, action: 'resolve', resolved: now, resolvedBy: c.resolvedBy, resolvedAt: c.resolvedAt }); +} + +function cyclePriority(id: string): void { + const c = state.comments.find((x) => x.id === id); + if (!c || c.author !== state.username) return; + c.priority = PRIORITY_CYCLE[c.priority] || 'must'; + render(); renderPins(); applyHighlights(onClickHighlight); + api('PUT', { id, action: 'cyclePriority', priority: c.priority }); +} + +function deleteComment(id: string): void { + state.comments = state.comments.filter((c) => c.id !== id && c.parentId !== id); + render(); renderPins(); applyHighlights(onClickHighlight); + api('DELETE', { id }); +} + +function deleteReply(id: string): void { + state.comments = state.comments.filter((c) => c.id !== id); + render(); + api('DELETE', { id }); +} + +function saveEdit(id: string): void { + const c = state.comments.find((x) => x.id === id); + if (!c) return; + c.content = state.editContent; + c.priority = state.editPriority; + state.editingId = null; + render(); renderPins(); applyHighlights(onClickHighlight); + api('PUT', { id, action: 'edit', content: c.content, priority: c.priority }); +} + +function submitReply(parentId: string): void { + if (!state.replyText.trim() || !state.username) return; + const r: FbComment = { + id: generateId(), author: state.username, type: 'comment', + quote: '', quoteContext: { beforeText: '', afterText: '' }, + content: state.replyText.trim(), priority: 'want', + parentId, pageUrl: window.location.href, + projectSlug: slug, timestamp: Date.now(), + resolved: false, resolvedBy: null, resolvedAt: null, updatedAt: null, + }; + state.comments.push(r); + state.replyingTo = null; + state.replyText = ''; + render(); + api('POST', r as unknown as Record); +} + +function finishNameEdit(): void { + if (state.nameInput.trim() && state.nameInput.trim() !== state.username) { + const oldName = state.username; + state.username = state.nameInput.trim(); + localStorage.setItem(USERNAME_KEY, state.username); + api('PUT', { id: '_rename', action: 'rename', author: state.username, oldAuthor: oldName, projectSlug: slug }).then(loadComments); + } + state.editingName = false; + render(); +} + +function onClickHighlight(id: string): void { + scrollToCard(id, toggleSidebar); +} + +function togglePinMode(): void { + if (state.pinMode) { + exitPinMode(); + } else { + enterPinMode(); + } + render(); + renderPins(); // 吹き出しの編集UI(バッジ/削除)の表示をモードに合わせて更新 +} + +function handleVoiceToggle(): void { + toggleRecording(); +} + +let voiceToastTimer: ReturnType | null = null; +/** 音声解析の失敗を画面に出す。console だけだと気づけず、原因不明のまま使い続けてしまう。 */ +function showVoiceToast(message: string): void { + let toast = document.getElementById('fb-voice-toast'); + if (!toast) { + toast = document.createElement('div'); + toast.id = 'fb-voice-toast'; + toast.className = 'fb-voice-toast'; + document.body.appendChild(toast); + } + toast.textContent = message; + // サイドバーが開いているとその上に重なって読めないので、開いている分だけ左に寄せる + toast.style.right = (state.sidebarOpen ? state.sidebarWidth + 16 : 16) + 'px'; + // 追加直後に show を付けるとトランジションが走らないため 1 フレーム待つ + requestAnimationFrame(() => toast.classList.add('show')); + if (voiceToastTimer) clearTimeout(voiceToastTimer); + voiceToastTimer = setTimeout(() => toast.classList.remove('show'), 6000); +} + +// Wire up dependencies before first render +setRenderDeps(closePopup, submitComment); +setToggleAreaHandler(togglePinMode); +setToggleVoiceHandler(handleVoiceToggle); +// 録音状態の変化やエラー、ピン配置完了時に UI を更新する +setVoiceOnChange(() => { + render(); + if (state.voiceError) { + showVoiceToast(state.voiceError); + state.voiceError = null; // 一度知らせたら消す(次の録音に持ち越さない) + } +}); +setSidebarActions({ + toggleSidebar, + cyclePriority, + scrollToQuote, + scrollToPin, + resolveComment, + deleteComment, + deleteReply, + saveEdit, + submitReply, + finishNameEdit, +}); + +// フィードバックモード ON 中の「クリック=ピン / ドラッグ=テキスト選択」自動判定。 +// クリックかドラッグかは mousedown→click の移動距離で判定し、テキスト選択時はピンを置かない。 +const WIDGET_UI_SELECTOR = '#fb-sidebar,#fb-toggle,#fb-pin-popup,.fb-pin-marker,.fb-pin-tooltip,.fb-pin-delete,.fb-popup'; +const CLICK_DRAG_THRESHOLD = 6; // px + +function closePinPopup(): void { + cancelPinPopup(); + state.editingPinId = null; + const popup = document.getElementById('fb-pin-popup'); + if (popup) popup.classList.remove('show'); + render(); +} + +function setupClickAndDrag(): void { + let downX = 0; + let downY = 0; + + document.addEventListener('mousedown', (e) => { + downX = e.clientX; + downY = e.clientY; + }, true); + + document.addEventListener('click', (e) => { + const inWidgetUi = !!(e.target as HTMLElement).closest(WIDGET_UI_SELECTOR); + + if (!state.pinMode) { + // モードOFFでもピンをクリックすれば編集ポップアップが開く。 + // モードON時のように「外側クリック=別の場所にピンを追加」へは + // 遷移しないので、ここで閉じないと閉じ手段が Escape だけになる。 + if (state.pinPopupPos && !inWidgetUi) closePinPopup(); + return; + } + if (inWidgetUi) return; + + // ドラッグでテキスト選択された場合はテキストフロー(mode1)に委譲 + const sel = window.getSelection(); + if (sel && sel.toString().trim()) return; + + // 移動が大きければドラッグとみなしてピンは置かない + if (Math.hypot(e.clientX - downX, e.clientY - downY) >= CLICK_DRAG_THRESHOLD) return; + + e.preventDefault(); + e.stopPropagation(); + handlePinClick(e); + if (state.pinPopupPos) { + renderPinPopup(render); + } + render(); + }, true); + + document.addEventListener('keydown', (e) => { + if (e.key === 'Escape') { + if (state.pinPopupPos) { + closePinPopup(); + } else if (state.pinMode) { + exitPinMode(); + render(); + renderPins(); + } + } + }); +} + +let resizeTimer: ReturnType | null = null; +function setupPinReposition(): void { + // 画面幅が変わるとアンカー基準位置も変わるため、ピンを再配置する(追従) + window.addEventListener('resize', () => { + if (resizeTimer) clearTimeout(resizeTimer); + resizeTimer = setTimeout(() => { renderPins(); }, 100); + }); +} + +function init(): void { + injectStyles(); + render(); + setupTextSelection(render, closePopup); + setupClickAndDrag(); + setupPinDrag(render); + setupPinReposition(); + loadComments(); // 取得後に renderPins も実行される(ピンは state.comments に含まれる) +} + +if (document.readyState === 'loading') { + document.addEventListener('DOMContentLoaded', init); +} else { + init(); +} diff --git a/src/widget/pin.ts b/src/widget/pin.ts new file mode 100644 index 0000000..9fe26b4 --- /dev/null +++ b/src/widget/pin.ts @@ -0,0 +1,513 @@ +import type { Priority } from '../shared/types'; +import { PRIORITY_COLORS } from '../shared/constants'; +import { generateId } from '../shared/slug'; +import { state, slug, type FbComment } from './state'; +import { api } from './api'; +import { esc } from './dom'; + +function getContainer(): HTMLElement { + return document.querySelector('main') || document.body; +} + +/** + * ピンの追従基準。 + * dx/dy は「基準要素の左上からの相対px」(手動ピン=クリックした一点を保つ)。 + * dx/dy が null のときは「その要素の文字の中央上端」を意味し、表示のたびに測り直す。 + * 音声ピンは要素そのものを指すので後者を使う。絶対pxで持つと、画面幅が変わって + * 要素が縮んだときにオフセットだけが元のまま残り、文字から大きく外れてしまう。 + */ +export interface PinAnchor { selector: string; dx: number | null; dy: number | null; } + +// 新規ピン配置時に handlePinClick が捕捉したアンカーを submit まで一時保持する +let pendingAnchor: PinAnchor | null = null; + +/** 要素から、document.querySelector で再取得できる安定的な CSS パスを生成する。 */ +function cssPath(el: Element): string { + const esc = (s: string) => (window.CSS && CSS.escape ? CSS.escape(s) : s); + const parts: string[] = []; + let node: Element | null = el; + while (node && node.nodeType === 1 && node !== document.body) { + if ((node as HTMLElement).id) { parts.unshift('#' + esc((node as HTMLElement).id)); break; } + let sel = node.tagName.toLowerCase(); + const parent: Element | null = node.parentElement; + if (parent) { + const sib = Array.from(parent.children).filter((c) => c.tagName === node!.tagName); + if (sib.length > 1) sel += ':nth-of-type(' + (sib.indexOf(node) + 1) + ')'; + } + parts.unshift(sel); + node = parent; + } + return parts.join('>'); +} + +const WIDGET_UI = '#fb-sidebar,#fb-toggle,#fb-pin-popup,.fb-pin-marker,.fb-pin-tooltip'; + +/** + * 画面座標(clientX,clientY)の下にある「コンテナ内の要素」をアンカーとして捕捉する。 + * elementFromPoint(単数)だと最前面で止まるため、その位置にピンのマーカーや + * 吹き出しが重なっていると本文を拾えずアンカーを失う。重なりを上から順に見て、 + * 最初に見つかった本文要素を採用する。 + */ +function captureAnchorAtPoint(clientX: number, clientY: number): PinAnchor | null { + const container = getContainer(); + const stack = document.elementsFromPoint(clientX, clientY) as HTMLElement[]; + const el = stack.find( + (e) => !e.closest(WIDGET_UI) && e !== container && e !== document.body && container.contains(e), + ); + if (!el) return null; + const eRect = el.getBoundingClientRect(); + return { selector: cssPath(el), dx: clientX - eRect.left, dy: clientY - eRect.top }; +} + +/** その座標がサイドバー・トグル・ポップアップに覆われているか。 */ +function droppedOnWidgetUi(clientX: number, clientY: number): boolean { + return document + .elementsFromPoint(clientX, clientY) + .some((e) => (e as HTMLElement).closest('#fb-sidebar,#fb-toggle,#fb-pin-popup')); +} + +/** + * 要素内で実際に描画されている「文字」の矩形を返す。 + * 要素の矩形をそのまま使うと、中央寄せの見出しのように box が横幅いっぱいの場合に + * 左端=文字から遠く離れた余白を指してしまう。Range で中身を選択すると + * 文字が占めている範囲だけが取れる。 + */ +function textRect(el: Element): DOMRect { + try { + const r = document.createRange(); + r.selectNodeContents(el); + const box = r.getBoundingClientRect(); + if (box.width >= 1 && box.height >= 1) return box; + } catch { + // Range が張れない要素(置換要素など)は要素矩形にフォールバック + } + return el.getBoundingClientRect(); +} + +/** + * 要素の「文字の中央上端」をビューポート座標で返す。 + * ピンは transform:translate(-50%,-100%) で先端が座標を指すため、 + * ここを指すとその文字の真上にピンが立つ。 + */ +function textAnchorPoint(el: Element): { x: number; y: number } { + const t = textRect(el); + return { x: t.left + t.width / 2, y: t.top }; +} + +/** 要素の左上から dx/dy だけずらした点(手動ピン:クリックした一点を保つ)。 */ +function offsetPoint(el: Element, dx: number, dy: number): { x: number; y: number } { + const e = el.getBoundingClientRect(); + return { x: e.left + dx, y: e.top + dy }; +} + +/** + * 要素そのものをアンカーにする(音声ピン)。 + * オフセットは持たせず、描画のたびに文字位置を測り直す(resolvePinPosition 参照)。 + */ +export function anchorFromElement(el: Element): PinAnchor { + return { selector: cssPath(el), dx: null, dy: null }; +} + +/** + * ピンの表示座標を解決する。 + * anchorSelector があり要素が見つかれば「要素位置 + 相対オフセット」で再計算(レスポンシブ追従)。 + * 見つからなければ保存済みの pinX%/pinY px にフォールバック。 + */ +function resolvePinPosition(c: { pinX?: number | null; pinY?: number | null; anchorSelector?: string | null; anchorDx?: number | null; anchorDy?: number | null; }): { xPct: number; yPx: number } { + const container = getContainer(); + const cRect = container.getBoundingClientRect(); + if (c.anchorSelector) { + let el: Element | null = null; + try { el = document.querySelector(c.anchorSelector); } catch { el = null; } + if (el) { + // オフセット無し = 要素の文字を指すピン。現在のレイアウトで測り直すので + // 画面幅が変わって折り返しが変化しても文字に追従する。 + const base = c.anchorDx == null || c.anchorDy == null + ? textAnchorPoint(el) + : offsetPoint(el, c.anchorDx, c.anchorDy); + let xPct = ((base.x - cRect.left) / cRect.width) * 100; + if (xPct < 5) xPct = 5; + if (xPct > 95) xPct = 95; + return { xPct, yPx: base.y - cRect.top }; + } + } + // コンテナの外(余白)をクリックして作られたピンは pinX が範囲外になりうる。 + // 画面幅が狭いとそのまま画面外に出て触れなくなるので、ここでも丸めておく。 + let xPct = c.pinX ?? 50; + if (xPct < 5) xPct = 5; + if (xPct > 95) xPct = 95; + return { xPct, yPx: c.pinY ?? 0 }; +} + +/** state.comments の中からピン(type==='pin' のトップレベル)を作成順で返す。 */ +export function getPins(): FbComment[] { + return state.comments.filter((c) => c.type === 'pin' && !c.parentId); +} + +/** ピンの通し番号(マーカーとサイドバーカードで一致させる。作成順=配列順)。 */ +export function pinNumber(id: string): number { + return getPins().findIndex((p) => p.id === id) + 1; +} + +/** + * ページ上の要素を、ピン座標系(x: コンテナ幅に対する %, y: コンテナ上端からの px)に変換する。 + * handlePinClick と同じ基準で座標を出すため、音声(voice.ts)から打つピンも手動ピンと整合する。 + * 指す先は要素の左上ではなく「文字の中央上端」。anchorFromElement と同じ基準にすること。 + */ +export function elementToPinCoords(target: Element): { x: number; y: number } { + const container = getContainer(); + const cRect = container.getBoundingClientRect(); + const p = textAnchorPoint(target); + let xPct = ((p.x - cRect.left) / cRect.width) * 100; + if (xPct < 5) xPct = 5; // 左端に寄りすぎるとピンが見切れる + if (xPct > 95) xPct = 95; + return { x: xPct, y: p.y - cRect.top }; // y はスクロールに依存しないコンテナ内オフセット +} + +export function enterPinMode(): void { + state.pinMode = true; + state.pinPopupPos = null; + document.body.classList.add('fb-pin-mode'); +} + +export function exitPinMode(): void { + state.pinMode = false; + state.pinPopupPos = null; + document.body.classList.remove('fb-pin-mode'); +} + +export function handlePinClick(e: MouseEvent): void { + if ((e.target as HTMLElement).closest('#fb-sidebar,#fb-toggle,#fb-pin-popup,.fb-pin-marker,.fb-pin-tooltip')) return; + + const container = getContainer(); + const containerRect = container.getBoundingClientRect(); + + const relX = e.clientX - containerRect.left; + const xPct = (relX / containerRect.width) * 100; + const yPx = e.pageY - (containerRect.top + window.scrollY); + + state.editingPinId = null; // 新規ピン追加(編集モードを解除) + state.pinPopupPos = { x: xPct, y: yPx }; + pendingAnchor = captureAnchorAtPoint(e.clientX, e.clientY); // 最寄り要素を追従基準に +} + +/** + * ピンを1件追加する汎用関数(手動クリック・音声解析の両方から呼ばれる)。 + * type='pin' のコメントとして state.comments に積み、DB に保存する。 + * 描画はまとめたいことがあるため、ここでは renderPins しない(呼び出し側で行う)。 + */ +export function addPin(x: number, y: number, content: string, priority: Priority, anchor?: PinAnchor | null): FbComment { + const c: FbComment = { + id: generateId(), + author: state.username || '匿名', + type: 'pin', + quote: '', + quoteContext: { beforeText: '', afterText: '' }, + content: content.trim() || '(コメントなし)', + priority, + parentId: null, + pageUrl: window.location.href, + projectSlug: slug, + timestamp: Date.now(), + resolved: false, + resolvedBy: null, + resolvedAt: null, + updatedAt: null, + pinX: x, + pinY: y, + anchorSelector: anchor?.selector ?? null, + anchorDx: anchor?.dx ?? null, + anchorDy: anchor?.dy ?? null, + }; + state.comments.push(c); + api('POST', c as unknown as Record); + return c; +} + +export function submitPinComment(content: string, priority: Priority): void { + if (!state.pinPopupPos) return; + addPin(state.pinPopupPos.x, state.pinPopupPos.y, content, priority, pendingAnchor); + pendingAnchor = null; + state.pinPopupPos = null; + renderPins(); +} + +export function cancelPinPopup(): void { + state.pinPopupPos = null; +} + +/** ピン(と返信)を削除。DBにも反映。 */ +export function deletePin(id: string): void { + state.comments = state.comments.filter((c) => c.id !== id && c.parentId !== id); + api('DELETE', { id }); + renderPins(); +} + +/** ピンの位置を更新(ドラッグ移動)。アンカーも取り直して DB に反映。 */ +export function movePin(id: string, x: number, y: number, anchor?: PinAnchor | null): void { + const c = state.comments.find((p) => p.id === id); + if (!c) return; + c.pinX = x; + c.pinY = y; + c.anchorSelector = anchor?.selector ?? null; + c.anchorDx = anchor?.dx ?? null; + c.anchorDy = anchor?.dy ?? null; + api('PUT', { id, action: 'move', pinX: x, pinY: y, anchorSelector: c.anchorSelector, anchorDx: c.anchorDx, anchorDy: c.anchorDy }); + renderPins(); +} + +/** ピンの本文・優先度を更新(編集ポップアップの保存)。DBにも反映。 */ +export function updatePin(id: string, content: string, priority: Priority): void { + const c = state.comments.find((p) => p.id === id); + if (!c) return; + c.content = content.trim() || '(コメントなし)'; + c.priority = priority; + api('PUT', { id, action: 'edit', content: c.content, priority }); + renderPins(); +} + +/** ピンをクリックしたとき、その場に編集ポップアップ(本文・優先度・削除)を開く。 */ +export function openPinEditor(id: string, onRender: () => void): void { + const c = getPins().find((p) => p.id === id); + if (!c) return; + state.editingPinId = id; + const pos = resolvePinPosition(c); // アンカーで再計算した現在位置にポップアップを出す + state.pinPopupPos = { x: pos.xPct, y: pos.yPx }; + renderPinPopup(onRender); +} + +/** + * ピンのドラッグ移動をセットアップする(委譲方式)。 + * sidebar のリサイズハンドル実装と同じ mousedown→mousemove→mouseup パターン。 + * ピン自体を掴む操作はフィードバックモードに関係なく受け付ける。 + * 音声コメントはモードを ON にしなくても打てるため、モードで縛ると + * 「ピンは出たのに動かせない」状態になる。 + */ +export function setupPinDrag(onRender: () => void): void { + document.addEventListener('mousedown', (e) => { + const target = e.target as HTMLElement; + const marker = target.closest('.fb-pin-marker') as HTMLElement | null; + if (!marker) return; + const id = marker.dataset.pinId; + if (!id) return; + + const startX = e.clientX; + const startY = e.clientY; + let moved = false; + + function onMove(ev: MouseEvent) { + if (!moved && Math.hypot(ev.clientX - startX, ev.clientY - startY) < 6) return; + if (!moved) { + moved = true; + document.body.classList.add('fb-dragging-pin'); + } + ev.preventDefault(); + const container = getContainer(); + const cRect = container.getBoundingClientRect(); + let xPct = ((ev.clientX - cRect.left) / cRect.width) * 100; + if (xPct < 5) xPct = 5; + if (xPct > 95) xPct = 95; + marker!.style.left = xPct + '%'; + marker!.style.top = (ev.clientY - cRect.top) + 'px'; + } + + function cleanup() { + document.removeEventListener('mousemove', onMove); + document.removeEventListener('mouseup', onUp); + window.removeEventListener('blur', onCancel); + document.body.classList.remove('fb-dragging-pin'); + } + + // ウィンドウ外でボタンを離すなどして mouseup を取り逃すと、body に + // user-select:none が残ってページ全体が選択できなくなる。 + function onCancel() { + const wasMoving = moved; + cleanup(); + if (wasMoving) renderPins(); // 保存せず元の位置に戻す + } + + function onUp(ev: MouseEvent) { + const wasMoving = moved; + cleanup(); + if (!wasMoving) { + openPinEditor(id!, onRender); // 動かさずに離した = クリック → 編集 + return; + } + // サイドバーやトグルの上で離した場合は移動をキャンセルして元に戻す。 + // その座標にピンは置けないので、端に丸めて別の場所へ飛ばすより予測しやすい。 + if (droppedOnWidgetUi(ev.clientX, ev.clientY)) { + renderPins(); + return; + } + const container = getContainer(); + const cRect = container.getBoundingClientRect(); + let xPct = ((ev.clientX - cRect.left) / cRect.width) * 100; + if (xPct < 5) xPct = 5; + if (xPct > 95) xPct = 95; + const yPx = ev.clientY - cRect.top; + const anchor = captureAnchorAtPoint(ev.clientX, ev.clientY); // 落とした先の要素を新しい追従基準に + movePin(id!, xPct, yPx, anchor); + } + + document.addEventListener('mousemove', onMove); + document.addEventListener('mouseup', onUp); + window.addEventListener('blur', onCancel); + }); +} + +export function renderPins(): void { + document.querySelectorAll('.fb-pin-marker').forEach((el) => el.remove()); + + const container = getContainer(); + if (getComputedStyle(container).position === 'static') { + container.style.position = 'relative'; + } + + // まず各ピンの表示座標を解決(アンカー追従。無ければ pinX/pinY フォールバック) + const pins = getPins(); + const pos = pins.map((p) => resolvePinPosition(p)); + + // 近接ピンをクラスタ化し、重なって見えないよう横に扇状オフセットする(データは変更しない)。 + const FAN_STEP = 26; // px + const FAN_EDGE = 16; // px。マーカーの半径ぶんコンテナの内側に留める + const cWidth = container.getBoundingClientRect().width; + const clusters: { x: number; y: number; items: number[] }[] = []; + pos.forEach((p, i) => { + let cl = clusters.find((c) => Math.abs(c.x - p.xPct) < 2 && Math.abs(c.y - p.yPx) < 24); + if (!cl) { cl = { x: p.xPct, y: p.yPx, items: [] }; clusters.push(cl); } + cl.items.push(i); + }); + const fanOffset: Record = {}; + clusters.forEach((c) => { + const n = c.items.length; + c.items.forEach((idx, k) => { fanOffset[idx] = (k - (n - 1) / 2) * FAN_STEP; }); + }); + + pins.forEach((comment, idx) => { + const pin = document.createElement('div'); + pin.className = 'fb-pin-marker' + (comment.resolved ? ' resolved' : ''); + pin.dataset.pinId = comment.id; + + const pc = PRIORITY_COLORS[comment.priority] || PRIORITY_COLORS.want; + const xPct = pos[idx].xPct; + // 扇状オフセットでコンテナの外にはみ出すと、狭い画面ではピンが画面外に出て + // 触れなくなる。はみ出す分は内側に押し戻す。 + const basePx = (xPct / 100) * cWidth; + const spread = Math.min(Math.max(basePx + (fanOffset[idx] || 0), FAN_EDGE), cWidth - FAN_EDGE); + const off = Math.round(spread - basePx); + // 横オフセットは calc で left に加算(transform は維持されるのでホバー拡大も効く) + pin.style.left = off ? 'calc(' + xPct + '% + ' + off + 'px)' : xPct + '%'; + pin.style.top = pos[idx].yPx + 'px'; + pin.style.setProperty('--pin-color', pc.bg); + + pin.innerHTML = '
' + (idx + 1) + '
'; + + // ホバー吹き出しは「内容の確認専用」。編集・削除・優先度はピンをクリックして開く編集ポップアップで。 + const tooltip = document.createElement('div'); + tooltip.className = 'fb-pin-tooltip'; + const priLabel = comment.priority.charAt(0).toUpperCase() + comment.priority.slice(1); + const badge = '' + priLabel + ''; + const hint = '
クリックで編集 / ドラッグで移動
'; + tooltip.innerHTML = '
' + badge + '' + esc(comment.author) + '
' + esc(comment.content) + '
' + hint; + + pin.appendChild(tooltip); + container.appendChild(pin); + }); +} + +export function renderPinPopup(onRender: () => void): void { + let popup = document.getElementById('fb-pin-popup'); + if (!popup) { + popup = document.createElement('div'); + popup.id = 'fb-pin-popup'; + popup.className = 'fb-pin-popup'; + document.body.appendChild(popup); + } + + if (!state.pinPopupPos) { + popup.classList.remove('show'); + return; + } + + popup.classList.add('show'); + + // 編集対象ピン(あれば編集モード、なければ新規追加モード) + const editing = state.editingPinId + ? getPins().find((p) => p.id === state.editingPinId) || null + : null; + + const container = getContainer(); + const containerRect = container.getBoundingClientRect(); + const absX = (state.pinPopupPos.x / 100) * containerRect.width + containerRect.left; + const absY = state.pinPopupPos.y + containerRect.top + window.scrollY; + + let h = '
' + (editing ? 'コメントを編集' : 'コメントを追加') + '
'; + h += ''; + h += '
'; + (['must', 'better', 'want'] as const).forEach((p) => { + const pc = PRIORITY_COLORS[p]; + h += ''; + }); + h += '
'; + // 送信/キャンセルは撤去(優先度ボタンで送信、ESC/外側クリックでキャンセル)。編集時のみ削除ボタンを残す。 + if (editing) { + h += '
'; + } + + popup.innerHTML = h; + + const pw = 300, m = 8; + let top = absY + 24 - window.scrollY; + if (top + 220 > window.innerHeight) top = absY - 220 - m - window.scrollY; + let left = absX - pw / 2; + if (left < m) left = m; + if (left + pw > window.innerWidth - m) left = window.innerWidth - pw - m; + + popup.style.position = 'fixed'; + popup.style.top = top + 'px'; + popup.style.left = left + 'px'; + + const textarea = popup.querySelector('#fb-pin-textarea') as HTMLTextAreaElement; + textarea.value = editing ? editing.content : ''; + setTimeout(() => textarea?.focus(), 50); + + const priBtns = popup.querySelectorAll('.fb-pin-pri-btn'); + let selectedPri: Priority = editing ? editing.priority : 'better'; + const paintPri = () => priBtns.forEach((b) => { + (b as HTMLElement).style.opacity = (b as HTMLElement).dataset.pri === selectedPri ? '1' : '0.4'; + }); + priBtns.forEach((btn) => { + btn.addEventListener('click', () => { + selectedPri = (btn as HTMLElement).dataset.pri as Priority; + commit(); // 優先度を押した時点で即送信/保存(ワンクリック) + }); + }); + paintPri(); + + const close = () => { + state.editingPinId = null; + state.pinPopupPos = null; + popup!.classList.remove('show'); + onRender(); + }; + + const commit = () => { + if (editing) { + updatePin(editing.id, textarea.value, selectedPri); + } else { + submitPinComment(textarea.value, selectedPri); + } + close(); + }; + + if (editing) { + popup.querySelector('.fb-pin-delete-btn')!.addEventListener('click', () => { + deletePin(editing.id); + close(); + }); + } + + textarea.addEventListener('keydown', (ev) => { + if ((ev.metaKey || ev.ctrlKey) && ev.key === 'Enter') commit(); + }); +} diff --git a/src/widget/render/card.ts b/src/widget/render/card.ts new file mode 100644 index 0000000..2df3eb1 --- /dev/null +++ b/src/widget/render/card.ts @@ -0,0 +1,83 @@ +import { PRIORITY_COLORS } from '../../shared/constants'; +import { fmtTime } from '../../shared/time'; +import { esc } from '../dom'; +import { icon } from '../icons'; +import { state, type FbComment } from '../state'; +import { pinNumber } from '../pin'; + +export function renderCard(c: FbComment): string { + const isOwn = c.author === state.username; + const pc = PRIORITY_COLORS[c.priority] || PRIORITY_COLORS.want; + + let h = '
'; + + h += '
'; + h += '
' + esc(c.author.charAt(0)) + '
'; + h += '' + esc(c.author) + ''; + h += '' + fmtTime(c.timestamp) + ''; + if (c.resolved) h += '' + icon('check', 12) + ' 解決済'; + h += '
'; + h += '' + esc(c.priority.charAt(0).toUpperCase() + c.priority.slice(1)) + ''; + h += '
'; + + if (c.type === 'pin') { + // ピンは引用の代わりに「📍 #N」インジケータ(クリックで該当ピンへスクロール) + h += '
' + pinNumber(c.id) + 'ピンを表示
'; + } else if (c.quote) { + const q = c.quote.length > 100 ? c.quote.substring(0, 100) + '...' : c.quote; + h += '
' + esc(q) + '
'; + } + + if (state.editingId === c.id) { + h += '
'; + h += '
'; + (['must', 'better', 'want'] as const).forEach((p) => { + const sel = state.editPriority === p; + const pc2 = PRIORITY_COLORS[p]; + h += ''; + }); + h += '
'; + h += ''; + h += '
'; + h += '
'; + } else { + h += '
' + esc(c.content) + '
'; + } + + if (state.editingId !== c.id) { + h += '
'; + h += ''; + h += ''; + if (isOwn) h += ''; + if (isOwn) h += ''; + h += '
'; + } + + const replies = state.comments.filter((r) => r.parentId === c.id).sort((a, b) => a.timestamp - b.timestamp); + if (replies.length > 0) { + h += '
'; + replies.forEach((r) => { + const isOwnReply = r.author === state.username; + h += '
' + esc(r.author.charAt(0)) + '
' + esc(r.author) + ' · ' + fmtTime(r.timestamp) + '
'; + if (state.editingId === r.id) { + h += '
'; + } else { + h += '
' + esc(r.content) + '
'; + h += '
'; + h += ''; + if (isOwnReply) h += ''; + if (isOwnReply) h += ''; + h += '
'; + } + h += '
'; + }); + h += '
'; + } + + if (state.replyingTo === c.id) { + h += '
'; + } + + h += '
'; + return h; +} diff --git a/src/widget/render/index.ts b/src/widget/render/index.ts new file mode 100644 index 0000000..50cd0fe --- /dev/null +++ b/src/widget/render/index.ts @@ -0,0 +1,22 @@ +import type { Priority } from '../../shared/types'; +import { renderToggle } from './toggle'; +import { renderSidebar, toggleSidebar } from './sidebar'; +import { renderPopup } from './popup'; +import { renderNameDialog } from './name-dialog'; + +let _closePopup: (() => void) | null = null; +let _submitComment: ((priority: Priority) => void) | null = null; + +export function setRenderDeps(closePopup: () => void, submitComment: (priority: Priority) => void): void { + _closePopup = closePopup; + _submitComment = submitComment; +} + +export function render(): void { + renderToggle(toggleSidebar); + renderSidebar(render); + renderPopup(render, _closePopup!, _submitComment!); + renderNameDialog(render); +} + +export { toggleSidebar }; diff --git a/src/widget/render/name-dialog.ts b/src/widget/render/name-dialog.ts new file mode 100644 index 0000000..05a9d5a --- /dev/null +++ b/src/widget/render/name-dialog.ts @@ -0,0 +1,26 @@ +import { USERNAME_KEY } from '../../shared/constants'; +import { el } from '../dom'; +import { state } from '../state'; + +export function renderNameDialog(onRender: () => void): void { + const existing = document.getElementById('fb-name-overlay'); + if (state.username) { if (existing) existing.remove(); return; } + if (existing) return; + const overlay = el('div', { id: 'fb-name-overlay', className: 'fb-name-overlay' }); + overlay.innerHTML = '

ようこそ

コメントに表示される名前を入力してください。

'; + document.body.appendChild(overlay); + const input = document.getElementById('fb-name-field') as HTMLInputElement; + const btn = document.getElementById('fb-name-submit') as HTMLButtonElement; + input.addEventListener('input', () => { btn.disabled = !input.value.trim(); }); + input.addEventListener('keydown', (e) => { if (e.key === 'Enter' && input.value.trim()) { setName(input.value.trim(), onRender); } }); + btn.addEventListener('click', () => { if (input.value.trim()) setName(input.value.trim(), onRender); }); + setTimeout(() => { input.focus(); }, 100); +} + +function setName(name: string, onRender: () => void): void { + state.username = name; + localStorage.setItem(USERNAME_KEY, name); + const overlay = document.getElementById('fb-name-overlay'); + if (overlay) overlay.remove(); + onRender(); +} diff --git a/src/widget/render/popup.ts b/src/widget/render/popup.ts new file mode 100644 index 0000000..d56c524 --- /dev/null +++ b/src/widget/render/popup.ts @@ -0,0 +1,54 @@ +import type { Priority } from '../../shared/types'; +import { PRIORITY_COLORS } from '../../shared/constants'; +import { el, esc } from '../dom'; +import { state } from '../state'; + +export function renderPopup(onRender: () => void, closePopup: () => void, submitComment: (priority: Priority) => void): void { + let popup = document.getElementById('fb-popup'); + const isNew = !popup; + if (isNew) { + popup = el('div', { id: 'fb-popup', className: 'fb-popup' }); + document.body.appendChild(popup); + popup.addEventListener('click', (e) => { + const t = (e.target as HTMLElement).closest('[data-action]') as HTMLElement | null; + if (!t) return; + if (t.dataset.action === 'cancel-popup') { closePopup(); } + // 優先度ボタンを押した時点で、その優先度で即送信(ワンクリック) + else if (t.dataset.action === 'set-popup-pri') { state.popupPriority = t.dataset.pri as Priority; submitComment(state.popupPriority); } + else if (t.dataset.action === 'submit-popup') { submitComment(state.popupPriority); } + }); + popup.addEventListener('input', (e) => { + if ((e.target as HTMLElement).dataset.action === 'popup-textarea') state.popupContent = (e.target as HTMLTextAreaElement).value; + }); + popup.addEventListener('keydown', (e) => { + if ((e.target as HTMLElement).dataset.action === 'popup-textarea' && (e.metaKey || e.ctrlKey) && e.key === 'Enter') { + submitComment(state.popupPriority); + } + }); + } + if (!state.selectedRect) { popup!.classList.remove('show'); return; } + popup!.classList.add('show'); + + const q = state.selectedText.length > 120 ? state.selectedText.substring(0, 120) + '...' : state.selectedText; + let h = '
コメントを追加
'; + h += '
' + esc(q) + '
'; + h += ''; + // ワンポチモードと同じ「ソリッド色=押すと送信」の優先度ボタン + h += '
'; + (['must', 'better', 'want'] as const).forEach((p) => { + const pc = PRIORITY_COLORS[p]; + h += ''; + }); + h += '
'; + popup!.innerHTML = h; + + const rect = state.selectedRect; + const pw = 320, ph = 230, m = 12; + let top = rect.bottom + m; + if (top + ph > window.innerHeight) top = rect.top - ph - m; + if (top < m) top = Math.max(m, (window.innerHeight - ph) / 2); + const availW = state.sidebarOpen ? window.innerWidth - state.sidebarWidth : window.innerWidth; + const left = Math.max(m, Math.min(rect.left, availW - pw - m)); + popup!.style.top = top + 'px'; + popup!.style.left = left + 'px'; +} diff --git a/src/widget/render/sidebar.ts b/src/widget/render/sidebar.ts new file mode 100644 index 0000000..b4a4393 --- /dev/null +++ b/src/widget/render/sidebar.ts @@ -0,0 +1,201 @@ +import type { Priority, FilterMode } from '../../shared/types'; +import { SIDEBAR_WIDTH_KEY } from '../../shared/constants'; +import { el, esc } from '../dom'; +import { icon } from '../icons'; +import { state } from '../state'; +import { renderCard } from './card'; + +function applySidebarWidth(): void { + const sb = document.getElementById('fb-sidebar'); + if (!sb) return; + const w = state.sidebarWidth; + sb.style.width = w + 'px'; + sb.style.right = state.sidebarOpen ? '0px' : (-(w + 20)) + 'px'; + document.body.style.marginRight = state.sidebarOpen ? w + 'px' : ''; +} + +export function toggleSidebar(): void { + state.sidebarOpen = !state.sidebarOpen; + const btn = document.getElementById('fb-toggle'); + if (btn) btn.style.display = state.sidebarOpen ? 'none' : ''; + document.body.style.transition = 'margin-right 0.3s ease'; + applySidebarWidth(); +} + +function topLevel() { return state.comments.filter((c) => !c.parentId); } + +function filtered() { + const tl = topLevel(); + if (state.filter === 'resolved') return tl.filter((c) => c.resolved); + if (state.filter === 'all') return tl; + return tl.filter((c) => !c.resolved); +} + +export function renderSidebar(onRender: () => void): void { + let sb = document.getElementById('fb-sidebar'); + const isNew = !sb; + if (isNew) { + sb = el('div', { id: 'fb-sidebar' }); + document.body.appendChild(sb); + sb.addEventListener('mouseover', (e) => { + const card = (e.target as HTMLElement).closest('.fb-card') as HTMLElement | null; + if (!card) return; + const mark = document.querySelector('.fb-highlight[data-comment-id="' + card.dataset.id + '"]'); + if (mark) mark.classList.add('fb-pulse'); + }); + sb.addEventListener('mouseout', (e) => { + const card = (e.target as HTMLElement).closest('.fb-card') as HTMLElement | null; + if (!card) return; + if (e.relatedTarget && card.contains(e.relatedTarget as Node)) return; + const mark = document.querySelector('.fb-highlight[data-comment-id="' + card.dataset.id + '"]'); + if (mark) mark.classList.remove('fb-pulse'); + }); + } + applySidebarWidth(); + + const tl = topLevel(); + const counts = { + unresolved: tl.filter((c) => !c.resolved).length, + resolved: tl.filter((c) => c.resolved).length, + all: tl.length, + }; + + let html = '
'; + + html += '
'; + html += '
コメント' + counts.all + '
'; + html += '
'; + html += ''; + html += '
'; + + if (state.username) { + if (state.editingName) { + html += '
'; + } else { + html += '
' + icon('user', 14) + esc(state.username) + '
'; + } + } + + html += '
'; + (['unresolved', 'resolved', 'all'] as const).forEach((f) => { + const label = f === 'unresolved' ? '未解決' : f === 'resolved' ? '解決済' : 'すべて'; + html += ''; + }); + html += '
'; + + const items = filtered().sort((a, b) => b.timestamp - a.timestamp); + html += '
'; + if (items.length === 0) { + html += '
' + icon('message', 40) + 'コメントはまだありません
テキストを選択してコメントを追加
'; + } else { + items.forEach((c) => { html += renderCard(c); }); + } + html += '
'; + + sb!.innerHTML = html; + if (isNew) bindSidebarEvents(sb!, onRender); +} + +export interface SidebarActions { + toggleSidebar: () => void; + cyclePriority: (id: string) => void; + scrollToQuote: (id: string) => void; + scrollToPin: (id: string) => void; + resolveComment: (id: string) => void; + deleteComment: (id: string) => void; + deleteReply: (id: string) => void; + saveEdit: (id: string) => void; + submitReply: (id: string) => void; + finishNameEdit: () => void; +} + +let _actions: SidebarActions | null = null; + +export function setSidebarActions(actions: SidebarActions): void { + _actions = actions; +} + +function bindSidebarEvents(sb: HTMLElement, onRender: () => void): void { + sb.addEventListener('click', (e) => { + const t = (e.target as HTMLElement).closest('[data-action]') as HTMLElement | null; + if (t) { + const action = t.dataset.action; + const id = t.dataset.id; + + if (action === 'close') { _actions?.toggleSidebar(); } + else if (action === 'edit-name') { state.editingName = true; state.nameInput = state.username; onRender(); } + else if (action === 'cycle' && id) { _actions?.cyclePriority(id); } + else if (action === 'scroll-quote' && id) { _actions?.scrollToQuote(id); } + else if (action === 'scroll-pin' && id) { _actions?.scrollToPin(id); } + else if (action === 'reply' && id) { state.replyingTo = state.replyingTo === id ? null : id; state.replyText = ''; onRender(); } + else if (action === 'resolve' && id) { _actions?.resolveComment(id); } + else if (action === 'edit' && id) { const c = state.comments.find((x) => x.id === id); if (c) { state.editingId = id; state.editContent = c.content; state.editPriority = c.priority; onRender(); } } + else if (action === 'delete' && id) { _actions?.deleteComment(id); } + else if (action === 'delete-reply' && id) { _actions?.deleteReply(id); } + else if (action === 'cancel-edit') { state.editingId = null; onRender(); } + else if (action === 'save-edit' && id) { _actions?.saveEdit(id); } + else if (action === 'submit-reply' && id) { _actions?.submitReply(id); } + else if (action === 'set-edit-pri') { state.editPriority = t.dataset.pri as Priority; onRender(); } + return; + } + const filterBtn = (e.target as HTMLElement).closest('[data-filter]') as HTMLElement | null; + if (filterBtn) { state.filter = filterBtn.dataset.filter as FilterMode; onRender(); return; } + + const card = (e.target as HTMLElement).closest('.fb-card') as HTMLElement | null; + if (card && card.dataset.id) { + const cm = state.comments.find((x) => x.id === card.dataset.id); + if (cm?.type === 'pin') _actions?.scrollToPin(card.dataset.id); + else _actions?.scrollToQuote(card.dataset.id); + } + }); + + sb.addEventListener('mousedown', (e) => { + if (!(e.target as HTMLElement).closest('.fb-resize-handle')) return; + e.preventDefault(); + const startX = e.clientX; + const startWidth = state.sidebarWidth; + const handle = (e.target as HTMLElement).closest('.fb-resize-handle')!; + document.body.classList.add('fb-resizing'); + handle.classList.add('active'); + function onMove(ev: MouseEvent) { + state.sidebarWidth = Math.min(800, Math.max(300, startWidth + (startX - ev.clientX))); + const s = document.getElementById('fb-sidebar'); + if (s) s.style.width = state.sidebarWidth + 'px'; + document.body.style.transition = 'none'; + document.body.style.marginRight = state.sidebarWidth + 'px'; + } + function onUp() { + document.removeEventListener('mousemove', onMove); + document.removeEventListener('mouseup', onUp); + document.body.classList.remove('fb-resizing'); + handle.classList.remove('active'); + document.body.style.transition = ''; + localStorage.setItem(SIDEBAR_WIDTH_KEY, String(state.sidebarWidth)); + } + document.addEventListener('mousemove', onMove); + document.addEventListener('mouseup', onUp); + }); + + sb.addEventListener('input', (e) => { + const t = e.target as HTMLInputElement; + if (t.dataset.action === 'name-input') { state.nameInput = t.value; } + else if (t.dataset.action === 'edit-textarea') { state.editContent = t.value; } + else if (t.dataset.action === 'reply-textarea') { state.replyText = t.value; const btn = sb.querySelector('[data-action="submit-reply"]') as HTMLButtonElement | null; if (btn) btn.disabled = !state.replyText.trim(); } + }); + + sb.addEventListener('keydown', (e) => { + const t = e.target as HTMLElement; + if (t.dataset.action === 'name-input') { + if (e.key === 'Enter') { _actions?.finishNameEdit(); } + else if (e.key === 'Escape') { state.editingName = false; onRender(); } + } + if (t.dataset.action === 'reply-textarea' && (e.metaKey || e.ctrlKey) && e.key === 'Enter') { + const id = (sb.querySelector('[data-action="submit-reply"]') as HTMLElement | null)?.dataset.id; + if (id) _actions?.submitReply(id); + } + }); + + sb.addEventListener('blur', (e) => { + if ((e.target as HTMLElement).dataset?.action === 'name-input') { _actions?.finishNameEdit(); } + }, true); +} diff --git a/src/widget/render/toggle.ts b/src/widget/render/toggle.ts new file mode 100644 index 0000000..95cca1d --- /dev/null +++ b/src/widget/render/toggle.ts @@ -0,0 +1,53 @@ +import { el } from '../dom'; +import { icon } from '../icons'; +import { state } from '../state'; + +let _onToggleArea: (() => void) | null = null; +let _onVoice: (() => void) | null = null; + +export function setToggleAreaHandler(handler: () => void): void { + _onToggleArea = handler; +} + +export function setToggleVoiceHandler(handler: () => void): void { + _onVoice = handler; +} + +export function renderToggle(toggleSidebar: () => void): void { + let btn = document.getElementById('fb-toggle'); + if (!btn) { + btn = el('button', { id: 'fb-toggle' }); + btn.addEventListener('click', (e) => { + const target = (e.target as HTMLElement).closest('[data-action]') as HTMLElement | null; + if (target?.dataset.action === 'area-mode') { + _onToggleArea?.(); + } else if (target?.dataset.action === 'voice') { + _onVoice?.(); + } else { + toggleSidebar(); + } + }); + document.body.appendChild(btn); + } + const unresolvedCount = state.comments.filter((c) => !c.parentId && !c.resolved).length; + const pinCount = state.comments.filter((c) => c.type === 'pin' && !c.parentId).length; + let h = '' + icon('panelRight', 16); + if (unresolvedCount > 0) h += '' + unresolvedCount + ''; + h += ''; + const pinStyle = state.pinMode ? 'color:var(--fb-accent);background:rgba(59,130,246,0.1);border-radius:4px' : ''; + const pinTitle = state.pinMode + ? 'フィードバックモード ON(クリックで終了)/クリック=ピン・ドラッグ=引用' + : 'フィードバックモード OFF(クリックで開始)'; + h += '' + + ''; + if (pinCount > 0) h += '' + pinCount + ''; + h += ''; + const voiceCls = state.voiceRecording ? ' recording' : state.voiceProcessing ? ' processing' : ''; + const voiceTitle = state.voiceRecording ? '録音中(クリックで停止して解析)' : state.voiceProcessing ? '解析中...' : '音声でフィードバック'; + // 解析中はマイクではなくスピナー(円形ローダー)を回す + const voiceIcon = state.voiceProcessing ? icon('loader', 14) : icon('mic', 14); + h += '' + voiceIcon + ''; + h += 'コメント'; + btn.innerHTML = h; + btn.style.display = state.sidebarOpen ? 'none' : ''; +} diff --git a/src/widget/scroll.ts b/src/widget/scroll.ts new file mode 100644 index 0000000..7c573e1 --- /dev/null +++ b/src/widget/scroll.ts @@ -0,0 +1,40 @@ +import type { Priority } from '../shared/types'; +import { state } from './state'; + +const PRIORITY_FLASH: Record = { + must: 'rgba(239,68,68,0.4)', + better: 'rgba(245,158,11,0.4)', + want: 'rgba(34,197,94,0.4)', +}; + +export function scrollToQuote(id: string): void { + const mark = document.querySelector('.fb-highlight[data-comment-id="' + id + '"]') as HTMLElement | null; + if (!mark) return; + mark.scrollIntoView({ behavior: 'smooth', block: 'center' }); + const c = state.comments.find((x) => x.id === id); + const flashColor = c ? (PRIORITY_FLASH[c.priority] || PRIORITY_FLASH.want) : PRIORITY_FLASH.want; + const orig = mark.style.backgroundColor; + mark.style.backgroundColor = flashColor; + mark.style.transition = 'background-color 0.3s'; + setTimeout(() => { mark.style.backgroundColor = orig; }, 1500); +} + +export function scrollToPin(id: string): void { + const marker = document.querySelector('.fb-pin-marker[data-pin-id="' + id + '"]') as HTMLElement | null; + if (!marker) return; + marker.scrollIntoView({ behavior: 'smooth', block: 'center' }); + marker.classList.add('fb-pin-flash'); + setTimeout(() => { marker.classList.remove('fb-pin-flash'); }, 1500); +} + +export function scrollToCard(id: string, toggleSidebar: () => void): void { + const wasClosed = !state.sidebarOpen; + if (wasClosed) { toggleSidebar(); } + setTimeout(() => { + const card = document.querySelector('.fb-card[data-id="' + id + '"]') as HTMLElement | null; + if (!card) return; + card.scrollIntoView({ behavior: 'smooth', block: 'center' }); + card.classList.add('fb-focused'); + setTimeout(() => { card.classList.remove('fb-focused'); }, 1500); + }, wasClosed ? 350 : 0); +} diff --git a/src/widget/selection.ts b/src/widget/selection.ts new file mode 100644 index 0000000..772bede --- /dev/null +++ b/src/widget/selection.ts @@ -0,0 +1,36 @@ +import { state, type QuoteContext } from './state'; + +export function getQuoteContext(range: Range): QuoteContext { + let before = '', after = ''; + try { + const br = document.createRange(); + br.setStart(document.body, 0); + br.setEnd(range.startContainer, range.startOffset); + before = br.toString().slice(-50).replace(/[\s\u00A0]+/g, ' ').trim(); + const ar = document.createRange(); + ar.setStart(range.endContainer, range.endOffset); + ar.setEnd(document.body, document.body.childNodes.length); + after = ar.toString().slice(0, 50).replace(/[\s\u00A0]+/g, ' ').trim(); + } catch (_) { /* ignore */ } + return { beforeText: before, afterText: after }; +} + +export function setupTextSelection(onRender: () => void, closePopup: () => void): void { + document.addEventListener('mouseup', (e) => { + // テキスト選択は FB モードに関係なく常に有効(ドラッグ選択は意図的なので邪魔にならない) + if ((e.target as HTMLElement).closest('#fb-sidebar,#fb-toggle,#fb-popup,#fb-name-overlay')) return; + const sel = window.getSelection(); + const text = sel?.toString().trim(); + if (!text || !sel || sel.rangeCount === 0) return; // 名前未設定でも可(送信時に匿名で扱う) + const range = sel.getRangeAt(0); + state.selectedText = text.replace(/[\s\u00A0]+/g, ' ').substring(0, 200); + state.selectedRect = range.getBoundingClientRect(); + state.selectedQuoteContext = getQuoteContext(range); + state.popupContent = ''; + onRender(); + }); + document.addEventListener('mousedown', (e) => { + if ((e.target as HTMLElement).closest('#fb-popup')) return; + if (state.selectedRect) closePopup(); + }); +} diff --git a/src/widget/state.ts b/src/widget/state.ts new file mode 100644 index 0000000..d482b90 --- /dev/null +++ b/src/widget/state.ts @@ -0,0 +1,85 @@ +import type { Priority, FilterMode } from '../shared/types'; +import { USERNAME_KEY, SIDEBAR_WIDTH_KEY } from '../shared/constants'; +import { slugify } from '../shared/slug'; + +export interface QuoteContext { + beforeText: string; + afterText: string; +} + +export interface FbComment { + id: string; + author: string; + type: string; // 'comment' | 'pin' など + quote: string; + quoteContext: QuoteContext; + content: string; + priority: Priority; + parentId: string | null; + resolved: boolean; + resolvedBy: string | null; + resolvedAt: number | null; + timestamp: number; + updatedAt: number | null; + pageUrl: string; + projectSlug?: string; + pinX?: number | null; // type==='pin' のときコンテナ幅に対する%(フォールバック) + pinY?: number | null; // type==='pin' のときコンテナ上端からのpx(フォールバック) + anchorSelector?: string | null; // 追従基準の最寄り要素セレクタ + anchorDx?: number | null; // 基準要素左上からの相対px(横) + anchorDy?: number | null; // 基準要素左上からの相対px(縦) +} + +export interface WidgetState { + username: string; + comments: FbComment[]; + filter: FilterMode; + sidebarOpen: boolean; + selectedText: string; + selectedQuoteContext: QuoteContext; + selectedRect: DOMRect | null; + popupContent: string; + editingId: string | null; + editContent: string; + editPriority: Priority; + replyingTo: string | null; + replyText: string; + editingName: boolean; + nameInput: string; + popupPriority: Priority; + sidebarWidth: number; + pinMode: boolean; + pinPopupPos: { x: number; y: number } | null; // クリック位置(%,px) + editingPinId: string | null; // 編集中ピンのID(null=新規ピン追加) + voiceRecording: boolean; // 録音中 + voiceProcessing: boolean; // 音声をGeminiで解析中 + voiceError: string | null; // 直近のエラーメッセージ +} + +export const state: WidgetState = { + username: localStorage.getItem(USERNAME_KEY) || '', + comments: [], + filter: 'unresolved', + sidebarOpen: false, + selectedText: '', + selectedQuoteContext: { beforeText: '', afterText: '' }, + selectedRect: null, + popupContent: '', + editingId: null, + editContent: '', + editPriority: 'want', + replyingTo: null, + replyText: '', + editingName: false, + nameInput: '', + popupPriority: 'must', + sidebarWidth: parseInt(localStorage.getItem(SIDEBAR_WIDTH_KEY) || '', 10) || 400, + pinMode: false, + pinPopupPos: null, + editingPinId: null, + voiceRecording: false, + voiceProcessing: false, + voiceError: null, +}; + +export const slug = slugify(window.location.href); diff --git a/src/widget/styles.ts b/src/widget/styles.ts new file mode 100644 index 0000000..702c78c --- /dev/null +++ b/src/widget/styles.ts @@ -0,0 +1,202 @@ +export function injectStyles(): void { + const style = document.createElement('style'); + style.id = 'fb-widget-styles'; + // ピン型(涙型マーカー)カーソル。先端(下端中央)=ホットスポット 14,26 + const pinCursor = + `url('data:image/svg+xml,%3Csvg%20xmlns="http://www.w3.org/2000/svg"%20width="28"%20height="28"%20viewBox="0%200%2028%2028"%3E%3Cpath%20d="M14%202a8%208%200%200%200-8%208c0%205.5%208%2016%208%2016s8-10.5%208-16a8%208%200%200%200-8-8Z"%20fill="%233b82f6"%20stroke="%23ffffff"%20stroke-width="1.6"%20stroke-linejoin="round"/%3E%3Ccircle%20cx="14"%20cy="10"%20r="3"%20fill="%23ffffff"/%3E%3C/svg%3E') 14 26, auto`; + style.textContent = [ + ':root{--fb-bg:#ffffff;--fb-fg:#0a0a0a;--fb-muted:#f5f5f5;--fb-muted-fg:#737373;--fb-border:#e5e5e5;--fb-primary:#171717;--fb-primary-fg:#fafafa;--fb-accent:#3b82f6;--fb-destructive:#ef4444;--fb-font:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Hiragino Sans",sans-serif}', + + '#fb-toggle{position:fixed;right:0;top:50%;transform:translateY(-50%);width:36px;background:var(--fb-bg);border:1px solid var(--fb-border);border-right:none;border-radius:8px 0 0 8px;cursor:pointer;z-index:99999;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:4px;font-family:var(--fb-font);transition:all .15s;box-shadow:-2px 0 8px rgba(0,0,0,0.06);padding:8px 0}', + '#fb-toggle:hover{background:var(--fb-muted);box-shadow:-2px 0 12px rgba(0,0,0,0.1)}', + '#fb-toggle .fb-toggle-icon{color:var(--fb-muted-fg);display:flex;align-items:center;justify-content:center;position:relative}', + '#fb-toggle .fb-toggle-label{font-size:10px;color:var(--fb-accent);font-weight:700;writing-mode:vertical-rl;letter-spacing:1px;line-height:1;white-space:nowrap}', + '#fb-toggle .fb-badge{position:absolute;top:-6px;left:-6px;background:var(--fb-destructive);color:#fff;font-size:9px;font-weight:700;min-width:16px;height:16px;border-radius:8px;display:flex;align-items:center;justify-content:center;padding:0 3px;line-height:1}', + + '#fb-sidebar{position:fixed;top:0;height:100vh;background:rgba(245,245,245,0.5);border-left:1px solid var(--fb-border);z-index:99998;transition:right .3s ease;display:flex;flex-direction:column;font-family:var(--fb-font);font-size:14px;color:var(--fb-fg)}', + '#fb-sidebar *{box-sizing:border-box}', + + '.fb-resize-handle{position:absolute;left:-3px;top:0;width:6px;height:100%;cursor:col-resize;z-index:1}', + '.fb-resize-handle:hover{background:rgba(59,130,246,0.15)}', + '.fb-resize-handle.active{background:rgba(59,130,246,0.3)}', + 'body.fb-resizing{cursor:col-resize !important;-webkit-user-select:none !important;user-select:none !important}', + 'body.fb-resizing *{cursor:col-resize !important}', + + '.fb-header{display:flex;align-items:center;justify-content:space-between;padding:12px 16px;border-bottom:1px solid var(--fb-border)}', + '.fb-header-left{display:flex;align-items:center;gap:8px}', + '.fb-header-title{font-size:14px;font-weight:700;color:var(--fb-accent)}', + '.fb-header-count{background:var(--fb-muted);color:var(--fb-muted-fg);font-size:11px;padding:1px 7px;border-radius:10px}', + '.fb-header-actions{display:flex;align-items:center;gap:2px}', + '.fb-hdr-btn{background:none;border:none;cursor:pointer;padding:6px;border-radius:6px;color:var(--fb-muted-fg);transition:all .15s;display:inline-flex;align-items:center;gap:4px;font-family:var(--fb-font);font-size:12px}', + '.fb-hdr-btn:hover{background:var(--fb-muted);color:var(--fb-fg)}', + + '.fb-user-row{display:flex;align-items:center;padding:8px 16px;border-bottom:1px solid var(--fb-border);font-size:13px;color:var(--fb-muted-fg);cursor:pointer;transition:color .15s;gap:6px}', + '.fb-user-row:hover{color:var(--fb-fg)}', + + '.fb-filters{display:flex;border-bottom:1px solid var(--fb-border)}', + '.fb-filter{flex:1;padding:10px 0;text-align:center;font-size:13px;color:var(--fb-muted-fg);border:none;background:none;cursor:pointer;border-bottom:2px solid transparent;transition:all .15s;font-family:var(--fb-font)}', + '.fb-filter:hover{color:var(--fb-fg)}', + '.fb-filter.active{color:var(--fb-fg);border-bottom-color:var(--fb-accent)}', + '.fb-filter .cnt{font-size:11px;margin-left:4px;padding:1px 5px;border-radius:8px;background:var(--fb-muted);color:var(--fb-muted-fg)}', + '.fb-filter.active .cnt{background:rgba(59,130,246,0.1);color:var(--fb-accent)}', + + '.fb-list{flex:1;overflow-y:auto;padding:8px}', + '.fb-empty{text-align:center;padding:60px 20px;color:var(--fb-muted-fg);font-size:13px}', + '.fb-empty svg{margin:0 auto 12px;display:block;color:var(--fb-border)}', + + '.fb-card{background:var(--fb-bg);border:1px solid var(--fb-border);border-left:3px solid var(--fb-border);border-radius:12px;padding:14px;margin-bottom:6px;transition:box-shadow .3s,background .3s;cursor:pointer}', + '.fb-card:hover{box-shadow:0 1px 3px rgba(0,0,0,0.05)}', + '.fb-card.resolved{opacity:.5}', + + '.fb-card-head{display:flex;align-items:center;justify-content:space-between;margin-bottom:4px}', + '.fb-card-head-left{display:flex;align-items:center;gap:6px}', + '.fb-avatar{width:22px;height:22px;border-radius:50%;background:var(--fb-primary);color:var(--fb-primary-fg);display:flex;align-items:center;justify-content:center;font-size:10px;font-weight:700;flex-shrink:0}', + '.fb-author{font-size:13px;font-weight:700;color:var(--fb-fg)}', + '.fb-time{font-size:11px;color:var(--fb-muted-fg)}', + '.fb-resolved-mark{font-size:11px;color:#22c55e;margin-left:4px;display:inline-flex;align-items:center;gap:2px}', + + '.fb-badge-p{font-size:11px;font-weight:700;padding:2px 8px;border-radius:4px;color:#fff;cursor:default;transition:all .15s}', + '.fb-badge-p.own{cursor:pointer}', + '.fb-badge-p.own:hover{transform:scale(1.1);box-shadow:0 0 0 2px rgba(0,0,0,0.08)}', + + '.fb-quote{font-size:12px;color:var(--fb-muted-fg);padding:6px 10px;background:var(--fb-muted);border-left:2px solid var(--fb-primary);border-radius:0 4px 4px 0;margin-bottom:8px;font-style:italic;line-height:1.5;cursor:pointer;transition:background .15s}', + '.fb-quote:hover{background:var(--fb-border)}', + + '.fb-body{font-size:13px;color:var(--fb-muted-fg);line-height:1.6;margin-bottom:6px;white-space:pre-wrap}', + + '.fb-actions{display:flex;gap:2px;flex-wrap:wrap}', + '.fb-act{font-size:12px;color:#a3a3a3;background:none;border:none;padding:4px 8px;border-radius:4px;cursor:pointer;transition:all .15s;display:inline-flex;align-items:center;gap:3px;font-family:var(--fb-font)}', + '.fb-act:hover{color:var(--fb-fg)}', + '.fb-act.del:hover{color:var(--fb-destructive)}', + '.fb-act.res:hover{color:#22c55e}', + + '.fb-replies{margin-top:8px;padding-top:8px;border-top:1px solid var(--fb-muted)}', + '.fb-reply-item{display:flex;gap:8px;padding:6px 0}', + '.fb-reply-item+.fb-reply-item{border-top:1px solid var(--fb-muted)}', + '.fb-reply-avatar{width:20px;height:20px;border-radius:50%;background:var(--fb-primary);color:var(--fb-primary-fg);display:flex;align-items:center;justify-content:center;font-size:9px;font-weight:700;flex-shrink:0;margin-top:2px}', + '.fb-reply-meta{font-size:12px;color:var(--fb-muted-fg);margin-bottom:2px}', + '.fb-reply-meta strong{color:#525252;font-weight:700}', + '.fb-reply-text{font-size:13px;color:var(--fb-muted-fg);line-height:1.5}', + + '.fb-reply-input{display:flex;gap:8px;margin-top:8px}', + '.fb-reply-input textarea{flex:1;padding:8px 10px;border:1px solid var(--fb-border);border-radius:6px;font-size:13px;font-family:var(--fb-font);resize:none;outline:none;min-height:36px;color:var(--fb-fg)}', + '.fb-reply-input textarea:focus{border-color:var(--fb-accent)}', + '.fb-reply-input button{padding:6px 14px;background:var(--fb-primary);color:var(--fb-primary-fg);border:none;border-radius:6px;font-size:12px;cursor:pointer;font-family:var(--fb-font);align-self:flex-end}', + '.fb-reply-input button:disabled{opacity:.4;cursor:default}', + + '.fb-edit-area textarea{width:100%;padding:8px 10px;border:1px solid var(--fb-border);border-radius:6px;font-size:13px;font-family:var(--fb-font);resize:vertical;outline:none;min-height:50px;margin-bottom:6px;color:var(--fb-fg)}', + '.fb-edit-area textarea:focus{border-color:var(--fb-accent)}', + '.fb-edit-btns{display:flex;gap:6px}', + '.fb-edit-btns button{padding:4px 12px;border-radius:6px;font-size:12px;cursor:pointer;border:1px solid var(--fb-border);background:var(--fb-bg);color:var(--fb-muted-fg);font-family:var(--fb-font)}', + '.fb-edit-btns button.save{background:var(--fb-primary);color:var(--fb-primary-fg);border-color:var(--fb-primary)}', + '.fb-edit-pri{display:flex;gap:4px;margin-bottom:6px}', + '.fb-edit-pri button{padding:2px 10px;border-radius:4px;font-size:11px;font-weight:700;cursor:pointer;border:1px solid var(--fb-border);background:var(--fb-bg);color:var(--fb-muted-fg);font-family:var(--fb-font);transition:all .15s}', + + '.fb-popup{position:fixed;z-index:100000;width:400px;background:var(--fb-bg);border:1px solid var(--fb-border);border-radius:12px;padding:16px;box-shadow:0 10px 25px rgba(0,0,0,0.1);font-family:var(--fb-font);display:none}', + '.fb-popup.show{display:block}', + '.fb-popup-head{margin-bottom:10px}', + '.fb-popup-head span{font-size:14px;font-weight:700;color:var(--fb-fg)}', + '.fb-popup-quote{font-size:13px;color:var(--fb-muted-fg);padding:8px 12px;background:var(--fb-muted);border-left:2px solid var(--fb-accent);border-radius:0 6px 6px 0;margin-bottom:10px;font-style:italic;line-height:1.5}', + '.fb-popup textarea{width:100%;min-height:70px;padding:10px 12px;border:1px solid var(--fb-border);border-radius:8px;font-size:14px;font-family:var(--fb-font);resize:vertical;outline:none;margin-bottom:10px;color:var(--fb-fg)}', + '.fb-popup textarea:focus{border-color:var(--fb-accent)}', + '.fb-popup textarea::placeholder{color:var(--fb-muted-fg)}', + '.fb-popup-pri{display:flex;gap:6px;margin-bottom:10px}', + '.fb-popup-pri button{flex:1;padding:7px 8px;border-radius:8px;font-size:13px;font-weight:600;cursor:pointer;border:2px solid transparent;transition:all .15s;font-family:var(--fb-font)}', + '.fb-popup-actions{display:flex;gap:8px;justify-content:flex-end}', + '.fb-popup-actions button{padding:8px 18px;border-radius:8px;font-size:13px;font-weight:600;cursor:pointer;transition:all .15s;font-family:var(--fb-font)}', + '.fb-popup-actions .cancel{background:none;border:1px solid var(--fb-border);color:var(--fb-muted-fg)}', + '.fb-popup-actions .cancel:hover{background:var(--fb-muted);color:var(--fb-fg)}', + '.fb-popup-actions .submit{border:none;color:#fff}', + + '.fb-name-overlay{position:fixed;inset:0;background:rgba(0,0,0,0.4);display:flex;align-items:center;justify-content:center;z-index:100001}', + '.fb-name-box{background:var(--fb-bg);border-radius:12px;padding:28px;width:360px;box-shadow:0 20px 40px rgba(0,0,0,0.15)}', + '.fb-name-box h2{font-size:18px;font-weight:700;margin:0 0 6px;color:var(--fb-fg)}', + '.fb-name-box p{font-size:14px;color:var(--fb-muted-fg);margin:0 0 16px}', + '.fb-name-box input{width:100%;padding:10px 14px;border:1px solid var(--fb-border);border-radius:8px;font-size:15px;outline:none;margin-bottom:12px;font-family:var(--fb-font);color:var(--fb-fg)}', + '.fb-name-box input:focus{border-color:var(--fb-accent)}', + '.fb-name-box input::placeholder{color:var(--fb-muted-fg)}', + '.fb-name-box button{width:100%;padding:10px;background:var(--fb-primary);color:var(--fb-primary-fg);border:none;border-radius:8px;font-size:14px;font-weight:600;cursor:pointer;font-family:var(--fb-font)}', + '.fb-name-box button:disabled{opacity:.4;cursor:default}', + + '.fb-name-input{width:100%;padding:4px 8px;border:1px solid var(--fb-border);border-radius:4px;font-size:12px;outline:none;font-family:var(--fb-font);color:var(--fb-fg)}', + '.fb-name-input:focus{border-color:var(--fb-accent)}', + + '.fb-highlight{padding:1px 0;cursor:pointer;transition:background .15s}', + '.fb-highlight-must{background:rgba(239,68,68,0.15);border-bottom:2px solid #ef4444}', + '.fb-highlight-must:hover{background:rgba(239,68,68,0.25)}', + '.fb-highlight-better{background:rgba(245,158,11,0.15);border-bottom:2px solid #f59e0b}', + '.fb-highlight-better:hover{background:rgba(245,158,11,0.25)}', + '.fb-highlight-want{background:rgba(34,197,94,0.15);border-bottom:2px solid #22c55e}', + '.fb-highlight-want:hover{background:rgba(34,197,94,0.25)}', + + '@keyframes fb-pulse{0%,100%{opacity:1}50%{opacity:0.4}}', + '.fb-highlight.fb-pulse{animation:fb-pulse 1s ease-in-out infinite}', + '.fb-card.fb-focused{box-shadow:0 0 0 2px var(--fb-accent);background:rgba(59,130,246,0.04)}', + + '#fb-sidebar svg,#fb-toggle svg,.fb-popup svg{pointer-events:none}', + + // Pin mode styles + 'body.fb-pin-mode{cursor:' + pinCursor + '}', + 'body.fb-pin-mode *:not(#fb-toggle):not(#fb-toggle *):not(#fb-sidebar):not(#fb-sidebar *):not(#fb-pin-popup):not(#fb-pin-popup *):not(.fb-pin-marker):not(.fb-pin-marker *){cursor:' + pinCursor + '}', + + '.fb-toggle-pin{display:flex;align-items:center;justify-content:center;padding:4px;cursor:pointer;transition:all .15s}', + '.fb-toggle-pin:hover{color:var(--fb-accent)}', + + // Voice (mic) button + '.fb-toggle-voice{display:flex;align-items:center;justify-content:center;padding:4px;cursor:pointer;color:var(--fb-muted-fg);border-radius:4px;transition:all .15s}', + '.fb-toggle-voice:hover{color:var(--fb-accent)}', + '.fb-toggle-voice.recording{color:#ef4444;background:rgba(239,68,68,0.12);animation:fb-mic-pulse 1.1s ease-in-out infinite}', + '.fb-toggle-voice.processing{color:var(--fb-accent)}', + '.fb-toggle-voice.processing svg{animation:fb-mic-spin .8s linear infinite;transform-origin:center}', + '@keyframes fb-mic-pulse{0%,100%{box-shadow:0 0 0 0 rgba(239,68,68,0.5)}50%{box-shadow:0 0 0 5px rgba(239,68,68,0)}}', + '@keyframes fb-mic-spin{to{transform:rotate(360deg)}}', + + // 音声解析の失敗を知らせるトースト(黙って先頭にピンが溜まるのを防ぐ) + '.fb-voice-toast{position:fixed;right:16px;bottom:70px;z-index:100001;max-width:320px;padding:10px 14px;border-radius:8px;background:var(--fb-fg);color:var(--fb-bg);font-family:var(--fb-font);font-size:13px;line-height:1.5;box-shadow:0 6px 20px rgba(0,0,0,0.2);opacity:0;transform:translateY(6px);transition:opacity .2s,transform .2s;pointer-events:none}', + '.fb-voice-toast.show{opacity:1;transform:translateY(0)}', + + // 掴んで移動できることを示す。フィードバックモードの ON/OFF に関わらず操作できる + '.fb-pin-marker{position:absolute;transform:translate(-50%,-100%);cursor:grab;z-index:99990;transition:transform .15s}', + '.fb-pin-marker:hover{transform:translate(-50%,-100%) scale(1.15)}', + // 解決済みピンは淡色表示 + '.fb-pin-marker.resolved{opacity:.4}', + // サイドバーのカードから飛んだとき、マーカーを点滅させる + '.fb-pin-marker.fb-pin-flash .fb-pin-icon{animation:fb-pin-ring 1.4s ease}', + '@keyframes fb-pin-ring{0%,100%{box-shadow:0 2px 6px rgba(0,0,0,0.25)}30%,60%{box-shadow:0 0 0 8px rgba(59,130,246,0.45)}}', + // サイドバーのピンカード内インジケータ + '.fb-pin-ref{display:flex;align-items:center;gap:6px;font-size:12px;color:var(--fb-muted-fg);padding:6px 10px;background:var(--fb-muted);border-radius:6px;margin-bottom:8px;cursor:pointer;transition:background .15s}', + '.fb-pin-ref:hover{background:var(--fb-border)}', + '.fb-pin-ref-badge{display:inline-flex;align-items:center;justify-content:center;width:18px;height:18px;border-radius:50%;color:#fff;font-size:10px;font-weight:700;flex-shrink:0}', + 'body.fb-dragging-pin,body.fb-dragging-pin *{cursor:grabbing !important;-webkit-user-select:none !important;user-select:none !important}', + // ドラッグ中はホバー拡大アニメを止めてカクつきを防ぐ + 'body.fb-dragging-pin .fb-pin-marker{transition:none}', + 'body.fb-dragging-pin .fb-pin-tooltip{display:none !important}', + // ホバー吹き出しの優先度バッジ(クリックで循環) + '.fb-pin-badge{cursor:pointer;transition:transform .1s}', + '.fb-pin-badge:hover{transform:scale(1.08)}', + '.fb-pin-badge.readonly{cursor:default}', + '.fb-pin-icon{width:24px;height:24px;border-radius:50%;color:#fff;font-size:11px;font-weight:700;display:flex;align-items:center;justify-content:center;box-shadow:0 2px 6px rgba(0,0,0,0.25);position:relative}', + '.fb-pin-icon::after{content:"";position:absolute;bottom:-5px;left:50%;transform:translateX(-50%);width:0;height:0;border-left:5px solid transparent;border-right:5px solid transparent;border-top:6px solid var(--pin-color,#3b82f6)}', + + '.fb-pin-tooltip{display:none;position:absolute;top:8px;left:28px;background:var(--fb-bg);border:1px solid var(--fb-border);border-radius:8px;padding:10px 12px;box-shadow:0 4px 12px rgba(0,0,0,0.1);min-width:180px;max-width:280px;z-index:99995;font-family:var(--fb-font)}', + '.fb-pin-marker:hover .fb-pin-tooltip{display:block}', + '.fb-pin-delete{position:absolute;top:4px;right:4px;width:18px;height:18px;border-radius:50%;background:rgba(0,0,0,0.5);color:#fff;border:none;font-size:13px;line-height:1;cursor:pointer;display:flex;align-items:center;justify-content:center;opacity:0;transition:opacity .15s}', + '.fb-pin-tooltip:hover .fb-pin-delete{opacity:1}', + + '.fb-pin-popup{position:fixed;z-index:100000;width:300px;background:var(--fb-bg);border:1px solid var(--fb-border);border-radius:12px;padding:16px;box-shadow:0 10px 25px rgba(0,0,0,0.12);font-family:var(--fb-font);display:none}', + '.fb-pin-popup.show{display:block}', + '.fb-pin-popup-head{font-size:14px;font-weight:700;color:var(--fb-fg);margin-bottom:10px}', + '.fb-pin-popup textarea{width:100%;min-height:60px;padding:10px 12px;border:1px solid var(--fb-border);border-radius:8px;font-size:14px;font-family:var(--fb-font);resize:vertical;outline:none;margin-bottom:10px;color:var(--fb-fg);box-sizing:border-box}', + '.fb-pin-popup textarea:focus{border-color:var(--fb-accent)}', + '.fb-pin-popup textarea::placeholder{color:var(--fb-muted-fg)}', + '.fb-pin-popup-pri{display:flex;gap:6px;margin-bottom:10px}', + '.fb-pin-pri-btn{flex:1;padding:6px 8px;border-radius:6px;font-size:12px;font-weight:700;cursor:pointer;border:none;transition:opacity .15s}', + '.fb-pin-popup-actions{display:flex;gap:8px;justify-content:flex-end}', + '.fb-pin-popup-actions button{padding:8px 16px;border-radius:8px;font-size:13px;font-weight:600;cursor:pointer;transition:all .15s;font-family:var(--fb-font)}', + '.fb-pin-cancel{background:none;border:1px solid var(--fb-border);color:var(--fb-muted-fg)}', + '.fb-pin-cancel:hover{background:var(--fb-muted);color:var(--fb-fg)}', + '.fb-pin-submit{background:var(--fb-accent);border:none;color:#fff}', + '.fb-pin-submit:hover{background:#2563eb}', + ].join('\n'); + document.head.appendChild(style); +} diff --git a/src/widget/voice.ts b/src/widget/voice.ts new file mode 100644 index 0000000..5abcaa9 --- /dev/null +++ b/src/widget/voice.ts @@ -0,0 +1,326 @@ +import type { Priority } from '../shared/types'; +import { state } from './state'; +import { addPin, renderPins, elementToPinCoords, anchorFromElement, type PinAnchor } from './pin'; +import { voiceApi, type VoiceResult } from './api'; + +interface Target { + index: number; + text: string; // その要素に表示されている文字 + heading: string; // 直前の見出し(Gemini に渡す文脈) + isHeading: boolean; // 見出し要素そのものか + x: number; // ピン座標系(%) + y: number; // ピン座標系(px) + anchor: PinAnchor; // 要素アンカー(レスポンシブ追従) +} + +let mediaRecorder: MediaRecorder | null = null; +let chunks: BlobPart[] = []; +let stream: MediaStream | null = null; +let onChange: () => void = () => {}; + +/** 録音状態が変わったときに UI を再描画させるためのコールバックを登録する。 */ +export function setVoiceOnChange(cb: () => void): void { + onChange = cb; +} + +function getContainer(): HTMLElement { + return document.querySelector('main') || document.body; +} + +const BLOCK_SELECTOR = 'div,p,li,td,th,dt,dd,figcaption,blockquote,h1,h2,h3,h4,h5,h6'; +const HEADING_TAGS = ['H1', 'H2', 'H3', 'H4', 'H5', 'H6']; +const WIDGET_UI = '#fb-sidebar,#fb-toggle,#fb-pin-popup,.fb-pin-marker,.fb-pin-tooltip,.fb-voice-toast'; +const MIN_TEXT = 2; // 「1」のような装飾番号を除く +const MAX_TEXT = 120; // これより長いものは「まとまり」ではなく本文の塊とみなす +const MAX_TARGETS = 600; + +/** + * 音声の指摘を紐づける候補要素を抽出する。 + * + * 見出しだけを候補にすると、図解の中身(カードのラベルや短い説明文)を + * 名指しされても一番近い見出しにしか置けない。そこで「テキストを持つ最小の + * ブロック」=それ以上分解できない表示単位を候補にする。 + * 各候補には直前の見出しを添えて、Gemini がどのセクションの話か判断できるようにする。 + */ +export function extractTargets(): Target[] { + const container = getContainer(); + + // テキストを持つブロックのうち、内側に別のブロックを含まないもの=最小単位 + const blocks = (Array.from(container.querySelectorAll(BLOCK_SELECTOR)) as HTMLElement[]).filter( + (el) => (el.textContent || '').trim() && !el.closest(WIDGET_UI), + ); + const leaves = new Set(blocks.filter((el) => !blocks.some((o) => o !== el && el.contains(o)))); + + const targets: Target[] = []; + let heading = ''; + + // 文書順に走査して「直前の見出し」を引き継ぎながら候補を積む + (Array.from(container.querySelectorAll('*')) as HTMLElement[]).forEach((el) => { + const isHeading = HEADING_TAGS.includes(el.tagName); + if (isHeading) { + const t = normalize(el.textContent || ''); + if (t) heading = t; + } + if (!leaves.has(el) || targets.length >= MAX_TARGETS) return; + + const text = normalize(el.textContent || ''); + if (text.length < MIN_TEXT || text.length > MAX_TEXT) return; + + const rect = el.getBoundingClientRect(); + if (rect.width < 4 || rect.height < 4) return; // 非表示・つぶれた要素 + + const { x, y } = elementToPinCoords(el); + targets.push({ + index: targets.length, + text, + // 見出し自身は自分の文言と重複するので文脈を持たせない + heading: isHeading ? '' : heading, + isHeading, + x, + y, + anchor: anchorFromElement(el), + }); + }); + + return targets; +} + +function normalize(s: string): string { + return s.replace(/\s+/g, ' ').trim(); +} + +export function toggleRecording(): void { + if (state.voiceProcessing) return; + if (state.voiceRecording) { + stopRecording(); + } else { + void startRecording(); + } +} + +export async function startRecording(): Promise { + if (state.voiceRecording || state.voiceProcessing) return; + state.voiceError = null; + + try { + stream = await navigator.mediaDevices.getUserMedia({ audio: true }); + } catch { + state.voiceError = 'マイクへのアクセスが許可されませんでした'; + onChange(); + return; + } + + chunks = []; + const mime = pickMime(); + mediaRecorder = new MediaRecorder(stream, mime ? { mimeType: mime } : undefined); + mediaRecorder.ondataavailable = (e) => { + if (e.data.size > 0) chunks.push(e.data); + }; + mediaRecorder.onstop = () => { + void handleStop(); + }; + mediaRecorder.start(); + + state.voiceRecording = true; + onChange(); +} + +export function stopRecording(): void { + if (!state.voiceRecording || !mediaRecorder) return; + mediaRecorder.stop(); + state.voiceRecording = false; + state.voiceProcessing = true; + onChange(); +} + +async function handleStop(): Promise { + // マイクを解放 + stream?.getTracks().forEach((t) => t.stop()); + stream = null; + + const blob = new Blob(chunks, { type: mediaRecorder?.mimeType || 'audio/webm' }); + chunks = []; + + const targets = extractTargets(); + if (targets.length === 0) { + state.voiceError = 'ピンを置ける要素が見つかりませんでした'; + state.voiceProcessing = false; + onChange(); + return; + } + if (targets.length >= MAX_TARGETS) { + // 上限で打ち切ると後半にピンを置けない。黙って効かないより伝える。 + state.voiceError = `ページが大きいため、先頭${MAX_TARGETS}箇所のみを対象にしています`; + } + + try { + // 録音(webm/mp4等) を Gemini が確実に扱える 16kHz モノラル WAV に変換して送る + const base64 = await blobToWavBase64(blob); + const resp = await voiceApi( + base64, + 'audio/wav', + targets.map((t) => ({ index: t.index, text: t.text, heading: t.heading, isHeading: t.isHeading })), + ); + if (resp.error) { + state.voiceError = resp.error; + } else { + placePins(resp.results || [], targets); + } + } catch { + state.voiceError = '音声の解析に失敗しました'; + } finally { + state.voiceProcessing = false; + onChange(); + } +} + +function placePins(results: VoiceResult[], targets: Target[]): void { + if (!Array.isArray(results) || results.length === 0) { + state.voiceError = 'フィードバックを聞き取れませんでした。もう一度お試しください'; + return; + } + let misplaced = 0; + results.forEach((r) => { + let target = targets.find((t) => t.index === r.target_index); + if (!target) { + // Gemini が該当なし(-1)や範囲外を返した場合。発言に含まれる語句と + // 要素のテキストを突き合わせて置き場所を探す。 + target = findByText(`${r.quote || ''} ${r.comment || ''}`, targets); + } + if (!target) { + // それでも決まらなければ捨てずに先頭へ。位置は当てにならないと伝える。 + target = targets[0]; + misplaced++; + } + if (!target) return; + addPin(target.x, target.y, r.comment, normalizePriority(r.priority), target.anchor); + }); + if (misplaced > 0) { + state.voiceError = `${misplaced}件は場所を特定できませんでした(先頭に置いています。ドラッグで移動できます)`; + } + renderPins(); +} + +const MATCH_THRESHOLD = 0.34; +const MATCH_MIN_LEN = 4; + +/** + * 発言テキストに最も近い要素を、2文字単位の一致率で探す。 + * + * 一致率は短い方を分母にするため、「0点」「講義」のような2〜3文字の要素は + * たまたま含まれるだけで満点になってしまう。照合対象はある程度の長さを持つ + * 要素に絞り、同点なら情報量の多い(長い)方を選ぶ。 + */ +function findByText(query: string, targets: Target[]): Target | undefined { + const q = bigrams(query); + if (q.size === 0) return undefined; + let best: Target | undefined; + let bestScore = MATCH_THRESHOLD; + targets.forEach((t) => { + if (t.text.length < MATCH_MIN_LEN) return; + const score = overlap(q, bigrams(t.text)); + if (score > bestScore || (best && score === bestScore && t.text.length > best.text.length)) { + bestScore = score; + best = t; + } + }); + return best; +} + +function bigrams(s: string): Set { + const t = s.replace(/\s+/g, '').toLowerCase(); + const out = new Set(); + for (let i = 0; i < t.length - 1; i++) out.add(t.slice(i, i + 2)); + return out; +} + +/** 短い方に対する一致率。要素テキストは短いので Jaccard より当たりやすい。 */ +function overlap(a: Set, b: Set): number { + if (a.size === 0 || b.size === 0) return 0; + let hit = 0; + a.forEach((g) => { if (b.has(g)) hit++; }); + return hit / Math.min(a.size, b.size); +} + +function normalizePriority(p: string): Priority { + return p === 'must' || p === 'better' || p === 'want' ? p : 'better'; +} + +/** MediaRecorder が対応する音声形式を優先順に選ぶ。 */ +function pickMime(): string { + const cands = ['audio/webm;codecs=opus', 'audio/webm', 'audio/ogg;codecs=opus', 'audio/ogg', 'audio/mp4']; + for (const c of cands) { + if (typeof MediaRecorder !== 'undefined' && MediaRecorder.isTypeSupported?.(c)) return c; + } + return ''; +} + +/** + * 録音 Blob を 16kHz モノラルの WAV (base64) に変換する。 + * Gemini の対応音声形式(wav/mp3/aiff/aac/ogg/flac)に確実に乗せ、 + * かつ 16kHz モノラルに落としてペイロードを小さくする。 + */ +async function blobToWavBase64(blob: Blob): Promise { + const arrayBuf = await blob.arrayBuffer(); + const AudioCtx: typeof AudioContext = + window.AudioContext || (window as unknown as { webkitAudioContext: typeof AudioContext }).webkitAudioContext; + const ctx = new AudioCtx(); + let decoded: AudioBuffer; + try { + decoded = await ctx.decodeAudioData(arrayBuf); + } finally { + void ctx.close(); + } + + const targetRate = 16000; + const length = Math.max(1, Math.ceil(decoded.duration * targetRate)); + // チャンネル数1の出力に繋ぐと自動でモノラルにダウンミックスされる + const offline = new OfflineAudioContext(1, length, targetRate); + const src = offline.createBufferSource(); + src.buffer = decoded; + src.connect(offline.destination); + src.start(); + const rendered = await offline.startRendering(); + + const wav = encodeWav(rendered.getChannelData(0), targetRate); + return arrayBufferToBase64(wav); +} + +/** Float32 PCM を 16bit モノラル WAV (ArrayBuffer) にエンコードする。 */ +function encodeWav(samples: Float32Array, sampleRate: number): ArrayBuffer { + const buffer = new ArrayBuffer(44 + samples.length * 2); + const view = new DataView(buffer); + const writeStr = (off: number, s: string) => { + for (let i = 0; i < s.length; i++) view.setUint8(off + i, s.charCodeAt(i)); + }; + writeStr(0, 'RIFF'); + view.setUint32(4, 36 + samples.length * 2, true); + writeStr(8, 'WAVE'); + writeStr(12, 'fmt '); + view.setUint32(16, 16, true); // PCM チャンクサイズ + view.setUint16(20, 1, true); // PCM + view.setUint16(22, 1, true); // モノラル + view.setUint32(24, sampleRate, true); + view.setUint32(28, sampleRate * 2, true); // byteRate = rate * blockAlign + view.setUint16(32, 2, true); // blockAlign = ch * bytesPerSample + view.setUint16(34, 16, true); // bitsPerSample + writeStr(36, 'data'); + view.setUint32(40, samples.length * 2, true); + let off = 44; + for (let i = 0; i < samples.length; i++) { + const s = Math.max(-1, Math.min(1, samples[i])); + view.setInt16(off, s < 0 ? s * 0x8000 : s * 0x7fff, true); + off += 2; + } + return buffer; +} + +function arrayBufferToBase64(buf: ArrayBuffer): string { + let binary = ''; + const bytes = new Uint8Array(buf); + const chunk = 0x8000; + for (let i = 0; i < bytes.length; i += chunk) { + binary += String.fromCharCode(...bytes.subarray(i, i + chunk)); + } + return btoa(binary); +} diff --git a/tests/shared.unit.test.ts b/tests/shared.unit.test.ts new file mode 100644 index 0000000..94bea9c --- /dev/null +++ b/tests/shared.unit.test.ts @@ -0,0 +1,129 @@ +import { describe, test, expect, vi, beforeEach, afterEach } from 'vitest'; +import { slugify, generateId } from '../src/shared/slug'; +import { fmtTime } from '../src/shared/time'; +import { PRIORITY_CYCLE, PRIORITY_COLORS, HIGHLIGHT_COLORS } from '../src/shared/constants'; +import { authHeaders } from '../src/shared/api-client'; +import { mapNormToOrig } from '../src/widget/highlight'; + +describe('slugify', () => { + test('HTTP プレフィックスを除去する', () => { + expect(slugify('https://example.com/page')).toBe('example_com_page'); + }); + + test('日本語文字を保持する', () => { + const result = slugify('https://example.com/テスト'); + expect(result).toContain('テスト'); + }); + + test('100文字で切り詰める', () => { + const long = 'https://example.com/' + 'a'.repeat(200); + expect(slugify(long).length).toBe(100); + }); + + test('記号をアンダースコアに変換する', () => { + expect(slugify('https://a.b/c?d=e&f=g')).toBe('a_b_c_d_e_f_g'); + }); +}); + +describe('generateId', () => { + test('文字列を返す', () => { + expect(typeof generateId()).toBe('string'); + }); + + test('一意性がある(100回生成してユニーク)', () => { + const ids = new Set(Array.from({ length: 100 }, () => generateId())); + expect(ids.size).toBe(100); + }); +}); + +describe('fmtTime', () => { + let realNow: () => number; + + beforeEach(() => { + realNow = Date.now; + vi.spyOn(Date, 'now').mockReturnValue(1700000000000); + }); + + afterEach(() => { + vi.restoreAllMocks(); + }); + + test('1分未満は「たった今」', () => { + expect(fmtTime(1700000000000 - 30000)).toBe('たった今'); + }); + + test('1時間未満は「N分前」', () => { + expect(fmtTime(1700000000000 - 300000)).toBe('5分前'); + }); + + test('24時間未満は「N時間前」', () => { + expect(fmtTime(1700000000000 - 7200000)).toBe('2時間前'); + }); + + test('24時間以上は日時文字列', () => { + const result = fmtTime(1700000000000 - 86400000 * 2); + expect(result).toMatch(/\d+月/); + }); +}); + +describe('PRIORITY_CYCLE', () => { + test('must → better → want → must の循環', () => { + expect(PRIORITY_CYCLE.must).toBe('better'); + expect(PRIORITY_CYCLE.better).toBe('want'); + expect(PRIORITY_CYCLE.want).toBe('must'); + }); + + test('3つの優先度が全て定義されている', () => { + expect(Object.keys(PRIORITY_CYCLE)).toEqual(['must', 'better', 'want']); + }); +}); + +describe('PRIORITY_COLORS', () => { + test('3つの優先度が全て定義されている', () => { + expect(Object.keys(PRIORITY_COLORS)).toEqual(['must', 'better', 'want']); + }); + + test('各色に bg, text, light, border がある', () => { + for (const p of ['must', 'better', 'want'] as const) { + expect(PRIORITY_COLORS[p]).toHaveProperty('bg'); + expect(PRIORITY_COLORS[p]).toHaveProperty('text'); + expect(PRIORITY_COLORS[p]).toHaveProperty('light'); + expect(PRIORITY_COLORS[p]).toHaveProperty('border'); + } + }); +}); + +describe('HIGHLIGHT_COLORS', () => { + test('3つの優先度が全て定義されている', () => { + expect(Object.keys(HIGHLIGHT_COLORS)).toEqual(['must', 'better', 'want']); + }); +}); + +describe('authHeaders', () => { + test('トークンありの場合 Authorization を含む', () => { + const h = authHeaders('my-token'); + expect(h['Authorization']).toBe('Bearer my-token'); + expect(h['Content-Type']).toBe('application/json'); + }); + + test('トークンなしの場合 Authorization を含まない', () => { + const h = authHeaders(''); + expect(h['Authorization']).toBeUndefined(); + expect(h['Content-Type']).toBe('application/json'); + }); +}); + +describe('mapNormToOrig', () => { + test('空白なしの単純マッチ', () => { + expect(mapNormToOrig('hello world', 0, 5)).toEqual([0, 5]); + }); + + test('余分な空白がある場合の正規化マッチ', () => { + // 正規化後のインデックス 6-11 は orig で 6-12 に対応(余分な空白を含む) + expect(mapNormToOrig('hello world', 6, 11)).toEqual([6, 12]); + }); + + test('範囲外の normStart は null を返す', () => { + expect(mapNormToOrig('short', 100, 105)).toBeNull(); + }); +}); diff --git a/tests/widget.integration.test.ts b/tests/widget.integration.test.ts new file mode 100644 index 0000000..9112db3 --- /dev/null +++ b/tests/widget.integration.test.ts @@ -0,0 +1,168 @@ +import { describe, test, expect, beforeEach, vi } from 'vitest'; +import { readFileSync } from 'fs'; +import { resolve } from 'path'; + +const WIDGET_JS = readFileSync( + resolve(__dirname, '../public/widget.js'), + 'utf-8' +); + +function loadWidget() { + document.head.innerHTML = ''; + document.body.innerHTML = '

テストテキスト

'; + + const script = document.createElement('script'); + script.src = 'https://my-app.vercel.app/widget.js'; + script.dataset.token = 'test-token-abc'; + Object.defineProperty(document, 'currentScript', { + value: script, + writable: true, + configurable: true, + }); + + window.localStorage.clear(); + + vi.stubGlobal( + 'fetch', + vi.fn(() => + Promise.resolve({ + json: () => Promise.resolve([]), + ok: true, + }) + ) + ); + + // eslint-disable-next-line no-eval + eval(WIDGET_JS); +} + +describe('widget.js 初期化', () => { + beforeEach(() => { + loadWidget(); + }); + + test('トグルボタンが DOM に追加される', () => { + const toggle = document.getElementById('fb-toggle'); + expect(toggle).toBeTruthy(); + expect(toggle!.tagName).toBe('BUTTON'); + }); + + test('サイドバーが DOM に追加される', () => { + const sidebar = document.getElementById('fb-sidebar'); + expect(sidebar).toBeTruthy(); + }); + + test('スタイルが注入される', () => { + const style = document.getElementById('fb-widget-styles'); + expect(style).toBeTruthy(); + expect(style!.tagName).toBe('STYLE'); + }); + + test('CSS 変数が定義されている', () => { + const style = document.getElementById('fb-widget-styles'); + const css = style!.textContent || ''; + expect(css).toContain('--fb-bg'); + expect(css).toContain('--fb-fg'); + expect(css).toContain('--fb-accent'); + expect(css).toContain('--fb-border'); + expect(css).toContain('--fb-muted'); + expect(css).toContain('--fb-primary'); + expect(css).toContain('--fb-destructive'); + }); + + test('トグルボタンに「コメント」ラベルがある', () => { + const toggle = document.getElementById('fb-toggle'); + expect(toggle!.innerHTML).toContain('コメント'); + }); + + test('サイドバーにフィルタボタンがある', () => { + const sidebar = document.getElementById('fb-sidebar'); + const html = sidebar!.innerHTML; + expect(html).toContain('未解決'); + expect(html).toContain('解決済'); + expect(html).toContain('すべて'); + }); + + test('サイドバーにリサイズハンドルがある', () => { + const sidebar = document.getElementById('fb-sidebar'); + const handle = sidebar!.querySelector('.fb-resize-handle'); + expect(handle).toBeTruthy(); + }); + + test('サイドバーは初期状態で非表示(右にオフセット)', () => { + const sidebar = document.getElementById('fb-sidebar') as HTMLElement; + const right = parseInt(sidebar.style.right, 10); + expect(right).toBeLessThan(0); + }); + + test('名前未入力時に名前入力ダイアログが表示される', () => { + const overlay = document.getElementById('fb-name-overlay'); + expect(overlay).toBeTruthy(); + expect(overlay!.innerHTML).toContain('ようこそ'); + }); +}); + +describe('widget.js ハイライト CSS', () => { + beforeEach(() => { + loadWidget(); + }); + + test('ハイライトスタイルが優先度別に3色定義されている', () => { + const style = document.getElementById('fb-widget-styles'); + const css = style!.textContent || ''; + expect(css).toContain('.fb-highlight-must'); + expect(css).toContain('.fb-highlight-better'); + expect(css).toContain('.fb-highlight-want'); + }); + + test('カードスタイルが定義されている', () => { + const style = document.getElementById('fb-widget-styles'); + const css = style!.textContent || ''; + expect(css).toContain('.fb-card'); + expect(css).toContain('.fb-card-head'); + expect(css).toContain('.fb-badge-p'); + }); + + test('ポップアップスタイルが定義されている', () => { + const style = document.getElementById('fb-widget-styles'); + const css = style!.textContent || ''; + expect(css).toContain('.fb-popup'); + expect(css).toContain('.fb-popup-pri'); + expect(css).toContain('.fb-popup-actions'); + }); +}); + +describe('widget.js コメントなし状態', () => { + beforeEach(() => { + loadWidget(); + }); + + test('コメントゼロ時にサイドバーに空状態メッセージが表示される', () => { + const sidebar = document.getElementById('fb-sidebar'); + const html = sidebar!.innerHTML; + expect(html).toContain('コメントはまだありません'); + }); + + test('コメントゼロ時にバッジが表示されない', () => { + const toggle = document.getElementById('fb-toggle'); + expect(toggle!.querySelector('.fb-badge')).toBeNull(); + }); +}); + +describe('widget.js SVG アイコン', () => { + beforeEach(() => { + loadWidget(); + }); + + test('トグルボタンに SVG アイコンが含まれる', () => { + const toggle = document.getElementById('fb-toggle'); + expect(toggle!.innerHTML).toContain(' { + const sidebar = document.getElementById('fb-sidebar'); + const closeBtn = sidebar!.querySelector('[data-action="close"]'); + expect(closeBtn).toBeTruthy(); + expect(closeBtn!.innerHTML).toContain('