diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 0000000..9d08a1a
--- /dev/null
+++ b/.editorconfig
@@ -0,0 +1,9 @@
+root = true
+
+[*]
+charset = utf-8
+indent_style = space
+indent_size = 2
+end_of_line = lf
+insert_final_newline = true
+trim_trailing_whitespace = true
diff --git a/.gitignore b/.gitignore
index ab234eb..c1c1f15 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,3 +2,4 @@
**/dist
**/out
**/tests.failed.json
+packages/playground/public/assets/locale/*
diff --git a/.localerc.json b/.localerc.json
new file mode 100644
index 0000000..dfa6dda
--- /dev/null
+++ b/.localerc.json
@@ -0,0 +1,10 @@
+{
+ "basic": {
+ "source": "packages/playground/node_modules/@fuyeor/locale/src/common.json",
+ "outDir": "packages/playground/public/assets/locale"
+ },
+ "main": {
+ "source": "packages/playground/src/locale/locale.json",
+ "outDir": "packages/playground/public/assets/locale"
+ }
+}
diff --git a/.npmrc b/.npmrc
new file mode 100644
index 0000000..424942b
--- /dev/null
+++ b/.npmrc
@@ -0,0 +1 @@
+@fuyeor:registry=https://fpm.fuyeor.com/v1/
diff --git a/.vscode/settings.json b/.vscode/settings.json
index 1223cd0..af9c146 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -4,7 +4,7 @@
"editor.formatOnSave": true,
// assign default formatting tool
"[html]": {
- "editor.defaultFormatter": "esbenp.prettier-vscode"
+ "editor.defaultFormatter": "vscode.html-language-features"
},
"[json]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
diff --git a/Dockerfile b/Dockerfile
index d9352d3..a0be6bf 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -26,8 +26,11 @@ COPY . .
# build parser package before workspace consumers resolve its package exports
RUN pnpm --filter @fuyeor/markdown-parser build
+# generate production locale assets before the playground build copies public files
+RUN pnpm locale make playground prod
+
# build playground
-RUN pnpm --filter @fuyeor/markdown-parser-playground build
+RUN pnpm --filter @fuyeor/markdown-playground build
# ==========================================
# release
diff --git a/package.json b/package.json
index c188742..6786f01 100644
--- a/package.json
+++ b/package.json
@@ -6,9 +6,11 @@
"scripts": {
"format": "prettier --write \"**/*.ts\"",
"test": "pnpm -F @fuyeor/markdown-parser test",
- "playground": "pnpm -F @fuyeor/markdown-playground dev"
+ "playground": "pnpm -F @fuyeor/markdown-playground dev",
+ "locale": "locale-cli"
},
"devDependencies": {
+ "@fuyeor/locale-cli": "^0.1.1",
"@types/node": "^25.6.0",
"jsdom": "^29.0.1",
"prettier": "^3.8.3",
diff --git a/packages/markdown-parser-vue/src/index.ts b/packages/markdown-parser-vue/src/index.ts
new file mode 100644
index 0000000..d54cf72
--- /dev/null
+++ b/packages/markdown-parser-vue/src/index.ts
@@ -0,0 +1,2 @@
+// @fuyeor/markdown-parser-vue/src/index.ts
+export { renderToVue } from './render';
diff --git a/packages/playground/index.html b/packages/playground/index.html
index 157bee3..64e5d83 100644
--- a/packages/playground/index.html
+++ b/packages/playground/index.html
@@ -1,20 +1,114 @@
-
-
-
-
-
- @fuyeor/markdown-parser
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Fuyeor Flavored Markdown Playground
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/packages/playground/package.json b/packages/playground/package.json
index fa5166f..6dd0db8 100644
--- a/packages/playground/package.json
+++ b/packages/playground/package.json
@@ -3,25 +3,32 @@
"license": "MIT",
"author": "Fuyeor ",
"type": "module",
- "sideEffects": false,
"scripts": {
"dev": "vite dev",
- "build": "vite build"
- },
- "imports": {
- "#/*": "./src/*"
+ "build": "vite build",
+ "typecheck": "vue-tsc --noEmit"
},
"exports": {
".": "./src/main.ts"
},
"dependencies": {
- "@fuyeor/markdown-parser-lit": "workspace:*",
- "@fuyeor/locale": "https://github.com/Fuyeor/webroamer.git#path:packages/locale",
- "lit": "^3.3.2"
+ "@fuyeor/commons": "^0.1.2",
+ "@fuyeor/config": "^0.1.1",
+ "@fuyeor/interactify": "^0.1.1",
+ "@fuyeor/locale": "^0.1.0",
+ "@fuyeor/markdown-formatter": "workspace:*",
+ "@fuyeor/markdown-parser": "workspace:*",
+ "@fuyeor/markdown-parser-vue": "workspace:*",
+ "@fuyeor/vue-router": "^0.0.9",
+ "vue": "^3.5.41"
},
"devDependencies": {
- "@types/node": "^25.6.0",
- "typescript": "^6.0.3",
- "vite": "^8.0.1"
+ "@fuyeor/tsconfig": "0.1.0",
+ "@types/node": "^26.2.0",
+ "@vitejs/plugin-vue": "^6.0.8",
+ "html-minifier-terser": "^7.2.0",
+ "lightningcss": "^1.33.0",
+ "typescript": "^7.0.2",
+ "vite": "8.0.1"
}
}
diff --git a/packages/playground/public/assets/css/style.css b/packages/playground/public/assets/css/style.css
new file mode 100644
index 0000000..19db6ff
--- /dev/null
+++ b/packages/playground/public/assets/css/style.css
@@ -0,0 +1,158 @@
+:root {
+ --width-body: 100%;
+ --width-main: 1000px;
+ --width-left-sidebar: 350px;
+ --width-right-sidebar: 0;
+
+ --playground-border: #e2e8f0;
+ --playground-bg-section: #ffffff;
+ --playground-bg-header: #f8fafc;
+ --playground-font-mono:
+ ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
+}
+
+@media (width >= 1000px) {
+ main {
+ width: 100%;
+ }
+
+ .header {
+ border: none;
+ }
+}
+
+h3::after {
+ display: none;
+}
+
+.markdown-rendered {
+ ol,
+ ul {
+ margin: 0 0 20px 20px;
+ padding: 0;
+ }
+
+ li {
+ margin: 10px 0 10px 0;
+ }
+
+ code,
+ pre,
+ pre * {
+ font-size: 0.9rem;
+ }
+}
+
+html {
+ /* remove scroll bar */
+ scrollbar-gutter: unset;
+}
+
+body {
+ overflow: hidden;
+}
+
+.playground-layout {
+ display: grid;
+ grid-template-columns: minmax(280px, 1fr) minmax(360px, 1.2fr);
+ height: calc(-110px + 100vh);
+}
+
+.section {
+ display: flex;
+ flex-direction: column;
+ min-width: 0;
+ min-height: 0;
+ overflow: hidden;
+}
+
+.editor {
+ border-right: var(--border-subtle);
+
+ textarea {
+ flex: 1;
+ resize: none;
+ background: transparent;
+ font: 400 13px / 1.6 var(--playground-font-mono);
+ tab-size: 2;
+ overflow-y: auto;
+ }
+
+ textarea:hover,
+ textarea:focus {
+ box-shadow: none;
+ }
+}
+
+@media (width <= 900px) {
+ .playground-layout {
+ height: auto;
+ grid-template-columns: 1fr;
+ grid-template-rows: 400px 520px;
+ position: absolute;
+ top: var(--height-header);
+ }
+
+ .preview {
+ border-top: var(--border-subtle);
+ }
+}
+
+/* FFM Ambassador Theme Tokens */
+:root {
+ --ink: #24354b;
+ --ink-soft: #64707d;
+ --paper: #fbfaf7;
+ --paper-deep: #f1efe8;
+ --lilac: #aea4e4;
+ --lilac-deep: #8275ce;
+ --lilac-pale: #eeebfb;
+ --sage: #8ba493;
+ --line: rgba(36, 53, 75, 0.12);
+ --card: rgba(255, 255, 255, 0.86);
+ --radius: 16px;
+ --ease-out: cubic-bezier(0.23, 1, 0.32, 1);
+ --background: var(--paper);
+ --foreground: var(--ink);
+ --card-foreground: var(--ink);
+ --popover: var(--paper);
+ --popover-foreground: var(--ink);
+ --primary: var(--lilac-deep);
+ --primary-foreground: #fff;
+ --secondary: var(--lilac-pale);
+ --secondary-foreground: var(--ink);
+ --muted: var(--paper-deep);
+ --muted-foreground: var(--ink-soft);
+ --accent: var(--lilac-pale);
+ --accent-foreground: var(--ink);
+ --destructive: #c84c4a;
+ --destructive-foreground: #fff;
+ --border: var(--line);
+ --input: var(--line);
+ --ring: var(--lilac);
+}
+
+.night {
+ --ink: #f0eff5;
+ --ink-soft: #b7b6c4;
+ --paper: #11101c;
+ --paper-deep: #191827;
+ --lilac-pale: #292743;
+ --line: rgba(239, 238, 247, 0.12);
+ --card: rgba(30, 29, 44, 0.9);
+}
+
+body.home-page {
+ overflow: auto;
+ background: var(--paper);
+ color: var(--ink);
+}
+
+.home-page .paper-grain {
+ mix-blend-mode: multiply;
+}
+
+.night .paper-grain {
+ mix-blend-mode: screen;
+ opacity: 0.13;
+}
diff --git a/packages/playground/public/assets/examples/en.ffm b/packages/playground/public/assets/examples/en.ffm
new file mode 100644
index 0000000..6d549d0
--- /dev/null
+++ b/packages/playground/public/assets/examples/en.ffm
@@ -0,0 +1,203 @@
+# Welcome to the Markdown Playground
+
+This is a sample document designed to showcase and test the syntax of Fuyeor Flavored Markdown. Feel free to edit and preview your changes in real time.
+
+## Basic Syntax
+
+### Headings
+
+```ffm
+# Level 1 Heading
+
+## Level 2 Heading
+
+### Level 3 Heading
+
+#### Level 4 Heading
+
+##### Level 5 Heading
+
+###### Level 6 Heading
+```
+
+### Emphasis
+
+- **Bold text**: use `**text**`
+- *Italic text*: use `*text*`
+- --Strikethrough--: use `--text--`
+- `Inline code`: use backticks `` `code` ``
+
+### Paragraphs & Line Breaks
+
+This is the first paragraph.
+In Markdown, consecutive lines without a blank line are merged into a single paragraph.
+
+This is the second paragraph. An empty line creates a new paragraph.
+
+## Lists
+
+### Unordered List
+
+- Apple
+- Banana
+ - Lady Finger Banana
+ - Cavendish Banana
+- Orange
+
+### Ordered List
+
+1. Step 1: Open the editor
+2. Step 2: Write your Markdown
+3. Step 3: Preview the rendered output
+ 1. Live preview
+ 2. Export HTML
+
+## Links & Images
+
+### Links
+
+[Example link with text](https://www.example.com) and an autolink: https://www.example.com
+
+### Images
+
+
+
+## Quotes
+
+```quote
+A small syntax can make a large pause.
+
+- Format in one breath
+- Preview as you write
+- Keep every idea close
+```
+
+## Code
+
+### Inline Code
+
+In Python, use `print("Hello, Markdown!")` to print text.
+
+### Code Blocks
+
+```fon
+{
+ identifier = `reference.fuyeor.com`
+ params = {
+ locale = .en
+ module = `ffm`
+ slug = [`tutorials`, `overview`]
+ }
+}
+```
+
+## Tables
+
+| Left-aligned | Center-aligned | Right-aligned |
+| :--- | :---: | ---: |
+| Content | Content | Content |
+| Longer content | Longer content | Longer content |
+
+## Horizontal Rule
+
+---
+
+## Interactive Components
+
+### Carousel Slides
+
+```slide
+**Ф social**
+- Character limit: 3,000 - 5,000 characters
+- Content format: Fuyeor Flavored Markdown
+
+---
+
+**Bluesky**
+- Character limit: 300 characters
+- Content format: Plain text
+
+---
+
+**Mastodon**
+- Character limit: Varies by instance (default is 500)
+- Content format: Plain text
+```
+
+### Chain of Thought
+
+```chain
+**[x] Book the party venue**
+Reserved the board game café everyone agreed on; the deposit has been paid.
+
+**[x] Confirm attendance**
+Everyone responded in the group chat; 8 people confirmed in total.
+
+**[ ] Buy snacks and drinks**
+Shopping list ready; planning to pick them up Saturday morning before heading over.
+
+**[ ] Prepare the mystery birthday gift**
+Ordered online and currently in transit; expected delivery by Friday afternoon.
+
+**Share on social media**
+Don't forget to post the party photos on social media afterward~
+```
+
+### Accordion
+
+```accordion
+**How can I join the volunteer team?**
+Click "Join Us" at the top right of the homepage to fill out a short form. Our coordinators will get in touch within three business days.
+
+**Do I need to bring my own tools to events?**
+Not at all! All necessary materials and tools will be provided on-site free of charge.
+
+**What if I can no longer make it?**
+No problem. Simply go to your profile and click "Cancel Booking" at least 24 hours prior to the event.
+```
+
+## Academic Extensions
+
+### LaTeX
+
+Inline formula: $E = mc^2$
+
+Block formula:
+
+$$
+\int_0^1 x^2 \, dx = \frac{1}{3}
+$$
+
+### Diagrams & Flowcharts
+
+```mermaid
+graph TD
+ A[Start] --> B(Processing)
+ B --> C{Decision}
+ C -- Approve --> D[End]
+ C -- Reject --> E[Revise]
+```
+
+### Chemical Formulas
+
+When it comes to pain relief, the well-known structural formula for aspirin is #[smiles = `CC(=O)OC1=CC=CC=C1C(=O)O`]; its counterpart, ibuprofen, has the molecular formula #[smiles = `CC(C)CC1=CC=C(C=C1)C(C)C(=O)O`].
+
+### Musical Score
+
+```abc
+X: 1
+T: Scale Example
+M: 4/4
+L: 1/4
+K: C
+C D E F | G A B c |
+```
+
+## Summary
+
+Congratulations on completing the Markdown Playground tutorial! You are now ready to craft your own Markdown documents:
+
+- Switch documents from the left sidebar (or the menu in the top-left corner on mobile).
+- All documents are saved locally and accessible only to you.
+
+Happy writing!
\ No newline at end of file
diff --git a/packages/playground/public/assets/examples/fr.ffm b/packages/playground/public/assets/examples/fr.ffm
new file mode 100644
index 0000000..7e32f3a
--- /dev/null
+++ b/packages/playground/public/assets/examples/fr.ffm
@@ -0,0 +1,203 @@
+# Bienvenue sur l'Espace Markdown
+
+Ce document est un exemple conçu pour tester et découvrir la syntaxe du Fuyeor Flavored Markdown. Vous pouvez le modifier et prévisualiser le rendu en temps réel.
+
+## Syntaxe de base
+
+### Titres
+
+```ffm
+# Titre de niveau 1
+
+## Titre de niveau 2
+
+### Titre de niveau 3
+
+#### Titre de niveau 4
+
+##### Titre de niveau 5
+
+###### Titre de niveau 6
+```
+
+### Emphase
+
+- **Texte en gras** : utilisez `**texte**`
+- *Texte en italique* : utilisez `*texte*`
+- --Texte barré-- : utilisez `--texte--`
+- `Code en ligne` : utilisez les accents graves `` `code` ``
+
+### Paragraphes et retours à la ligne
+
+Ceci est le premier paragraphe.
+En Markdown, deux lignes consécutives sans ligne vide sont fusionnées dans le même paragraphe.
+
+Ceci est le second paragraphe. Une ligne vide crée un nouveau paragraphe.
+
+## Listes
+
+### Liste à puces
+
+- Pomme
+- Banane
+ - Banane Frécinette
+ - Banane Cavendish
+- Orange
+
+### Liste numérotée
+
+1. Étape 1 : Ouvrez l'éditeur
+2. Étape 2 : Rédigez votre Markdown
+3. Étape 3 : Prévisualisez le résultat
+ 1. Aperçu en temps réel
+ 2. Exporter en HTML
+
+## Liens et images
+
+### Liens
+
+[Exemple de lien avec texte](https://www.example.com) et lien brut : https://www.example.com
+
+### Images
+
+
+
+## Citations
+
+```quote
+A small syntax can make a large pause.
+
+- Format in one breath
+- Preview as you write
+- Keep every idea close
+```
+
+## Code
+
+### Code en ligne
+
+En Python, utilisez `print("Hello, Markdown!")` pour afficher du texte.
+
+### Blocs de code
+
+```fon
+{
+ identifier = `reference.fuyeor.com`
+ params = {
+ locale = .en
+ module = `ffm`
+ slug = [`tutorials`, `overview`]
+ }
+}
+```
+
+## Tableaux
+
+| Aligné à gauche | Centré | Aligné à droite |
+| :--- | :---: | ---: |
+| Contenu | Contenu | Contenu |
+| Contenu plus long | Contenu plus long | Contenu plus long |
+
+## Ligne de séparation
+
+---
+
+## Composants interactifs
+
+### Diapositives
+
+```slide
+**Ф social**
+- Limite de caractères : 3 000 - 5 000 caractères
+- Format de contenu : Fuyeor Flavored Markdown
+
+---
+
+**Bluesky**
+- Limite de caractères : 300 caractères
+- Format de contenu : Texte brut
+
+---
+
+**Mastodon**
+- Limite de caractères : Variable selon l'instance (500 par défaut)
+- Format de contenu : Texte brut
+```
+
+### Fil de pensée
+
+```chain
+**[x] Réserver le lieu de la soirée**
+Le bar à jeux choisi à l'unanimité est réservé, et l'acompte est versé.
+
+**[x] Confirmer le nombre de participants**
+Tout le monde a répondu sur le groupe de discussion ; 8 personnes confirmées au total.
+
+**[ ] Acheter les boissons et les collations**
+Liste de courses prête ; prévu de faire les achats samedi matin avant de s'y rendre.
+
+**[ ] Préparer le cadeau d'anniversaire surprise**
+Cadeau commandé en ligne, en cours de livraison ; arrivée prévue vendredi après-midi.
+
+**Publier sur les réseaux sociaux**
+N'oubliez pas de partager les photos de la fête sur les réseaux sociaux ensuite~
+```
+
+### Accordéon
+
+```accordion
+**Comment rejoindre notre équipe de bénévoles ?**
+Cliquez sur « Rejoignez-nous » en haut à droite de la page d'accueil pour remplir un court formulaire. Notre équipe vous contactera sous trois jours ouvrés.
+
+**Dois-je apporter mes propres outils pour les événements ?**
+Pas du tout ! Tout le matériel et les outils nécessaires sont fournis gratuitement sur place.
+
+**Que faire en cas d'empêchement ?**
+Aucun problème. Il vous suffit de vous rendre dans votre espace personnel et de cliquer sur « Annuler la réservation » au moins 24 heures avant l'événement.
+```
+
+## Extensions académiques
+
+### Formules mathématiques
+
+Formule en ligne : $E = mc^2$
+
+Formule en bloc :
+
+$$
+\int_0^1 x^2 \, dx = \frac{1}{3}
+$$
+
+### Diagrammes et organigrammes
+
+```mermaid
+graph TD
+ A[Début] --> B(Traitement)
+ B --> C{Condition}
+ C -- Approuvé --> D[Fin]
+ C -- Refusé --> E[À modifier]
+```
+
+### Formules chimiques
+
+Pour soulager la douleur, la structure de l'aspirine la plus connue est #[smiles = `CC(=O)OC1=CC=CC=C1C(=O)O`] ; son pendant, l'ibuprofène, a pour formule moléculaire #[smiles = `CC(C)CC1=CC=C(C=C1)C(C)C(=O)O`].
+
+### Partition de musique
+
+```abc
+X: 1
+T: Exemple de gamme (Scale Example)
+M: 4/4
+L: 1/4
+K: C
+C D E F | G A B c |
+```
+
+## Conclusion
+
+Félicitations pour avoir terminé le guide de l'espace Markdown ! Vous êtes maintenant prêt(e) à rédiger vos propres documents :
+
+- Changez de document depuis la barre latérale gauche (ou le menu en haut à gauche sur mobile).
+- Tous vos documents sont enregistrés localement et vous êtes la seule personne à pouvoir y accéder.
+
+Bonne rédaction !
\ No newline at end of file
diff --git a/packages/playground/public/assets/examples/ja.ffm b/packages/playground/public/assets/examples/ja.ffm
new file mode 100644
index 0000000..4359988
--- /dev/null
+++ b/packages/playground/public/assets/examples/ja.ffm
@@ -0,0 +1,203 @@
+# Markdown プレイグラウンドへようこそ
+
+本ドキュメントは、Fuyeor Flavored Markdown の構文をテスト・体験するためのサンプルドキュメントです。ここで自由に編集し、リアルタイムでプレビューを確認できます。
+
+## 基本構文
+
+### 見出し
+
+```ffm
+# レベル 1 見出し
+
+## レベル 2 見出し
+
+### レベル 3 見出し
+
+#### レベル 4 見出し
+
+##### レベル 5 見出し
+
+###### レベル 6 見出し
+```
+
+### 強調
+
+- **太字テキスト**:`**テキスト**` を使用
+- *斜体テキスト*:`*テキスト*` を使用
+- --取り消し線--:`--テキスト--` を使用
+- `インラインコード`:バッククォート `` `コード` `` を使用
+
+### 段落と改行
+
+これは 1 つ目の段落です。
+Markdown では、空行を挟まない連続した行は同じ段落として結合されます。
+
+これは 2 つ目の段落です。空行を入れることで新しい段落が作成されます。
+
+## リスト
+
+### 箇条書きリスト
+
+- りんご
+- バナナ
+ - モンキーバナナ
+ - キャベンディッシュ
+- オレンジ
+
+### 番号付きリスト
+
+1. ステップ 1:エディタを開く
+2. ステップ 2:Markdown を記述する
+3. ステップ 3:プレビューを確認する
+ 1. リアルタイムプレビュー
+ 2. HTML のエクスポート
+
+## リンクと画像
+
+### リンク
+
+[テキスト付きリンクの例](https://www.example.com) と 自動リンク:https://www.example.com
+
+### 画像
+
+
+
+## 引用
+
+```quote
+A small syntax can make a large pause.
+
+- Format in one breath
+- Preview as you write
+- Keep every idea close
+```
+
+## コード
+
+### インラインコード
+
+Python では、`print("Hello, Markdown!")` を使用してテキストを出力します。
+
+### コードブロック
+
+```fon
+{
+ identifier = `reference.fuyeor.com`
+ params = {
+ locale = .en
+ module = `ffm`
+ slug = [`tutorials`, `overview`]
+ }
+}
+```
+
+## テーブル
+
+| 左揃え | 中央揃え | 右揃え |
+| :--- | :---: | ---: |
+| コンテンツ | コンテンツ | コンテンツ |
+| 長いコンテンツ | 長いコンテンツ | 長いコンテンツ |
+
+## 区切り線
+
+---
+
+## レイアウトコンポーネント
+
+### スライド
+
+```slide
+**Ф social**
+- 文字数制限:3,000 - 5,000 文字
+- フォーマット:Fuyeor Flavored Markdown
+
+---
+
+**Bluesky**
+- 文字数制限:300 文字
+- フォーマット:プレーンテキスト
+
+---
+
+**Mastodon**
+- 文字数制限:インスタンスにより異なりますが、デフォルトは 500 文字です
+- フォーマット:プレーンテキスト
+```
+
+### 思考チェーン
+
+```chain
+**[x] パーティー会場の予約**
+みんなで決めたボードゲームカフェを予約し、予約金の支払いも完了。
+
+**[x] 当日の参加人数の確認**
+グループチャットでの出欠確認が完了。合計 8 名が参加予定。
+
+**[ ] お菓子と飲み物の買い出し**
+買い物リストを作成済み。土曜の午前に購入して直接お店へ持参する予定。
+
+**[ ] サプライズ誕生日プレゼントの準備**
+ネット注文したプレゼントは配送中。金曜日の午後に到着予定。
+
+**SNS に投稿**
+最後にパーティーの写真を SNS にシェアするのをお忘れなく〜
+```
+
+### アコーディオン
+
+```accordion
+**ボランティアチームに参加するには?**
+ホームページ右上の「参加登録」から簡単なフォームに入力してください。3 営業日以内に担当コーディネーターよりご連絡いたします。
+
+**イベントへの参加に道具の持参は必要ですか?**
+いいえ、必要な資材や道具はすべて会場にて無料でご用意しております。
+
+**急用で参加できなくなった場合は?**
+問題ありません。イベント開始の 24 時間前までに、マイページから「予約をキャンセル」をクリックしてください。
+```
+
+## アカデミック拡張
+
+### 数式
+
+インライン数式:$E = mc^2$
+
+ブロック数式:
+
+$$
+\int_0^1 x^2 \, dx = \frac{1}{3}
+$$
+
+### フローチャート・ダイアグラム
+
+```mermaid
+graph TD
+ A[開始ノード] --> B(処理プロセス)
+ B --> C{条件分岐}
+ C -- 承認 --> D[終了ノード]
+ C -- 却下 --> E[修正へ戻る]
+```
+
+### 化学構造式
+
+鎮痛剤として広く知られるアスピリンの構造式は #[smiles = `CC(=O)OC1=CC=CC=C1C(=O)O`] であり、その対抗薬であるイブプロフェンの分子式は #[smiles = `CC(C)CC1=CC=C(C=C1)C(C)C(=O)O`] です。
+
+### 楽譜
+
+```abc
+X: 1
+T: 練習曲 (Scale Example)
+M: 4/4
+L: 1/4
+K: C
+C D E F | G A B c |
+```
+
+## まとめ
+
+Markdown プレイグラウンドのチュートリアル完了、お疲れさまでした!これで独自の Markdown ドキュメントを作成する準備が整いました:
+
+- 左サイドバー(モバイル端末では左上のメニュー)からドキュメントを切り替えられます。
+- すべてのドキュメントはローカルに保存され、あなただけが閲覧できます。
+
+素敵なライティング体験を!
\ No newline at end of file
diff --git a/packages/playground/public/assets/examples/zh-hans.ffm b/packages/playground/public/assets/examples/zh-hans.ffm
new file mode 100644
index 0000000..b5ea937
--- /dev/null
+++ b/packages/playground/public/assets/examples/zh-hans.ffm
@@ -0,0 +1,203 @@
+# 欢迎使用 Markdown 演练场
+
+这是一个用于测试和展示 Fuyeor Flavored Markdown 语法的示例文档。你可以在这里随意修改、预览。
+
+## 基础语法
+
+### 标题
+
+```ffm
+# 一级标题
+
+## 二级标题
+
+### 三级标题
+
+#### 四级标题
+
+##### 五级标题
+
+###### 六级标题
+```
+
+### 强调
+
+- **粗体文本**:使用 `**文本**`
+- *斜体文本*:使用 `*文本*`
+- --删除线--:使用 `--文本--`
+- `行内代码`:使用反引号 `` `代码` ``
+
+### 段落与换行
+
+这是第一段。
+在 Markdown 中,连续的两行会合并为同一段落。
+
+这是第二段。空一行会创建新的段落。
+
+## 列表
+
+### 无序列表
+
+- 苹果
+- 香蕉
+ - 小米蕉
+ - 芝麻蕉
+- 橙子
+
+### 有序列表
+
+1. 第一步:打开编辑器
+2. 第二步:编写 Markdown
+3. 第三步:预览效果
+ 1. 实时预览
+ 2. 导出 HTML
+
+## 链接与图片
+
+### 链接
+
+[带文字的链接示例](https://www.example.com)和不带文字的链接:https://www.example.com
+
+### 图片
+
+
+
+## 引用
+
+```quote
+A small syntax can make a large pause.
+
+- Format in one breath
+- Preview as you write
+- Keep every idea close
+```
+
+## 代码
+
+### 行内代码
+
+在 Python 中,使用 `print("Hello, Markdown!")` 输出文本。
+
+### 代码块
+
+```fon
+{
+ identifier = `reference.fuyeor.com`
+ params = {
+ locale = .en
+ module = `ffm`
+ slug = [`tutorials`, `overview`]
+ }
+}
+```
+
+## 表格
+
+| 左对齐 | 居中对齐 | 右对齐 |
+| :--- | :---: | ---: |
+| 内容 | 内容 | 内容 |
+| 长内容 | 长内容 | 长内容 |
+
+## 分割线
+
+---
+
+## 排版
+
+### 滑块
+
+```slide
+**Ф social**
+- 字数限制:3000 - 5000(字符数)
+- 内容格式:Fuyeor Flavored Markdown
+
+---
+
+**Bluesky**
+- 字数限制:300(字符数)
+- 内容格式:纯文本
+
+---
+
+**Mastodon**
+- 字数限制:视实例而不同,但大多数实例为默认的 500
+- 内容格式:纯文本
+```
+
+### 思维链
+
+```chain
+**[x] 定好聚会场地**
+已经预订好了大家一致同意的桌游店,定金也付过了。
+
+**[x] 统计当天到场人数**
+群聊里大家都接龙完毕,总共有 8 个人参加。
+
+**[ ] 买好零食和饮料**
+列好了购物清单,打算周六早上买完直接带去店里。
+
+**[ ] 准备神秘生日礼物**
+网购的礼物还在路上,预计周五下午送到。
+
+**发布社交媒体**
+最后记得把聚会照片发在社交媒体〜
+```
+
+### 手风琴
+
+```accordion
+**如何加入我们的志愿者团队?**
+点击首页右上角的「加入我们」填写一张简单的报名表,我们的管理员会在三个工作日内联系你。
+
+**参与活动需要自己带工具吗?**
+不需要哦,每次活动所需的全部材料和工具,我们都会在现场为你免费提供。
+
+**如果临时有事去不了怎么办?**
+没关系,只需在活动开始前 24 小时,在个人中心点击「取消预约」即可。
+```
+
+## 学术扩展
+
+### 数学公式
+
+行内公式:$E = mc^2$
+
+块级公式:
+
+$$
+\int_0^1 x^2 \, dx = \frac{1}{3}
+$$
+
+### 流程图与图表
+
+```mermaid
+graph TD
+ A[开始节点] --> B(处理过程)
+ B --> C{条件判断}
+ C -- 同意 --> D[结束节点]
+ C -- 拒绝 --> E[返回修改]
+```
+
+### 化学式
+
+提到止痛,大家最熟悉的阿司匹林结构式为 #[smiles = `CC(=O)OC1=CC=CC=C1C(=O)O`];而它的死对头布洛芬,分子式则是 #[smiles = `CC(C)CC1=CC=C(C=C1)C(C)C(=O)O`]。
+
+### 五线谱
+
+```abc
+X: 1
+T: 示例乐曲 (Scale Example)
+M: 4/4
+L: 1/4
+K: C
+C D E F | G A B c |
+```
+
+## 总结
+
+恭喜你完成了 Markdown 演练场的教程!现在你可以开始创建自己的 Markdown 文档了:
+
+- 点击左侧边栏(移动端点击左上角菜单)切换文档。
+- 所有的文档保存于本地,只有自己能看到。
+
+祝你写作愉快!
\ No newline at end of file
diff --git a/packages/playground/favicon.svg b/packages/playground/public/favicon.svg
similarity index 100%
rename from packages/playground/favicon.svg
rename to packages/playground/public/favicon.svg
diff --git a/packages/playground/public/robots.txt b/packages/playground/public/robots.txt
new file mode 100644
index 0000000..26b4d70
--- /dev/null
+++ b/packages/playground/public/robots.txt
@@ -0,0 +1,2 @@
+User-Agent: *
+Allow: /
\ No newline at end of file
diff --git a/packages/playground/src/App.vue b/packages/playground/src/App.vue
new file mode 100644
index 0000000..7e33d8c
--- /dev/null
+++ b/packages/playground/src/App.vue
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/packages/playground/src/api/examples.ts b/packages/playground/src/api/examples.ts
new file mode 100644
index 0000000..b06e81f
--- /dev/null
+++ b/packages/playground/src/api/examples.ts
@@ -0,0 +1,11 @@
+// @/api/examples.ts
+import apiClient from './index';
+
+export async function fetchExample(locale: string): Promise {
+ try {
+ return await apiClient.get(`assets/examples/${locale}.ffm`);
+ } catch (error) {
+ // Fallback to English example if the requested locale is not found
+ return await apiClient.get('assets/examples/en.ffm');
+ }
+}
diff --git a/packages/playground/src/api/index.ts b/packages/playground/src/api/index.ts
new file mode 100644
index 0000000..549b061
--- /dev/null
+++ b/packages/playground/src/api/index.ts
@@ -0,0 +1,9 @@
+// @/api/index.ts
+import { HttpClient } from '@fuyeor/commons';
+
+const apiClient = new HttpClient({
+ baseURL: '/',
+ timeout: 10000,
+});
+
+export default apiClient;
diff --git a/packages/playground/src/components/Home/HomeCampaign.vue b/packages/playground/src/components/Home/HomeCampaign.vue
new file mode 100644
index 0000000..dde68e3
--- /dev/null
+++ b/packages/playground/src/components/Home/HomeCampaign.vue
@@ -0,0 +1,160 @@
+
+
+
+
+
+ {{ t('home.campaign.label') }}
+
+
{{ t('home.campaign.quote') }}
+
{{ t('home.campaign.description') }}
+
+ {{ t('home.campaign.cta') }} ↗
+
+
+
+
+
+
+
+
+
diff --git a/packages/playground/src/components/Home/HomeFeatures.vue b/packages/playground/src/components/Home/HomeFeatures.vue
new file mode 100644
index 0000000..959cc4e
--- /dev/null
+++ b/packages/playground/src/components/Home/HomeFeatures.vue
@@ -0,0 +1,201 @@
+
+
+
+
+
+
+ {{ t('home.features.imageCaption') }}
+ {{ t('home.features.imageTitle') }}
+
+
+
+
{{
+ t('home.features.label')
+ }}
+
+ {{ t('home.features.titleLead') }}
{{
+ t('home.features.titleEmphasis')
+ }}
+
+
+
+
{{ feature.index }}
+
+
{{ t(feature.titleKey) }}
+
{{ t(feature.descriptionKey) }}
+
+
↗
+
+
+
+
+
+
+
+
+
diff --git a/packages/playground/src/components/Home/HomeFooter.vue b/packages/playground/src/components/Home/HomeFooter.vue
new file mode 100644
index 0000000..c137b51
--- /dev/null
+++ b/packages/playground/src/components/Home/HomeFooter.vue
@@ -0,0 +1,101 @@
+
+
+
+
+
+
+
+
diff --git a/packages/playground/src/components/Home/HomeHeader.vue b/packages/playground/src/components/Home/HomeHeader.vue
new file mode 100644
index 0000000..8ab61d3
--- /dev/null
+++ b/packages/playground/src/components/Home/HomeHeader.vue
@@ -0,0 +1,223 @@
+
+
+
+
+
+
+
+
diff --git a/packages/playground/src/components/Home/HomeHero.vue b/packages/playground/src/components/Home/HomeHero.vue
new file mode 100644
index 0000000..4ce0811
--- /dev/null
+++ b/packages/playground/src/components/Home/HomeHero.vue
@@ -0,0 +1,323 @@
+
+
+
+
+
+ ✦ {{ t('home.hero.eyebrow') }}
+
+
{{ t('home.hero.kicker') }}
+
+ {{ t('home.hero.titleLead') }}
{{
+ t('home.hero.titleEmphasis')
+ }}
+
+
{{ t('home.hero.description') }}
+
+
+ {{ t('home.hero.captionSentence') }}
+
+ {{ t('home.hero.captionMargin') }}
+
+
+
+
+
+
+ # {{ t('home.hero.sampleTitle') }}
+
+
+ *
+ {{ t('home.hero.attribution') }}
+
+
+
+
+
+
+
+
diff --git a/packages/playground/src/components/Home/HomeImagePlaceholder.vue b/packages/playground/src/components/Home/HomeImagePlaceholder.vue
new file mode 100644
index 0000000..ec7a366
--- /dev/null
+++ b/packages/playground/src/components/Home/HomeImagePlaceholder.vue
@@ -0,0 +1,89 @@
+
+
+
+ {{ label }}
+
+
+
+
+
+
diff --git a/packages/playground/src/components/Home/HomeIntro.vue b/packages/playground/src/components/Home/HomeIntro.vue
new file mode 100644
index 0000000..831c2d9
--- /dev/null
+++ b/packages/playground/src/components/Home/HomeIntro.vue
@@ -0,0 +1,71 @@
+
+
+
+
+ {{ t('home.intro.lead') }}{{ t('home.intro.emphasis') }}
+
+ {{ t('home.intro.caption') }}
+
+
+
+
+
+
diff --git a/packages/playground/src/components/Home/HomeSectionLabel.vue b/packages/playground/src/components/Home/HomeSectionLabel.vue
new file mode 100644
index 0000000..c2b42f5
--- /dev/null
+++ b/packages/playground/src/components/Home/HomeSectionLabel.vue
@@ -0,0 +1,58 @@
+
+
+
+ {{ number }}
+
+ {{ syntax }}
+
+
+
+
+
+
diff --git a/packages/playground/src/components/Home/HomeStatement.vue b/packages/playground/src/components/Home/HomeStatement.vue
new file mode 100644
index 0000000..bd0c2db
--- /dev/null
+++ b/packages/playground/src/components/Home/HomeStatement.vue
@@ -0,0 +1,119 @@
+
+
+
+
+ {{ t('home.statement.label') }}
+
+
+
+
{{ t('home.statement.overline') }}
+
+ {{ t('home.statement.titleLead') }}
+ {{ t('home.statement.titleMiddle')
+ }}{{ t('home.statement.titleEmphasis') }}
+
+
+
+
{{ t('home.statement.description') }}
+
+ {{ t('home.statement.cta') }} ↗
+
+
+
+
+
+
+
+
+
diff --git a/packages/playground/src/components/Playground/DocumentHistory.vue b/packages/playground/src/components/Playground/DocumentHistory.vue
new file mode 100644
index 0000000..84e7b40
--- /dev/null
+++ b/packages/playground/src/components/Playground/DocumentHistory.vue
@@ -0,0 +1,371 @@
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ t('documents.clear') }}
+
+
+
{{ t('documents.clearConfirm') }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{
+ document.title || t('documents.untitled')
+ }}
+
+ {{ formatTime(document.updated_at) }}
+
+ {{
+ t('documents.stats.words', {
+ count: document.word_count,
+ })
+ }}
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/packages/playground/src/components/Playground/DocumentStatsBar.vue b/packages/playground/src/components/Playground/DocumentStatsBar.vue
new file mode 100644
index 0000000..2299fdf
--- /dev/null
+++ b/packages/playground/src/components/Playground/DocumentStatsBar.vue
@@ -0,0 +1,60 @@
+
+
+
+
+
+
+
+
diff --git a/packages/playground/src/components/Playground/MarkdownToolbar.vue b/packages/playground/src/components/Playground/MarkdownToolbar.vue
new file mode 100644
index 0000000..13d21d0
--- /dev/null
+++ b/packages/playground/src/components/Playground/MarkdownToolbar.vue
@@ -0,0 +1,149 @@
+
+
+
+
+
+
+
+
diff --git a/packages/playground/src/components/Playground/PlaygroundEditor.vue b/packages/playground/src/components/Playground/PlaygroundEditor.vue
new file mode 100644
index 0000000..b157e39
--- /dev/null
+++ b/packages/playground/src/components/Playground/PlaygroundEditor.vue
@@ -0,0 +1,263 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/packages/playground/src/components/Playground/PlaygroundPreview.vue b/packages/playground/src/components/Playground/PlaygroundPreview.vue
new file mode 100644
index 0000000..b35b9d3
--- /dev/null
+++ b/packages/playground/src/components/Playground/PlaygroundPreview.vue
@@ -0,0 +1,104 @@
+
+
+
+
+ {{ t('playground.preview.render') }}
+ JSON/AST
+ HTML
+
+
+
+
+
+
{{ astJson }}
+
{{ renderedHtml }}
+
+
+
+
+
+
+
diff --git a/packages/playground/src/components/Playground/PlaygroundShare.vue b/packages/playground/src/components/Playground/PlaygroundShare.vue
new file mode 100644
index 0000000..0484696
--- /dev/null
+++ b/packages/playground/src/components/Playground/PlaygroundShare.vue
@@ -0,0 +1,130 @@
+
+
+
+
+
+
+ {{ t('playground.share') }}
+
+
+
{{ t('playground.share.desc') }}
+
+
+
+
+
+
+
+
+
+
+
diff --git a/packages/playground/src/composables/useCompression.ts b/packages/playground/src/composables/useCompression.ts
new file mode 100644
index 0000000..b960404
--- /dev/null
+++ b/packages/playground/src/composables/useCompression.ts
@@ -0,0 +1,59 @@
+// @/composables/useCompression.ts
+
+/**
+ * Encode a string to a base64-encoded compressed format using the CompressionStream API.
+ */
+export async function encodeSnippet(content: string): Promise {
+ if (!content) return '';
+ try {
+ const stream = new Blob([content])
+ .stream()
+ .pipeThrough(new CompressionStream('deflate-raw'));
+ const response = new Response(stream);
+ const blob = await response.blob();
+ const buffer = await blob.arrayBuffer();
+ const bytes = new Uint8Array(buffer);
+
+ // Convert to base64url format
+ let binary = '';
+ for (let i = 0; i < bytes.length; i++) {
+ binary += String.fromCharCode(bytes[i]!);
+ }
+ return btoa(binary)
+ .replace(/\+/g, '-')
+ .replace(/\//g, '_')
+ .replace(/=+$/, '');
+ } catch (error) {
+ console.error('Failed to compress snippet:', error);
+ return '';
+ }
+}
+
+/**
+ * Decode a base64-encoded compressed format back to a string using the DecompressionStream API.
+ */
+export async function decodeSnippet(snippet: string): Promise {
+ if (!snippet) return '';
+ try {
+ // Convert base64url back to base64
+ let base64 = snippet.replace(/-/g, '+').replace(/_/g, '/');
+ while (base64.length % 4) {
+ base64 += '=';
+ }
+
+ const binary = atob(base64);
+ const bytes = new Uint8Array(binary.length);
+ for (let i = 0; i < binary.length; i++) {
+ bytes[i] = binary.charCodeAt(i);
+ }
+
+ const stream = new Blob([bytes])
+ .stream()
+ .pipeThrough(new DecompressionStream('deflate-raw'));
+ const response = new Response(stream);
+ return await response.text();
+ } catch (error) {
+ console.error('Failed to decompress snippet:', error);
+ return '';
+ }
+}
diff --git a/packages/playground/src/composables/useDocumentStats.ts b/packages/playground/src/composables/useDocumentStats.ts
new file mode 100644
index 0000000..181c4f9
--- /dev/null
+++ b/packages/playground/src/composables/useDocumentStats.ts
@@ -0,0 +1,43 @@
+// @/composables/useDocumentStats.ts
+import { computed, type Ref } from 'vue';
+
+export interface DocumentStats {
+ words: number;
+ characters: number;
+ sentences: number;
+ paragraphs: number;
+}
+
+export function countDocumentStats(text: string): DocumentStats {
+ if (!text) {
+ return { words: 0, characters: 0, sentences: 0, paragraphs: 0 };
+ }
+
+ const wordSegmenter = new window.Intl.Segmenter(undefined, {
+ granularity: 'word',
+ });
+ const words = [...wordSegmenter.segment(text)].filter(
+ ({ isWordLike }) => isWordLike,
+ ).length;
+
+ const characterSegmenter = new window.Intl.Segmenter(undefined, {
+ granularity: 'grapheme',
+ });
+ const characters = [...characterSegmenter.segment(text)].length;
+
+ const sentenceSegmenter = new window.Intl.Segmenter(undefined, {
+ granularity: 'sentence',
+ });
+ const sentences = [...sentenceSegmenter.segment(text)].length;
+
+ const paragraphs = text
+ .split(/\n\s*\n/)
+ .filter((paragraph) => paragraph.trim().length > 0).length;
+
+ return { words, characters, sentences, paragraphs };
+}
+
+export function useDocumentStats(source: Ref) {
+ const stats = computed(() => countDocumentStats(source.value));
+ return { stats };
+}
diff --git a/packages/playground/src/composables/useIndexedDb.ts b/packages/playground/src/composables/useIndexedDb.ts
new file mode 100644
index 0000000..f22ec8f
--- /dev/null
+++ b/packages/playground/src/composables/useIndexedDb.ts
@@ -0,0 +1,226 @@
+// @fuyeor/markdown-parser-playground/src/composables/useIndexedDb.ts
+import { onMounted, ref } from 'vue';
+import { countDocumentStats } from '@/composables/useDocumentStats';
+
+export interface HistoryDocument {
+ id: string;
+ title: string;
+ created_at: number;
+ updated_at: number;
+ content: string;
+ word_count?: number;
+}
+
+type SyncMessage =
+ | { type: 'SAVE'; document: HistoryDocument }
+ | { type: 'DELETE'; id: string }
+ | { type: 'CLEAR' };
+
+const DB_NAME = 'fuyeor-markdown-playground';
+const STORE_NAME = 'documents';
+const UPDATED_AT_INDEX = 'updated_at';
+const DB_VERSION = 1;
+
+const documents = ref([]);
+const isReady = ref(false);
+const error = ref(null);
+let database: IDBDatabase | null = null;
+let initialization: Promise | null = null;
+let broadcastChannel: BroadcastChannel | null = null;
+
+const initBroadcastChannel = () => {
+ if (broadcastChannel) return;
+ broadcastChannel = new window.BroadcastChannel(
+ 'fuyeor_markdown_playground_sync',
+ );
+
+ broadcastChannel.onmessage = (event: MessageEvent) => {
+ const message = event.data;
+ if (message.type === 'SAVE') {
+ const index = documents.value.findIndex(
+ (item) => item.id === message.document.id,
+ );
+ if (index >= 0) documents.value[index] = message.document;
+ else documents.value.push(message.document);
+ documents.value.sort((a, b) => b.updated_at - a.updated_at);
+ } else if (message.type === 'DELETE') {
+ documents.value = documents.value.filter(
+ (item) => item.id !== message.id,
+ );
+ } else if (message.type === 'CLEAR') {
+ documents.value = [];
+ }
+ };
+};
+
+const loadAllDocuments = (): Promise => {
+ return new Promise((resolve, reject) => {
+ if (!database) {
+ reject(new Error('IndexedDB is not initialized'));
+ return;
+ }
+
+ const transaction = database.transaction(STORE_NAME, 'readwrite');
+ const request = transaction
+ .objectStore(STORE_NAME)
+ .index(UPDATED_AT_INDEX)
+ .openCursor(null, 'prev');
+ const result: HistoryDocument[] = [];
+
+ request.onsuccess = () => {
+ const cursor = request.result;
+ if (!cursor) {
+ transaction.oncomplete = () => {
+ documents.value = result;
+ resolve();
+ };
+ return;
+ }
+
+ const document = cursor.value as HistoryDocument;
+ const normalizedDocument =
+ document.word_count === undefined
+ ? {
+ ...document,
+ word_count: countDocumentStats(document.content).words,
+ }
+ : document;
+ result.push(normalizedDocument);
+ if (normalizedDocument !== document) cursor.update(normalizedDocument);
+ cursor.continue();
+ };
+
+ request.onerror = () =>
+ reject(request.error ?? new Error('Failed to read documents'));
+ transaction.onerror = () =>
+ reject(transaction.error ?? new Error('Failed to read documents'));
+ transaction.onabort = () =>
+ reject(transaction.error ?? new Error('Failed to read documents'));
+ });
+};
+
+const initialize = (): Promise => {
+ if (initialization) return initialization;
+
+ initialization = new Promise((resolve, reject) => {
+ if (!window.indexedDB) {
+ reject(new Error('IndexedDB is not supported'));
+ return;
+ }
+
+ const request = window.indexedDB.open(DB_NAME, DB_VERSION);
+
+ request.onerror = () =>
+ reject(request.error ?? new Error('Failed to open IndexedDB'));
+ request.onupgradeneeded = () => {
+ const upgradedDatabase = request.result;
+ const upgradeTransaction = request.transaction;
+ const store = upgradedDatabase.objectStoreNames.contains(STORE_NAME)
+ ? upgradeTransaction?.objectStore(STORE_NAME)
+ : upgradedDatabase.createObjectStore(STORE_NAME, { keyPath: 'id' });
+
+ if (store && !store.indexNames.contains(UPDATED_AT_INDEX)) {
+ store.createIndex(UPDATED_AT_INDEX, 'updated_at', { unique: false });
+ }
+ };
+ request.onsuccess = () => {
+ database = request.result;
+ database.onversionchange = () => database?.close();
+ loadAllDocuments().then(resolve).catch(reject);
+ };
+ }).then(
+ () => {
+ isReady.value = true;
+ },
+ (reason: unknown) => {
+ error.value =
+ reason instanceof Error ? reason : new Error(String(reason));
+ isReady.value = true;
+ throw error.value;
+ },
+ );
+
+ return initialization;
+};
+
+const requireDatabase = async (): Promise => {
+ await initialize();
+ if (!database) throw new Error('IndexedDB is not available');
+ return database;
+};
+
+export function useIndexedDb() {
+ const saveDocument = async (document: HistoryDocument): Promise => {
+ const activeDatabase = await requireDatabase();
+
+ await new Promise((resolve, reject) => {
+ const transaction = activeDatabase.transaction(STORE_NAME, 'readwrite');
+ const request = transaction.objectStore(STORE_NAME).put({ ...document });
+
+ request.onerror = () =>
+ reject(request.error ?? new Error('Failed to save document'));
+ transaction.onabort = () =>
+ reject(transaction.error ?? new Error('Failed to save document'));
+ transaction.oncomplete = resolve;
+ });
+
+ const index = documents.value.findIndex((item) => item.id === document.id);
+ if (index >= 0) documents.value[index] = { ...document };
+ else documents.value.push({ ...document });
+ documents.value.sort((a, b) => b.updated_at - a.updated_at);
+
+ broadcastChannel?.postMessage({ type: 'SAVE', document });
+ };
+
+ const deleteDocument = async (id: string): Promise => {
+ const activeDatabase = await requireDatabase();
+
+ await new Promise((resolve, reject) => {
+ const transaction = activeDatabase.transaction(STORE_NAME, 'readwrite');
+ const request = transaction.objectStore(STORE_NAME).delete(id);
+
+ request.onerror = () =>
+ reject(request.error ?? new Error('Failed to delete document'));
+ transaction.onabort = () =>
+ reject(transaction.error ?? new Error('Failed to delete document'));
+ transaction.oncomplete = resolve;
+ });
+
+ documents.value = documents.value.filter((item) => item.id !== id);
+ broadcastChannel?.postMessage({ type: 'DELETE', id });
+ };
+
+ // Clear every local document in one transaction and update shared state after commit.
+ const clearDocuments = async (): Promise => {
+ const activeDatabase = await requireDatabase();
+
+ await new Promise((resolve, reject) => {
+ const transaction = activeDatabase.transaction(STORE_NAME, 'readwrite');
+ const request = transaction.objectStore(STORE_NAME).clear();
+
+ request.onerror = () =>
+ reject(request.error ?? new Error('Failed to clear documents'));
+ transaction.onabort = () =>
+ reject(transaction.error ?? new Error('Failed to clear documents'));
+ transaction.oncomplete = resolve;
+ });
+
+ documents.value = [];
+ broadcastChannel?.postMessage({ type: 'CLEAR' });
+ };
+
+ onMounted(() => {
+ initBroadcastChannel();
+ void initialize().catch(() => undefined);
+ });
+
+ return {
+ documents,
+ isReady,
+ error,
+ saveDocument,
+ deleteDocument,
+ clearDocuments,
+ loadAllDocuments,
+ };
+}
diff --git a/packages/playground/src/composables/useMarkdownEditor.ts b/packages/playground/src/composables/useMarkdownEditor.ts
new file mode 100644
index 0000000..67a9422
--- /dev/null
+++ b/packages/playground/src/composables/useMarkdownEditor.ts
@@ -0,0 +1,200 @@
+// @/composables/useMarkdownEditor.ts
+import { computed, nextTick, ref, type Ref } from 'vue';
+import { format as formatMarkdown } from '@fuyeor/markdown-formatter';
+
+export type MarkdownTool =
+ | 'bold'
+ | 'italic'
+ | 'heading'
+ | 'strike'
+ | 'unordered-list'
+ | 'ordered-list'
+ | 'checklist'
+ | 'quote'
+ | 'code'
+ | 'link'
+ | 'table';
+
+export function useMarkdownEditor(
+ source: Ref,
+ editor: Ref,
+) {
+ const history = ref([source.value]);
+ const historyIndex = ref(0);
+ const canUndo = computed(() => historyIndex.value > 0);
+ const canRedo = computed(() => historyIndex.value < history.value.length - 1);
+
+ // Keep the editor history bounded so repeated formatting does not grow memory indefinitely.
+ const recordHistory = (value: string) => {
+ const nextHistory = history.value.slice(0, historyIndex.value + 1);
+ if (nextHistory.at(-1) === value) return;
+ nextHistory.push(value);
+ if (nextHistory.length > 100) nextHistory.shift();
+ history.value = nextHistory;
+ historyIndex.value = nextHistory.length - 1;
+ };
+
+ const replaceSource = (value: string) => {
+ source.value = value;
+ history.value = [value];
+ historyIndex.value = 0;
+ };
+
+ const recordInput = () => recordHistory(source.value);
+
+ const setSource = (
+ value: string,
+ selectionStart: number,
+ selectionEnd: number,
+ scrollTop?: number,
+ ) => {
+ const element = editor.value;
+ if (element) {
+ element.focus();
+ // 使用 document.execCommand 来触发原生的输入事件,这样可以被 Ctrl+Z 撤销
+ // 由于 execCommand 已经被弃用但依然是所有浏览器支持插入文本且进入撤销栈的唯一标准方式
+ // 我们全选后替换来更新整个值,或只替换选区
+ element.select();
+ const success = document.execCommand('insertText', false, value);
+
+ // 如果 execCommand 失败(某些环境可能禁用),回退到直接赋值
+ if (!success) {
+ source.value = value;
+ element.value = value;
+ element.dispatchEvent(new Event('input', { bubbles: true }));
+ }
+ } else {
+ source.value = value;
+ }
+
+ recordHistory(value);
+ void nextTick(() => {
+ if (!element) return;
+ element.focus();
+ element.setSelectionRange(selectionStart, selectionEnd);
+ if (scrollTop !== undefined) {
+ element.scrollTop = scrollTop;
+ }
+ });
+ };
+
+ const applyTool = (tool: MarkdownTool) => {
+ const element = editor.value;
+ if (!element) return;
+ const start = element.selectionStart;
+ const end = element.selectionEnd;
+ const text = source.value.slice(start, end);
+ const fallback = text || 'text';
+
+ // 检查成对包裹语法是否已经存在
+ const toggleWrap = (prefix: string, suffix: string = prefix) => {
+ const isWrapped =
+ text.startsWith(prefix) &&
+ text.endsWith(suffix) &&
+ text.length >= prefix.length + suffix.length;
+ if (isWrapped) {
+ // 取消包裹
+ return text.slice(prefix.length, text.length - suffix.length);
+ }
+ // 添加包裹
+ return `${prefix}${fallback}${suffix}`;
+ };
+
+ let replacement = fallback;
+
+ if (tool === 'bold') replacement = toggleWrap('**');
+ else if (tool === 'italic') replacement = toggleWrap('*');
+ else if (tool === 'strike') replacement = toggleWrap('~~');
+ else if (tool === 'code') replacement = toggleWrap('`');
+ else if (tool === 'link')
+ replacement = `[${fallback}](https://example.com)`;
+ else if (tool === 'heading') replacement = `# ${fallback}`;
+ else if (tool === 'quote')
+ replacement = fallback
+ .split('\n')
+ .map((line) => `> ${line}`)
+ .join('\n');
+ else if (tool === 'unordered-list')
+ replacement = fallback
+ .split('\n')
+ .map((line) => `- ${line}`)
+ .join('\n');
+ else if (tool === 'ordered-list')
+ replacement = fallback
+ .split('\n')
+ .map((line, index) => `${index + 1}. ${line}`)
+ .join('\n');
+ else if (tool === 'checklist')
+ replacement = fallback
+ .split('\n')
+ .map((line) => `- [ ] ${line}`)
+ .join('\n');
+ else if (tool === 'table')
+ replacement = '| Column 1 | Column 2 |\n| --- | --- |\n| Value | Value |';
+
+ const selectionStart = text ? start : start + replacement.length;
+ const selectionEnd = text ? start + replacement.length : selectionStart;
+
+ // 如果工具栏触发时不需要替换全部文档,我们可以只选中文本并执行 insertText 替换选区
+ // 这样能保留更细粒度的 Ctrl+Z 历史
+ element.focus();
+ element.setSelectionRange(start, end);
+ const success = document.execCommand('insertText', false, replacement);
+
+ if (!success) {
+ // 降级策略
+ setSource(
+ `${source.value.slice(0, start)}${replacement}${source.value.slice(end)}`,
+ selectionStart,
+ selectionEnd,
+ element.scrollTop,
+ );
+ } else {
+ recordInput();
+ void nextTick(() => {
+ element.focus();
+ element.setSelectionRange(selectionStart, selectionEnd);
+ });
+ }
+ };
+
+ const formatDocument = () => {
+ const formatted = formatMarkdown(source.value);
+ if (formatted === source.value) return;
+
+ const element = editor.value;
+ if (element) {
+ setSource(
+ formatted,
+ element.selectionStart,
+ element.selectionEnd,
+ element.scrollTop,
+ );
+ } else {
+ setSource(formatted, 0, 0);
+ }
+ };
+
+ const undo = () => {
+ if (!canUndo.value) return;
+ historyIndex.value -= 1;
+ source.value = history.value[historyIndex.value];
+ };
+
+ const redo = () => {
+ if (!canRedo.value) return;
+ historyIndex.value += 1;
+ source.value = history.value[historyIndex.value];
+ };
+
+ return {
+ canUndo,
+ canRedo,
+ replaceSource,
+ recordInput,
+ applyTool,
+ formatDocument,
+ undo,
+ redo,
+ };
+}
diff --git a/packages/playground/src/composables/useMarkdownHighlighter.ts b/packages/playground/src/composables/useMarkdownHighlighter.ts
new file mode 100644
index 0000000..41c90b1
--- /dev/null
+++ b/packages/playground/src/composables/useMarkdownHighlighter.ts
@@ -0,0 +1,209 @@
+// @fuyeor/markdown-parser-playground/src/composables/useMarkdownHighlighter.ts
+import { computed, nextTick, onMounted, watch, type Ref } from 'vue';
+
+type CustomHighlightRegistry = {
+ delete: (name: string) => void;
+ set: (name: string, highlight: object) => void;
+};
+
+type CustomHighlightConstructor = new (...ranges: Range[]) => object;
+
+const customHighlightStyles = `
+::highlight(md-punctuation) { color: #a0aec0; }
+::highlight(md-heading) { color: #553c9a; }
+::highlight(md-quote) { color: #718096; }
+::highlight(md-list) { color: #2d3748; }
+::highlight(md-bold) { color: #1a202c; }
+::highlight(md-italic) { color: #1a202c; }
+::highlight(md-code) { color: #d53f8c; background-color: #faf5ff; }
+::highlight(md-strike) { text-decoration: line-through; color: #a0aec0; }
+::highlight(md-code-fence) { color: #805ad5; }
+`;
+
+const getCustomHighlightRegistry = (): CustomHighlightRegistry | null => {
+ const css = Reflect.get(globalThis, 'CSS');
+ const registry = css && Reflect.get(css, 'highlights');
+ return registry && typeof registry.delete === 'function' && typeof registry.set === 'function'
+ ? registry as CustomHighlightRegistry
+ : null;
+};
+
+const getCustomHighlightConstructor = (): CustomHighlightConstructor | null => {
+ const constructor = Reflect.get(globalThis, 'Highlight');
+ return typeof constructor === 'function' ? constructor as CustomHighlightConstructor : null;
+};
+
+const ensureCustomHighlightStyles = () => {
+ const styleId = 'fuyeor-playground-custom-highlight-styles';
+ if (document.getElementById(styleId)) return;
+
+ const style = document.createElement('style');
+ style.id = styleId;
+ style.textContent = customHighlightStyles;
+ document.head.append(style);
+};
+
+export function useMarkdownHighlighter(
+ source: Ref,
+ highlightTarget: Ref,
+ supportsCustomHighlight: boolean,
+) {
+ const lines = computed(() => source.value.split('\n'));
+
+ const lineNumbers = computed(() => {
+ return lines.value.map((_, i) => i + 1);
+ });
+
+ // Keep a small regex fallback for browsers without the Custom Highlight API.
+ const highlightedLines = computed(() => {
+ if (supportsCustomHighlight) return [];
+
+ return lines.value.map((line) => {
+ let html = line
+ .replace(/&/g, '&')
+ .replace(//g, '>');
+
+ if (/^#{1,6}\s/.test(html)) {
+ html = html.replace(/^(#{1,6})(\s.*)$/, '$1$2');
+ } else if (/^>\s/.test(html)) {
+ html = html.replace(/^(>\s)(.*)$/, '$1$2');
+ } else if (/^(\s*)([-*+]|\d+\.)(\s)/.test(html)) {
+ html = html.replace(/^(\s*)([-*+]|\d+\.)(\s)(.*)$/, '$1$2$3$4');
+ } else if (/^```/.test(html)) {
+ html = `${html}`;
+ } else {
+ html = html
+ .replace(/(\*\*)(.*?)\1/g, '**$2**')
+ .replace(/(\*)(.*?)\1/g, '*$2*')
+ .replace(/(`)(.*?)\1/g, '`$2`')
+ .replace(/(~~)(.*?)\1/g, '~~$2~~');
+ }
+
+ return html || ' ';
+ });
+ });
+
+ const tokenNames = [
+ 'md-punctuation',
+ 'md-heading',
+ 'md-quote',
+ 'md-list',
+ 'md-bold',
+ 'md-italic',
+ 'md-code',
+ 'md-strike',
+ 'md-code-fence',
+ ];
+
+ const removeCustomHighlights = (registry: CustomHighlightRegistry) => {
+ for (const name of tokenNames) registry.delete(name);
+ };
+
+ // Rebuild ranges against the mirrored text node without replacing editor DOM.
+ const updateCustomHighlights = () => {
+ if (!supportsCustomHighlight || !highlightTarget.value) return;
+
+ const registry = getCustomHighlightRegistry();
+ const Highlight = getCustomHighlightConstructor();
+ const textNode = highlightTarget.value.firstChild;
+
+ if (!registry || !Highlight || !(textNode instanceof Text)) {
+ if (registry) removeCustomHighlights(registry);
+ return;
+ }
+
+ const text = textNode.nodeValue ?? '';
+ const tokens: Record = {
+ 'md-punctuation': [],
+ 'md-heading': [],
+ 'md-quote': [],
+ 'md-list': [],
+ 'md-bold': [],
+ 'md-italic': [],
+ 'md-code': [],
+ 'md-strike': [],
+ 'md-code-fence': [],
+ };
+
+ const addRange = (category: string, start: number, end: number) => {
+ if (start < 0 || end > text.length || start >= end) return;
+
+ const range = new Range();
+ range.setStart(textNode, start);
+ range.setEnd(textNode, end);
+ tokens[category].push(range);
+ };
+
+ let currentPosition = 0;
+ for (const line of text.split('\n')) {
+ const lineStart = currentPosition;
+ const heading = /^(#{1,6})(\s.*)$/.exec(line);
+ const quote = /^(>\s)(.*)$/.exec(line);
+ const list = /^(\s*)([-*+]|\d+\.)(\s)(.*)$/.exec(line);
+
+ if (heading) {
+ addRange('md-punctuation', lineStart, lineStart + heading[1].length);
+ addRange('md-heading', lineStart + heading[1].length, lineStart + line.length);
+ } else if (quote) {
+ addRange('md-punctuation', lineStart, lineStart + quote[1].length);
+ addRange('md-quote', lineStart + quote[1].length, lineStart + line.length);
+ } else if (list) {
+ const markerStart = lineStart + list[1].length;
+ const markerEnd = markerStart + list[2].length;
+ addRange('md-punctuation', markerStart, markerEnd);
+ addRange('md-list', markerEnd + list[3].length, lineStart + line.length);
+ } else if (line.startsWith('```')) {
+ addRange('md-code-fence', lineStart, lineStart + line.length);
+ } else {
+ const inlinePattern = /(\*\*.*?\*\*|\*.*?\*|`.*?`|~~.*?~~)/g;
+ for (const match of line.matchAll(inlinePattern)) {
+ const matchText = match[0];
+ const matchStart = lineStart + (match.index ?? 0);
+ const matchEnd = matchStart + matchText.length;
+
+ if (matchText.startsWith('**')) {
+ addRange('md-punctuation', matchStart, matchStart + 2);
+ addRange('md-bold', matchStart + 2, matchEnd - 2);
+ addRange('md-punctuation', matchEnd - 2, matchEnd);
+ } else if (matchText.startsWith('*')) {
+ addRange('md-punctuation', matchStart, matchStart + 1);
+ addRange('md-italic', matchStart + 1, matchEnd - 1);
+ addRange('md-punctuation', matchEnd - 1, matchEnd);
+ } else if (matchText.startsWith('`')) {
+ addRange('md-punctuation', matchStart, matchStart + 1);
+ addRange('md-code', matchStart + 1, matchEnd - 1);
+ addRange('md-punctuation', matchEnd - 1, matchEnd);
+ } else if (matchText.startsWith('~~')) {
+ addRange('md-punctuation', matchStart, matchStart + 2);
+ addRange('md-strike', matchStart + 2, matchEnd - 2);
+ addRange('md-punctuation', matchEnd - 2, matchEnd);
+ }
+ }
+ }
+
+ currentPosition += line.length + 1;
+ }
+
+ removeCustomHighlights(registry);
+ for (const [name, ranges] of Object.entries(tokens)) {
+ if (ranges.length > 0) registry.set(name, new Highlight(...ranges));
+ }
+ };
+
+ if (supportsCustomHighlight) {
+ onMounted(ensureCustomHighlightStyles);
+ watch([source, highlightTarget], () => {
+ void nextTick(updateCustomHighlights);
+ }, { immediate: true });
+ }
+
+ return {
+ lines,
+ lineNumbers,
+ highlightedLines,
+ updateCustomHighlights,
+ };
+}
+
+export { customHighlightStyles };
diff --git a/packages/playground/src/composables/usePlaygroundLink.ts b/packages/playground/src/composables/usePlaygroundLink.ts
new file mode 100644
index 0000000..faa9215
--- /dev/null
+++ b/packages/playground/src/composables/usePlaygroundLink.ts
@@ -0,0 +1,15 @@
+// @/composables/usePlaygroundLink.ts
+import { computed } from 'vue';
+import { useRoute, type RouteLocationRaw } from '@fuyeor/vue-router';
+
+// Build a locale-preserving link to the blank playground document.
+export function usePlaygroundLink() {
+ const route = useRoute();
+
+ return computed(() => ({
+ name: 'Playground',
+ params: { ...route.params, id: undefined },
+ }));
+}
+
+export type PlaygroundLink = ReturnType;
diff --git a/packages/playground/src/composables/usePlaygroundSource.ts b/packages/playground/src/composables/usePlaygroundSource.ts
new file mode 100644
index 0000000..b7ce5d3
--- /dev/null
+++ b/packages/playground/src/composables/usePlaygroundSource.ts
@@ -0,0 +1,10 @@
+// @/composables/usePlaygroundSource.ts
+import { ref } from 'vue';
+
+const source = ref('');
+
+export function usePlaygroundSource() {
+ return {
+ source,
+ };
+}
diff --git a/packages/playground/src/config/locales.ts b/packages/playground/src/config/locales.ts
new file mode 100644
index 0000000..8a6fed1
--- /dev/null
+++ b/packages/playground/src/config/locales.ts
@@ -0,0 +1,16 @@
+// @/config/locales.ts
+
+export type SupportedLocale = (typeof SUPPORTED_LOCALES)[number];
+
+/**
+ * front-end UI supported locales
+ */
+// prettier-ignore
+export const SUPPORTED_LOCALES = [
+ 'en', 'fr', 'es', 'pt', 'de',
+ 'ar', 'ru', 'ja', 'ko',
+ 'zh-hans', 'zh-hant'
+] as const;
+
+// generate regex string for router
+export const LOCALE_REGEX = SUPPORTED_LOCALES.join('|');
diff --git a/packages/playground/src/config/sidebar/menu.config.ts b/packages/playground/src/config/sidebar/menu.config.ts
new file mode 100644
index 0000000..c7f7f5a
--- /dev/null
+++ b/packages/playground/src/config/sidebar/menu.config.ts
@@ -0,0 +1,11 @@
+// @/config/sidebar/menu.config.ts
+import { getIconUrl } from '@fuyeor/commons';
+import type { SidebarItemConfig } from '@fuyeor/interactify';
+
+export const sidebarItemsRaw: SidebarItemConfig[] = [
+ {
+ target: '/',
+ icon: getIconUrl('home'),
+ textKey: 'home',
+ },
+];
diff --git a/packages/playground/src/layout/Left.vue b/packages/playground/src/layout/Left.vue
new file mode 100644
index 0000000..24d9fc7
--- /dev/null
+++ b/packages/playground/src/layout/Left.vue
@@ -0,0 +1,81 @@
+
+
+
+
+
+
+
+ {{ t('playground') }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/packages/playground/src/locale/locale.json b/packages/playground/src/locale/locale.json
new file mode 100644
index 0000000..ebc4273
--- /dev/null
+++ b/packages/playground/src/locale/locale.json
@@ -0,0 +1,974 @@
+{
+ "documents": {
+ "ar": "المستندات",
+ "de": "Dokumente",
+ "en": "Documents",
+ "es": "Documentos",
+ "fr": "Documents",
+ "ja": "ドキュメント",
+ "ko": "문서",
+ "pt": "Documentos",
+ "ru": "Документы",
+ "zh-hans": "文档"
+ },
+ "documents.clear": {
+ "ar": "مسح المستندات",
+ "de": "Dokumente löschen",
+ "en": "Clear documents",
+ "es": "Borrar documentos",
+ "fr": "Effacer les documents",
+ "ja": "ドキュメントを消去",
+ "ko": "문서 지우기",
+ "pt": "Limpar documentos",
+ "ru": "Очистить документы",
+ "zh-hans": "清空文档"
+ },
+ "documents.clearConfirm": {
+ "ar": "سيؤدي هذا إلى إزالة جميع المستندات المحلية نهائيًا. هل تريد المتابعة؟",
+ "de": "Dadurch werden alle lokalen Dokumente dauerhaft entfernt. Fortfahren?",
+ "en": "This will permanently remove all local documents. Continue?",
+ "es": "Esto eliminará permanentemente todos los documentos locales. ¿Continuar?",
+ "fr": "Tous les documents locaux seront supprimés définitivement. Continuer ?",
+ "ja": "すべてのローカルドキュメントが完全に削除されます。続行しますか?",
+ "ko": "모든 로컬 문서가 영구적으로 삭제됩니다. 계속하시겠습니까?",
+ "pt": "Isso removerá permanentemente todos os documentos locais. Continuar?",
+ "ru": "Все локальные документы будут удалены навсегда. Продолжить?",
+ "zh-hans": "这将永久删除所有本地文档。是否继续?"
+ },
+ "documents.createdAt": {
+ "ar": "تاريخ الإنشاء",
+ "de": "Erstellt",
+ "en": "Created",
+ "es": "Creado",
+ "fr": "Créé",
+ "ja": "作成",
+ "ko": "생성",
+ "pt": "Criado",
+ "ru": "Создано",
+ "zh-hans": "创建于"
+ },
+ "documents.daysAgo": {
+ "ar": "منذ {value} يوم",
+ "de": "vor {value} Tagen",
+ "en": "{value} days ago",
+ "es": "hace {value} días",
+ "fr": "il y a {value} jours",
+ "ja": "{value}日前",
+ "ko": "{value}일 전",
+ "pt": "há {value} dias",
+ "ru": "{value} дн. назад",
+ "zh-hans": "{value} 天前"
+ },
+ "documents.hoursAgo": {
+ "ar": "منذ {value} ساعة",
+ "de": "vor {value} Std.",
+ "en": "{value} hr ago",
+ "es": "hace {value} h",
+ "fr": "il y a {value} h",
+ "ja": "{value}時間前",
+ "ko": "{value}시간 전",
+ "pt": "há {value} h",
+ "ru": "{value} ч назад",
+ "zh-hans": "{value} 小时前"
+ },
+ "documents.justNow": {
+ "ar": "الآن",
+ "de": "Gerade eben",
+ "en": "Just now",
+ "es": "Ahora mismo",
+ "fr": "À l'instant",
+ "ja": "たった今",
+ "ko": "방금 전",
+ "pt": "Agora mesmo",
+ "ru": "Только что",
+ "zh-hans": "刚刚"
+ },
+ "documents.minutesAgo": {
+ "ar": "منذ {value} دقيقة",
+ "de": "vor {value} Min.",
+ "en": "{value} min ago",
+ "es": "hace {value} min",
+ "fr": "il y a {value} min",
+ "ja": "{value}分前",
+ "ko": "{value}분 전",
+ "pt": "há {value} min",
+ "ru": "{value} мин назад",
+ "zh-hans": "{value} 分钟前"
+ },
+ "documents.new": {
+ "ar": "جديد",
+ "de": "Neu",
+ "en": "New",
+ "es": "Nuevo",
+ "fr": "Nouveau",
+ "ja": "新規",
+ "ko": "새로 만들기",
+ "pt": "Novo",
+ "ru": "Новый",
+ "zh-hans": "新建"
+ },
+ "documents.search": {
+ "ar": "البحث في المستندات...",
+ "de": "Dokumente durchsuchen...",
+ "en": "Search documents...",
+ "es": "Buscar documentos...",
+ "fr": "Rechercher des documents...",
+ "ja": "ドキュメントを検索...",
+ "ko": "문서 검색...",
+ "pt": "Pesquisar documentos...",
+ "ru": "Поиск документов...",
+ "zh-hans": "搜索文档..."
+ },
+ "documents.stats.characters": {
+ "ar": "{count} حرف",
+ "de": "{count} Zeichen",
+ "en": "{count} characters",
+ "es": "{count} caracteres",
+ "fr": "{count} caractères",
+ "ja": "{count} 文字",
+ "ko": "{count}자",
+ "pt": "{count} caracteres",
+ "ru": "{count} символов",
+ "zh-hans": "{count} 字符"
+ },
+ "documents.stats.paragraphs": {
+ "ar": "{count} فقرة",
+ "de": "{count} Absätze",
+ "en": "{count} paragraphs",
+ "es": "{count} párrafos",
+ "fr": "{count} paragraphes",
+ "ja": "{count} 段落",
+ "ko": "{count}개의 문단",
+ "pt": "{count} parágrafos",
+ "ru": "{count} абзацев",
+ "zh-hans": "{count} 段落"
+ },
+ "documents.stats.sentences": {
+ "ar": "{count} جملة",
+ "de": "{count} Sätze",
+ "en": "{count} sentences",
+ "es": "{count} oraciones",
+ "fr": "{count} phrases",
+ "ja": "{count} 文",
+ "ko": "{count}개의 문장",
+ "pt": "{count} frases",
+ "ru": "{count} предложений",
+ "zh-hans": "{count} 句"
+ },
+ "documents.stats.words": {
+ "ar": "{count} كلمة",
+ "de": "{count} Wörter",
+ "en": "{count} words",
+ "es": "{count} palabras",
+ "fr": "{count} mots",
+ "ja": "{count} 単語",
+ "ko": "{count}단어",
+ "pt": "{count} palavras",
+ "ru": "{count} слов",
+ "zh-hans": "{count} 词"
+ },
+ "documents.untitled": {
+ "ar": "مستند بلا عنوان",
+ "de": "Unbenanntes Dokument",
+ "en": "Untitled document",
+ "es": "Documento sin título",
+ "fr": "Document sans titre",
+ "ja": "無題のドキュメント",
+ "ko": "제목 없는 문서",
+ "pt": "Documento sem título",
+ "ru": "Документ без названия",
+ "zh-hans": "未命名文档"
+ },
+ "documents.updatedAt": {
+ "ar": "تم التعديل",
+ "de": "Überarbeitet",
+ "en": "Revised",
+ "es": "Revisado",
+ "fr": "Révisé",
+ "ja": "修訂",
+ "ko": "수정됨",
+ "pt": "Revisado",
+ "ru": "Изменено",
+ "zh-hans": "修订于"
+ },
+ "documents.weeksAgo": {
+ "ar": "منذ {value} أسبوع",
+ "de": "vor {value} Wochen",
+ "en": "{value} wk ago",
+ "es": "hace {value} sem",
+ "fr": "il y a {value} sem.",
+ "ja": "{value}週間前",
+ "ko": "{value}주 전",
+ "pt": "há {value} sem.",
+ "ru": "{value} нед. назад",
+ "zh-hans": "{value} 周前"
+ },
+ "documents.yesterday": {
+ "ar": "أمس",
+ "de": "Gestern",
+ "en": "Yesterday",
+ "es": "Ayer",
+ "fr": "Hier",
+ "ja": "昨日",
+ "ko": "어제",
+ "pt": "Ontem",
+ "ru": "Вчера",
+ "zh-hans": "昨天"
+ },
+ "home.brand.alt": {
+ "ar": "شعار FFM",
+ "de": "FFM-Logo",
+ "en": "FFM mark",
+ "es": "Logotipo de FFM",
+ "fr": "Logo FFM",
+ "ja": "FFM ロゴ",
+ "ko": "FFM 로고",
+ "pt": "Logotipo FFM",
+ "ru": "Логотип FFM",
+ "zh-hans": "FFM 图形标志"
+ },
+ "home.brand.ariaLabel": {
+ "ar": "الصفحة الرئيسية لـ FFM",
+ "de": "FFM-Startseite",
+ "en": "FFM home",
+ "es": "Página de inicio de FFM",
+ "fr": "Accueil FFM",
+ "ja": "FFM ホーム",
+ "ko": "FFM 홈",
+ "pt": "Página inicial do FFM",
+ "ru": "Главная страница FFM",
+ "zh-hans": "FFM 首页"
+ },
+ "home.campaign.cta": {
+ "ar": "ابدأ صفحتك الأولى",
+ "de": "Mit der ersten Seite beginnen",
+ "en": "Start your first page",
+ "es": "Empieza tu primera página",
+ "fr": "Commencer votre première page",
+ "ja": "最初のページを始める",
+ "ko": "첫 페이지 시작하기",
+ "pt": "Comece sua primeira página",
+ "ru": "Начать первую страницу",
+ "zh-hans": "开始你的第一页"
+ },
+ "home.campaign.description": {
+ "ar": "صُمم FFM ليجعل كل سطر جديد وكل نص عريض جزءًا من إيقاع الكتابة، لا مصدرًا للتشتيت.",
+ "de": "FFM wurde entwickelt, damit jeder Zeilenumbruch und jede Hervorhebung Teil des Schreibrhythmus werden, nicht eine Ablenkung.",
+ "en": "FFM was designed to make every line break and every bold mark part of the writing rhythm, not a distraction.",
+ "es": "FFM fue diseñado para que cada salto de línea y cada negrita formen parte del ritmo de escritura, no una distracción.",
+ "fr": "FFM a été conçu pour que chaque retour à la ligne et chaque emphase deviennent une partie du rythme d'écriture, pas une distraction.",
+ "ja": "FFM は、改行も太字も、邪魔ではなく書くリズムの一部になるように設計されています。",
+ "ko": "FFM은 모든 줄바꿈과 굵은 글씨가 방해가 아닌 글쓰기 리듬의 일부가 되도록 설계되었습니다.",
+ "pt": "O FFM foi criado para que cada quebra de linha e cada destaque façam parte do ritmo da escrita, não sejam uma distração.",
+ "ru": "FFM создан, чтобы каждый перенос строки и каждое выделение были частью ритма письма, а не отвлечением.",
+ "zh-hans": "FFM 的设计初衷,是让每一次换行、每一个加粗,都成为写作节奏的一部分,而不是干扰。"
+ },
+ "home.campaign.imagePlaceholder": {
+ "ar": "عنصر نائب لصورة الحملة",
+ "de": "Platzhalter für das Kampagnenbild",
+ "en": "Campaign image placeholder",
+ "es": "Marcador de imagen de campaña",
+ "fr": "Espace réservé à l'image de campagne",
+ "ja": "キャンペーン画像のプレースホルダー",
+ "ko": "캠페인 이미지 자리표시자",
+ "pt": "Espaço reservado para imagem da campanha",
+ "ru": "Заполнитель изображения кампании",
+ "zh-hans": "宣传图片占位符"
+ },
+ "home.campaign.label": {
+ "ar": "كلمة من السفير",
+ "de": "Ein Wort vom Ambassador",
+ "en": "A word from the ambassador",
+ "es": "Una palabra del embajador",
+ "fr": "Un mot de l'ambassadeur",
+ "ja": "アンバサダーからの言葉",
+ "ko": "앰배서더의 한마디",
+ "pt": "Uma palavra do embaixador",
+ "ru": "Слово амбассадора",
+ "zh-hans": "来自大使的一句话"
+ },
+ "home.campaign.quote": {
+ "ar": "عندما لا تعود بحاجة إلى التفكير في كيفية استخدام الأداة، يمكنك أخيرًا سماع صوتك الداخلي.",
+ "de": "Wenn du nicht mehr darüber nachdenken musst, wie ein Werkzeug funktioniert, kannst du endlich deine eigene Stimme hören.",
+ "en": "When you no longer need to think about how a tool works, you can finally hear the voice in your own head.",
+ "es": "Cuando ya no necesitas pensar en cómo usar una herramienta, por fin puedes escuchar la voz de tu propia mente.",
+ "fr": "Quand vous n'avez plus besoin de réfléchir à l'outil, vous pouvez enfin entendre votre propre voix.",
+ "ja": "ツールの使い方を考えなくなったとき、ようやく自分の頭の中の声を聞ける。",
+ "ko": "도구를 어떻게 써야 하는지 더 이상 생각하지 않을 때, 비로소 머릿속의 목소리를 들을 수 있습니다.",
+ "pt": "Quando você não precisa mais pensar em como uma ferramenta funciona, finalmente pode ouvir a voz na sua própria cabeça.",
+ "ru": "Когда вам больше не нужно думать о том, как пользоваться инструментом, вы наконец слышите голос в своей голове.",
+ "zh-hans": "当你不再需要思考工具该怎么用时,你才能听见自己脑海里的声音。"
+ },
+ "home.features.imageCaption": {
+ "ar": "مصمم للهامش",
+ "de": "Für den Rand entworfen",
+ "en": "Designed for the margin",
+ "es": "Diseñado para el margen",
+ "fr": "Conçu pour la marge",
+ "ja": "余白のために設計",
+ "ko": "여백을 위해 설계됨",
+ "pt": "Projetado para a margem",
+ "ru": "Создано для полей",
+ "zh-hans": "为边缘留白而设计"
+ },
+ "home.features.imagePlaceholder": {
+ "ar": "عنصر نائب لصورة الميزة",
+ "de": "Platzhalter für das Feature-Bild",
+ "en": "Feature image placeholder",
+ "es": "Marcador de imagen de función",
+ "fr": "Espace réservé à l'image de fonctionnalité",
+ "ja": "機能イメージのプレースホルダー",
+ "ko": "기능 이미지 자리표시자",
+ "pt": "Espaço reservado para imagem do recurso",
+ "ru": "Заполнитель изображения возможности",
+ "zh-hans": "功能图片占位符"
+ },
+ "home.features.imageTitle": {
+ "ar": "اكتب دون مغادرة الصفحة.",
+ "de": "Schreiben, ohne die Seite zu verlassen.",
+ "en": "Write without leaving the page.",
+ "es": "Escribe sin salir de la página.",
+ "fr": "Écrire sans quitter la page.",
+ "ja": "ページを離れずに書く。",
+ "ko": "페이지를 떠나지 않고 쓰세요.",
+ "pt": "Escreva sem sair da página.",
+ "ru": "Пишите, не покидая страницу.",
+ "zh-hans": "写作,无需离开页面。"
+ },
+ "home.features.items.format.description": {
+ "ar": "أنجز كل الأنماط ضمن السطر دون مغادرة لوحة المفاتيح أو البحث عن قائمة.",
+ "de": "Erledige alle Formatierungen direkt im Text, ohne Tastatur oder Seite zu verlassen.",
+ "en": "Handle every style inline without leaving the keyboard or hunting through menus.",
+ "es": "Aplica todos los estilos en línea sin dejar el teclado ni buscar menús.",
+ "fr": "Appliquez chaque style dans la ligne, sans quitter le clavier ni chercher un menu.",
+ "ja": "キーボードを離れたりメニューを探したりせず、行内ですべてのスタイルを整えます。",
+ "ko": "키보드를 떠나거나 메뉴를 찾지 않고 줄 안에서 모든 스타일을 완성합니다.",
+ "pt": "Aplique todos os estilos na linha, sem sair do teclado nem procurar menus.",
+ "ru": "Оформляйте всё прямо в строке, не отрываясь от клавиатуры и меню.",
+ "zh-hans": "在行内完成所有样式,无需频繁离开键盘或寻找菜单。"
+ },
+ "home.features.items.format.title": {
+ "ar": "تنسيق طبيعي كالتنفس",
+ "de": "Formatierung so natürlich wie Atmen",
+ "en": "Formatting as natural as breathing",
+ "es": "Formato tan natural como respirar",
+ "fr": "Une mise en forme aussi naturelle que la respiration",
+ "ja": "呼吸のように自然な書式",
+ "ko": "호흡처럼 자연스러운 서식",
+ "pt": "Formatação tão natural quanto respirar",
+ "ru": "Форматирование естественно, как дыхание",
+ "zh-hans": "格式化如呼吸般自然"
+ },
+ "home.features.items.output.description": {
+ "ar": "هيكل نظيف وقياسي، جاهز للدخول في أي مسار نشر.",
+ "de": "Eine saubere, standardisierte Struktur, bereit für jeden Veröffentlichungsprozess.",
+ "en": "A clean, standard structure ready to enter any publishing flow.",
+ "es": "Una estructura limpia y estándar, lista para cualquier flujo de publicación.",
+ "fr": "Une structure propre et standard, prête à intégrer n'importe quel flux de publication.",
+ "ja": "クリーンで標準的な構造を、どんな公開フローにもすぐ渡せます。",
+ "ko": "깨끗하고 표준적인 구조로 어떤 게시 흐름에도 바로 사용할 수 있습니다.",
+ "pt": "Uma estrutura limpa e padrão, pronta para qualquer fluxo de publicação.",
+ "ru": "Чистая стандартная структура, готовая для любого процесса публикации.",
+ "zh-hans": "生成的结构干净标准,随时准备好进入任何发布流。"
+ },
+ "home.features.items.output.title": {
+ "ar": "مخرجات جاهزة للنشر",
+ "de": "Ausgaben bereit für die Veröffentlichung",
+ "en": "Output ready for publishing",
+ "es": "Salida lista para publicar",
+ "fr": "Une sortie prête à publier",
+ "ja": "公開のための出力",
+ "ko": "게시를 위한 출력",
+ "pt": "Saída pronta para publicar",
+ "ru": "Результат готов к публикации",
+ "zh-hans": "为发布准备的输出"
+ },
+ "home.features.items.preview.description": {
+ "ar": "يظهر النص والمعاينة معًا، ولكل فقرة مساحتها المناسبة.",
+ "de": "Text und Vorschau erscheinen gemeinsam, mit dem richtigen Raum für jeden Absatz.",
+ "en": "Text and preview appear together, giving every paragraph the space it deserves.",
+ "es": "El texto y la vista previa aparecen juntos, con el espacio que merece cada párrafo.",
+ "fr": "Le texte et la prévisualisation apparaissent ensemble, avec l'espace que mérite chaque paragraphe.",
+ "ja": "文章とプレビューを同時に表示し、すべての段落にふさわしい余白を与えます。",
+ "ko": "텍스트와 미리보기를 함께 보여 주어 모든 문단에 알맞은 여백을 제공합니다.",
+ "pt": "Texto e pré-visualização aparecem juntos, dando a cada parágrafo o espaço que merece.",
+ "ru": "Текст и предпросмотр отображаются вместе, оставляя каждому абзацу нужное место.",
+ "zh-hans": "文字与预览同时呈现,每一段都有它该有的留白。"
+ },
+ "home.features.items.preview.title": {
+ "ar": "إيقاع ما تراه هو ما تحصل عليه",
+ "de": "Der Rhythmus dessen, was du siehst",
+ "en": "The rhythm of what you see",
+ "es": "El ritmo de lo que ves",
+ "fr": "Le rythme de ce que vous voyez",
+ "ja": "見たままのリズム",
+ "ko": "보이는 그대로의 리듬",
+ "pt": "O ritmo do que você vê",
+ "ru": "Ритм того, что вы видите",
+ "zh-hans": "所见即所得的节奏"
+ },
+ "home.features.label": {
+ "ar": "ميزات مصممة للتركيز",
+ "de": "Funktionen für konzentriertes Arbeiten",
+ "en": "Features built for focus",
+ "es": "Funciones creadas para concentrarse",
+ "fr": "Des fonctionnalités conçues pour se concentrer",
+ "ja": "集中のために作られた機能",
+ "ko": "집중을 위해 만든 기능",
+ "pt": "Recursos feitos para o foco",
+ "ru": "Возможности для сосредоточенной работы",
+ "zh-hans": "为专注时刻打造的功能"
+ },
+ "home.features.titleEmphasis": {
+ "ar": "لحظات التركيز.",
+ "de": "fokussierte Momente.",
+ "en": "focused moments.",
+ "es": "momentos de concentración.",
+ "fr": "moments de concentration.",
+ "ja": "すべての機能。",
+ "ko": "모든 기능.",
+ "pt": "momentos de foco.",
+ "ru": "моменты концентрации.",
+ "zh-hans": "每一项功能。"
+ },
+ "home.features.titleLead": {
+ "ar": "كل ميزة",
+ "de": "Jede Funktion für",
+ "en": "Every feature for",
+ "es": "Cada función para",
+ "fr": "Chaque fonctionnalité pour",
+ "ja": "集中する時間の",
+ "ko": "집중하는 순간을 위한",
+ "pt": "Cada recurso para",
+ "ru": "Каждая возможность для",
+ "zh-hans": "写给专注时刻的"
+ },
+ "home.footer.ariaLabel": {
+ "ar": "تذييل الموقع",
+ "de": "Website-Fußzeile",
+ "en": "Site footer",
+ "es": "Pie de página del sitio",
+ "fr": "Pied de page du site",
+ "ja": "サイトフッター",
+ "ko": "사이트 푸터",
+ "pt": "Rodapé do site",
+ "ru": "Подвал сайта",
+ "zh-hans": "网站页脚"
+ },
+ "home.footer.copyright": {
+ "ar": "© 2026 Fuyeor. جميع الحقوق محفوظة.",
+ "de": "© 2026 Fuyeor. Alle Rechte vorbehalten.",
+ "en": "© 2026 Fuyeor. All rights reserved.",
+ "es": "© 2026 Fuyeor. Todos los derechos reservados.",
+ "fr": "© 2026 Fuyeor. Tous droits réservés.",
+ "ja": "© 2026 Fuyeor. All rights reserved.",
+ "ko": "© 2026 Fuyeor. All rights reserved.",
+ "pt": "© 2026 Fuyeor. Todos os direitos reservados.",
+ "ru": "© 2026 Fuyeor. Все права защищены.",
+ "zh-hans": "© 2026 Fuyeor。保留所有权利。"
+ },
+ "home.footer.privacy": {
+ "ar": "الخصوصية",
+ "de": "Datenschutz",
+ "en": "Privacy",
+ "es": "Privacidad",
+ "fr": "Confidentialité",
+ "ja": "プライバシー",
+ "ko": "개인정보 보호",
+ "pt": "Privacidade",
+ "ru": "Конфиденциальность",
+ "zh-hans": "隐私"
+ },
+ "home.footer.terms": {
+ "ar": "الشروط",
+ "de": "Bedingungen",
+ "en": "Terms",
+ "es": "Términos",
+ "fr": "Conditions",
+ "ja": "利用規約",
+ "ko": "약관",
+ "pt": "Termos",
+ "ru": "Условия",
+ "zh-hans": "条款"
+ },
+ "home.header.features": {
+ "ar": "الميزات",
+ "de": "Funktionen",
+ "en": "Features",
+ "es": "Funciones",
+ "fr": "Fonctionnalités",
+ "ja": "機能",
+ "ko": "기능",
+ "pt": "Recursos",
+ "ru": "Возможности",
+ "zh-hans": "功能"
+ },
+ "home.header.navigation": {
+ "ar": "التنقل الرئيسي",
+ "de": "Hauptnavigation",
+ "en": "Main navigation",
+ "es": "Navegación principal",
+ "fr": "Navigation principale",
+ "ja": "メインナビゲーション",
+ "ko": "주요 탐색",
+ "pt": "Navegação principal",
+ "ru": "Основная навигация",
+ "zh-hans": "主导航"
+ },
+ "home.header.notes": {
+ "ar": "ملاحظات",
+ "de": "Notizen",
+ "en": "Notes",
+ "es": "Notas",
+ "fr": "Notes",
+ "ja": "ノート",
+ "ko": "노트",
+ "pt": "Notas",
+ "ru": "Заметки",
+ "zh-hans": "笔记"
+ },
+ "home.header.theme.light": {
+ "ar": "فاتح",
+ "de": "Hell",
+ "en": "Light",
+ "es": "Claro",
+ "fr": "Clair",
+ "ja": "ライト",
+ "ko": "라이트",
+ "pt": "Claro",
+ "ru": "Светлая",
+ "zh-hans": "浅色"
+ },
+ "home.header.theme.night": {
+ "ar": "ليلي",
+ "de": "Nacht",
+ "en": "Night",
+ "es": "Noche",
+ "fr": "Nuit",
+ "ja": "ナイト",
+ "ko": "나이트",
+ "pt": "Noite",
+ "ru": "Ночная",
+ "zh-hans": "夜间"
+ },
+ "home.header.theme.toggle": {
+ "ar": "تبديل سمة الألوان",
+ "de": "Farbmodus umschalten",
+ "en": "Toggle color theme",
+ "es": "Cambiar tema de color",
+ "fr": "Changer de thème de couleur",
+ "ja": "カラーテーマを切り替え",
+ "ko": "색상 테마 전환",
+ "pt": "Alternar tema de cores",
+ "ru": "Переключить цветовую тему",
+ "zh-hans": "切换颜色主题"
+ },
+ "home.header.why": {
+ "ar": "لماذا FFM",
+ "de": "Warum FFM",
+ "en": "Why FFM",
+ "es": "Por qué FFM",
+ "fr": "Pourquoi FFM",
+ "ja": "FFM とは",
+ "ko": "왜 FFM인가",
+ "pt": "Por que FFM",
+ "ru": "Почему FFM",
+ "zh-hans": "为什么选择 FFM"
+ },
+ "home.header.writeLine": {
+ "ar": "اكتب سطرًا",
+ "de": "Eine Zeile schreiben",
+ "en": "Write a line",
+ "es": "Escribe una línea",
+ "fr": "Écrire une ligne",
+ "ja": "一行を書く",
+ "ko": "한 줄 쓰기",
+ "pt": "Escreva uma linha",
+ "ru": "Написать строку",
+ "zh-hans": "写下一行"
+ },
+ "home.hero.attribution": {
+ "ar": "اكتب دون أن تقاطعك الأدوات",
+ "de": "Schreiben, ohne die Seite zu verlassen",
+ "en": "Write without leaving the page",
+ "es": "Escribe sin salir de la página",
+ "fr": "Écrire sans quitter la page",
+ "ja": "ページを離れずに書く",
+ "ko": "페이지를 떠나지 않고 쓰기",
+ "pt": "Escreva sem sair da página",
+ "ru": "Пишите, не покидая страницу",
+ "zh-hans": "写作不必被工具打断"
+ },
+ "home.hero.captionMargin": {
+ "ar": "مصممة للهامش",
+ "de": "Für den Rand gemacht",
+ "en": "Made for the margin",
+ "es": "Hecho para el margen",
+ "fr": "Pensé pour la marge",
+ "ja": "余白のために",
+ "ko": "여백을 위해 만들어졌습니다",
+ "pt": "Feito para a margem",
+ "ru": "Создано для полей",
+ "zh-hans": "为边缘留白而生"
+ },
+ "home.hero.captionSentence": {
+ "ar": "أبقِ الجملة قريبة",
+ "de": "Den Satz nah halten",
+ "en": "Keep the sentence close",
+ "es": "Mantén cerca la frase",
+ "fr": "Garder la phrase près de soi",
+ "ja": "文をそばに置く",
+ "ko": "문장을 가까이 두기",
+ "pt": "Mantenha a frase por perto",
+ "ru": "Держите предложение рядом",
+ "zh-hans": "让句子保持靠近"
+ },
+ "home.hero.description": {
+ "ar": "مساحة Markdown مصممة لمن يكتبون بجدية. واضحة، ذات إحساس، وتضع الكلمات أولاً.",
+ "de": "Ein Markdown-Raum für Menschen, die ernsthaft schreiben. Klar, charaktervoll und mit den Worten im Vordergrund.",
+ "en": "A Markdown space for people who write with intention. Clear, considered, and always putting words first.",
+ "es": "Un espacio Markdown para quienes escriben con intención. Claro, sensible y siempre con las palabras en primer plano.",
+ "fr": "Un espace Markdown pour celles et ceux qui écrivent avec intention. Clair, sensible et toujours centré sur les mots.",
+ "ja": "真剣に書く人のための Markdown 空間。明快で、感覚的で、いつも言葉を最優先にします。",
+ "ko": "진지하게 쓰는 사람을 위한 Markdown 공간입니다. 명확하고 감각적이며 언제나 단어를 먼저 생각합니다.",
+ "pt": "Um espaço Markdown para quem escreve com intenção. Claro, sensível e sempre colocando as palavras em primeiro lugar.",
+ "ru": "Пространство Markdown для тех, кто пишет осознанно. Ясное, выразительное и всегда ставящее слова на первое место.",
+ "zh-hans": "一个为认真写作的人设计的 Markdown 空间。它清楚、有感觉,并且始终把文字放在前面。"
+ },
+ "home.hero.eyebrow": {
+ "ar": "نحو، مع مساحة للتنفس",
+ "de": "Syntax mit Raum zum Atmen",
+ "en": "Syntax, with room to breathe",
+ "es": "Sintaxis, con espacio para respirar",
+ "fr": "La syntaxe, avec de l'espace pour respirer",
+ "ja": "呼吸できる余白のある構文",
+ "ko": "숨 쉴 여백이 있는 구문",
+ "pt": "Sintaxe, com espaço para respirar",
+ "ru": "Синтаксис с пространством для дыхания",
+ "zh-hans": "语法,也需要呼吸的空间"
+ },
+ "home.hero.kicker": {
+ "ar": "FFM / 01",
+ "de": "FFM / 01",
+ "en": "FFM / 01",
+ "es": "FFM / 01",
+ "fr": "FFM / 01",
+ "ja": "FFM / 01",
+ "ko": "FFM / 01",
+ "pt": "FFM / 01",
+ "ru": "FFM / 01",
+ "zh-hans": "FFM / 01"
+ },
+ "home.hero.learnMore": {
+ "ar": "تعرّف على FFM",
+ "de": "FFM kennenlernen",
+ "en": "Meet FFM",
+ "es": "Conoce FFM",
+ "fr": "Découvrir FFM",
+ "ja": "FFM を知る",
+ "ko": "FFM 알아보기",
+ "pt": "Conheça o FFM",
+ "ru": "Узнать о FFM",
+ "zh-hans": "认识 FFM"
+ },
+ "home.hero.openPage": {
+ "ar": "افتح هذه الصفحة",
+ "de": "Diese Seite öffnen",
+ "en": "Open this page",
+ "es": "Abrir esta página",
+ "fr": "Ouvrir cette page",
+ "ja": "このページを開く",
+ "ko": "이 페이지 열기",
+ "pt": "Abrir esta página",
+ "ru": "Открыть эту страницу",
+ "zh-hans": "打开这一页"
+ },
+ "home.hero.portraitPlaceholder": {
+ "ar": "عنصر نائب لصورة البطل",
+ "de": "Platzhalter für das Hero-Bild",
+ "en": "Hero portrait placeholder",
+ "es": "Marcador de retrato principal",
+ "fr": "Espace réservé au portrait principal",
+ "ja": "ヒーロー肖像プレースホルダー",
+ "ko": "히어로 초상화 자리표시자",
+ "pt": "Espaço reservado para o retrato principal",
+ "ru": "Заполнитель портрета героя",
+ "zh-hans": "首屏肖像图占位符"
+ },
+ "home.hero.sampleTitle": {
+ "ar": "العنوان يعرف أين يستريح",
+ "de": "Ein Titel weiß, wo er ruhen darf",
+ "en": "a title knows where to rest",
+ "es": "un título sabe dónde descansar",
+ "fr": "un titre sait où se poser",
+ "ja": "タイトルには落ち着く場所がある",
+ "ko": "제목은 쉴 곳을 안다",
+ "pt": "um título sabe onde repousar",
+ "ru": "заголовок знает, где остановиться",
+ "zh-hans": "标题知道该在哪里停留"
+ },
+ "home.hero.titleEmphasis": {
+ "ar": "ودع التعبير يظهر.",
+ "de": "damit Ausdruck sichtbar wird.",
+ "en": "let expression appear.",
+ "es": "deja que aparezca la expresión.",
+ "fr": "laissez l'expression apparaître.",
+ "ja": "表現を浮かび上がらせる。",
+ "ko": "표현이 드러나게 하세요.",
+ "pt": "deixe a expressão aparecer.",
+ "ru": "пусть появится выражение.",
+ "zh-hans": "让表达出现。"
+ },
+ "home.hero.titleLead": {
+ "ar": "دع التنسيق يتراجع،",
+ "de": "Lass die Formatierung zurücktreten,",
+ "en": "Let format step back,",
+ "es": "Deja que el formato retroceda,",
+ "fr": "Laissez la mise en forme s'effacer,",
+ "ja": "書式を一歩引かせ、",
+ "ko": "형식은 한 걸음 물러나고,",
+ "pt": "Deixe a formatação recuar,",
+ "ru": "Пусть формат отступит,",
+ "zh-hans": "让格式退后,"
+ },
+ "home.intro.ariaLabel": {
+ "ar": "فلسفة FFM",
+ "de": "Die Idee von FFM",
+ "en": "The FFM idea",
+ "es": "La idea de FFM",
+ "fr": "L'idée de FFM",
+ "ja": "FFM の理念",
+ "ko": "FFM의 철학",
+ "pt": "A ideia do FFM",
+ "ru": "Идея FFM",
+ "zh-hans": "FFM 理念"
+ },
+ "home.intro.caption": {
+ "ar": "علامة واحدة في كل مرة",
+ "de": "Ein Zeichen nach dem anderen",
+ "en": "One mark at a time",
+ "es": "Una marca a la vez",
+ "fr": "Un signe à la fois",
+ "ja": "一つずつ記号を",
+ "ko": "한 기호씩",
+ "pt": "Uma marca de cada vez",
+ "ru": "По одному знаку",
+ "zh-hans": "一次一个标记"
+ },
+ "home.intro.emphasis": {
+ "ar": "حالة كتابة أكثر سهولة.",
+ "de": "leichteres Schreibgefühl.",
+ "en": "more effortless writing state.",
+ "es": "estado de escritura más ligero.",
+ "fr": "état d'écriture plus léger.",
+ "ja": "もっと軽やかな書く状態。",
+ "ko": "더 편안한 글쓰기 상태입니다.",
+ "pt": "estado de escrita mais leve.",
+ "ru": "более лёгкое состояние письма.",
+ "zh-hans": "更轻松的写作状态。"
+ },
+ "home.intro.lead": {
+ "ar": "ليس محررًا أكثر تعقيدًا، بل ",
+ "de": "Kein komplexerer Editor, sondern ein ",
+ "en": "Not a more complex editor, but a ",
+ "es": "No un editor más complejo, sino un ",
+ "fr": "Pas un éditeur plus complexe, mais un ",
+ "ja": "複雑なエディターではなく、",
+ "ko": "더 복잡한 편집기가 아니라, ",
+ "pt": "Não um editor mais complexo, mas um ",
+ "ru": "Не более сложный редактор, а ",
+ "zh-hans": "不是更复杂的编辑器,而是"
+ },
+ "home.statement.cta": {
+ "ar": "اكتشف إيقاع الكتابة",
+ "de": "Den Schreibrhythmus entdecken",
+ "en": "Explore the writing rhythm",
+ "es": "Explora el ritmo de escritura",
+ "fr": "Explorer le rythme de l'écriture",
+ "ja": "書くリズムを探る",
+ "ko": "글쓰기 리듬 탐색하기",
+ "pt": "Explore o ritmo da escrita",
+ "ru": "Исследовать ритм письма",
+ "zh-hans": "探索写作节奏"
+ },
+ "home.statement.description": {
+ "ar": "يضع FFM صيغة Markdown المألوفة في تجربة تهتم بالتدرج والإيقاع والتغذية الراجعة للمعاينة. لا تحتاج إلى تعلم لغة جديدة، ما عليك سوى الاستمرار في الكتابة.",
+ "de": "FFM setzt vertraute Markdown-Syntax in eine Erfahrung mit Sinn für Hierarchie, Rhythmus und Vorschau-Feedback. Du musst keine neue Sprache lernen, sondern nur weiterschreiben.",
+ "en": "FFM places familiar Markdown syntax in an experience shaped around hierarchy, rhythm, and preview feedback. You do not need to learn a new language; you only need to keep writing.",
+ "es": "FFM sitúa la sintaxis Markdown conocida en una experiencia atenta a la jerarquía, el ritmo y la respuesta de la vista previa. No necesitas aprender un idioma nuevo; solo seguir escribiendo.",
+ "fr": "FFM place la syntaxe Markdown familière dans une expérience attentive à la hiérarchie, au rythme et au retour de prévisualisation. Inutile d'apprendre un nouveau langage : continuez simplement à écrire.",
+ "ja": "FFM はおなじみの Markdown 構文を、階層・リズム・プレビューのフィードバックを大切にする体験へ置きます。新しい言語を学ぶ必要はありません。ただ書き続ければよいのです。",
+ "ko": "FFM은 익숙한 Markdown 구문을 계층, 리듬, 미리보기 피드백에 집중한 경험으로 확장합니다. 새로운 언어를 배울 필요 없이 계속 써 내려가면 됩니다.",
+ "pt": "O FFM coloca a sintaxe Markdown familiar em uma experiência guiada por hierarquia, ritmo e retorno da pré-visualização. Você não precisa aprender uma nova linguagem; basta continuar escrevendo.",
+ "ru": "FFM помещает знакомый синтаксис Markdown в опыт, построенный вокруг иерархии, ритма и обратной связи предпросмотра. Вам не нужно учить новый язык — просто продолжайте писать.",
+ "zh-hans": "FFM 将熟悉的 Markdown 语法,放进一套更注重层级、节奏与预览反馈的体验里。你不需要学习新语言,只需要继续写下去。"
+ },
+ "home.statement.label": {
+ "ar": "نظام صغير للأفكار الحقيقية",
+ "de": "Ein kleines System für echte Gedanken",
+ "en": "A small system for real thoughts",
+ "es": "Un pequeño sistema para pensamientos reales",
+ "fr": "Un petit système pour les pensées réelles",
+ "ja": "本当の思考のための小さなシステム",
+ "ko": "진짜 생각을 위한 작은 시스템",
+ "pt": "Um pequeno sistema para pensamentos reais",
+ "ru": "Небольшая система для настоящих мыслей",
+ "zh-hans": "为真实想法设计的小系统"
+ },
+ "home.statement.overline": {
+ "ar": "مقروء افتراضيًا",
+ "de": "Standardmäßig lesbar",
+ "en": "Readable by default",
+ "es": "Legible por defecto",
+ "fr": "Lisible par défaut",
+ "ja": "読みやすさを標準に",
+ "ko": "기본적으로 읽기 쉽게",
+ "pt": "Legível por padrão",
+ "ru": "Читаемо по умолчанию",
+ "zh-hans": "默认即可读"
+ },
+ "home.statement.titleEmphasis": {
+ "ar": "موضع يتنفس فيه.",
+ "de": "Atemraum.",
+ "en": "breathing room.",
+ "es": "espacio para respirar.",
+ "fr": "espace pour respirer.",
+ "ja": "呼吸する場所がある。",
+ "ko": "숨 쉴 자리가 있습니다.",
+ "pt": "espaço para respirar.",
+ "ru": "пространство для дыхания.",
+ "zh-hans": "呼吸位置。"
+ },
+ "home.statement.titleLead": {
+ "ar": "كل رمز،",
+ "de": "Jedes Zeichen",
+ "en": "Every mark",
+ "es": "Cada signo",
+ "fr": "Chaque signe",
+ "ja": "すべての記号に、",
+ "ko": "모든 기호에는",
+ "pt": "Cada marca",
+ "ru": "Каждый знак",
+ "zh-hans": "每一个符号,"
+ },
+ "home.statement.titleMiddle": {
+ "ar": "له ",
+ "de": "findet seinen ",
+ "en": "has its ",
+ "es": "tiene su ",
+ "fr": "a sa ",
+ "ja": "には、",
+ "ko": "에는",
+ "pt": "tem seu ",
+ "ru": "имеет своё ",
+ "zh-hans": "都有它的"
+ },
+ "playground": {
+ "ar": "ساحة التجربة",
+ "de": "Spielwiese",
+ "en": "Playground",
+ "es": "Área de pruebas",
+ "fr": "Aire de jeux",
+ "ja": "プレイグラウンド",
+ "ko": "플레이그라운드",
+ "pt": "Área de testes",
+ "ru": "Песочница",
+ "zh-hans": "实验场"
+ },
+ "playground.editor.richText": {
+ "ar": "نص منسق",
+ "de": "Rich-Text",
+ "en": "Rich text",
+ "es": "Texto enriquecido",
+ "fr": "Texte enrichi",
+ "ja": "リッチテキスト",
+ "ko": "서식 있는 텍스트",
+ "pt": "Texto formatado",
+ "ru": "Форматированный текст",
+ "zh-hans": "富文本"
+ },
+ "playground.editor.source": {
+ "ar": "مصدر Markdown",
+ "de": "Markdown-Quelle",
+ "en": "Markdown source",
+ "es": "Fuente Markdown",
+ "fr": "Source Markdown",
+ "ja": "Markdown ソース",
+ "ko": "Markdown 소스",
+ "pt": "Fonte Markdown",
+ "ru": "Исходный Markdown",
+ "zh-hans": "Markdown 源码"
+ },
+ "playground.preview.render": {
+ "ar": "المعاينة",
+ "de": "Vorschau",
+ "en": "Preview",
+ "es": "Vista previa",
+ "fr": "Aperçu",
+ "ja": "プレビュー",
+ "ko": "미리보기",
+ "pt": "Pré-visualização",
+ "ru": "Предпросмотр",
+ "zh-hans": "渲染预览"
+ },
+ "playground.share": {
+ "ar": "مشاركة محتوى Markdown",
+ "de": "Markdown-Inhalt teilen",
+ "en": "Share Markdown content",
+ "es": "Compartir contenido Markdown",
+ "fr": "Partager le contenu Markdown",
+ "ja": "Markdownコンテンツを共有",
+ "ko": "Markdown 콘텐츠 공유",
+ "pt": "Compartilhar conteúdo Markdown",
+ "ru": "Поделиться контентом Markdown",
+ "zh-hans": "分享 Markdown 内容"
+ },
+ "playground.share.button": {
+ "ar": "مشاركة",
+ "de": "Teilen",
+ "en": "Share",
+ "es": "Compartir",
+ "fr": "Partager",
+ "ja": "共有",
+ "ko": "공유",
+ "pt": "Compartilhar",
+ "ru": "Поделиться",
+ "zh-hans": "分享"
+ },
+ "playground.share.desc": {
+ "ar": "انسخ الرابط التالي وأرسله:",
+ "de": "Kopieren Sie den folgenden Link zum Senden:",
+ "en": "Copy the following link to send:",
+ "es": "Copia el siguiente enlace para enviar:",
+ "fr": "Copiez le lien suivant à envoyer :",
+ "ja": "次のリンクをコピーして送信:",
+ "ko": "보내려면 다음 링크를 복사하세요:",
+ "pt": "Copie o link abaixo para enviar:",
+ "ru": "Скопируйте следующую ссылку для отправки:",
+ "zh-hans": "复制以下链接发送:"
+ },
+ "site.name": {
+ "ar": "Fuyeor Flavored Markdown",
+ "de": "Fuyeor Flavored Markdown",
+ "en": "Fuyeor Flavored Markdown",
+ "es": "Fuyeor Flavored Markdown",
+ "fr": "Fuyeor Flavored Markdown",
+ "ja": "Fuyeor Flavored Markdown",
+ "ko": "Fuyeor Flavored Markdown",
+ "pt": "Fuyeor Flavored Markdown",
+ "ru": "Fuyeor Flavored Markdown",
+ "zh-hans": "Fuyeor Flavored Markdown"
+ },
+ "site.title": {
+ "ar": "ساحة التجربة",
+ "de": "Spielwiese",
+ "en": "Playground",
+ "es": "Área de pruebas",
+ "fr": "Aire de jeux",
+ "ja": "プレイグラウンド",
+ "ko": "플레이그라운드",
+ "pt": "Área de testes",
+ "ru": "Песочница",
+ "zh-hans": "实验场"
+ }
+}
diff --git a/packages/playground/src/main.styles.ts b/packages/playground/src/main.styles.ts
deleted file mode 100644
index 202f5f7..0000000
--- a/packages/playground/src/main.styles.ts
+++ /dev/null
@@ -1,57 +0,0 @@
-// @fuyeor/markdown-parser-playground/src/main.styles.ts
-import { css } from 'lit';
-
-export const styles = css`
- :host {
- display: grid;
- grid-template-columns: 1fr 1fr;
- grid-template-rows: 1fr 300px;
- height: 100vh;
- gap: 1px;
- background: #333;
- font-family: 'Fuyeor Sans', system-ui, sans-serif;
- }
- .pane {
- background: #1a1a1a;
- color: #ccc;
- display: flex;
- flex-direction: column;
- overflow: hidden;
- }
- .header {
- padding: 8px 16px;
- background: #252525;
- font-size: 12px;
- text-transform: uppercase;
- letter-spacing: 1px;
- border-bottom: 1px solid #333;
- }
- textarea {
- flex: 1;
- background: transparent;
- border: none;
- color: #fff;
- padding: 20px;
- font-family: 'Fuyeor Mono', monospace;
- font-size: 14px;
- resize: none;
- outline: none;
- line-height: 1.6;
- }
- #preview {
- flex: 1;
- background: #fff;
- color: #333;
- padding: 20px;
- overflow-y: auto;
- }
- #ast {
- grid-column: span 2;
- background: #000;
- color: #0f0;
- padding: 10px;
- font-family: monospace;
- font-size: 12px;
- overflow-y: auto;
- }
-`;
diff --git a/packages/playground/src/main.ts b/packages/playground/src/main.ts
index 681b400..8c8b4e6 100644
--- a/packages/playground/src/main.ts
+++ b/packages/playground/src/main.ts
@@ -1,45 +1,25 @@
-// @fuyeor/markdown-parser-playground/src/main.ts
-import { LitElement, html } from 'lit';
-import { customElement, state } from 'lit/decorators.js';
-import {
- createFuyeorMarkdownParser,
- render as renderMarkdown,
-} from '@fuyeor/markdown-parser-lit';
-import { styles as mainStyles } from './main.styles';
-import { styles as markdownStyles } from './markdown.styles';
+// @/main.ts
+// pnpm playground
+import App from './App.vue';
+import router from './router';
-@customElement('markdown-playground')
-export class MarkdownPlayground extends LitElement {
- static styles = [mainStyles, markdownStyles];
+import { createApp } from 'vue';
+import { initializeLocale, createHead } from '@fuyeor/commons';
+import { vRipple, vTooltip } from '@fuyeor/interactify';
- #parser = createFuyeorMarkdownParser();
+async function bootstrap() {
+ const app = createApp(App);
+ const head = createHead();
- @state()
- private _source =
- '# Welcome to Markdown Playground\n\nTry writing some **markdown** here!';
+ app.use(router);
+ app.use(head);
- render() {
- const ast = this.#parser(this._source);
+ await initializeLocale({ app });
- return html`
-
-
-
-
+ app.directive('ripple', vRipple);
+ app.directive('tooltip', vTooltip);
-
-
-
${renderMarkdown(ast)}
-
-
-
-
-
${JSON.stringify(ast, null, 2)}
-
- `;
- }
-
- #onInput(e: Event) {
- this._source = (e.target as HTMLTextAreaElement).value;
- }
+ app.mount('#app');
}
+
+bootstrap();
diff --git a/packages/playground/src/markdown.styles.ts b/packages/playground/src/markdown.styles.ts
deleted file mode 100644
index 64781fa..0000000
--- a/packages/playground/src/markdown.styles.ts
+++ /dev/null
@@ -1,588 +0,0 @@
-// @fuyeor/markdown-parser-playground/src/markdown.styles.ts
-import { css } from 'lit';
-
-export const styles = css`
- :host {
- /* 品牌/意图色 */
- --color-brand: #aea4e4;
- --color-brand-deeper: #9f8dff;
- --color-warn: #d93026;
- --color-success: #28a745;
- --color-note: #ffc107;
- --color-info: var(--interact-blue-2);
-
- /* 固定圆角:用于卡片、输入框 */
- --radius-sm: 8px;
- --radius-md: 12px;
- --radius-lg: 16px;
-
- /* 地面 */
- --surface: #f7f7f8;
- --surface-hover: #edeef1;
- --surface-transparent: #ffffffd0;
- /* 浮起块 */
- --surface-raised: #fff;
- --surface-raised-hover: rgba(0, 0, 0, 0.03);
- /* 顶端 */
- --surface-top: #f5f5f6;
- --surface-top-hover: #f0f2f4;
-
- /* 文字颜色 */
- --text-primary: #345;
- --text-secondary: #4d5763;
- --text-tertiary: #67717d;
- /* 链接及 hover */
- --text-accent: var(--interact-blue);
- --text-on-accent: #9c1a1b;
-
- /* 框架边框 */
- --border-subtle: 1px solid #f0f0f1;
- --border-default: 1px solid #dee2e6;
- }
-
- /*表格*/
- .table-container {
- width: 100%;
- overflow-x: auto;
- /* 允许水平滚动 */
- border: var(--border-default);
- /* 给容器添加边框 */
- border-radius: var(--radius-lg);
- /* 给容器添加圆角 */
- margin: 20px 0;
- }
-
- table {
- width: 100%;
- border-collapse: collapse;
- table-layout: auto;
- }
-
- table {
- /* 表格头部添加边框 */
- thead tr {
- border-bottom: var(--border-default);
- background: var(--surface-hover);
- }
-
- thead tr th:not(:first-child):not(:last-child) {
- border-left: var(--border-default);
- border-right: var(--border-default);
- }
-
- th,
- td,
- tr {
- min-width: 100px;
- text-align: left;
- word-wrap: break-word;
- white-space: normal;
- max-width: 400px;
- padding: 10px 4px 10px 10px;
- }
-
- /* 为非角落单元格添加边框 */
- tr td:not(:first-child):not(:last-child) {
- border-left: var(--border-default);
- /* 添加左侧边框 */
- border-right: var(--border-default);
- /* 添加右侧边框 */
- }
-
- tr:not(:first-child) td {
- border-top: var(--border-default);
- /* 添加顶部边框 */
- }
- }
-
- /* 内容区 markdown 富文本 */
- #preview {
- word-wrap: break-word;
- word-break: break-word;
-
- a {
- color: var(--text-accent);
- transition: color 0.3s;
- }
-
- a:hover,
- a:focus {
- color: var(--text-on-accent);
- }
-
- a[href*='www.fuyeor.com']:before {
- content: '';
- position: relative;
- right: 0;
- vertical-align: middle;
- margin-right: 4px;
- display: inline-block;
- width: 18px;
- height: 18px;
- background-size: 18px;
- background-repeat: no-repeat;
- background-position: 50%;
- }
-
- a[href*='www.fuyeor.com']:before {
- width: 20px;
- height: 20px;
- background-size: 20px;
- background-image: url('https://www.fuyeor.com/favicon.svg');
- }
-
- /*标题*/
- h1,
- h2,
- h3 {
- color: var(--text-secondary);
- margin: 24px 0 24px 0;
- }
-
- h2 {
- padding-bottom: 10px;
- border-bottom: var(--border-default);
- font-size: 23px;
- }
-
- pre {
- padding: 10px;
- border-radius: var(--radius-sm);
- overflow-x: auto;
- background: var(--surface-top);
- }
-
- code {
- margin: 0 4px;
- padding: 2px 4px;
- /* 使用等宽字体 */
- font-family: 'Courier New', Courier, monospace;
- font-size: 0.9rem;
- background: var(--surface-top);
- border-radius: 4px;
- }
-
- pre > code {
- background: none;
- white-space: pre;
- margin: 0;
- padding: 0;
- }
-
- p {
- color: var(--text-secondary);
- }
-
- u {
- text-decoration: none;
- border-bottom: var(--border-subtle);
- }
-
- hr {
- /* 先把祖传的 3D 边框全干掉! */
- border: none;
- border-top: var(--border-subtle);
- margin: 2rem 0px;
- }
-
- ol,
- ul {
- margin: 0 0 0 10px;
- padding: 0;
- }
-
- li {
- margin: 10px 0 10px 0;
- color: var(--text-secondary);
- }
-
- code,
- pre,
- pre * {
- font-family: 'Google Sans Code';
- }
-
- .code-block-wrapper {
- margin: 0 0 20px 0;
- background: var(--surface-top);
- border: var(--border-default);
- border-radius: var(--radius-lg);
- }
-
- .pre-language {
- font-size: 0.85rem;
- border-bottom: var(--border-default);
- padding: 10px;
- }
-
- pre {
- position: relative;
- padding: 10px 20px 10px 20px;
- overflow-x: auto;
- /* 横向滚动 */
- white-space: pre;
- /* 保留空格和换行,不自动折行 */
- word-break: normal;
- /* 禁止单词内断行 */
- scrollbar-width: none;
- }
-
- pre > code {
- background: none;
- white-space: pre;
- /* 保留空格和换行,不自动折行 */
- word-break: normal;
- /* 禁止单词内断行 */
- }
-
- /* Katex 公式 */
- .math-block {
- overflow-x: auto;
- overflow-y: hidden;
- white-space: nowrap;
- scrollbar-width: none;
- }
-
- .math-block > .katex-display {
- white-space: normal;
- /* 防止嵌套问题 */
- }
-
- blockquote {
- position: relative;
- padding: 4px 12px 4px 20px;
- margin: 0 0 10px 0;
- background: var(--surface-raised);
- background-image: url(https://deliver.fuyeor.net/@fu/commons/icons/filled/quote.svg);
- background-repeat: no-repeat;
- background-position: 8px 4px;
- background-size: 32px;
- color: var(--text-primary);
- border-radius: var(--radius-sm);
- cursor: pointer;
- }
-
- blockquote::before {
- content: '';
- position: absolute;
- top: 0;
- left: -8px;
- width: 5px;
- height: 100%;
- background-color: var(--surface-raised-hover);
- border-radius: 4px;
- }
-
- img {
- max-width: 100%;
- border-radius: 6px;
- }
-
- .blocked-image {
- padding: 20px;
- background: var(--surface-hover);
- border-radius: 6px;
- }
-
- .emoji {
- margin: 0 0.05rem;
- /* 微调左右间距 */
- position: relative;
- /* 用于微调定位 */
- top: -0.1rem;
- /* 精细微调位置(按需调整) */
- vertical-align: middle;
- /* 关键:垂直居中对齐 */
- border-radius: 0;
- }
-
- .emoji,
- img.emoji,
- span.emoji {
- height: 1.05rem;
- /* 高度与文字保持一致 */
- width: auto;
- /* 宽度按比例自适应 */
- display: inline-block;
- /* 保持行内特性 */
- }
-
- /* [UX 优化] Twemoji 样式:确保 emoji 图片和它的文字回退 都保持一致的尺寸和对齐方式
- img.emoji, span.emoji {
- margin: 0 0.05em 0 0.1rem;
- vertical-align: -0.15rem;
- }*/
- /* Youtube 播放器 */
- .youtube-facade {
- position: relative;
- width: 100%;
- /* 强制锁定 16:9 宽高比,防止页面跳动 (Layout Shift) */
- aspect-ratio: 16 / 9;
- background: #000;
- border-radius: var(--radius-md);
- overflow: hidden;
- cursor: pointer;
- margin: 1.5rem 0;
- border: var(--border-subtle);
- transition: 0.3s all;
-
- &:hover {
- .play-button {
- background: #ea333d;
- }
- }
-
- img {
- display: block;
- width: 100%;
- height: 100%;
- object-fit: cover;
- /* 裁剪模式,确保填满 */
- opacity: 0.9;
- /* 稍微暗一点,突显播放按钮 */
- transition: opacity 0.3s;
- }
-
- /* 纯 CSS 绘制的播放按钮 */
- .play-button {
- position: absolute;
- top: 50%;
- left: 50%;
- width: 50px;
- height: 50px;
- transform: translate(-50%, -50%);
- background: rgba(33, 33, 33, 0.8);
- border-radius: 50%;
- z-index: 2;
- transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
-
- /* 绘制三角形 */
- &::after {
- content: '';
- position: absolute;
- top: 50%;
- left: 50%;
- transform: translate(-50%, -50%);
- border-style: solid;
- border-width: 10px 0 10px 18px;
- /* 三角形大小 */
- border-color: transparent transparent transparent #fff;
- /* 白色三角形 */
- margin-left: 2px;
- /* 视觉修正居中 */
- }
- }
-
- /* 播放状态 */
- &.is-playing {
- background: #000;
- }
- }
-
- .slide-container-wrapper {
- /* 作为一个容器块,上下留白 */
- margin: 1.5rem -1rem;
- /* 负 margin 让它在移动端可以贴边滑动 */
- padding: 0 1rem;
- /* 补偿 padding */
- overflow: hidden;
-
- /* 防止滚动条溢出容器外 */
- @media (width >= 768px) {
- /* 桌面端恢复正常边距 */
- margin: 1.5rem 0;
- padding: 0;
- }
- }
-
- .slide-container {
- display: flex;
- gap: 1rem;
- /* 卡片间距 */
- overflow-x: auto;
- /* 允许横向滚动 */
- scroll-snap-type: x mandatory;
- /* 隐藏滚动条 */
- scrollbar-width: none;
- }
-
- .slide-item {
- /* 布局核心 */
- flex: 0 0 88%;
- /* 移动端占据 88% 宽度,露出下一张的一点点 */
- scroll-snap-align: center;
- /* 停止时居中对齐 */
- border: var(--border-default);
- border-radius: var(--radius-md);
- padding: 0rem 1rem 0.5rem;
- box-sizing: border-box;
-
- /* 视频播放器取消边距 */
- .youtube-facade {
- margin: 0;
- }
-
- @media (width >= 768px) {
- /* 桌面端可以并列显示两个,或者宽度更小一点 */
- flex: 0 0 60%;
- scroll-snap-align: start;
- /* 桌面端左对齐更自然 */
- }
- }
-
- /* Smiles 化学结构式样式 */
- /* .smiles-block {} */
-
- /* 行内显示 ([smiles]) */
- .smiles-inline {
- display: inline-block;
- height: 1.5em;
- /* 高度跟随文字大小,稍微大一点点看着舒服 */
- vertical-align: middle;
- /* 与文字中线对齐 */
- margin: 0 2px;
- /* 左右留一点点呼吸空间 */
- transform: translateY(-2px);
- /* 微调视觉中心 */
- transition: filter 0.3s ease;
- }
-
- /* Chain (思考链/时间轴) 样式 */
- .chain-container {
- display: flex;
- flex-direction: column;
- /* 去掉 gap,让 item 紧贴,靠 padding 控制间距,这样线才好连 */
- margin: 1.5rem 0;
- padding-left: 1rem;
- font-style: italic;
- }
-
- .chain-item {
- position: relative;
- /* 用 padding 替代原来的 gap,给下一段留出呼吸空间 */
- padding-bottom: 1.2rem;
-
- /* 这是一条贯穿整个 item 的线 */
- &::before {
- content: '';
- position: absolute;
- /* 这里的 left 要和 marker 的中心对齐 */
- /* marker left 是 -18px, width 是 12px -> 中心点大概在 -12px 左右 */
- /* 我们微调一下,假设 marker 还是相对于 content 定位 */
- left: -13px;
- top: 0;
- bottom: 0;
- /* 撑满整个高度! */
- width: 1.5px;
- background: var(--text-tertiary);
- opacity: 0.3;
- /* 稍微淡一点,不要抢了圆圈的风头,显得极简 */
- z-index: 0;
- }
-
- /* 处理最后一个节点:线不要拖到底,只要连到圆圈就行 */
- &:last-child {
- padding-bottom: 0;
-
- &::before {
- /* 让线只延伸到圆圈的位置,或者直接隐藏下半部分 */
- /* 简单的做法:把线的高度设为圆圈的 top 值 */
- bottom: auto;
- height: 6px;
- }
- }
-
- /* 如果第一个节点上面不需要线 */
- &:first-child::before {
- top: 6px;
- /* 从圆圈中间开始往下画 */
- }
- }
-
- /* 圆圈 */
- .chain-marker {
- position: absolute;
- left: -20px;
- top: 0;
- /* 调整为 0 或配合 line-height 对齐 */
- margin-top: 6px;
- /* 微调垂直对齐,视字体大小而定 */
- width: 1rem;
- height: 1rem;
- border-radius: 50%;
- /* 加上边框颜色,强调一下 */
- border: 1.5px solid var(--color-brand);
- /* 背景色遮住后面的线 */
- background: var(--surface);
- z-index: 1;
- /* 必须比线高,压住线 */
- box-sizing: border-box;
- }
-
- .chain-content-wrapper {
- padding-left: 1rem;
- position: relative;
- }
-
- .chain-title {
- font-weight: bold;
- margin-bottom: 1rem;
- }
-
- .chain-body {
- color: var(--text-secondary);
- font-size: 0.95em;
-
- p {
- margin: 0.5rem 0;
- }
- }
-
- .chain-item.is-completed,
- .chain-item.is-pending {
- /* 圆圈里的图片样式 */
- .chain-marker::after {
- content: '';
- position: absolute;
- top: 50%;
- left: 50%;
- transform: translate(-48%, -48%);
- width: 14px;
- height: 14px;
- background-size: contain;
- background-repeat: no-repeat;
- background-position: center;
- z-index: 5;
- }
- }
-
- /* 状态:已完成 (is-completed) */
- .chain-item.is-completed {
- /* 圆圈变实心 */
- .chain-marker {
- border-color: var(--color-success);
- }
-
- /* 在圆圈里加个对勾 */
- .chain-marker::after {
- background-image: url('https://deliver.fuyeor.net/@fu/icons/outlined/checked.svg');
- }
-
- /* 标题稍微淡一点,表示已过去 */
- .chain-title {
- opacity: 0.8;
- }
- }
-
- /* 状态:待办 (is-pending) */
- .chain-item.is-pending {
- .chain-marker {
- border-color: var(--color-note);
- }
-
- .chain-marker::after {
- background-image: url('https://deliver.fuyeor.net/@fu/icons/outlined/more-horizon.svg');
- }
- }
- }
-`;
diff --git a/packages/playground/src/router/index.ts b/packages/playground/src/router/index.ts
new file mode 100644
index 0000000..714ae72
--- /dev/null
+++ b/packages/playground/src/router/index.ts
@@ -0,0 +1,93 @@
+// @/router/index.ts
+import { createRouter, RouterView, type RouteRecord } from '@fuyeor/vue-router';
+import { useTransitionBar } from '@fuyeor/interactify';
+import { useLocaleStore } from '@fuyeor/commons';
+import { LOCALE_REGEX } from '@/config/locales';
+
+const { start, done } = useTransitionBar();
+
+const appRoutes: Array = [
+ {
+ // flavored.fuyeor.com/
+ path: '',
+ name: 'Home',
+ component: () => import('@/views/Home.vue'),
+ meta: {
+ overrideTitle: ['site.name', '/', 'site.title'],
+ },
+ },
+ {
+ // flavored.fuyeor.com/playground or /playground/:id
+ // The optional segment uses URLPattern syntax required by @fuyeor/vue-router.
+ path: 'playground{/:id}?',
+ name: 'Playground',
+ component: () => import('@/views/Playground.vue'),
+ meta: {
+ titleKey: 'playground',
+ },
+ },
+];
+
+// root router
+const routes: Array = [
+ {
+ // optional locale prefix wrapper, only allows supported locales
+ path: `{/:locale(${LOCALE_REGEX})}?`,
+ component: RouterView,
+ // 将所有应用路由放入 children
+ // 注意:子路由的 path 如果不以 / 开头,会拼接在父路由后面
+ // 例如:/en/thought/123 或 /thought/123
+ children: appRoutes,
+ },
+
+ // 404 路由必须放在最外层,且在最后
+ // 如果 URL 是 /xx/thought/123 (不支持的语言),正则匹配失败,会落到这里
+ {
+ path: '/*', // 通配符路由,捕获所有未匹配的路径
+ name: 'NotFound',
+ component: () =>
+ import('@fuyeor/interactify/views').then((m) => m.NotFoundView),
+ meta: {
+ titleKey: 'notFound.title',
+ },
+ },
+];
+
+// 创建路由实例
+const router = createRouter({ routes });
+
+// 路由守卫
+router.beforeEach(async (to) => {
+ // 启动顶部进度条
+ start();
+
+ // 获取语言 store
+ const localeStore = useLocaleStore();
+
+ // 从路由参数中获取 locale (例如 'en' 或 undefined)
+ const routeLocale = to.params.locale as string | undefined;
+
+ // 如果没有语言参数就添加语言
+ if (!routeLocale) {
+ return {
+ name: to.name,
+ params: { ...to.params, locale: localeStore.locale },
+ };
+ }
+
+ // Load the route locale before rendering so all translated components stay in sync.
+ await localeStore.setLocale(routeLocale);
+
+ // 如果代码能执行到这里,说明所有检查都通过了,允许导航
+ return true;
+});
+
+router.afterEach(() => {
+ done();
+});
+
+router.onError(() => {
+ done();
+});
+
+export default router;
diff --git a/packages/playground/src/views/Home.vue b/packages/playground/src/views/Home.vue
new file mode 100644
index 0000000..76585ea
--- /dev/null
+++ b/packages/playground/src/views/Home.vue
@@ -0,0 +1,169 @@
+
+
+
+
+
+
+
+ ✦
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/packages/playground/src/views/Playground.vue b/packages/playground/src/views/Playground.vue
new file mode 100644
index 0000000..9adbc79
--- /dev/null
+++ b/packages/playground/src/views/Playground.vue
@@ -0,0 +1,340 @@
+
+
+
+
+
+
+
+
diff --git a/packages/playground/tsconfig.json b/packages/playground/tsconfig.json
index 1c78ed6..b664f43 100644
--- a/packages/playground/tsconfig.json
+++ b/packages/playground/tsconfig.json
@@ -1,20 +1,16 @@
{
- "$schema": "https://json.schemastore.org/tsconfig",
+ "extends": "@fuyeor/tsconfig/base.json",
"compilerOptions": {
"paths": {
- "#/*": ["./src/*"]
+ "@/*": ["./src/*"]
},
- "target": "ESNext",
- "module": "ESNext",
- "moduleResolution": "Bundler",
- "lib": ["ESNext", "DOM"],
- "strict": true,
- "isolatedModules": true,
- "resolveJsonModule": true,
- "skipLibCheck": true,
- "noEmit": true,
- "experimentalDecorators": true,
- "useDefineForClassFields": false
+ "types": ["vite/client", "node"]
},
- "include": ["src/**/*.ts", "vite.config.ts"]
+ "include": [
+ "vite.config.ts",
+ "vite-env.d.ts",
+ "src/**/*.ts",
+ "src/**/*.d.ts",
+ "src/**/*.vue"
+ ]
}
diff --git a/packages/playground/vite.config.ts b/packages/playground/vite.config.ts
index 9937df7..3548bf0 100644
--- a/packages/playground/vite.config.ts
+++ b/packages/playground/vite.config.ts
@@ -1,20 +1,15 @@
// @fuyeor/markdown-parser-playground/vite.config.ts
import { defineConfig } from 'vite';
+import { createViteConfig } from '@fuyeor/config/vite.config.js';
-export default defineConfig({
- plugins: [],
- server: {
- port: 7820,
- },
- build: {
- // generate to dist/assets/build/
- outDir: 'dist',
- rollupOptions: {
- output: {
- entryFileNames: `assets/entry.[hash].js`,
- chunkFileNames: `assets/chunk.[hash].js`,
- assetFileNames: `assets/asset.[hash].[ext]`,
+export default defineConfig(() => {
+ return createViteConfig(
+ {
+ server: {
+ host: '0.0.0.0',
+ port: 7820,
},
},
- },
+ import.meta.dirname,
+ );
});
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 0835787..db9d443 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -8,6 +8,9 @@ importers:
.:
devDependencies:
+ '@fuyeor/locale-cli':
+ specifier: ^0.1.1
+ version: 0.1.1
'@types/node':
specifier: ^25.6.0
version: 25.6.0
@@ -22,7 +25,7 @@ importers:
version: 6.0.3
vitest:
specifier: ^4.1.0
- version: 4.1.4(@types/node@25.6.0)(@vitest/coverage-v8@4.1.4)(jsdom@29.0.2)(vite@8.0.9(@types/node@25.6.0)(esbuild@0.27.7)(tsx@4.21.0))
+ version: 4.1.4(@types/node@25.6.0)(@vitest/coverage-v8@4.1.4)(jsdom@29.0.2)(vite@8.0.9(@types/node@25.6.0)(esbuild@0.27.7)(terser@5.50.0)(tsx@4.21.0))
packages/benchmarks:
dependencies:
@@ -56,7 +59,7 @@ importers:
version: 6.0.3
vitest:
specifier: ^4.1.0
- version: 4.1.4(@types/node@25.6.0)(@vitest/coverage-v8@4.1.4)(jsdom@29.0.2)(vite@8.0.9(@types/node@25.6.0)(esbuild@0.27.7)(tsx@4.21.0))
+ version: 4.1.4(@types/node@25.6.0)(@vitest/coverage-v8@4.1.4)(jsdom@29.0.2)(vite@8.0.9(@types/node@25.6.0)(esbuild@0.27.7)(terser@5.50.0)(tsx@4.21.0))
packages/markdown-formatter:
devDependencies:
@@ -68,7 +71,7 @@ importers:
version: 6.0.3
vitest:
specifier: ^4.1.0
- version: 4.1.4(@types/node@25.6.0)(@vitest/coverage-v8@4.1.4)(jsdom@29.0.2)(vite@8.0.9(@types/node@25.6.0)(esbuild@0.27.7)(tsx@4.21.0))
+ version: 4.1.4(@types/node@25.6.0)(@vitest/coverage-v8@4.1.4)(jsdom@29.0.2)(vite@8.0.9(@types/node@25.6.0)(esbuild@0.27.7)(terser@5.50.0)(tsx@4.21.0))
packages/markdown-parser:
devDependencies:
@@ -80,7 +83,7 @@ importers:
version: 6.0.3
vitest:
specifier: ^4.1.0
- version: 4.1.4(@types/node@25.6.0)(@vitest/coverage-v8@4.1.4)(jsdom@29.0.2)(vite@8.0.9(@types/node@25.6.0)(esbuild@0.27.7)(tsx@4.21.0))
+ version: 4.1.4(@types/node@25.6.0)(@vitest/coverage-v8@4.1.4)(jsdom@29.0.2)(vite@8.0.9(@types/node@25.6.0)(esbuild@0.27.7)(terser@5.50.0)(tsx@4.21.0))
packages/markdown-parser-lit:
dependencies:
@@ -116,44 +119,55 @@ importers:
packages/playground:
dependencies:
+ '@fuyeor/commons':
+ specifier: ^0.1.2
+ version: 0.1.2(vue@3.5.41(typescript@7.0.2))
+ '@fuyeor/config':
+ specifier: ^0.1.1
+ version: 0.1.1(vite@8.0.1(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2)(@types/node@26.2.0)(esbuild@0.27.7)(terser@5.50.0)(tsx@4.21.0))(vue@3.5.41(typescript@7.0.2))
+ '@fuyeor/interactify':
+ specifier: ^0.1.1
+ version: 0.1.1(vue@3.5.41(typescript@7.0.2))
'@fuyeor/locale':
- specifier: https://github.com/Fuyeor/webroamer.git#path:packages/locale
- version: https://codeload.github.com/Fuyeor/webroamer/tar.gz/c502f739f4b633ab24386fe9f90fb77ed9f6ca9d#path:packages/locale(@lit-labs/signals@0.2.0)(lit@3.3.2)(signal-polyfill@0.2.2)
- '@fuyeor/markdown-parser-lit':
- specifier: workspace:*
- version: link:../markdown-parser-lit
- lit:
- specifier: ^3.3.2
- version: 3.3.2
- devDependencies:
- '@types/node':
- specifier: ^25.6.0
- version: 25.6.0
- typescript:
- specifier: ^6.0.3
- version: 6.0.3
- vite:
- specifier: ^8.0.1
- version: 8.0.9(@types/node@25.6.0)(esbuild@0.27.7)(tsx@4.21.0)
-
- packages/vscode-extension:
- dependencies:
+ specifier: ^0.1.0
+ version: 0.1.0(vue@3.5.41(typescript@7.0.2))
'@fuyeor/markdown-formatter':
specifier: workspace:*
version: link:../markdown-formatter
+ '@fuyeor/markdown-parser':
+ specifier: workspace:*
+ version: link:../markdown-parser
+ '@fuyeor/markdown-parser-vue':
+ specifier: workspace:*
+ version: link:../markdown-parser-vue
+ '@fuyeor/vue-router':
+ specifier: ^0.0.9
+ version: 0.0.9(vue@3.5.41(typescript@7.0.2))
+ vue:
+ specifier: ^3.5.41
+ version: 3.5.41(typescript@7.0.2)
devDependencies:
- '@types/vscode':
- specifier: ^1.80.0
- version: 1.134.0
- esbuild:
- specifier: ^0.27.7
- version: 0.27.7
+ '@fuyeor/tsconfig':
+ specifier: 0.1.0
+ version: 0.1.0
+ '@types/node':
+ specifier: ^26.2.0
+ version: 26.2.0
+ '@vitejs/plugin-vue':
+ specifier: ^6.0.8
+ version: 6.0.8(vite@8.0.1(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2)(@types/node@26.2.0)(esbuild@0.27.7)(terser@5.50.0)(tsx@4.21.0))(vue@3.5.41(typescript@7.0.2))
+ html-minifier-terser:
+ specifier: ^7.2.0
+ version: 7.2.0
+ lightningcss:
+ specifier: ^1.33.0
+ version: 1.33.0
typescript:
- specifier: ^6.0.3
- version: 6.0.3
- vitest:
- specifier: ^4.1.0
- version: 4.1.4(@types/node@25.6.0)(@vitest/coverage-v8@4.1.4)(jsdom@29.0.2)(vite@8.0.9(@types/node@25.6.0)(esbuild@0.27.7)(tsx@4.21.0))
+ specifier: ^7.0.2
+ version: 7.0.2
+ vite:
+ specifier: 8.0.1
+ version: 8.0.1(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2)(@types/node@26.2.0)(esbuild@0.27.7)(terser@5.50.0)(tsx@4.21.0)
packages:
@@ -176,19 +190,36 @@ packages:
resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==}
engines: {node: '>=6.9.0'}
+ '@babel/helper-string-parser@7.29.7':
+ resolution: {integrity: sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==}
+ engines: {node: '>=6.9.0'}
+
'@babel/helper-validator-identifier@7.28.5':
resolution: {integrity: sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==}
engines: {node: '>=6.9.0'}
+ '@babel/helper-validator-identifier@7.29.7':
+ resolution: {integrity: sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==}
+ engines: {node: '>=6.9.0'}
+
'@babel/parser@7.29.2':
resolution: {integrity: sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA==}
engines: {node: '>=6.0.0'}
hasBin: true
+ '@babel/parser@7.29.8':
+ resolution: {integrity: sha512-E8lTAYNB1KW+FH+VGJuZM1ioAx2E6oVlvQFRrf5P8ZZmsiJXYAD9vTFV7yyEURNzgh1dFqMZuO6tUwcARbqFCA==}
+ engines: {node: '>=6.0.0'}
+ hasBin: true
+
'@babel/types@7.29.0':
resolution: {integrity: sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==}
engines: {node: '>=6.9.0'}
+ '@babel/types@7.29.8':
+ resolution: {integrity: sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg==}
+ engines: {node: '>=6.9.0'}
+
'@bcoe/v8-coverage@1.0.2':
resolution: {integrity: sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==}
engines: {node: '>=18'}
@@ -407,27 +438,97 @@ packages:
'@noble/hashes':
optional: true
- '@fuyeor/locale@https://codeload.github.com/Fuyeor/webroamer/tar.gz/c502f739f4b633ab24386fe9f90fb77ed9f6ca9d#path:packages/locale':
- resolution: {gitHosted: true, path: packages/locale, tarball: https://codeload.github.com/Fuyeor/webroamer/tar.gz/c502f739f4b633ab24386fe9f90fb77ed9f6ca9d}
- version: 1.0.0
+ '@floating-ui/core@1.8.0':
+ resolution: {integrity: sha512-0CIZ5itps/8x7BG8dEIhs53BvCUH2PCoogtakwRTut+Arm58sJooJ0AuZhLw2HJYIR5cMLNPBSS728sPho2khQ==}
+
+ '@floating-ui/dom@1.8.0':
+ resolution: {integrity: sha512-yXSrzeHZBTZadLOlfyhCkJHNeLJnHRnRInwdZ40L7ZiaAtrBwoYlsDrX3v5zB1Utk7CLfzcOVnVVWoXEky7Ceg==}
+
+ '@floating-ui/utils@0.2.12':
+ resolution: {integrity: sha512-HpCo8tmWzLVad5s2d19EhAz5zqrrQ6s69qd6moPMQvkOuSwDT1YgRfWSVuc4ennqrgv3OHppiOGMQ7oC13yIww==}
+
+ '@floating-ui/vue@1.1.11':
+ resolution: {integrity: sha512-HzHKCNVxnGS35r9fCHBc3+uCnjw9IWIlCPL683cGgM9Kgj2BiAl8x1mS7vtvP6F9S/e/q4O6MApwSHj8hNLGfw==}
+
+ '@fuyeor/commons@0.1.0':
+ resolution: {integrity: sha256-0ftDuLXPoQiLotiSRC2cmVF/+cViXO11ZV2pTpbkXbM=, tarball: https://fpm.fuyeor.net/packages/@fuyeor/commons/0.1.0.tgz}
+ peerDependencies:
+ vue: '*'
+
+ '@fuyeor/commons@0.1.2':
+ resolution: {integrity: sha256-X1Ejy4MgCaO8iFkvph8SavvY2kFJtaCXdJ6OYG8ceJE=, tarball: https://fpm.fuyeor.net/packages/@fuyeor/commons/0.1.2.tgz}
+ peerDependencies:
+ vue: '*'
+
+ '@fuyeor/config@0.1.1':
+ resolution: {integrity: sha256-YdN7bfe71NeQWfak1cpnKna7byyJ3PYhnBbqDFCDRVo=, tarball: https://fpm.fuyeor.net/packages/@fuyeor/config/0.1.1.tgz}
+ peerDependencies:
+ vite: ' ^8.0.0'
+
+ '@fuyeor/interactify@0.1.1':
+ resolution: {integrity: sha256-rz+0xlSjMcNjFnmliyDT3TkB8Y08XLiuRiTfZz7FqTQ=, tarball: https://fpm.fuyeor.net/packages/@fuyeor/interactify/0.1.1.tgz}
+ peerDependencies:
+ vue: '*'
+
+ '@fuyeor/locale-cli-linux-x64@0.1.1':
+ resolution: {integrity: sha256-yUuBN7NGq6kvNjAx+gK/FjSAAQIsQXdTrUOIPmDAlPU=, tarball: https://fpm.fuyeor.net/packages/@fuyeor/locale-cli-linux-x64/0.1.1.tgz}
+ cpu: [x64]
+ os: [linux]
+
+ '@fuyeor/locale-cli-win32-x64@0.1.1':
+ resolution: {integrity: sha256-kMoqlLWBKOyK1QSmiu50OCPt+oqfFRX1CF702XSum94=, tarball: https://fpm.fuyeor.net/packages/@fuyeor/locale-cli-win32-x64/0.1.1.tgz}
+ cpu: [x64]
+ os: [win32]
+
+ '@fuyeor/locale-cli@0.1.1':
+ resolution: {integrity: sha256-i4zBm0Akv/BHJaBQmt/kXHqNYcBcCVQYVFcLVqpO6Fw=, tarball: https://fpm.fuyeor.net/packages/@fuyeor/locale-cli/0.1.1.tgz}
+ hasBin: true
+
+ '@fuyeor/locale@0.1.0':
+ resolution: {integrity: sha256-ph3M1g9OPWb8WVAt+W0NeoDJT2uhb9ALn/NnCN0wvZU=, tarball: https://fpm.fuyeor.net/packages/@fuyeor/locale/0.1.0.tgz}
+ peerDependencies:
+ vue: '*'
+
+ '@fuyeor/tsconfig@0.1.0':
+ resolution: {integrity: sha256-j5XnwkpNS2BAC5xfOcxuE25IxO8TPT0XDjtQ7j8r2HU=, tarball: https://fpm.fuyeor.net/packages/@fuyeor/tsconfig/0.1.0.tgz}
+
+ '@fuyeor/vue-router@0.0.9':
+ resolution: {integrity: sha256-Hhbeyxi0tBIO7qPueEvCC1uzWdO2S3yCPZ8hPAQCMdM=, tarball: https://fpm.fuyeor.net/packages/@fuyeor/vue-router/0.0.9.tgz}
+ peerDependencies:
+ vue: '*'
+
+ '@fuyeor/vue-router@0.1.0':
+ resolution: {integrity: sha256-+y1kO8p9TdcQaYtZFjc/PJBs+Yykhr8h8mPjbGzJ95k=, tarball: https://fpm.fuyeor.net/packages/@fuyeor/vue-router/0.1.0.tgz}
peerDependencies:
- '@lit-labs/signals': '*'
- lit: '*'
- signal-polyfill: '*'
+ vue: '*'
+
+ '@fuyeor/vue-vaul@0.4.8':
+ resolution: {integrity: sha256-0dlkmUCIbw4hFtYoMO678Si2tTOF3dfSVN8fWWau2Eg=, tarball: https://fpm.fuyeor.net/packages/@fuyeor/vue-vaul/0.4.8.tgz}
+ peerDependencies:
+ vue: ^3.4.0
+
+ '@internationalized/date@3.12.3':
+ resolution: {integrity: sha512-fuLX+3ZKLsxI73y8b01EG/WjHb6gE6weCqlfawPO27kBWGMh9G1yH6Csv1uU7/cac9H2GHmOMt6CjmuQ1aia4Q==}
+
+ '@internationalized/number@3.6.7':
+ resolution: {integrity: sha512-3ji1fcrT+FPAK86UqEhB/psHixYo6niWPJtt7+qRaYFynt/BaJG8GhAPimtWUpEiVSTq8ZM8L5psMxGquiB/Vg==}
+
+ '@jridgewell/gen-mapping@0.3.13':
+ resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==}
'@jridgewell/resolve-uri@3.1.2':
resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==}
engines: {node: '>=6.0.0'}
+ '@jridgewell/source-map@0.3.11':
+ resolution: {integrity: sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==}
+
'@jridgewell/sourcemap-codec@1.5.5':
resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==}
'@jridgewell/trace-mapping@0.3.31':
resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==}
- '@lit-labs/signals@0.2.0':
- resolution: {integrity: sha512-68plyIbciumbwKaiilhLNyhz4Vg6/+nJwDufG2xxWA9r/fUw58jxLHCAlKs+q1CE5Lmh3cZ3ShyYKnOCebEpVA==}
-
'@lit-labs/ssr-dom-shim@1.5.1':
resolution: {integrity: sha512-Aou5UdlSpr5whQe8AA/bZG0jMj96CoJIWbGfZ91qieWu5AWUMKw8VR/pAkQkJYvBNhmCcWnZlyyk5oze8JIqYA==}
@@ -440,39 +541,79 @@ packages:
'@emnapi/core': ^1.7.1
'@emnapi/runtime': ^1.7.1
+ '@oxc-project/types@0.120.0':
+ resolution: {integrity: sha512-k1YNu55DuvAip/MGE1FTsIuU3FUCn6v/ujG9V7Nq5Df/kX2CWb13hhwD0lmJGMGqE+bE1MXvv9SZVnMzEXlWcg==}
+
'@oxc-project/types@0.126.0':
resolution: {integrity: sha512-oGfVtjAgwQVVpfBrbtk4e1XDyWHRFta6BS3GWVzrF8xYBT2VGQAk39yJS/wFSMrZqoiCU4oghT3Ch0HaHGIHcQ==}
+ '@rolldown/binding-android-arm64@1.0.0-rc.10':
+ resolution: {integrity: sha512-jOHxwXhxmFKuXztiu1ORieJeTbx5vrTkcOkkkn2d35726+iwhrY1w/+nYY/AGgF12thg33qC3R1LMBF5tHTZHg==}
+ engines: {node: ^20.19.0 || >=22.12.0}
+ cpu: [arm64]
+ os: [android]
+
'@rolldown/binding-android-arm64@1.0.0-rc.16':
resolution: {integrity: sha512-rhY3k7Bsae9qQfOtph2Pm2jZEA+s8Gmjoz4hhmx70K9iMQ/ddeae+xhRQcM5IuVx5ry1+bGfkvMn7D6MJggVSA==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [arm64]
os: [android]
+ '@rolldown/binding-darwin-arm64@1.0.0-rc.10':
+ resolution: {integrity: sha512-gED05Teg/vtTZbIJBc4VNMAxAFDUPkuO/rAIyyxZjTj1a1/s6z5TII/5yMGZ0uLRCifEtwUQn8OlYzuYc0m70w==}
+ engines: {node: ^20.19.0 || >=22.12.0}
+ cpu: [arm64]
+ os: [darwin]
+
'@rolldown/binding-darwin-arm64@1.0.0-rc.16':
resolution: {integrity: sha512-rNz0yK078yrNn3DrdgN+PKiMOW8HfQ92jQiXxwX8yW899ayV00MLVdaCNeVBhG/TbH3ouYVObo8/yrkiectkcQ==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [arm64]
os: [darwin]
+ '@rolldown/binding-darwin-x64@1.0.0-rc.10':
+ resolution: {integrity: sha512-rI15NcM1mA48lqrIxVkHfAqcyFLcQwyXWThy+BQ5+mkKKPvSO26ir+ZDp36AgYoYVkqvMcdS8zOE6SeBsR9e8A==}
+ engines: {node: ^20.19.0 || >=22.12.0}
+ cpu: [x64]
+ os: [darwin]
+
'@rolldown/binding-darwin-x64@1.0.0-rc.16':
resolution: {integrity: sha512-r/OmdR00HmD4i79Z//xO06uEPOq5hRXdhw7nzkxQxwSavs3PSHa1ijntdpOiZ2mzOQ3fVVu8C1M19FoNM+dMUQ==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [x64]
os: [darwin]
+ '@rolldown/binding-freebsd-x64@1.0.0-rc.10':
+ resolution: {integrity: sha512-XZRXHdTa+4ME1MuDVp021+doQ+z6Ei4CCFmNc5/sKbqb8YmkiJdj8QKlV3rCI0AJtAeSB5n0WGPuJWNL9p/L2w==}
+ engines: {node: ^20.19.0 || >=22.12.0}
+ cpu: [x64]
+ os: [freebsd]
+
'@rolldown/binding-freebsd-x64@1.0.0-rc.16':
resolution: {integrity: sha512-KcRE5w8h0OnjUatG8pldyD14/CQ5Phs1oxfR+3pKDjboHRo9+MkqQaiIZlZRpsxC15paeXme/I127tUa9TXJ6g==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [x64]
os: [freebsd]
+ '@rolldown/binding-linux-arm-gnueabihf@1.0.0-rc.10':
+ resolution: {integrity: sha512-R0SQMRluISSLzFE20sPWYHVmJdDQnRyc/FzSCN72BqQmh2SOZUFG+N3/vBZpR4C6WpEUVYJLrYUXaj43sJsNLA==}
+ engines: {node: ^20.19.0 || >=22.12.0}
+ cpu: [arm]
+ os: [linux]
+
'@rolldown/binding-linux-arm-gnueabihf@1.0.0-rc.16':
resolution: {integrity: sha512-bT0guA1bpxEJ/ZhTRniQf7rNF8ybvXOuWbNIeLABaV5NGjx4EtOWBTSRGWFU9ZWVkPOZ+HNFP8RMcBokBiZ0Kg==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [arm]
os: [linux]
+ '@rolldown/binding-linux-arm64-gnu@1.0.0-rc.10':
+ resolution: {integrity: sha512-Y1reMrV/o+cwpduYhJuOE3OMKx32RMYCidf14y+HssARRmhDuWXJ4yVguDg2R/8SyyGNo+auzz64LnPK9Hq6jg==}
+ engines: {node: ^20.19.0 || >=22.12.0}
+ cpu: [arm64]
+ os: [linux]
+ libc: [glibc]
+
'@rolldown/binding-linux-arm64-gnu@1.0.0-rc.16':
resolution: {integrity: sha512-+tHktCHWV8BDQSjemUqm/Jl/TPk3QObCTIjmdDy/nlupcujZghmKK2962LYrqFpWu+ai01AN/REOH3NEpqvYQg==}
engines: {node: ^20.19.0 || >=22.12.0}
@@ -480,6 +621,13 @@ packages:
os: [linux]
libc: [glibc]
+ '@rolldown/binding-linux-arm64-musl@1.0.0-rc.10':
+ resolution: {integrity: sha512-vELN+HNb2IzuzSBUOD4NHmP9yrGwl1DVM29wlQvx1OLSclL0NgVWnVDKl/8tEks79EFek/kebQKnNJkIAA4W2g==}
+ engines: {node: ^20.19.0 || >=22.12.0}
+ cpu: [arm64]
+ os: [linux]
+ libc: [musl]
+
'@rolldown/binding-linux-arm64-musl@1.0.0-rc.16':
resolution: {integrity: sha512-3fPzdREH806oRLxpTWW1Gt4tQHs0TitZFOECB2xzCFLPKnSOy90gwA7P29cksYilFO6XVRY1kzga0cL2nRjKPg==}
engines: {node: ^20.19.0 || >=22.12.0}
@@ -487,6 +635,13 @@ packages:
os: [linux]
libc: [musl]
+ '@rolldown/binding-linux-ppc64-gnu@1.0.0-rc.10':
+ resolution: {integrity: sha512-ZqrufYTgzxbHwpqOjzSsb0UV/aV2TFIY5rP8HdsiPTv/CuAgCRjM6s9cYFwQ4CNH+hf9Y4erHW1GjZuZ7WoI7w==}
+ engines: {node: ^20.19.0 || >=22.12.0}
+ cpu: [ppc64]
+ os: [linux]
+ libc: [glibc]
+
'@rolldown/binding-linux-ppc64-gnu@1.0.0-rc.16':
resolution: {integrity: sha512-EKwI1tSrLs7YVw+JPJT/G2dJQ1jl9qlTTTEG0V2Ok/RdOenRfBw2PQdLPyjhIu58ocdBfP7vIRN/pvMsPxs/AQ==}
engines: {node: ^20.19.0 || >=22.12.0}
@@ -494,6 +649,13 @@ packages:
os: [linux]
libc: [glibc]
+ '@rolldown/binding-linux-s390x-gnu@1.0.0-rc.10':
+ resolution: {integrity: sha512-gSlmVS1FZJSRicA6IyjoRoKAFK7IIHBs7xJuHRSmjImqk3mPPWbR7RhbnfH2G6bcmMEllCt2vQ/7u9e6bBnByg==}
+ engines: {node: ^20.19.0 || >=22.12.0}
+ cpu: [s390x]
+ os: [linux]
+ libc: [glibc]
+
'@rolldown/binding-linux-s390x-gnu@1.0.0-rc.16':
resolution: {integrity: sha512-Uknladnb3Sxqu6SEcqBldQyJUpk8NleooZEc0MbRBJ4inEhRYWZX0NJu12vNf2mqAq7gsofAxHrGghiUYjhaLQ==}
engines: {node: ^20.19.0 || >=22.12.0}
@@ -501,6 +663,13 @@ packages:
os: [linux]
libc: [glibc]
+ '@rolldown/binding-linux-x64-gnu@1.0.0-rc.10':
+ resolution: {integrity: sha512-eOCKUpluKgfObT2pHjztnaWEIbUabWzk3qPZ5PuacuPmr4+JtQG4k2vGTY0H15edaTnicgU428XW/IH6AimcQw==}
+ engines: {node: ^20.19.0 || >=22.12.0}
+ cpu: [x64]
+ os: [linux]
+ libc: [glibc]
+
'@rolldown/binding-linux-x64-gnu@1.0.0-rc.16':
resolution: {integrity: sha512-FIb8+uG49sZBtLTn+zt1AJ20TqVcqWeSIyoVt0or7uAWesgKaHbiBh6OpA/k9v0LTt+PTrb1Lao133kP4uVxkg==}
engines: {node: ^20.19.0 || >=22.12.0}
@@ -508,6 +677,13 @@ packages:
os: [linux]
libc: [glibc]
+ '@rolldown/binding-linux-x64-musl@1.0.0-rc.10':
+ resolution: {integrity: sha512-Xdf2jQbfQowJnLcgYfD/m0Uu0Qj5OdxKallD78/IPPfzaiaI4KRAwZzHcKQ4ig1gtg1SuzC7jovNiM2TzQsBXA==}
+ engines: {node: ^20.19.0 || >=22.12.0}
+ cpu: [x64]
+ os: [linux]
+ libc: [musl]
+
'@rolldown/binding-linux-x64-musl@1.0.0-rc.16':
resolution: {integrity: sha512-RuERhF9/EgWxZEXYWCOaViUWHIboceK4/ivdtQ3R0T44NjLkIIlGIAVAuCddFxsZ7vnRHtNQUrt2vR2n2slB2w==}
engines: {node: ^20.19.0 || >=22.12.0}
@@ -515,35 +691,75 @@ packages:
os: [linux]
libc: [musl]
+ '@rolldown/binding-openharmony-arm64@1.0.0-rc.10':
+ resolution: {integrity: sha512-o1hYe8hLi1EY6jgPFyxQgQ1wcycX+qz8eEbVmot2hFkgUzPxy9+kF0u0NIQBeDq+Mko47AkaFFaChcvZa9UX9Q==}
+ engines: {node: ^20.19.0 || >=22.12.0}
+ cpu: [arm64]
+ os: [openharmony]
+
'@rolldown/binding-openharmony-arm64@1.0.0-rc.16':
resolution: {integrity: sha512-mXcXnvd9GpazCxeUCCnZ2+YF7nut+ZOEbE4GtaiPtyY6AkhZWbK70y1KK3j+RDhjVq5+U8FySkKRb/+w0EeUwA==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [arm64]
os: [openharmony]
+ '@rolldown/binding-wasm32-wasi@1.0.0-rc.10':
+ resolution: {integrity: sha512-Ugv9o7qYJudqQO5Y5y2N2SOo6S4WiqiNOpuQyoPInnhVzCY+wi/GHltcLHypG9DEUYMB0iTB/huJrpadiAcNcA==}
+ engines: {node: '>=14.0.0'}
+ cpu: [wasm32]
+
'@rolldown/binding-wasm32-wasi@1.0.0-rc.16':
resolution: {integrity: sha512-3Q2KQxnC8IJOLqXmUMoYwyIPZU9hzRbnHaoV3Euz+VVnjZKcY8ktnNP8T9R4/GGQtb27C/UYKABxesKWb8lsvQ==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [wasm32]
+ '@rolldown/binding-win32-arm64-msvc@1.0.0-rc.10':
+ resolution: {integrity: sha512-7UODQb4fQUNT/vmgDZBl3XOBAIOutP5R3O/rkxg0aLfEGQ4opbCgU5vOw/scPe4xOqBwL9fw7/RP1vAMZ6QlAQ==}
+ engines: {node: ^20.19.0 || >=22.12.0}
+ cpu: [arm64]
+ os: [win32]
+
'@rolldown/binding-win32-arm64-msvc@1.0.0-rc.16':
resolution: {integrity: sha512-tj7XRemQcOcFwv7qhpUxMTBbI5mWMlE4c1Omhg5+h8GuLXzyj8HviYgR+bB2DMDgRqUE+jiDleqSCRjx4aYk/Q==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [arm64]
os: [win32]
+ '@rolldown/binding-win32-x64-msvc@1.0.0-rc.10':
+ resolution: {integrity: sha512-PYxKHMVHOb5NJuDL53vBUl1VwUjymDcYI6rzpIni0C9+9mTiJedvUxSk7/RPp7OOAm3v+EjgMu9bIy3N6b408w==}
+ engines: {node: ^20.19.0 || >=22.12.0}
+ cpu: [x64]
+ os: [win32]
+
'@rolldown/binding-win32-x64-msvc@1.0.0-rc.16':
resolution: {integrity: sha512-PH5DRZT+F4f2PTXRXR8uJxnBq2po/xFtddyabTJVJs/ZYVHqXPEgNIr35IHTEa6bpa0Q8Awg+ymkTaGnKITw4g==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [x64]
os: [win32]
+ '@rolldown/pluginutils@1.0.0-rc.10':
+ resolution: {integrity: sha512-UkVDEFk1w3mveXeKgaTuYfKWtPbvgck1dT8TUG3bnccrH0XtLTuAyfCoks4Q/M5ZGToSVJTIQYCzy2g/atAOeg==}
+
'@rolldown/pluginutils@1.0.0-rc.16':
resolution: {integrity: sha512-45+YtqxLYKDWQouLKCrpIZhke+nXxhsw+qAHVzHDVwttyBlHNBVs2K25rDXrZzhpTp9w1FlAlvweV1H++fdZoA==}
+ '@rolldown/pluginutils@1.0.1':
+ resolution: {integrity: sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==}
+
'@standard-schema/spec@1.1.0':
resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==}
+ '@swc/helpers@0.5.23':
+ resolution: {integrity: sha512-5lSsMOTXURePglDfvuAQUqkGek9Hg2kksOYay2m0+XR++b2NWYL/4sWyuvVBIs8oKnJaxkdi9whaL/sqN13afw==}
+
+ '@tanstack/virtual-core@3.17.8':
+ resolution: {integrity: sha512-BfEvehNpOT75r5Ksc5xW6NZuXujTfb7nlSEyVu4XHG3gdxNg1KqXruWbDewXOUaUYIo4oRbSfkjIajz4MAT8tA==}
+
+ '@tanstack/vue-virtual@3.13.36':
+ resolution: {integrity: sha512-gKpExv4RbB9luVG+SucTXoqPZv/gzu/Yvz6BNO+8kpNxJ2x+I/ulryzl5W9BRciahZGp5Tls3Dp5XP1ztVGbMw==}
+ peerDependencies:
+ vue: ^2.7.0 || ^3.0.0
+
'@tybys/wasm-util@0.10.1':
resolution: {integrity: sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==}
@@ -559,11 +775,141 @@ packages:
'@types/node@25.6.0':
resolution: {integrity: sha512-+qIYRKdNYJwY3vRCZMdJbPLJAtGjQBudzZzdzwQYkEPQd+PJGixUL5QfvCLDaULoLv+RhT3LDkwEfKaAkgSmNQ==}
+ '@types/node@26.2.0':
+ resolution: {integrity: sha512-5IviulTZeRNp2vAJ514cc/HUlY5nZ9fCbq9DMyC52BrhFZACo3nI0R7qBxhQmo/d27NFe96ur/b7Wwxklda+kg==}
+
'@types/trusted-types@2.0.7':
resolution: {integrity: sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==}
- '@types/vscode@1.134.0':
- resolution: {integrity: sha512-NDEu0hg4sF7+vvFsADsktqUJ6f80LHSZvVK2Ovo1XiQ0/VHck1O3zst+ZZyVA/uvz6vo6LcuoqU2q48YMqOwWw==}
+ '@types/web-bluetooth@0.0.21':
+ resolution: {integrity: sha512-oIQLCGWtcFZy2JW77j9k8nHzAOpqMHLQejDA48XXMWH6tjCQHz5RCFz1bzsmROyL6PUm+LLnUiI4BCn221inxA==}
+
+ '@typescript/typescript-aix-ppc64@7.0.2':
+ resolution: {integrity: sha512-MTKKkWB7p/0E9xi1d1tHtZ5PiLkGEMIq88pK2CubZjOsLtYTLqhgIgi6zepFa+9GHZ6h05NMCkQxGKiPXMxXtQ==}
+ engines: {node: '>=16.20.0'}
+ cpu: [ppc64]
+ os: [aix]
+
+ '@typescript/typescript-darwin-arm64@7.0.2':
+ resolution: {integrity: sha512-gowzar9MwS/aRWp6f3a4KUqzRjAZjOsmGNCM6LcTgXum+dBfgsBVMN+AgvOCCbguXyick6LJhpBszxMebJ8syA==}
+ engines: {node: '>=16.20.0'}
+ cpu: [arm64]
+ os: [darwin]
+
+ '@typescript/typescript-darwin-x64@7.0.2':
+ resolution: {integrity: sha512-SZ9xZInqApNlNGc9s0W1VSsktYSOe9cFqNOIqmN1Gs8SmkjKZYFt017G4VwPxASInODuAdbTW7sXiFUf893RgA==}
+ engines: {node: '>=16.20.0'}
+ cpu: [x64]
+ os: [darwin]
+
+ '@typescript/typescript-freebsd-arm64@7.0.2':
+ resolution: {integrity: sha512-W5NH4y/J0plIIS5b2xvTEkU7JFxyqdMAOgf+Ilhl0vHQXKO5dZoxd+C/jEtq56c4F3wk71RB4BMRQ2XdI+bwYQ==}
+ engines: {node: '>=16.20.0'}
+ cpu: [arm64]
+ os: [freebsd]
+
+ '@typescript/typescript-freebsd-x64@7.0.2':
+ resolution: {integrity: sha512-UMGDx5sTpzNw3WiPebH7l90IWfJggEd+egHt/q6p7/Cm3zqoV7VxkGXt+3DxPIw8CcmvAB0j3sVVfbhX+M4Tpw==}
+ engines: {node: '>=16.20.0'}
+ cpu: [x64]
+ os: [freebsd]
+
+ '@typescript/typescript-linux-arm64@7.0.2':
+ resolution: {integrity: sha512-Qh4eU4/y3yDjnfjjyPYihMj5/ODIlmt+Bzu17OI+fiSRDW57QmU5SiN63exPRNJPKUzcc1INa1NXdrJ+MqHjUQ==}
+ engines: {node: '>=16.20.0'}
+ cpu: [arm64]
+ os: [linux]
+
+ '@typescript/typescript-linux-arm@7.0.2':
+ resolution: {integrity: sha512-gffT3xPz9sR7j/YJExkyPntrI0P2EP9XbOyWzth2/Gs0RstK+90RBcO0ncXoXy/beYll1SXw846Nf2zdnEz0QQ==}
+ engines: {node: '>=16.20.0'}
+ cpu: [arm]
+ os: [linux]
+
+ '@typescript/typescript-linux-loong64@7.0.2':
+ resolution: {integrity: sha512-uEHck9i8hoAzXPiYRib1O7miOnz23SxIeVl6F4LXox+qov1K35jHcEW6VHKvZI+pyvl7fZEP4MCU5LYvIq1GuQ==}
+ engines: {node: '>=16.20.0'}
+ cpu: [loong64]
+ os: [linux]
+
+ '@typescript/typescript-linux-mips64el@7.0.2':
+ resolution: {integrity: sha512-R4KvAMnE43W5Qeqb0Ly56O3mWMWIAgsMyz36DCaycd5nbg/9kzm0liw3JocfRqyJY0KPmzFjbswozXyW0DnIYA==}
+ engines: {node: '>=16.20.0'}
+ cpu: [mips64el]
+ os: [linux]
+
+ '@typescript/typescript-linux-ppc64@7.0.2':
+ resolution: {integrity: sha512-DORx5b3sd/4S7eayxm4FQv+A7CrkUIGRaHiwI8oiHTAI1fAPWhF4J0vAlkC8biAlHSVVwxMQ3tjZ2/DVbnQiiA==}
+ engines: {node: '>=16.20.0'}
+ cpu: [ppc64]
+ os: [linux]
+
+ '@typescript/typescript-linux-riscv64@7.0.2':
+ resolution: {integrity: sha512-wf0jqEDOjrPRnKwYRyyJDRo11KMbvMFrU+q4zqKyChODBzvlkbhNQfKvLxQCcwTpdDaXSHZTVuh0JoCrKCUMHQ==}
+ engines: {node: '>=16.20.0'}
+ cpu: [riscv64]
+ os: [linux]
+
+ '@typescript/typescript-linux-s390x@7.0.2':
+ resolution: {integrity: sha512-IkwJc3L7yhytWd/ewjyxNDfOmswCm9GWMJT/ue/dU4aZNbwZeYAetq42VyLmsmSjvoX7z74X6ZaYCtzAr0EuGw==}
+ engines: {node: '>=16.20.0'}
+ cpu: [s390x]
+ os: [linux]
+
+ '@typescript/typescript-linux-x64@7.0.2':
+ resolution: {integrity: sha512-EYdf2cNg7rgCWJnxCdJ+F3V39O8ihb37eHAu1LK8oAFizgTQbPOK7zHHXbPt8rX24COqODXeI3sIf0fCXG7H/A==}
+ engines: {node: '>=16.20.0'}
+ cpu: [x64]
+ os: [linux]
+
+ '@typescript/typescript-netbsd-arm64@7.0.2':
+ resolution: {integrity: sha512-+polYF4MF04aPpO5FTkHran9yUQDSXqy5GiSDKpsll5jy3l3+g9QLhpf39T+ePtefhXLOGrLl0QIjkQP6VnelA==}
+ engines: {node: '>=16.20.0'}
+ cpu: [arm64]
+ os: [netbsd]
+
+ '@typescript/typescript-netbsd-x64@7.0.2':
+ resolution: {integrity: sha512-8YIT0EHM/3dq10ZOVF/A7pc/YSMtbcecct4rWtexrnSCHOPcpC2KTLXfTCR6vDpnSiY12heNb1GiN/wu+T/FyA==}
+ engines: {node: '>=16.20.0'}
+ cpu: [x64]
+ os: [netbsd]
+
+ '@typescript/typescript-openbsd-arm64@7.0.2':
+ resolution: {integrity: sha512-APT8+ClYnuYm1u9+kgGXoMj2VzWzcymwh2gNSQVySHfkRDGOTVkoWLjCmOQSaO+PoqQ57B0flRp9SA+7GnnkzQ==}
+ engines: {node: '>=16.20.0'}
+ cpu: [arm64]
+ os: [openbsd]
+
+ '@typescript/typescript-openbsd-x64@7.0.2':
+ resolution: {integrity: sha512-yX7s+Q0Dln0Dt9tEzZsAjXXR/+ytBM7AlglaqyeMPxQszJ1JhlJdZ6jLA+IzldHtflX81em7lDao1xXu+aRRkg==}
+ engines: {node: '>=16.20.0'}
+ cpu: [x64]
+ os: [openbsd]
+
+ '@typescript/typescript-sunos-x64@7.0.2':
+ resolution: {integrity: sha512-dLJDGaLZ1D4HPQn62u1n8mBDkJREwMsAkCdkwd4Ieqw+x3TUyTsqY0YiBCtE6H6OzzgGk3iuZ3vFWRS+E8/d1g==}
+ engines: {node: '>=16.20.0'}
+ cpu: [x64]
+ os: [sunos]
+
+ '@typescript/typescript-win32-arm64@7.0.2':
+ resolution: {integrity: sha512-Gyl1Vy6OsWesLzmq+EP0Fb7b4Nid5232AvcA2SFcdYreldpNtYFFofPjnt62y9hQy7VTaZp65ICJjuAQRaVcIQ==}
+ engines: {node: '>=16.20.0'}
+ cpu: [arm64]
+ os: [win32]
+
+ '@typescript/typescript-win32-x64@7.0.2':
+ resolution: {integrity: sha512-0BQ3HkAHHlKLSp1qRvf3SUhGpGsDuhB/jgFw75guyqbxJqEaS0Cw/VFO8i2nHglJUzQCRtMMR/IBAKE3ETMC4g==}
+ engines: {node: '>=16.20.0'}
+ cpu: [x64]
+ os: [win32]
+
+ '@vitejs/plugin-vue@6.0.8':
+ resolution: {integrity: sha512-0ZjgOg7oO6farnNGup7yvoM/YXZV84OZxHAwtflItNa/6zzQyVb5LNxyea3FEKEX2XlagIKzrlH7wwxkKgtiew==}
+ engines: {node: ^20.19.0 || >=22.12.0}
+ peerDependencies:
+ vite: ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0
+ vue: ^3.2.25
'@vitest/coverage-v8@4.1.4':
resolution: {integrity: sha512-x7FptB5oDruxNPDNY2+S8tCh0pcq7ymCe1gTHcsp733jYjrJl8V1gMUlVysuCD9Kz46Xz9t1akkv08dPcYDs1w==}
@@ -606,35 +952,84 @@ packages:
'@vue/compiler-core@3.5.32':
resolution: {integrity: sha512-4x74Tbtqnda8s/NSD6e1Dr5p1c8HdMU5RWSjMSUzb8RTcUQqevDCxVAitcLBKT+ie3o0Dl9crc/S/opJM7qBGQ==}
+ '@vue/compiler-core@3.5.41':
+ resolution: {integrity: sha512-q0Xtv/F9w2YO/7htQhtiL+Ev2WCJbe5N2hc+XfgyKkEKqWpSxknmT8QOuGdEKNdjPq0c3F7rNpFkTo3Kfrm7pg==}
+
'@vue/compiler-dom@3.5.32':
resolution: {integrity: sha512-ybHAu70NtiEI1fvAUz3oXZqkUYEe5J98GjMDpTGl5iHb0T15wQYLR4wE3h9xfuTNA+Cm2f4czfe8B4s+CCH57Q==}
+ '@vue/compiler-dom@3.5.41':
+ resolution: {integrity: sha512-oKacVfNglLvGjnS6BXOlGL7EyG2h8X03pqXCjzotRZUaXGjbrTJUnVAQjrCqUnS+lyu31nwQjZY/d817GmCnfw==}
+
'@vue/compiler-sfc@3.5.32':
resolution: {integrity: sha512-8UYUYo71cP/0YHMO814TRZlPuUUw3oifHuMR7Wp9SNoRSrxRQnhMLNlCeaODNn6kNTJsjFoQ/kqIj4qGvya4Xg==}
+ '@vue/compiler-sfc@3.5.41':
+ resolution: {integrity: sha512-XJhip7R2wy6vX3knCxdZN4KracFaZUef58s1KYewqluedHIJaPIVfXoYT7MF1F8nCvv6k8bWWxDC8opMkg1VTQ==}
+
'@vue/compiler-ssr@3.5.32':
resolution: {integrity: sha512-Gp4gTs22T3DgRotZ8aA/6m2jMR+GMztvBXUBEUOYOcST+giyGWJ4WvFd7QLHBkzTxkfOt8IELKNdpzITLbA2rw==}
+ '@vue/compiler-ssr@3.5.41':
+ resolution: {integrity: sha512-U3v5OejKEGqOI0Wy0+Sz7hGuIFZHA4LSXzrNM3IMIeDyJEBBfTpX26n3SDgToRpP2bLc9FfI2j/kSgcJ8Emq5A==}
+
'@vue/reactivity@3.5.32':
resolution: {integrity: sha512-/ORasxSGvZ6MN5gc+uE364SxFdJ0+WqVG0CENXaGW58TOCdrAW76WWaplDtECeS1qphvtBZtR+3/o1g1zL4xPQ==}
+ '@vue/reactivity@3.5.41':
+ resolution: {integrity: sha512-rznsqKM0np0x18EjzF8x88MpEhdNsffbvFbckLL5+oUKz1BxAImEmO7J1ArRYSyo6aQaVoBDp7jEkT91OOxydA==}
+
'@vue/runtime-core@3.5.32':
resolution: {integrity: sha512-pDrXCejn4UpFDFmMd27AcJEbHaLemaE5o4pbb7sLk79SRIhc6/t34BQA7SGNgYtbMnvbF/HHOftYBgFJtUoJUQ==}
+ '@vue/runtime-core@3.5.41':
+ resolution: {integrity: sha512-Vcry58hiAKwGen9Z1jUZE0feFsNArPCMOImYI8el48A9Idf6DuQYD0U05zZIF2Iad1hGhPSvcbBbAOhNr55fhg==}
+
'@vue/runtime-dom@3.5.32':
resolution: {integrity: sha512-1CDVv7tv/IV13V8Nip1k/aaObVbWqRlVCVezTwx3K07p7Vxossp5JU1dcPNhJk3w347gonIUT9jQOGutyJrSVQ==}
+ '@vue/runtime-dom@3.5.41':
+ resolution: {integrity: sha512-3vVBahVBS9+U6cmXBLyb8nE6/yYo4J/CGI9eVFs3KiMc0YHuudwKyShTD65jtJy/L9PUUxNAFu4cj4LiJ0UFbw==}
+
'@vue/server-renderer@3.5.32':
resolution: {integrity: sha512-IOjm2+JQwRFS7W28HNuJeXQle9KdZbODFY7hFGVtnnghF51ta20EWAZJHX+zLGtsHhaU6uC9BGPV52KVpYryMQ==}
peerDependencies:
vue: 3.5.32
+ '@vue/server-renderer@3.5.41':
+ resolution: {integrity: sha512-n6hx/pNFfbD6SuyeuMVkvqox8bwf/ET9JlA/kAz/imw8sw++wkqKe2mHX5KutjPpbKE4Z56yTHszoOjGMI9igQ==}
+
'@vue/shared@3.5.32':
resolution: {integrity: sha512-ksNyrmRQzWJJ8n3cRDuSF7zNNontuJg1YHnmWRJd2AMu8Ij2bqwiiri2lH5rHtYPZjj4STkNcgcmiQqlOjiYGg==}
+ '@vue/shared@3.5.41':
+ resolution: {integrity: sha512-IOnwSCma8j+9xJT6b8H0dEYidC80NsYmNMlZxRsukYcSoGaDBohog5hDxzeUXdFeGWFA++vWvxqOmrr96VlqMA==}
+
+ '@vueuse/core@14.4.0':
+ resolution: {integrity: sha512-X4WHz1HlCzCBoYXesUkifzzWBAcZgXG8Fi5iNPQg/epdzOB3gu8Fawj3hvuwYR1nGcXGnvxwYYcUC/71++svtQ==}
+ peerDependencies:
+ vue: ^3.5.0
+
+ '@vueuse/metadata@14.4.0':
+ resolution: {integrity: sha512-swx/255R6JyHZFJhx845iz5CRWDZdCfvkZOpACWc5+c5WHcG24mv8gUT1WIdFQaHt6dq79rvILd9QnCWiyVm9g==}
+
+ '@vueuse/shared@14.4.0':
+ resolution: {integrity: sha512-JRgY90Sz8DDtPMsaDflvPMp9xYk69JZAmbuDvAquUVXKr2gEjqtzGNTTthLfckH0BzBqvnu31gb4a8TGLRe79g==}
+ peerDependencies:
+ vue: ^3.5.0
+
+ acorn@8.18.0:
+ resolution: {integrity: sha512-lGq+9yr1/GuAWaVYIHRjvvySG5/4VfKIvC8EWxStPdcDh/Ka7FG3twP6v4d5BkravUilhIAsG4Qj83t02LWUPQ==}
+ engines: {node: '>=0.4.0'}
+ hasBin: true
+
argparse@2.0.1:
resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==}
+ aria-hidden@1.2.6:
+ resolution: {integrity: sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA==}
+ engines: {node: '>=10'}
+
assertion-error@2.0.1:
resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==}
engines: {node: '>=12'}
@@ -645,10 +1040,27 @@ packages:
bidi-js@1.0.3:
resolution: {integrity: sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw==}
+ buffer-from@1.1.2:
+ resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==}
+
+ camel-case@4.1.2:
+ resolution: {integrity: sha512-gxGWBrTT1JuMx6R+o5PTXMmUnhnVzLQ9SNutD4YqKtI6ap897t3tKECYla6gCWEkplXnlNybEkZg9GEGxKFCgw==}
+
chai@6.2.2:
resolution: {integrity: sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==}
engines: {node: '>=18'}
+ clean-css@5.3.3:
+ resolution: {integrity: sha512-D5J+kHaVb/wKSFcyyV75uCn8fiY4sV38XJoe4CUyGQ+mOU/fMVYUdH1hJC+CJQ5uY3EnW27SbJYS4X8BiLrAFg==}
+ engines: {node: '>= 10.0'}
+
+ commander@10.0.1:
+ resolution: {integrity: sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==}
+ engines: {node: '>=14'}
+
+ commander@2.20.3:
+ resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==}
+
convert-source-map@2.0.0:
resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==}
@@ -666,10 +1078,16 @@ packages:
decimal.js@10.6.0:
resolution: {integrity: sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==}
+ defu@6.1.7:
+ resolution: {integrity: sha512-7z22QmUWiQ/2d0KkdYmANbRUVABpZ9SNYyH5vx6PZ+nE5bcC0l7uFvEfHlyld/HcGBFTL536ClDt3DEcSlEJAQ==}
+
detect-libc@2.1.2:
resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==}
engines: {node: '>=8'}
+ dot-case@3.0.4:
+ resolution: {integrity: sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==}
+
entities@4.5.0:
resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==}
engines: {node: '>=0.12'}
@@ -728,6 +1146,11 @@ packages:
html-escaper@2.0.2:
resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==}
+ html-minifier-terser@7.2.0:
+ resolution: {integrity: sha512-tXgn3QfqPIpGl9o+K5tpcj3/MN4SfLtsx2GWwBC3SSd0tXQGyF3gsSqad8loJgKZGM3ZxbYDd5yhiBIdWpmvLA==}
+ engines: {node: ^14.13.1 || >=16.0.0}
+ hasBin: true
+
is-potential-custom-element-name@1.0.1:
resolution: {integrity: sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==}
@@ -761,30 +1184,60 @@ packages:
cpu: [arm64]
os: [android]
+ lightningcss-android-arm64@1.33.0:
+ resolution: {integrity: sha512-gEpRTalKdosp4Bb8qWtc2iOgE5SeIHlpS1up9bFq2wAyYhl1UdTObYiHe98zEM9SQvSoqQZ1IQD0JNpg3Ml5pg==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [arm64]
+ os: [android]
+
lightningcss-darwin-arm64@1.32.0:
resolution: {integrity: sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==}
engines: {node: '>= 12.0.0'}
cpu: [arm64]
os: [darwin]
+ lightningcss-darwin-arm64@1.33.0:
+ resolution: {integrity: sha512-Sciaz8eenNTKn9b3t7+xr0ipTp9YxKQY4npwQ3mrRuL0BAVHBLyZxofhaKBAVtzmtRZ/zTyo0/to4B1uWG/Djg==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [arm64]
+ os: [darwin]
+
lightningcss-darwin-x64@1.32.0:
resolution: {integrity: sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==}
engines: {node: '>= 12.0.0'}
cpu: [x64]
os: [darwin]
+ lightningcss-darwin-x64@1.33.0:
+ resolution: {integrity: sha512-Z5UPAxzrjlWNNyGy6i65cJzzvgJ5D3T6wMvs+gWpY9d7qRhANrxqAp6LhxIgZhWEw18RfJTGcRxjuLIBr+m8XQ==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [x64]
+ os: [darwin]
+
lightningcss-freebsd-x64@1.32.0:
resolution: {integrity: sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==}
engines: {node: '>= 12.0.0'}
cpu: [x64]
os: [freebsd]
+ lightningcss-freebsd-x64@1.33.0:
+ resolution: {integrity: sha512-QQM/Ti/hQajJwCY+RiWuCZ9sdtI/XQk7nDK5vC8kkdwixezOlDgvDx7+RT+QjK6FcFT4MpsuoBnHIo/O3StRRg==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [x64]
+ os: [freebsd]
+
lightningcss-linux-arm-gnueabihf@1.32.0:
resolution: {integrity: sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==}
engines: {node: '>= 12.0.0'}
cpu: [arm]
os: [linux]
+ lightningcss-linux-arm-gnueabihf@1.33.0:
+ resolution: {integrity: sha512-N7FVBe6iS24MlM6R/4RBTxGhQheZGs7tiQ9U32UtF75NzP5Q7xWPRqLBCKxlRQRk3rY1jCIPLzx7WzOhuUIRLQ==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [arm]
+ os: [linux]
+
lightningcss-linux-arm64-gnu@1.32.0:
resolution: {integrity: sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==}
engines: {node: '>= 12.0.0'}
@@ -792,6 +1245,13 @@ packages:
os: [linux]
libc: [glibc]
+ lightningcss-linux-arm64-gnu@1.33.0:
+ resolution: {integrity: sha512-j2v/itmy4HlNxlc6voKXYgBqNi0Ng2LShg4z7GufpEgs05P+2suBVyi9I6YHq5uoVFx9ETin3eCEhLVyXGQnKg==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [arm64]
+ os: [linux]
+ libc: [glibc]
+
lightningcss-linux-arm64-musl@1.32.0:
resolution: {integrity: sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==}
engines: {node: '>= 12.0.0'}
@@ -799,6 +1259,13 @@ packages:
os: [linux]
libc: [musl]
+ lightningcss-linux-arm64-musl@1.33.0:
+ resolution: {integrity: sha512-yiO5ROMuYQgXbC60yjZU5CYSFZGKXL0HFATXt9mHJn1+zW55oCtMI9NfcVhYLMFDL7gV7oBPon/EmMMGg2OvtQ==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [arm64]
+ os: [linux]
+ libc: [musl]
+
lightningcss-linux-x64-gnu@1.32.0:
resolution: {integrity: sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==}
engines: {node: '>= 12.0.0'}
@@ -806,6 +1273,13 @@ packages:
os: [linux]
libc: [glibc]
+ lightningcss-linux-x64-gnu@1.33.0:
+ resolution: {integrity: sha512-ar+Ju7LmcN0Jo4FpL4hpFybwNG9/3A/Br5KW2n2jyODg3MEZXaDYADdemoNS+BDNfMgKvylJLj4S5tyRActuAg==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [x64]
+ os: [linux]
+ libc: [glibc]
+
lightningcss-linux-x64-musl@1.32.0:
resolution: {integrity: sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==}
engines: {node: '>= 12.0.0'}
@@ -813,25 +1287,51 @@ packages:
os: [linux]
libc: [musl]
+ lightningcss-linux-x64-musl@1.33.0:
+ resolution: {integrity: sha512-RYiYbkokw0trfKqqzfF55lginwEPrD3OJDfTuJzFs1MK6iFnDenaz1fqLLtX4ITG3OktJQXOeTaw1awrBAlZPw==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [x64]
+ os: [linux]
+ libc: [musl]
+
lightningcss-win32-arm64-msvc@1.32.0:
resolution: {integrity: sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==}
engines: {node: '>= 12.0.0'}
cpu: [arm64]
os: [win32]
+ lightningcss-win32-arm64-msvc@1.33.0:
+ resolution: {integrity: sha512-1K+MPfLSFVpphzpdbfkhlWk6wBrTObBzS2T6db10PNOZgR9GoVsAWzwNyuhUYYbTp23j+4RrncfujZ4uAzXvwA==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [arm64]
+ os: [win32]
+
lightningcss-win32-x64-msvc@1.32.0:
resolution: {integrity: sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==}
engines: {node: '>= 12.0.0'}
cpu: [x64]
os: [win32]
+ lightningcss-win32-x64-msvc@1.33.0:
+ resolution: {integrity: sha512-OlEICDx/Xl0FqSp4bry8zFnCvGpig3Gl4gCquvYwHuqJKEC1+n9NgDniFvqHGmMv1ZkqDJrDqKKSykTDX+ehuA==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [x64]
+ os: [win32]
+
lightningcss@1.32.0:
resolution: {integrity: sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==}
engines: {node: '>= 12.0.0'}
+ lightningcss@1.33.0:
+ resolution: {integrity: sha512-WkUDrojuJs0xkgGf2udWxa3yGBRxPtxUkB79i6aCZLRgc7PM8fZe9TosfPDcvEpQZbuFASnHYmRLBLUbmLOIIA==}
+ engines: {node: '>= 12.0.0'}
+
linkify-it@5.0.0:
resolution: {integrity: sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==}
+ linkify-it@5.0.2:
+ resolution: {integrity: sha512-ONTm2jCMAVZjgQa/Fy1kScXsuOoF5NPTsoFBdE1KVIZ2vAh/r9+Bqo+0jINCBYnavTPQZz38QzFTme79ENoN3Q==}
+
linkify-it@6.1.0:
resolution: {integrity: sha512-wJ/TwpSDTLepCrQoYWYIExIKg5Zchex2Nn5yk2mFnB+6PtdkHtyLx742md9csRjjOnGkKIS/RrbY7l8D6gT9Vw==}
@@ -844,6 +1344,9 @@ packages:
lit@3.3.2:
resolution: {integrity: sha512-NF9zbsP79l4ao2SNrH3NkfmFgN/hBYSQo90saIVI1o5GpjAdCPVstVzO1MrLOakHoEhYkrtRjPK6Ob521aoYWQ==}
+ lower-case@2.0.2:
+ resolution: {integrity: sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==}
+
lru-cache@11.3.5:
resolution: {integrity: sha512-NxVFwLAnrd9i7KUBxC4DrUhmgjzOs+1Qm50D3oF1/oL+r1NpZ4gA7xvG0/zJ8evR7zIKn4vLf7qTNduWFtCrRw==}
engines: {node: 20 || >=22}
@@ -862,6 +1365,10 @@ packages:
resolution: {integrity: sha512-BuU2qnTti9YKgK5N+IeMubp14ZUKUUw7yeJbkjtosvHiP0AZ5c8IAgEMk79D0eC8F23r4Ac/q8cAIFdm2FtyoA==}
hasBin: true
+ markdown-it@14.3.0:
+ resolution: {integrity: sha512-RCEsPjR+sr0x+AuYp601tKTkgFG4YEPLCzHST3cQ/fhlJkqAkz1L2/Qbp1j9qw5SBwQHFBoW8+hoN5xssOF0Tw==}
+ hasBin: true
+
marked@18.0.2:
resolution: {integrity: sha512-NsmlUYBS/Zg57rgDWMYdnre6OTj4e+qq/JS2ot3KrYLSoHLw+sDu0Nm1ZGpRgYAq6c+b1ekaY5NzVchMCQnzcg==}
engines: {node: '>= 20'}
@@ -876,20 +1383,36 @@ packages:
mitata@0.1.14:
resolution: {integrity: sha512-8kRs0l636eT4jj68PFXOR2D5xl4m56T478g16SzUPOYgkzQU+xaw62guAQxzBPm+SXb15GQi1cCpDxJfkr4CSA==}
- nanoid@3.3.11:
- resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==}
+ nanoid@3.3.18:
+ resolution: {integrity: sha512-DTg4MJbGMWkfi6VZFdNt2/caMbQy4Ou+Op/hJQvGEWcnVfoA1QA+xzRKAzw9jD6+GVOOeYr/mIcuDSdug6F6+w==}
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
hasBin: true
+ no-case@3.0.4:
+ resolution: {integrity: sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==}
+
obug@2.1.1:
resolution: {integrity: sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==}
+ ohash@2.0.12:
+ resolution: {integrity: sha512-65S/5gk9YSsaRjcyf7Nfa6h/d3E8/1gslpXfI4W7Dxn/oap8IKRuNT5VXkLQ1YFKIEg4apRY4Pj6aiwFzrDdmw==}
+
+ param-case@3.0.4:
+ resolution: {integrity: sha512-RXlj7zCYokReqWpOPH9oYivUzLYZ5vAPIfEmCTNViosC78F8F0H9y7T7gG2M39ymgutxF5gcFEsyZQSph9Bp3A==}
+
parse5@8.0.1:
resolution: {integrity: sha512-z1e/HMG90obSGeidlli3hj7cbocou0/wa5HacvI3ASx34PecNjNQeaHNo5WIZpWofN9kgkqV1q5YvXe3F0FoPw==}
+ pascal-case@3.1.2:
+ resolution: {integrity: sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g==}
+
pathe@2.0.3:
resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==}
+ photoswipe@5.4.4:
+ resolution: {integrity: sha512-WNFHoKrkZNnvFFhbHL93WDkW3ifwVOXSW3w1UuZZelSmgXpIGiZSNlZJq37rR8YejqME2rHs9EhH9ZvlvFH2NA==}
+ engines: {node: '>= 0.12.0'}
+
picocolors@1.1.1:
resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==}
@@ -897,8 +1420,8 @@ packages:
resolution: {integrity: sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==}
engines: {node: '>=12'}
- postcss@8.5.10:
- resolution: {integrity: sha512-pMMHxBOZKFU6HgAZ4eyGnwXF/EvPGGqUr0MnZ5+99485wwW41kW91A4LOGxSHhgugZmSChL5AlElNdwlNgcnLQ==}
+ postcss@8.5.26:
+ resolution: {integrity: sha512-u82N74LFzG8ca+dD8puPnplTXoGH4fTPpVGuIbt36G3qvNlkvfD0lEAZSxaly3KX8TS/L1A1gsCEmvKmBcVbkQ==}
engines: {node: ^10 || ^12 || >=14}
prettier@3.8.3:
@@ -914,6 +1437,15 @@ packages:
resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==}
engines: {node: '>=6'}
+ reka-ui@2.10.3:
+ resolution: {integrity: sha512-nJGZbwcha8AcP2wbnjodfzsciTKBqp1mIzepC9Pi2xDI/YK3++ej3vpJOSPyxqrkq1Oorj4K+BdJkbVCV6x6ag==}
+ peerDependencies:
+ vue: '>= 3.4.0'
+
+ relateurl@0.2.7:
+ resolution: {integrity: sha512-G08Dxvm4iDN3MLM0EsP62EDV9IuhXPR6blNz6Utcp7zyV3tr4HVNINt6MpaRWbxoOHT3Q7YN2P+jaHX8vUbgog==}
+ engines: {node: '>= 0.10'}
+
require-from-string@2.0.2:
resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==}
engines: {node: '>=0.10.0'}
@@ -921,6 +1453,11 @@ packages:
resolve-pkg-maps@1.0.0:
resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==}
+ rolldown@1.0.0-rc.10:
+ resolution: {integrity: sha512-q7j6vvarRFmKpgJUT8HCAUljkgzEp4LAhPlJUvQhA5LA1SUL36s5QCysMutErzL3EbNOZOkoziSx9iZC4FddKA==}
+ engines: {node: ^20.19.0 || >=22.12.0}
+ hasBin: true
+
rolldown@1.0.0-rc.16:
resolution: {integrity: sha512-rzi5WqKzEZw3SooTt7cgm4eqIoujPIyGcJNGFL7iPEuajQw7vxMHUkXylu4/vhCkJGXsgRmxqMKXUpT6FEgl0g==}
engines: {node: ^20.19.0 || >=22.12.0}
@@ -938,13 +1475,17 @@ packages:
siginfo@2.0.0:
resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==}
- signal-polyfill@0.2.2:
- resolution: {integrity: sha512-p63Y4Er5/eMQ9RHg0M0Y64NlsQKpiu6MDdhBXpyywRuWiPywhJTpKJ1iB5K2hJEbFZ0BnDS7ZkJ+0AfTuL37Rg==}
-
source-map-js@1.2.1:
resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==}
engines: {node: '>=0.10.0'}
+ source-map-support@0.5.21:
+ resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==}
+
+ source-map@0.6.1:
+ resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==}
+ engines: {node: '>=0.10.0'}
+
stackback@0.0.2:
resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==}
@@ -958,6 +1499,11 @@ packages:
symbol-tree@3.2.4:
resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==}
+ terser@5.50.0:
+ resolution: {integrity: sha512-CN9BVxWhgS/hRxtUMjtC2uRWSTcSfQFHMDWma6sKKfIivCD91sM+FOPfvwoaRMqCSrUpe1nv3jDamd9eEQ4y+w==}
+ engines: {node: '>=10'}
+ hasBin: true
+
tinybench@2.9.0:
resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==}
@@ -1001,6 +1547,11 @@ packages:
engines: {node: '>=14.17'}
hasBin: true
+ typescript@7.0.2:
+ resolution: {integrity: sha512-8FYau96o3NKOhbjKi/qNvG/W5jhzxkbdm5sj9AbZ/5T5sWqn3hJgLfGx27sRKZWTvyzCP8dLRBTf5tBTSRVUNA==}
+ engines: {node: '>=16.20.0'}
+ hasBin: true
+
uc.micro@2.1.0:
resolution: {integrity: sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==}
@@ -1010,10 +1561,56 @@ packages:
undici-types@7.19.2:
resolution: {integrity: sha512-qYVnV5OEm2AW8cJMCpdV20CDyaN3g0AjDlOGf1OW4iaDEx8MwdtChUp4zu4H0VP3nDRF/8RKWH+IPp9uW0YGZg==}
+ undici-types@8.3.0:
+ resolution: {integrity: sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ==}
+
undici@7.25.0:
resolution: {integrity: sha512-xXnp4kTyor2Zq+J1FfPI6Eq3ew5h6Vl0F/8d9XU5zZQf1tX9s2Su1/3PiMmUANFULpmksxkClamIZcaUqryHsQ==}
engines: {node: '>=20.18.1'}
+ vite@8.0.1:
+ resolution: {integrity: sha512-wt+Z2qIhfFt85uiyRt5LPU4oVEJBXj8hZNWKeqFG4gRG/0RaRGJ7njQCwzFVjO+v4+Ipmf5CY7VdmZRAYYBPHw==}
+ engines: {node: ^20.19.0 || >=22.12.0}
+ hasBin: true
+ peerDependencies:
+ '@types/node': ^20.19.0 || >=22.12.0
+ '@vitejs/devtools': ^0.1.0
+ esbuild: ^0.27.0
+ jiti: '>=1.21.0'
+ less: ^4.0.0
+ sass: ^1.70.0
+ sass-embedded: ^1.70.0
+ stylus: '>=0.54.8'
+ sugarss: ^5.0.0
+ terser: ^5.16.0
+ tsx: ^4.8.1
+ yaml: ^2.4.2
+ peerDependenciesMeta:
+ '@types/node':
+ optional: true
+ '@vitejs/devtools':
+ optional: true
+ esbuild:
+ optional: true
+ jiti:
+ optional: true
+ less:
+ optional: true
+ sass:
+ optional: true
+ sass-embedded:
+ optional: true
+ stylus:
+ optional: true
+ sugarss:
+ optional: true
+ terser:
+ optional: true
+ tsx:
+ optional: true
+ yaml:
+ optional: true
+
vite@8.0.9:
resolution: {integrity: sha512-t7g7GVRpMXjNpa67HaVWI/8BWtdVIQPCL2WoozXXA7LBGEFK4AkkKkHx2hAQf5x1GZSlcmEDPkVLSGahxnEEZw==}
engines: {node: ^20.19.0 || >=22.12.0}
@@ -1098,6 +1695,17 @@ packages:
jsdom:
optional: true
+ vue-demi@0.14.10:
+ resolution: {integrity: sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==}
+ engines: {node: '>=12'}
+ hasBin: true
+ peerDependencies:
+ '@vue/composition-api': ^1.0.0-rc.1
+ vue: ^3.0.0-0 || ^2.6.0
+ peerDependenciesMeta:
+ '@vue/composition-api':
+ optional: true
+
vue@3.5.32:
resolution: {integrity: sha512-vM4z4Q9tTafVfMAK7IVzmxg34rSzTFMyIe0UUEijUCkn9+23lj0WRfA83dg7eQZIUlgOSGrkViIaCfqSAUXsMw==}
peerDependencies:
@@ -1106,6 +1714,14 @@ packages:
typescript:
optional: true
+ vue@3.5.41:
+ resolution: {integrity: sha512-2laE0p+aK+/AOPG/XL/WepOs/GlK755LJ1XECi9kDUrz1FKNw8rb2Xzlw9JS1rqEV55nb0ttsKxVlTCcd+R5cg==}
+ peerDependencies:
+ typescript: '*'
+ peerDependenciesMeta:
+ typescript:
+ optional: true
+
w3c-xmlserializer@5.0.0:
resolution: {integrity: sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==}
engines: {node: '>=18'}
@@ -1158,17 +1774,30 @@ snapshots:
'@babel/helper-string-parser@7.27.1': {}
+ '@babel/helper-string-parser@7.29.7': {}
+
'@babel/helper-validator-identifier@7.28.5': {}
+ '@babel/helper-validator-identifier@7.29.7': {}
+
'@babel/parser@7.29.2':
dependencies:
'@babel/types': 7.29.0
+ '@babel/parser@7.29.8':
+ dependencies:
+ '@babel/types': 7.29.8
+
'@babel/types@7.29.0':
dependencies:
'@babel/helper-string-parser': 7.27.1
'@babel/helper-validator-identifier': 7.28.5
+ '@babel/types@7.29.8':
+ dependencies:
+ '@babel/helper-string-parser': 7.29.7
+ '@babel/helper-validator-identifier': 7.29.7
+
'@bcoe/v8-coverage@1.0.2':
optional: true
@@ -1296,27 +1925,117 @@ snapshots:
'@exodus/bytes@1.15.0': {}
- '@fuyeor/locale@https://codeload.github.com/Fuyeor/webroamer/tar.gz/c502f739f4b633ab24386fe9f90fb77ed9f6ca9d#path:packages/locale(@lit-labs/signals@0.2.0)(lit@3.3.2)(signal-polyfill@0.2.2)':
+ '@floating-ui/core@1.8.0':
dependencies:
- '@lit-labs/signals': 0.2.0
- lit: 3.3.2
- signal-polyfill: 0.2.2
+ '@floating-ui/utils': 0.2.12
- '@jridgewell/resolve-uri@3.1.2':
+ '@floating-ui/dom@1.8.0':
+ dependencies:
+ '@floating-ui/core': 1.8.0
+ '@floating-ui/utils': 0.2.12
+
+ '@floating-ui/utils@0.2.12': {}
+
+ '@floating-ui/vue@1.1.11(vue@3.5.41(typescript@7.0.2))':
+ dependencies:
+ '@floating-ui/dom': 1.8.0
+ '@floating-ui/utils': 0.2.12
+ vue-demi: 0.14.10(vue@3.5.41(typescript@7.0.2))
+ transitivePeerDependencies:
+ - '@vue/composition-api'
+ - vue
+
+ '@fuyeor/commons@0.1.0(vue@3.5.41(typescript@7.0.2))':
+ dependencies:
+ '@fuyeor/locale': 0.1.0(vue@3.5.41(typescript@7.0.2))
+ '@fuyeor/vue-router': 0.1.0(vue@3.5.41(typescript@7.0.2))
+ vue: 3.5.41(typescript@7.0.2)
+
+ '@fuyeor/commons@0.1.2(vue@3.5.41(typescript@7.0.2))':
+ dependencies:
+ '@fuyeor/locale': 0.1.0(vue@3.5.41(typescript@7.0.2))
+ '@fuyeor/vue-router': 0.0.9(vue@3.5.41(typescript@7.0.2))
+ vue: 3.5.41(typescript@7.0.2)
+
+ '@fuyeor/config@0.1.1(vite@8.0.1(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2)(@types/node@26.2.0)(esbuild@0.27.7)(terser@5.50.0)(tsx@4.21.0))(vue@3.5.41(typescript@7.0.2))':
+ dependencies:
+ '@vitejs/plugin-vue': 6.0.8(vite@8.0.1(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2)(@types/node@26.2.0)(esbuild@0.27.7)(terser@5.50.0)(tsx@4.21.0))(vue@3.5.41(typescript@7.0.2))
+ html-minifier-terser: 7.2.0
+ lightningcss: 1.33.0
+ vite: 8.0.1(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2)(@types/node@26.2.0)(esbuild@0.27.7)(terser@5.50.0)(tsx@4.21.0)
+ transitivePeerDependencies:
+ - vue
+
+ '@fuyeor/interactify@0.1.1(vue@3.5.41(typescript@7.0.2))':
+ dependencies:
+ '@fuyeor/commons': 0.1.0(vue@3.5.41(typescript@7.0.2))
+ '@fuyeor/locale': 0.1.0(vue@3.5.41(typescript@7.0.2))
+ '@fuyeor/vue-router': 0.0.9(vue@3.5.41(typescript@7.0.2))
+ '@fuyeor/vue-vaul': 0.4.8(vue@3.5.41(typescript@7.0.2))
+ markdown-it: 14.3.0
+ photoswipe: 5.4.4
+ vue: 3.5.41(typescript@7.0.2)
+ transitivePeerDependencies:
+ - '@vue/composition-api'
+
+ '@fuyeor/locale-cli-linux-x64@0.1.1':
+ optional: true
+
+ '@fuyeor/locale-cli-win32-x64@0.1.1':
optional: true
+ '@fuyeor/locale-cli@0.1.1':
+ optionalDependencies:
+ '@fuyeor/locale-cli-linux-x64': 0.1.1
+ '@fuyeor/locale-cli-win32-x64': 0.1.1
+
+ '@fuyeor/locale@0.1.0(vue@3.5.41(typescript@7.0.2))':
+ dependencies:
+ vue: 3.5.41(typescript@7.0.2)
+
+ '@fuyeor/tsconfig@0.1.0': {}
+
+ '@fuyeor/vue-router@0.0.9(vue@3.5.41(typescript@7.0.2))':
+ dependencies:
+ vue: 3.5.41(typescript@7.0.2)
+
+ '@fuyeor/vue-router@0.1.0(vue@3.5.41(typescript@7.0.2))':
+ dependencies:
+ vue: 3.5.41(typescript@7.0.2)
+
+ '@fuyeor/vue-vaul@0.4.8(vue@3.5.41(typescript@7.0.2))':
+ dependencies:
+ reka-ui: 2.10.3(vue@3.5.41(typescript@7.0.2))
+ vue: 3.5.41(typescript@7.0.2)
+ transitivePeerDependencies:
+ - '@vue/composition-api'
+
+ '@internationalized/date@3.12.3':
+ dependencies:
+ '@swc/helpers': 0.5.23
+
+ '@internationalized/number@3.6.7':
+ dependencies:
+ '@swc/helpers': 0.5.23
+
+ '@jridgewell/gen-mapping@0.3.13':
+ dependencies:
+ '@jridgewell/sourcemap-codec': 1.5.5
+ '@jridgewell/trace-mapping': 0.3.31
+
+ '@jridgewell/resolve-uri@3.1.2': {}
+
+ '@jridgewell/source-map@0.3.11':
+ dependencies:
+ '@jridgewell/gen-mapping': 0.3.13
+ '@jridgewell/trace-mapping': 0.3.31
+
'@jridgewell/sourcemap-codec@1.5.5': {}
'@jridgewell/trace-mapping@0.3.31':
dependencies:
'@jridgewell/resolve-uri': 3.1.2
'@jridgewell/sourcemap-codec': 1.5.5
- optional: true
-
- '@lit-labs/signals@0.2.0':
- dependencies:
- lit: 3.3.2
- signal-polyfill: 0.2.2
'@lit-labs/ssr-dom-shim@1.5.1': {}
@@ -1331,44 +2050,90 @@ snapshots:
'@tybys/wasm-util': 0.10.1
optional: true
+ '@oxc-project/types@0.120.0': {}
+
'@oxc-project/types@0.126.0': {}
+ '@rolldown/binding-android-arm64@1.0.0-rc.10':
+ optional: true
+
'@rolldown/binding-android-arm64@1.0.0-rc.16':
optional: true
+ '@rolldown/binding-darwin-arm64@1.0.0-rc.10':
+ optional: true
+
'@rolldown/binding-darwin-arm64@1.0.0-rc.16':
optional: true
+ '@rolldown/binding-darwin-x64@1.0.0-rc.10':
+ optional: true
+
'@rolldown/binding-darwin-x64@1.0.0-rc.16':
optional: true
+ '@rolldown/binding-freebsd-x64@1.0.0-rc.10':
+ optional: true
+
'@rolldown/binding-freebsd-x64@1.0.0-rc.16':
optional: true
+ '@rolldown/binding-linux-arm-gnueabihf@1.0.0-rc.10':
+ optional: true
+
'@rolldown/binding-linux-arm-gnueabihf@1.0.0-rc.16':
optional: true
+ '@rolldown/binding-linux-arm64-gnu@1.0.0-rc.10':
+ optional: true
+
'@rolldown/binding-linux-arm64-gnu@1.0.0-rc.16':
optional: true
+ '@rolldown/binding-linux-arm64-musl@1.0.0-rc.10':
+ optional: true
+
'@rolldown/binding-linux-arm64-musl@1.0.0-rc.16':
optional: true
+ '@rolldown/binding-linux-ppc64-gnu@1.0.0-rc.10':
+ optional: true
+
'@rolldown/binding-linux-ppc64-gnu@1.0.0-rc.16':
optional: true
+ '@rolldown/binding-linux-s390x-gnu@1.0.0-rc.10':
+ optional: true
+
'@rolldown/binding-linux-s390x-gnu@1.0.0-rc.16':
optional: true
+ '@rolldown/binding-linux-x64-gnu@1.0.0-rc.10':
+ optional: true
+
'@rolldown/binding-linux-x64-gnu@1.0.0-rc.16':
optional: true
+ '@rolldown/binding-linux-x64-musl@1.0.0-rc.10':
+ optional: true
+
'@rolldown/binding-linux-x64-musl@1.0.0-rc.16':
optional: true
+ '@rolldown/binding-openharmony-arm64@1.0.0-rc.10':
+ optional: true
+
'@rolldown/binding-openharmony-arm64@1.0.0-rc.16':
optional: true
+ '@rolldown/binding-wasm32-wasi@1.0.0-rc.10(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2)':
+ dependencies:
+ '@napi-rs/wasm-runtime': 1.1.4(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2)
+ transitivePeerDependencies:
+ - '@emnapi/core'
+ - '@emnapi/runtime'
+ optional: true
+
'@rolldown/binding-wasm32-wasi@1.0.0-rc.16':
dependencies:
'@emnapi/core': 1.9.2
@@ -1376,16 +2141,37 @@ snapshots:
'@napi-rs/wasm-runtime': 1.1.4(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2)
optional: true
+ '@rolldown/binding-win32-arm64-msvc@1.0.0-rc.10':
+ optional: true
+
'@rolldown/binding-win32-arm64-msvc@1.0.0-rc.16':
optional: true
+ '@rolldown/binding-win32-x64-msvc@1.0.0-rc.10':
+ optional: true
+
'@rolldown/binding-win32-x64-msvc@1.0.0-rc.16':
optional: true
+ '@rolldown/pluginutils@1.0.0-rc.10': {}
+
'@rolldown/pluginutils@1.0.0-rc.16': {}
+ '@rolldown/pluginutils@1.0.1': {}
+
'@standard-schema/spec@1.1.0': {}
+ '@swc/helpers@0.5.23':
+ dependencies:
+ tslib: 2.8.1
+
+ '@tanstack/virtual-core@3.17.8': {}
+
+ '@tanstack/vue-virtual@3.13.36(vue@3.5.41(typescript@7.0.2))':
+ dependencies:
+ '@tanstack/virtual-core': 3.17.8
+ vue: 3.5.41(typescript@7.0.2)
+
'@tybys/wasm-util@0.10.1':
dependencies:
tslib: 2.8.1
@@ -1404,9 +2190,79 @@ snapshots:
dependencies:
undici-types: 7.19.2
+ '@types/node@26.2.0':
+ dependencies:
+ undici-types: 8.3.0
+
'@types/trusted-types@2.0.7': {}
- '@types/vscode@1.134.0': {}
+ '@types/web-bluetooth@0.0.21': {}
+
+ '@typescript/typescript-aix-ppc64@7.0.2':
+ optional: true
+
+ '@typescript/typescript-darwin-arm64@7.0.2':
+ optional: true
+
+ '@typescript/typescript-darwin-x64@7.0.2':
+ optional: true
+
+ '@typescript/typescript-freebsd-arm64@7.0.2':
+ optional: true
+
+ '@typescript/typescript-freebsd-x64@7.0.2':
+ optional: true
+
+ '@typescript/typescript-linux-arm64@7.0.2':
+ optional: true
+
+ '@typescript/typescript-linux-arm@7.0.2':
+ optional: true
+
+ '@typescript/typescript-linux-loong64@7.0.2':
+ optional: true
+
+ '@typescript/typescript-linux-mips64el@7.0.2':
+ optional: true
+
+ '@typescript/typescript-linux-ppc64@7.0.2':
+ optional: true
+
+ '@typescript/typescript-linux-riscv64@7.0.2':
+ optional: true
+
+ '@typescript/typescript-linux-s390x@7.0.2':
+ optional: true
+
+ '@typescript/typescript-linux-x64@7.0.2':
+ optional: true
+
+ '@typescript/typescript-netbsd-arm64@7.0.2':
+ optional: true
+
+ '@typescript/typescript-netbsd-x64@7.0.2':
+ optional: true
+
+ '@typescript/typescript-openbsd-arm64@7.0.2':
+ optional: true
+
+ '@typescript/typescript-openbsd-x64@7.0.2':
+ optional: true
+
+ '@typescript/typescript-sunos-x64@7.0.2':
+ optional: true
+
+ '@typescript/typescript-win32-arm64@7.0.2':
+ optional: true
+
+ '@typescript/typescript-win32-x64@7.0.2':
+ optional: true
+
+ '@vitejs/plugin-vue@6.0.8(vite@8.0.1(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2)(@types/node@26.2.0)(esbuild@0.27.7)(terser@5.50.0)(tsx@4.21.0))(vue@3.5.41(typescript@7.0.2))':
+ dependencies:
+ '@rolldown/pluginutils': 1.0.1
+ vite: 8.0.1(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2)(@types/node@26.2.0)(esbuild@0.27.7)(terser@5.50.0)(tsx@4.21.0)
+ vue: 3.5.41(typescript@7.0.2)
'@vitest/coverage-v8@4.1.4(vitest@4.1.4)':
dependencies:
@@ -1420,7 +2276,7 @@ snapshots:
obug: 2.1.1
std-env: 4.1.0
tinyrainbow: 3.1.0
- vitest: 4.1.4(@types/node@25.6.0)(@vitest/coverage-v8@4.1.4)(jsdom@29.0.2)(vite@8.0.9(@types/node@25.6.0)(esbuild@0.27.7)(tsx@4.21.0))
+ vitest: 4.1.4(@types/node@25.6.0)(@vitest/coverage-v8@4.1.4)(jsdom@29.0.2)(vite@8.0.9(@types/node@25.6.0)(esbuild@0.27.7)(terser@5.50.0)(tsx@4.21.0))
optional: true
'@vitest/expect@4.1.4':
@@ -1432,13 +2288,13 @@ snapshots:
chai: 6.2.2
tinyrainbow: 3.1.0
- '@vitest/mocker@4.1.4(vite@8.0.9(@types/node@25.6.0)(esbuild@0.27.7)(tsx@4.21.0))':
+ '@vitest/mocker@4.1.4(vite@8.0.9(@types/node@25.6.0)(esbuild@0.27.7)(terser@5.50.0)(tsx@4.21.0))':
dependencies:
'@vitest/spy': 4.1.4
estree-walker: 3.0.3
magic-string: 0.30.21
optionalDependencies:
- vite: 8.0.9(@types/node@25.6.0)(esbuild@0.27.7)(tsx@4.21.0)
+ vite: 8.0.9(@types/node@25.6.0)(esbuild@0.27.7)(terser@5.50.0)(tsx@4.21.0)
'@vitest/pretty-format@4.1.4':
dependencies:
@@ -1472,11 +2328,24 @@ snapshots:
estree-walker: 2.0.2
source-map-js: 1.2.1
+ '@vue/compiler-core@3.5.41':
+ dependencies:
+ '@babel/parser': 7.29.8
+ '@vue/shared': 3.5.41
+ entities: 7.0.1
+ estree-walker: 2.0.2
+ source-map-js: 1.2.1
+
'@vue/compiler-dom@3.5.32':
dependencies:
'@vue/compiler-core': 3.5.32
'@vue/shared': 3.5.32
+ '@vue/compiler-dom@3.5.41':
+ dependencies:
+ '@vue/compiler-core': 3.5.41
+ '@vue/shared': 3.5.41
+
'@vue/compiler-sfc@3.5.32':
dependencies:
'@babel/parser': 7.29.2
@@ -1486,7 +2355,19 @@ snapshots:
'@vue/shared': 3.5.32
estree-walker: 2.0.2
magic-string: 0.30.21
- postcss: 8.5.10
+ postcss: 8.5.26
+ source-map-js: 1.2.1
+
+ '@vue/compiler-sfc@3.5.41':
+ dependencies:
+ '@babel/parser': 7.29.8
+ '@vue/compiler-core': 3.5.41
+ '@vue/compiler-dom': 3.5.41
+ '@vue/compiler-ssr': 3.5.41
+ '@vue/shared': 3.5.41
+ estree-walker: 2.0.2
+ magic-string: 0.30.21
+ postcss: 8.5.26
source-map-js: 1.2.1
'@vue/compiler-ssr@3.5.32':
@@ -1494,15 +2375,29 @@ snapshots:
'@vue/compiler-dom': 3.5.32
'@vue/shared': 3.5.32
+ '@vue/compiler-ssr@3.5.41':
+ dependencies:
+ '@vue/compiler-dom': 3.5.41
+ '@vue/shared': 3.5.41
+
'@vue/reactivity@3.5.32':
dependencies:
'@vue/shared': 3.5.32
+ '@vue/reactivity@3.5.41':
+ dependencies:
+ '@vue/shared': 3.5.41
+
'@vue/runtime-core@3.5.32':
dependencies:
'@vue/reactivity': 3.5.32
'@vue/shared': 3.5.32
+ '@vue/runtime-core@3.5.41':
+ dependencies:
+ '@vue/reactivity': 3.5.41
+ '@vue/shared': 3.5.41
+
'@vue/runtime-dom@3.5.32':
dependencies:
'@vue/reactivity': 3.5.32
@@ -1510,16 +2405,50 @@ snapshots:
'@vue/shared': 3.5.32
csstype: 3.2.3
+ '@vue/runtime-dom@3.5.41':
+ dependencies:
+ '@vue/reactivity': 3.5.41
+ '@vue/runtime-core': 3.5.41
+ '@vue/shared': 3.5.41
+ csstype: 3.2.3
+
'@vue/server-renderer@3.5.32(vue@3.5.32(typescript@6.0.3))':
dependencies:
'@vue/compiler-ssr': 3.5.32
'@vue/shared': 3.5.32
vue: 3.5.32(typescript@6.0.3)
+ '@vue/server-renderer@3.5.41':
+ dependencies:
+ '@vue/compiler-ssr': 3.5.41
+ '@vue/runtime-dom': 3.5.41
+ '@vue/shared': 3.5.41
+
'@vue/shared@3.5.32': {}
+ '@vue/shared@3.5.41': {}
+
+ '@vueuse/core@14.4.0(vue@3.5.41(typescript@7.0.2))':
+ dependencies:
+ '@types/web-bluetooth': 0.0.21
+ '@vueuse/metadata': 14.4.0
+ '@vueuse/shared': 14.4.0(vue@3.5.41(typescript@7.0.2))
+ vue: 3.5.41(typescript@7.0.2)
+
+ '@vueuse/metadata@14.4.0': {}
+
+ '@vueuse/shared@14.4.0(vue@3.5.41(typescript@7.0.2))':
+ dependencies:
+ vue: 3.5.41(typescript@7.0.2)
+
+ acorn@8.18.0: {}
+
argparse@2.0.1: {}
+ aria-hidden@1.2.6:
+ dependencies:
+ tslib: 2.8.1
+
assertion-error@2.0.1: {}
ast-v8-to-istanbul@1.0.0:
@@ -1533,8 +2462,23 @@ snapshots:
dependencies:
require-from-string: 2.0.2
+ buffer-from@1.1.2: {}
+
+ camel-case@4.1.2:
+ dependencies:
+ pascal-case: 3.1.2
+ tslib: 2.8.1
+
chai@6.2.2: {}
+ clean-css@5.3.3:
+ dependencies:
+ source-map: 0.6.1
+
+ commander@10.0.1: {}
+
+ commander@2.20.3: {}
+
convert-source-map@2.0.0: {}
css-tree@3.2.1:
@@ -1553,8 +2497,15 @@ snapshots:
decimal.js@10.6.0: {}
+ defu@6.1.7: {}
+
detect-libc@2.1.2: {}
+ dot-case@3.0.4:
+ dependencies:
+ no-case: 3.0.4
+ tslib: 2.8.1
+
entities@4.5.0: {}
entities@7.0.1: {}
@@ -1623,6 +2574,16 @@ snapshots:
html-escaper@2.0.2:
optional: true
+ html-minifier-terser@7.2.0:
+ dependencies:
+ camel-case: 4.1.2
+ clean-css: 5.3.3
+ commander: 10.0.1
+ entities: 4.5.0
+ param-case: 3.0.4
+ relateurl: 0.2.7
+ terser: 5.50.0
+
is-potential-custom-element-name@1.0.1: {}
istanbul-lib-coverage@3.2.2:
@@ -1673,36 +2634,69 @@ snapshots:
lightningcss-android-arm64@1.32.0:
optional: true
+ lightningcss-android-arm64@1.33.0:
+ optional: true
+
lightningcss-darwin-arm64@1.32.0:
optional: true
+ lightningcss-darwin-arm64@1.33.0:
+ optional: true
+
lightningcss-darwin-x64@1.32.0:
optional: true
+ lightningcss-darwin-x64@1.33.0:
+ optional: true
+
lightningcss-freebsd-x64@1.32.0:
optional: true
+ lightningcss-freebsd-x64@1.33.0:
+ optional: true
+
lightningcss-linux-arm-gnueabihf@1.32.0:
optional: true
+ lightningcss-linux-arm-gnueabihf@1.33.0:
+ optional: true
+
lightningcss-linux-arm64-gnu@1.32.0:
optional: true
+ lightningcss-linux-arm64-gnu@1.33.0:
+ optional: true
+
lightningcss-linux-arm64-musl@1.32.0:
optional: true
+ lightningcss-linux-arm64-musl@1.33.0:
+ optional: true
+
lightningcss-linux-x64-gnu@1.32.0:
optional: true
+ lightningcss-linux-x64-gnu@1.33.0:
+ optional: true
+
lightningcss-linux-x64-musl@1.32.0:
optional: true
+ lightningcss-linux-x64-musl@1.33.0:
+ optional: true
+
lightningcss-win32-arm64-msvc@1.32.0:
optional: true
+ lightningcss-win32-arm64-msvc@1.33.0:
+ optional: true
+
lightningcss-win32-x64-msvc@1.32.0:
optional: true
+ lightningcss-win32-x64-msvc@1.33.0:
+ optional: true
+
lightningcss@1.32.0:
dependencies:
detect-libc: 2.1.2
@@ -1719,10 +2713,30 @@ snapshots:
lightningcss-win32-arm64-msvc: 1.32.0
lightningcss-win32-x64-msvc: 1.32.0
+ lightningcss@1.33.0:
+ dependencies:
+ detect-libc: 2.1.2
+ optionalDependencies:
+ lightningcss-android-arm64: 1.33.0
+ lightningcss-darwin-arm64: 1.33.0
+ lightningcss-darwin-x64: 1.33.0
+ lightningcss-freebsd-x64: 1.33.0
+ lightningcss-linux-arm-gnueabihf: 1.33.0
+ lightningcss-linux-arm64-gnu: 1.33.0
+ lightningcss-linux-arm64-musl: 1.33.0
+ lightningcss-linux-x64-gnu: 1.33.0
+ lightningcss-linux-x64-musl: 1.33.0
+ lightningcss-win32-arm64-msvc: 1.33.0
+ lightningcss-win32-x64-msvc: 1.33.0
+
linkify-it@5.0.0:
dependencies:
uc.micro: 2.1.0
+ linkify-it@5.0.2:
+ dependencies:
+ uc.micro: 2.1.0
+
linkify-it@6.1.0:
dependencies:
uc.micro: 3.0.0
@@ -1743,6 +2757,10 @@ snapshots:
lit-element: 4.2.2
lit-html: 3.3.2
+ lower-case@2.0.2:
+ dependencies:
+ tslib: 2.8.1
+
lru-cache@11.3.5: {}
magic-string@0.30.21:
@@ -1751,8 +2769,8 @@ snapshots:
magicast@0.5.2:
dependencies:
- '@babel/parser': 7.29.2
- '@babel/types': 7.29.0
+ '@babel/parser': 7.29.8
+ '@babel/types': 7.29.8
source-map-js: 1.2.1
optional: true
@@ -1770,6 +2788,15 @@ snapshots:
punycode.js: 2.3.1
uc.micro: 2.1.0
+ markdown-it@14.3.0:
+ dependencies:
+ argparse: 2.0.1
+ entities: 4.5.0
+ linkify-it: 5.0.2
+ mdurl: 2.0.0
+ punycode.js: 2.3.1
+ uc.micro: 2.1.0
+
marked@18.0.2: {}
mdn-data@2.27.1: {}
@@ -1778,23 +2805,42 @@ snapshots:
mitata@0.1.14: {}
- nanoid@3.3.11: {}
+ nanoid@3.3.18: {}
+
+ no-case@3.0.4:
+ dependencies:
+ lower-case: 2.0.2
+ tslib: 2.8.1
obug@2.1.1: {}
+ ohash@2.0.12: {}
+
+ param-case@3.0.4:
+ dependencies:
+ dot-case: 3.0.4
+ tslib: 2.8.1
+
parse5@8.0.1:
dependencies:
entities: 8.0.0
+ pascal-case@3.1.2:
+ dependencies:
+ no-case: 3.0.4
+ tslib: 2.8.1
+
pathe@2.0.3: {}
+ photoswipe@5.4.4: {}
+
picocolors@1.1.1: {}
picomatch@4.0.4: {}
- postcss@8.5.10:
+ postcss@8.5.26:
dependencies:
- nanoid: 3.3.11
+ nanoid: 3.3.18
picocolors: 1.1.1
source-map-js: 1.2.1
@@ -1804,10 +2850,52 @@ snapshots:
punycode@2.3.1: {}
+ reka-ui@2.10.3(vue@3.5.41(typescript@7.0.2)):
+ dependencies:
+ '@floating-ui/dom': 1.8.0
+ '@floating-ui/vue': 1.1.11(vue@3.5.41(typescript@7.0.2))
+ '@internationalized/date': 3.12.3
+ '@internationalized/number': 3.6.7
+ '@tanstack/vue-virtual': 3.13.36(vue@3.5.41(typescript@7.0.2))
+ '@vueuse/core': 14.4.0(vue@3.5.41(typescript@7.0.2))
+ '@vueuse/shared': 14.4.0(vue@3.5.41(typescript@7.0.2))
+ aria-hidden: 1.2.6
+ defu: 6.1.7
+ ohash: 2.0.12
+ vue: 3.5.41(typescript@7.0.2)
+ transitivePeerDependencies:
+ - '@vue/composition-api'
+
+ relateurl@0.2.7: {}
+
require-from-string@2.0.2: {}
resolve-pkg-maps@1.0.0: {}
+ rolldown@1.0.0-rc.10(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2):
+ dependencies:
+ '@oxc-project/types': 0.120.0
+ '@rolldown/pluginutils': 1.0.0-rc.10
+ optionalDependencies:
+ '@rolldown/binding-android-arm64': 1.0.0-rc.10
+ '@rolldown/binding-darwin-arm64': 1.0.0-rc.10
+ '@rolldown/binding-darwin-x64': 1.0.0-rc.10
+ '@rolldown/binding-freebsd-x64': 1.0.0-rc.10
+ '@rolldown/binding-linux-arm-gnueabihf': 1.0.0-rc.10
+ '@rolldown/binding-linux-arm64-gnu': 1.0.0-rc.10
+ '@rolldown/binding-linux-arm64-musl': 1.0.0-rc.10
+ '@rolldown/binding-linux-ppc64-gnu': 1.0.0-rc.10
+ '@rolldown/binding-linux-s390x-gnu': 1.0.0-rc.10
+ '@rolldown/binding-linux-x64-gnu': 1.0.0-rc.10
+ '@rolldown/binding-linux-x64-musl': 1.0.0-rc.10
+ '@rolldown/binding-openharmony-arm64': 1.0.0-rc.10
+ '@rolldown/binding-wasm32-wasi': 1.0.0-rc.10(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2)
+ '@rolldown/binding-win32-arm64-msvc': 1.0.0-rc.10
+ '@rolldown/binding-win32-x64-msvc': 1.0.0-rc.10
+ transitivePeerDependencies:
+ - '@emnapi/core'
+ - '@emnapi/runtime'
+
rolldown@1.0.0-rc.16:
dependencies:
'@oxc-project/types': 0.126.0
@@ -1838,10 +2926,15 @@ snapshots:
siginfo@2.0.0: {}
- signal-polyfill@0.2.2: {}
-
source-map-js@1.2.1: {}
+ source-map-support@0.5.21:
+ dependencies:
+ buffer-from: 1.1.2
+ source-map: 0.6.1
+
+ source-map@0.6.1: {}
+
stackback@0.0.2: {}
std-env@4.1.0: {}
@@ -1853,6 +2946,13 @@ snapshots:
symbol-tree@3.2.4: {}
+ terser@5.50.0:
+ dependencies:
+ '@jridgewell/source-map': 0.3.11
+ acorn: 8.18.0
+ commander: 2.20.3
+ source-map-support: 0.5.21
+
tinybench@2.9.0: {}
tinyexec@1.1.1: {}
@@ -1878,8 +2978,7 @@ snapshots:
dependencies:
punycode: 2.3.1
- tslib@2.8.1:
- optional: true
+ tslib@2.8.1: {}
tsx@4.21.0:
dependencies:
@@ -1890,31 +2989,74 @@ snapshots:
typescript@6.0.3: {}
+ typescript@7.0.2:
+ optionalDependencies:
+ '@typescript/typescript-aix-ppc64': 7.0.2
+ '@typescript/typescript-darwin-arm64': 7.0.2
+ '@typescript/typescript-darwin-x64': 7.0.2
+ '@typescript/typescript-freebsd-arm64': 7.0.2
+ '@typescript/typescript-freebsd-x64': 7.0.2
+ '@typescript/typescript-linux-arm': 7.0.2
+ '@typescript/typescript-linux-arm64': 7.0.2
+ '@typescript/typescript-linux-loong64': 7.0.2
+ '@typescript/typescript-linux-mips64el': 7.0.2
+ '@typescript/typescript-linux-ppc64': 7.0.2
+ '@typescript/typescript-linux-riscv64': 7.0.2
+ '@typescript/typescript-linux-s390x': 7.0.2
+ '@typescript/typescript-linux-x64': 7.0.2
+ '@typescript/typescript-netbsd-arm64': 7.0.2
+ '@typescript/typescript-netbsd-x64': 7.0.2
+ '@typescript/typescript-openbsd-arm64': 7.0.2
+ '@typescript/typescript-openbsd-x64': 7.0.2
+ '@typescript/typescript-sunos-x64': 7.0.2
+ '@typescript/typescript-win32-arm64': 7.0.2
+ '@typescript/typescript-win32-x64': 7.0.2
+
uc.micro@2.1.0: {}
uc.micro@3.0.0: {}
undici-types@7.19.2: {}
+ undici-types@8.3.0: {}
+
undici@7.25.0: {}
- vite@8.0.9(@types/node@25.6.0)(esbuild@0.27.7)(tsx@4.21.0):
+ vite@8.0.1(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2)(@types/node@26.2.0)(esbuild@0.27.7)(terser@5.50.0)(tsx@4.21.0):
dependencies:
lightningcss: 1.32.0
picomatch: 4.0.4
- postcss: 8.5.10
+ postcss: 8.5.26
+ rolldown: 1.0.0-rc.10(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2)
+ tinyglobby: 0.2.16
+ optionalDependencies:
+ '@types/node': 26.2.0
+ esbuild: 0.27.7
+ fsevents: 2.3.3
+ terser: 5.50.0
+ tsx: 4.21.0
+ transitivePeerDependencies:
+ - '@emnapi/core'
+ - '@emnapi/runtime'
+
+ vite@8.0.9(@types/node@25.6.0)(esbuild@0.27.7)(terser@5.50.0)(tsx@4.21.0):
+ dependencies:
+ lightningcss: 1.33.0
+ picomatch: 4.0.4
+ postcss: 8.5.26
rolldown: 1.0.0-rc.16
tinyglobby: 0.2.16
optionalDependencies:
'@types/node': 25.6.0
esbuild: 0.27.7
fsevents: 2.3.3
+ terser: 5.50.0
tsx: 4.21.0
- vitest@4.1.4(@types/node@25.6.0)(@vitest/coverage-v8@4.1.4)(jsdom@29.0.2)(vite@8.0.9(@types/node@25.6.0)(esbuild@0.27.7)(tsx@4.21.0)):
+ vitest@4.1.4(@types/node@25.6.0)(@vitest/coverage-v8@4.1.4)(jsdom@29.0.2)(vite@8.0.9(@types/node@25.6.0)(esbuild@0.27.7)(terser@5.50.0)(tsx@4.21.0)):
dependencies:
'@vitest/expect': 4.1.4
- '@vitest/mocker': 4.1.4(vite@8.0.9(@types/node@25.6.0)(esbuild@0.27.7)(tsx@4.21.0))
+ '@vitest/mocker': 4.1.4(vite@8.0.9(@types/node@25.6.0)(esbuild@0.27.7)(terser@5.50.0)(tsx@4.21.0))
'@vitest/pretty-format': 4.1.4
'@vitest/runner': 4.1.4
'@vitest/snapshot': 4.1.4
@@ -1931,7 +3073,7 @@ snapshots:
tinyexec: 1.1.1
tinyglobby: 0.2.16
tinyrainbow: 3.1.0
- vite: 8.0.9(@types/node@25.6.0)(esbuild@0.27.7)(tsx@4.21.0)
+ vite: 8.0.9(@types/node@25.6.0)(esbuild@0.27.7)(terser@5.50.0)(tsx@4.21.0)
why-is-node-running: 2.3.0
optionalDependencies:
'@types/node': 25.6.0
@@ -1940,6 +3082,10 @@ snapshots:
transitivePeerDependencies:
- msw
+ vue-demi@0.14.10(vue@3.5.41(typescript@7.0.2)):
+ dependencies:
+ vue: 3.5.41(typescript@7.0.2)
+
vue@3.5.32(typescript@6.0.3):
dependencies:
'@vue/compiler-dom': 3.5.32
@@ -1950,6 +3096,16 @@ snapshots:
optionalDependencies:
typescript: 6.0.3
+ vue@3.5.41(typescript@7.0.2):
+ dependencies:
+ '@vue/compiler-dom': 3.5.41
+ '@vue/compiler-sfc': 3.5.41
+ '@vue/runtime-dom': 3.5.41
+ '@vue/server-renderer': 3.5.41
+ '@vue/shared': 3.5.41
+ optionalDependencies:
+ typescript: 7.0.2
+
w3c-xmlserializer@5.0.0:
dependencies:
xml-name-validator: 5.0.0
diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml
index 72b69d9..ea631ff 100644
--- a/pnpm-workspace.yaml
+++ b/pnpm-workspace.yaml
@@ -1,3 +1,5 @@
# /pnpm-workspace.yaml
packages:
- 'packages/*'
+allowBuilds:
+ vue-demi: true