diff --git a/README.md b/README.md
index 098fdbc..24f66ac 100644
--- a/README.md
+++ b/README.md
@@ -19,13 +19,13 @@
## Key Features
-* **Crafting Recipes** – browse every craft with filters, icons, ingredients, HQ yields, and cross-links to inventory.
-* **Inventory Manager** – compact grid with tooltips, duplicate highlighting, scroll badges, and wiki links.
-* **Item Explorer** – searchable catalogue with dynamic sub-tabs for each item type.
-* **Spell Database** – scroll parsing auto-populates a `spells` table with job-level learn data.
-* **Responsive UI** – desktop / tablet / mobile layouts.
+* **Crafting Recipes** – colour-coded craft tabs (woodworking brown, smithing grey, etc.), full filters, icons, ingredients, HQ yields, and cross-links to inventory.
+* **Inventory Manager** – grid view with sorting (slot ▶ default, name, type), duplicate-item indicator (orange bar on top), tooltips, quantity badges, and direct wiki links.
+* **Item Explorer** – fast, fuzzy search across all items with type sub-tabs and recipe cross-navigation.
+* **Item Detail Dialog** – centred modal with icon, description, usage breakdown, and craft-coloured section headers.
+* **Responsive UI** – desktop / tablet / mobile layouts with dropdown storage selector on narrow screens.
* **Database-backed** – PostgreSQL stores normalised recipe / item / inventory / spells data.
-* **Automation Scripts** – Python ETL & loaders for recipes and spells.
+* **Automation Scripts** – Python ETL & loaders for recipes and spells, plus scroll → spell parser.
## Project Structure
diff --git a/TODO.md b/TODO.md
index 0494cdc..0ef4592 100644
--- a/TODO.md
+++ b/TODO.md
@@ -1,9 +1,11 @@
# Project TODO
## Bugs
+- [ ] Add automated regression test for explorer search (Vitest + RTL)
+
- [X] Pagination not working
-- [ ] Fix search in explorer
+- [X] Fix search in explorer
## UI Improvements
diff --git a/backend/app/router.py b/backend/app/router.py
index d185dfd..065e9bc 100644
--- a/backend/app/router.py
+++ b/backend/app/router.py
@@ -1,8 +1,8 @@
from typing import List, Optional, Tuple, Any
from datetime import datetime
-from fastapi import APIRouter, Depends, Query, HTTPException, Path, Response
-from sqlalchemy import text, select
+from fastapi import APIRouter, Depends, Query, HTTPException, Path, Response, UploadFile, File
+from sqlalchemy import text, select, insert
from sqlalchemy.ext.asyncio import AsyncSession
from pydantic import BaseModel
@@ -14,6 +14,52 @@ router = APIRouter()
# ---------------------------------------------------------------------------
# Crafting Recipes endpoints
+@router.post("/inventory/import")
+async def import_inventory_csv(
+ file: UploadFile = File(...),
+ session: AsyncSession = Depends(get_session),
+):
+ """Replace the entire inventory table with contents from an uploaded CSV.
+
+ The CSV must use the delimiter ``;`` and column headers: ``char;storage;item;quantity``.
+ """
+ import csv
+ import io
+ # Read file bytes and decode
+ contents = await file.read()
+ try:
+ text_data = contents.decode("utf-8")
+ except UnicodeDecodeError:
+ raise HTTPException(status_code=400, detail="CSV must be UTF-8 encoded")
+
+ reader = csv.DictReader(io.StringIO(text_data), delimiter=";", quotechar='"')
+ rows = []
+ for r in reader:
+ try:
+ qty = int(r["quantity"].strip()) if r["quantity"].strip() else 0
+ except (KeyError, ValueError):
+ raise HTTPException(status_code=400, detail="Invalid CSV schema or quantity value")
+ rows.append(
+ {
+ "character_name": r["char"].strip(),
+ "storage_type": r["storage"].strip(),
+ "item_name": r["item"].strip(),
+ "quantity": qty,
+ }
+ )
+
+ # Replace table contents inside a transaction
+ try:
+ await session.execute(text("TRUNCATE TABLE inventory;"))
+ if rows:
+ await session.execute(insert(Inventory), rows)
+ await session.commit()
+ except Exception as e:
+ await session.rollback()
+ raise HTTPException(status_code=500, detail=f"Failed to import CSV: {e}")
+
+ return {"imported": len(rows)}
+
ALLOWED_CRAFTS = {
"woodworking": "recipes_woodworking",
# Future crafts can be added here, e.g. "smithing": "recipes_smithing"
@@ -102,6 +148,7 @@ class ItemSummary(BaseModel):
name: str
icon_id: Optional[str]
type_description: Optional[str]
+ jobs_description: Optional[List[str]]
@router.get("/items", response_model=List[ItemSummary])
@@ -139,12 +186,14 @@ async def items(
total_count = total_res.scalar() or 0
response.headers["X-Total-Count"] = str(total_count)
+ # Use LEFT JOIN to fetch jobs_description from armor_items table; it will be NULL for non-armor.
+ join_sql = "FROM all_items a LEFT JOIN armor_items ai ON ai.id = a.id"
q = text(
- f"SELECT id, name, icon_id, type_description FROM all_items {where_sql} ORDER BY id LIMIT :limit OFFSET :offset"
+ f"SELECT a.id, a.name, a.icon_id, a.type_description, ai.jobs_description {join_sql} {where_sql} ORDER BY a.id LIMIT :limit OFFSET :offset"
)
result = await session.execute(q, params)
rows = result.fetchall()
- return [ItemSummary(id=r.id, name=r.name, icon_id=r.icon_id, type_description=r.type_description) for r in rows]
+ return [ItemSummary(id=r.id, name=r.name, icon_id=r.icon_id, type_description=r.type_description, jobs_description=r.jobs_description) for r in rows]
class ItemDetail(BaseModel):
diff --git a/backend/requirements.txt b/backend/requirements.txt
index d2ac312..2db5881 100644
--- a/backend/requirements.txt
+++ b/backend/requirements.txt
@@ -4,3 +4,4 @@ SQLAlchemy[asyncio]==2.0.27
asyncpg==0.29.0
pydantic==2.7.1
python-dotenv==1.0.1
+python-multipart==0.0.9
diff --git a/datasets/inventory.csv b/datasets/inventory.csv
new file mode 100755
index 0000000..4024686
--- /dev/null
+++ b/datasets/inventory.csv
@@ -0,0 +1,967 @@
+"char";"storage";"item";"quantity"
+"Rynore";"inventory";"Bone Quiver";"1"
+"Rynore";"inventory";"Instant Warp";"1"
+"Rynore";"inventory";"Bone Chip";"1"
+"Rynore";"inventory";"Moko Grass";"1"
+"Rynore";"inventory";"Florid Leaf Mold";"7"
+"Rynore";"inventory";"Adoulinian Kelp";"1"
+"Rynore";"inventory";"Linkpearl";"1"
+"Rynore";"inventory";"Frgtn. Thought";"1"
+"Rynore";"inventory";"Goblin Stir-Fry";"1"
+"Rynore";"inventory";"Potion";"1"
+"Rynore";"inventory";"Onion Dagger";"2"
+"Rynore";"inventory";"Maple Table";"3"
+"Rynore";"inventory";"Hatchet";"10"
+"Rynore";"inventory";"Onion Rod";"2"
+"Rynore";"inventory";"Onion Staff";"1"
+"Rynore";"inventory";"Ark Shield";"1"
+"Rynore";"inventory";"Apple Pie";"1"
+"Rynore";"inventory";"Echo Drops";"1"
+"Rynore";"inventory";"Distilled Water";"1"
+"Rynore";"inventory";"Derfland Pear";"1"
+"Rynore";"inventory";"Rem's Tale Ch.2";"2"
+"Rynore";"inventory";"Vlr. Gauntlets -1";"1"
+"Rynore";"inventory";"Eggplant";"1"
+"Rynore";"inventory";"Roast Mushroom";"5"
+"Rynore";"inventory";"Ginger";"1"
+"Rynore";"inventory";"Little Worm";"5"
+"Rynore";"inventory";"Pugil Scales";"3"
+"Rynore";"inventory";"Vile Elixir";"1"
+"Rynore";"inventory";"Chariot Band";"1"
+"Rynore";"inventory";"Sickle";"1"
+"Rynore";"inventory";"San d'Or. Carrot";"6"
+"Rynore";"inventory";"Woozyshroom";"12"
+"Rynore";"inventory";"Mythril Ore";"1"
+"Rynore";"inventory";"Elm Strongbox";"1"
+"Rynore";"inventory";"Mahogany Bed";"1"
+"Rynore";"inventory";"Venus Orb";"1"
+"Rynore";"inventory";"Win. Tea Leaves";"4"
+"Rynore";"inventory";"Baked Popoto";"8"
+"Rynore";"inventory";"M Rice Cake";"1"
+"Rynore";"inventory";"Lilac";"2"
+"Rynore";"inventory";"Rockoil";"7"
+"Rynore";"inventory";"Flint Caviar";"1"
+"Rynore";"inventory";"Selbina Clay";"1"
+"Rynore";"inventory";"Napa";"2"
+"Rynore";"inventory";"Spider Web";"1"
+"Rynore";"inventory";"Iolite";"1"
+"Rynore";"inventory";"Rusty Bucket";"5"
+"Rynore";"inventory";"Sea Foliage";"1"
+"Rynore";"inventory";"Pea Soup";"1"
+"Rynore";"inventory";"Auric Sand";"1"
+"Rynore";"inventory";"Beetle Jaw";"3"
+"Rynore";"inventory";"Roast Mutton";"2"
+"Rynore";"inventory";"Saruta Orange";"1"
+"Rynore";"inventory";"Herb Seeds";"4"
+"Rynore";"inventory";"Vegetable Gruel";"1"
+"Rynore";"inventory";"Moldy Torque";"1"
+"Rynore";"inventory";"Crawler Cocoon";"5"
+"Rynore";"inventory";"Voidleg: DRG";"1"
+"Rynore";"inventory";"Parchment";"1"
+"Rynore";"inventory";"G. Seed Pouch";"11"
+"Rynore";"inventory";"King Locust";"1"
+"Rynore";"inventory";"Snapping Mole";"1"
+"Rynore";"inventory";"Moat Carp";"5"
+"Rynore";"inventory";"Insect Wing";"1"
+"Rynore";"inventory";"E Rice Cake";"1"
+"Rynore";"inventory";"Gnatbane";"1"
+"Rynore";"inventory";"Whine Cellar Key";"1"
+"Rynore";"inventory";"Burdock";"1"
+"Rynore";"inventory";"Date";"1"
+"Rynore";"inventory";"Sheep Tooth";"7"
+"Rynore";"inventory";"Tomato Juice";"1"
+"Rynore";"wardrobe";"Windshear Hat";"1"
+"Rynore";"wardrobe";"Duelist's Gloves";"1"
+"Rynore";"wardrobe";"Asn. Poulaines +1";"1"
+"Rynore";"wardrobe";"Beetle Leggings";"1"
+"Rynore";"wardrobe";"Cuffs";"1"
+"Rynore";"wardrobe";"Chocobo Masque +1";"1"
+"Rynore";"wardrobe";"She-Slime Hat";"1"
+"Rynore";"wardrobe";"Shade Tights";"1"
+"Rynore";"wardrobe";"Chocobo Shirt";"1"
+"Rynore";"wardrobe";"Targe";"1"
+"Rynore";"wardrobe";"Bl. Chocobo Suit";"1"
+"Rynore";"wardrobe";"Studded Gloves";"1"
+"Rynore";"wardrobe";"Slacks";"1"
+"Rynore";"wardrobe";"Scale Greaves";"1"
+"Rynore";"wardrobe";"Lizard Ledelsens";"1"
+"Rynore";"wardrobe";"Rusty Cap";"3"
+"Rynore";"wardrobe";"Purple Ribbon";"1"
+"Rynore";"wardrobe";"Scale Cuisses";"1"
+"Rynore";"wardrobe";"Chain Hose";"1"
+"Rynore";"wardrobe";"Garish Crown";"1"
+"Rynore";"wardrobe";"Cotton Gaiters";"1"
+"Rynore";"wardrobe";"Vgd. Gloves";"2"
+"Rynore";"wardrobe";"Studded Vest";"1"
+"Rynore";"wardrobe";"Alumine Moufles";"1"
+"Rynore";"wardrobe";"Alumine Sollerets";"1"
+"Rynore";"wardrobe";"Mitts";"1"
+"Rynore";"wardrobe";"Trader's Slops";"1"
+"Rynore";"wardrobe";"Lizard Trousers";"1"
+"Rynore";"wardrobe";"Scale Fng. Gnt.";"1"
+"Rynore";"wardrobe";"Chocobo Suit +1";"1"
+"Rynore";"wardrobe";"Gloves";"1"
+"Rynore";"wardrobe";"Blue Ribbon";"1"
+"Rynore";"wardrobe";"Raising Earring";"1"
+"Rynore";"wardrobe";"Destrier Beret";"1"
+"Rynore";"wardrobe";"Naga Hakama";"1"
+"Rynore";"wardrobe";"Thur. Gloves +1";"1"
+"Rynore";"wardrobe";"White Mitts";"1"
+"Rynore";"wardrobe";"Thur. Tabard +1";"1"
+"Rynore";"wardrobe";"Bone Pick";"1"
+"Rynore";"wardrobe";"Slime Cap";"1"
+"Rynore";"wardrobe";"Elm Pole";"1"
+"Rynore";"wardrobe";"Tabar";"1"
+"Rynore";"wardrobe";"Dream Hat";"1"
+"Rynore";"wardrobe";"Bone Hairpin";"1"
+"Rynore";"wardrobe";"Alumine Salade";"1"
+"Rynore";"wardrobe";"Studded Trousers";"1"
+"Rynore";"wardrobe";"Iron Cuisses";"1"
+"Rynore";"wardrobe";"Overalls";"1"
+"Rynore";"wardrobe";"Trader's Cuffs";"1"
+"Rynore";"wardrobe";"Garish Tunic";"1"
+"Rynore";"wardrobe";"Alumine Brayettes";"1"
+"Rynore";"wardrobe";"Alumine Haubert";"1"
+"Rynore";"wardrobe";"Trader's Pigaches";"1"
+"Rynore";"wardrobe";"Brass Subligar";"1"
+"Rynore";"wardrobe";"Lizard Gloves";"1"
+"Rynore";"wardrobe";"Tide Gages";"1"
+"Rynore";"wardrobe";"Brass Mittens";"1"
+"Rynore";"wardrobe";"Bl. Chocobo Cap";"1"
+"Rynore";"wardrobe";"Phl. Trousers";"1"
+"Rynore";"wardrobe";"Snowman Cap";"1"
+"Rynore";"wardrobe";"Slops";"1"
+"Rynore";"wardrobe";"Greaves";"1"
+"Rynore";"wardrobe";"Thur. Chapeau +1";"1"
+"Rynore";"wardrobe";"Cotton Brais";"1"
+"Rynore";"wardrobe";"Sitabaki";"1"
+"Rynore";"wardrobe";"Thur. Boots +1";"1"
+"Rynore";"wardrobe";"Thur. Tights +1";"1"
+"Rynore";"wardrobe";"Temple Torque";"1"
+"Rynore";"wardrobe";"Kingdom Aketon";"1"
+"Rynore";"wardrobe";"Metal Slime Hat";"1"
+"Rynore";"wardrobe";"Solea";"1"
+"Rynore";"wardrobe";"Garish Slacks";"1"
+"Rynore";"wardrobe";"Cotton Gloves";"1"
+"Rynore";"safe";"Bookshelf";"2"
+"Rynore";"safe";"Treant Bulb";"8"
+"Rynore";"safe";"Giant Fish Bones";"1"
+"Rynore";"safe";"Wool Thread";"1"
+"Rynore";"safe";"Cabinet";"1"
+"Rynore";"safe";"Pugil Scales";"10"
+"Rynore";"safe";"Cupboard";"1"
+"Rynore";"safe";"VCS Reg. Card";"1"
+"Rynore";"safe";"Wastebasket";"1"
+"Rynore";"safe";"Qdv. Mage Blood";"1"
+"Rynore";"safe";"Gnole Pellets";"1"
+"Rynore";"safe";"Mannequin Feet";"1"
+"Rynore";"safe";"Recital Bench";"1"
+"Rynore";"safe";"Bahut";"1"
+"Rynore";"safe";"Bookstack";"1"
+"Rynore";"safe";"Regen III";"1"
+"Rynore";"safe";"Slime Rocket";"9"
+"Rynore";"safe";"R. Orchid Vase";"1"
+"Rynore";"safe";"Stationery Set";"1"
+"Rynore";"safe";"Ceramic Flowerpot";"1"
+"Rynore";"safe";"Ram Skin";"12"
+"Rynore";"safe";"Lizard Skin";"8"
+"Rynore";"safe";"Dial Key #Ab";"16"
+"Rynore";"safe";"Butterfly Cage";"1"
+"Rynore";"safe";"Chest";"1"
+"Rynore";"safe";"Wicker Box";"1"
+"Rynore";"safe";"Maple Table";"1"
+"Rynore";"safe";"Wolf Hide";"7"
+"Rynore";"safe";"San d'Orian Tree";"1"
+"Rynore";"safe";"Noble's Bed";"1"
+"Rynore";"safe";"Vana'clock";"1"
+"Rynore";"safe";"Thundaga II";"1"
+"Rynore";"safe";"Reraise III";"1"
+"Rynore";"safe";"Snowman Knight";"1"
+"Rynore";"safe";"Gain-STR";"1"
+"Rynore";"safe";"Firesand";"1"
+"Rynore";"safe";"Fiend Blood";"5"
+"Rynore";"safe";"Brass Ingot";"2"
+"Rynore";"safe";"Silver Beastcoin";"12"
+"Rynore";"safe";"6-Drawer Almirah";"1"
+"Rynore";"safe";"F.Abjuration: Bd.";"1"
+"Rynore";"safe";"Shellra IV";"1"
+"Rynore";"safe";"Wardrobe";"1"
+"Rynore";"safe";"Foe Lullaby";"1"
+"Rynore";"safe";"Tantra Seal: Ft.";"4"
+"Rynore";"safe";"San d'Orian Flag";"1"
+"Rynore";"safe";"Bonbori";"1"
+"Rynore";"safe";"Arrowwood Lbr.";"5"
+"Rynore";"safe";"Refresh";"1"
+"Rynore";"safe";"Ram Horn";"5"
+"Rynore";"safe";"Luminicloth";"4"
+"Rynore";"safe";"Giant Stinger";"4"
+"Rynore";"safe";"Cotton Cloth";"4"
+"Rynore";"safe";"Yel. VCS Plaque";"1"
+"Rynore";"safe";"Deton. Sphere";"5"
+"Rynore";"safe";"Ornate Fragment";"1"
+"Rynore";"safe";"Adaman Ore";"4"
+"Rynore";"safe";"Prismatic Chest";"1"
+"Rynore";"safe";"Insect Wing";"12"
+"Rynore";"storage";"Red Moko Grass";"4"
+"Rynore";"storage";"Va.Abjuration: Ft.";"1"
+"Rynore";"storage";"Mahogany Log";"3"
+"Rynore";"storage";"Ephemeral Cloth";"1"
+"Rynore";"storage";"Sleepshroom";"1"
+"Rynore";"storage";"Coeurl Meat";"1"
+"Rynore";"storage";"Vegetable Seeds";"7"
+"Rynore";"storage";"Mythril Leaf";"1"
+"Rynore";"storage";"Titanite";"4"
+"Rynore";"storage";"Ash Lumber";"12"
+"Rynore";"storage";"Land Crab Meat";"12"
+"Rynore";"storage";"Vanilla";"5"
+"Rynore";"storage";"Chamomile";"1"
+"Rynore";"storage";"Bukktooth";"1"
+"Rynore";"storage";"Mhaura Garlic";"3"
+"Rynore";"storage";"Cockatrice Meat";"1"
+"Rynore";"storage";"Poison Dust";"2"
+"Rynore";"storage";"Mushrm. Locust";"3"
+"Rynore";"storage";"Cactus Stems";"3"
+"Rynore";"storage";"Dried Marjoram";"1"
+"Rynore";"storage";"Elm Log";"12"
+"Rynore";"storage";"Rotten Meat";"17"
+"Rynore";"storage";"Red Terrapin";"16"
+"Rynore";"storage";"Mighty Sardonyx";"1"
+"Rynore";"storage";"Quus";"18"
+"Rynore";"storage";"Hare Meat";"29"
+"Rynore";"storage";"Insect Wing";"6"
+"Rynore";"storage";"Lizard Egg";"2"
+"Rynore";"storage";"Bomb Ash";"1"
+"Rynore";"storage";"Eastern Gem";"3"
+"Rynore";"storage";"Dhalmel Hide";"1"
+"Rynore";"storage";"Blk. Tiger Fang";"12"
+"Rynore";"storage";"Mulsum";"11"
+"Rynore";"storage";"Crawler Calculus";"1"
+"Rynore";"storage";"Royal Jelly";"1"
+"Rynore";"storage";"Crystal Petrifact";"1"
+"Rynore";"storage";"Chestnut";"7"
+"Rynore";"storage";"Win. Tea Leaves";"10"
+"Rynore";"storage";"Honey";"31"
+"Rynore";"storage";"Hecteyes Eye";"2"
+"Rynore";"storage";"Crawler Cocoon";"11"
+"Rynore";"storage";"Acorn";"5"
+"Rynore";"storage";"Chocobo Fltchg.";"2"
+"Rynore";"storage";"Faerie Apple";"8"
+"Rynore";"storage";"Deathball";"1"
+"Rynore";"storage";"Seedspall Lux";"1"
+"Rynore";"storage";"Poison Potion";"3"
+"Rynore";"storage";"Recoll. of Pain";"1"
+"Rynore";"storage";"Chestnut Log";"1"
+"Rynore";"storage";"Pine Nuts";"7"
+"Rynore";"storage";"Scop's Operetta";"1"
+"Rynore";"storage";"Black Pepper";"7"
+"Rynore";"storage";"Jade Cell";"2"
+"Rynore";"storage";"Wild Onion";"5"
+"Rynore";"storage";"Yogurt";"6"
+"Rynore";"storage";"Yagudo Feather";"12"
+"Rynore";"storage";"Black C. Feather";"1"
+"Rynore";"storage";"Kazham Peppers";"9"
+"Rynore";"storage";"Crawler Egg";"1"
+"Rynore";"storage";"Fish Mithkabob";"12"
+"Rynore";"storage";"Toko. Wildgrass";"12"
+"Rynore";"storage";"Dryad Root";"2"
+"Rynore";"storage";"Mtl. Beastcoin";"2"
+"Rynore";"storage";"Dhalmel Meat";"11"
+"Rynore";"storage";"Arrowwood Lbr.";"12"
+"Rynore";"storage";"Tarutaru Rice";"12"
+"Rynore";"storage";"Danceshroom";"1"
+"Rynore";"storage";"G. Sheep Meat";"4"
+"Rynore";"storage";"Beetle Shell";"24"
+"Rynore";"storage";"Rosewood Log";"1"
+"Rynore";"storage";"Soy Milk";"16"
+"Rynore";"satchel";"H.Q. Antlion Jaw";"10"
+"Rynore";"satchel";"King Truffle";"6"
+"Rynore";"satchel";"Slime Oil";"3"
+"Rynore";"satchel";"Adamantoise Shell";"5"
+"Rynore";"satchel";"Phrygian Ore";"1"
+"Rynore";"satchel";"Fire V";"1"
+"Rynore";"satchel";"Moko Grass";"1"
+"Rynore";"satchel";"Holy Basil";"2"
+"Rynore";"satchel";"Adoulinian Kelp";"31"
+"Rynore";"satchel";"Bat Fang";"9"
+"Rynore";"satchel";"Maple Log";"4"
+"Rynore";"satchel";"Antlion Jaw";"4"
+"Rynore";"satchel";"Silver Nugget";"2"
+"Rynore";"satchel";"Sage";"3"
+"Rynore";"satchel";"Eudaemon Blade";"1"
+"Rynore";"satchel";"H.Q. Marid Hide";"1"
+"Rynore";"satchel";"Tree Cuttings";"5"
+"Rynore";"satchel";"Chicken Bone";"1"
+"Rynore";"satchel";"Fresh Marjoram";"12"
+"Rynore";"satchel";"Senroh Sardine";"20"
+"Rynore";"satchel";"Saruta Cotton";"12"
+"Rynore";"satchel";"Blue Peas";"2"
+"Rynore";"satchel";"Elm Log";"1"
+"Rynore";"satchel";"Mackerel";"12"
+"Rynore";"satchel";"H.Q. B. Tiger Hide";"1"
+"Rynore";"satchel";"Bat Wing";"7"
+"Rynore";"satchel";"Saffron";"1"
+"Rynore";"satchel";"Insect Wing";"9"
+"Rynore";"satchel";"Scorpion Claw";"21"
+"Rynore";"satchel";"Eggplant";"11"
+"Rynore";"satchel";"Herb Seeds";"12"
+"Rynore";"satchel";"Pugil Scales";"10"
+"Rynore";"satchel";"Malboro Vine";"1"
+"Rynore";"satchel";"Kukuru Bean";"3"
+"Rynore";"satchel";"Tomato Juice";"1"
+"Rynore";"satchel";"Snow Geode";"1"
+"Rynore";"satchel";"Blizzard III";"1"
+"Rynore";"satchel";"Flax Flower";"11"
+"Rynore";"satchel";"Wyvern Wing";"1"
+"Rynore";"satchel";"Scorpion Shell";"3"
+"Rynore";"satchel";"Crawler Cocoon";"12"
+"Rynore";"satchel";"Stonega III";"1"
+"Rynore";"satchel";"Grain Seeds";"12"
+"Rynore";"satchel";"Mercury";"10"
+"Rynore";"satchel";"Bay Leaves";"5"
+"Rynore";"satchel";"Lacquer Tree Log";"4"
+"Rynore";"satchel";"Fire III";"1"
+"Rynore";"satchel";"Sheep Tooth";"12"
+"Rynore";"satchel";"Ladybug Wing";"7"
+"Rynore";"satchel";"Rabbit Hide";"4"
+"Rynore";"satchel";"Stone IV";"1"
+"Rynore";"satchel";"Ash Log";"2"
+"Rynore";"satchel";"San d'Or. Flour";"4"
+"Rynore";"satchel";"Crab Shell";"1"
+"Rynore";"satchel";"Crayfish";"42"
+"Rynore";"satchel";"Sacrifice";"1"
+"Rynore";"satchel";"Sunflower Seeds";"2"
+"Rynore";"satchel";"Tin Ore";"2"
+"Rynore";"satchel";"Lizard Tail";"4"
+"Rynore";"satchel";"Fresh Mugwort";"3"
+"Rynore";"satchel";"King Locust";"3"
+"Rynore";"satchel";"Snapping Mole";"12"
+"Rynore";"satchel";"Mannequin Body";"1"
+"Rynore";"satchel";"Copper Frog";"12"
+"Rynore";"satchel";"Ulbukan Lobster";"15"
+"Rynore";"satchel";"Spider Web";"21"
+"Rynore";"satchel";"Giant Bird Fthr.";"1"
+"Rynore";"satchel";"Fish Bones";"5"
+"Rynore";"satchel";"Beetle Shell";"4"
+"Rynore";"satchel";"Saffron Blossom";"2"
+"Rynore";"satchel";"Bee Pollen";"1"
+"Rynore";"sack";"Honey Wine";"1"
+"Rynore";"sack";"Damselfly Worm";"1"
+"Rynore";"sack";"Gold Ore";"2"
+"Rynore";"sack";"Crab Sushi";"12"
+"Rynore";"sack";"Bone Chip";"30"
+"Rynore";"sack";"Maple Lumber";"2"
+"Rynore";"sack";"Roasted Corn";"6"
+"Rynore";"sack";"Antlion Spirit";"59"
+"Rynore";"sack";"Yag. Holy Water";"1"
+"Rynore";"sack";"Cactus Arm";"3"
+"Rynore";"sack";"Ash Lumber";"7"
+"Rynore";"sack";"Silent Oil";"12"
+"Rynore";"sack";"Beetle Spirit";"53"
+"Rynore";"sack";"Sage";"4"
+"Rynore";"sack";"Platinum Ore";"4"
+"Rynore";"sack";"Igneous Rock";"8"
+"Rynore";"sack";"Marguerite";"2"
+"Rynore";"sack";"Graubg. Lettuce";"1"
+"Rynore";"sack";"Mola Mola";"2"
+"Rynore";"sack";"Frost Turnip";"1"
+"Rynore";"sack";"Trail Cookie";"3"
+"Rynore";"sack";"Saruta Cotton";"9"
+"Rynore";"sack";"Distilled Water";"1"
+"Rynore";"sack";"Bkn. Fast. Rod";"1"
+"Rynore";"sack";"Silk Cloth";"1"
+"Rynore";"sack";"Asphodel";"2"
+"Rynore";"sack";"Walnut Log";"1"
+"Rynore";"sack";"La Theine Cbg.";"3"
+"Rynore";"sack";"Yayla Corbasi";"1"
+"Rynore";"sack";"Black Pearl";"1"
+"Rynore";"sack";"Gem of the South";"1"
+"Rynore";"sack";"Grass Thread";"6"
+"Rynore";"sack";"Sole Sushi";"11"
+"Rynore";"sack";"San d'Or. Carrot";"8"
+"Rynore";"sack";"Mythril Ore";"5"
+"Rynore";"sack";"Prism Powder";"12"
+"Rynore";"sack";"Cld. Coffer Key";"1"
+"Rynore";"sack";"Fruit Seeds";"8"
+"Rynore";"sack";"Hard-boiled Egg";"2"
+"Rynore";"sack";"Win. Tea Leaves";"12"
+"Rynore";"sack";"Beastcoin";"1"
+"Rynore";"sack";"Pachy. Spirit";"5"
+"Rynore";"sack";"Ladybug Wing";"2"
+"Rynore";"sack";"Loc. Elutriator";"1"
+"Rynore";"sack";"Vanadium Ore";"1"
+"Rynore";"sack";"Popoto";"3"
+"Rynore";"sack";"Rancor Tank";"1"
+"Rynore";"sack";"O. Bronzepiece";"1"
+"Rynore";"sack";"Beastman Blood";"2"
+"Rynore";"sack";"Burdock";"1"
+"Rynore";"sack";"Deodorizer";"12"
+"Rynore";"sack";"Monkey Wine";"1"
+"Rynore";"sack";"Twitherym Wing";"1"
+"Rynore";"sack";"Beetle Jaw";"8"
+"Rynore";"sack";"Rancor Handle";"1"
+"Rynore";"sack";"Isleracea";"1"
+"Rynore";"sack";"Teak Log";"1"
+"Rynore";"sack";"Yagudo Cherry";"3"
+"Rynore";"sack";"P. DRK Card";"1"
+"Rynore";"sack";"Snowy Cermet";"1"
+"Rynore";"sack";"Umbril Ooze";"1"
+"Rynore";"sack";"Remedy";"3"
+"Rynore";"sack";"Misx. Parsley";"7"
+"Rynore";"sack";"Shrimp Lantern";"1"
+"Rynore";"sack";"Darksteel Ore";"11"
+"Rynore";"sack";"Star Spinel";"1"
+"Rynore";"sack";"Vegetable Seeds";"8"
+"Rynore";"sack";"Auric Sand";"6"
+"Rynore";"sack";"Counterfeit Gil";"3"
+"Rynore";"sack";"Beetle Shell";"3"
+"Rynore";"sack";"Shall Shell";"16"
+"Rynore";"sack";"Meteorite";"2"
+"Rynore";"sack";"Ph. Gold Ingot";"1"
+"Rynore";"sack";"Obr. Bull. Pouch";"1"
+"Rynore";"case";"Prelate Key";"1"
+"Rynore";"case";"Beitetsu";"58"
+"Rynore";"case";"Kitchen Stove";"1"
+"Rynore";"case";"Goblin Helm";"3"
+"Rynore";"case";"Stone Arrowhd.";"36"
+"Rynore";"case";"Bat Fang";"60"
+"Rynore";"case";"Voiddust";"1"
+"Rynore";"case";"Ram Skin";"2"
+"Rynore";"case";"Tonberry Lantern";"1"
+"Rynore";"case";"Ametrine";"1"
+"Rynore";"case";"G. Bird Plume";"1"
+"Rynore";"case";"Nyumomo Doll";"1"
+"Rynore";"case";"Goblin Mail";"2"
+"Rynore";"case";"Xhifhut Body";"1"
+"Rynore";"case";"Cactus Stems";"1"
+"Rynore";"case";"Fluorite";"1"
+"Rynore";"case";"Silver Beastcoin";"1"
+"Rynore";"case";"Unlit Lantern";"1"
+"Rynore";"case";"Breeze Geode";"1"
+"Rynore";"case";"Bat Wing";"22"
+"Rynore";"case";"Echo Drops";"12"
+"Rynore";"case";"Xhifhut Strings";"1"
+"Rynore";"case";"Antican Pauldron";"1"
+"Rynore";"case";"Gold Beastcoin";"1"
+"Rynore";"case";"Roast Mushroom";"12"
+"Rynore";"case";"Raptor Skin";"2"
+"Rynore";"case";"Cobalt Cell";"1"
+"Rynore";"case";"Amemet Skin";"1"
+"Rynore";"case";"Blk. Tiger Fang";"3"
+"Rynore";"case";"Gigas Socks";"5"
+"Rynore";"case";"Prism Powder";"12"
+"Rynore";"case";"Mermaid Hands";"1"
+"Rynore";"case";"Xhifhut Bow";"1"
+"Rynore";"case";"Xanthous Cell";"1"
+"Rynore";"case";"Rabbit Hide";"20"
+"Rynore";"case";"Percolator";"1"
+"Rynore";"case";"Sun Water";"1"
+"Rynore";"case";"Diorite";"1"
+"Rynore";"case";"P. WHM Card";"1"
+"Rynore";"case";"Chocobo Fltchg.";"198"
+"Rynore";"case";"Animal Glue";"3"
+"Rynore";"case";"Sanguinet";"1"
+"Rynore";"case";"Revival Root";"19"
+"Rynore";"case";"Seasoning Stone";"24"
+"Rynore";"case";"Fish Bones";"6"
+"Rynore";"case";"Cotton Thread";"9"
+"Rynore";"case";"Samwell's Shank";"1"
+"Rynore";"case";"Beetle Jaw";"12"
+"Rynore";"case";"Kitchen Brick";"1"
+"Rynore";"case";"Jade Cell";"1"
+"Rynore";"case";"Tree Sap";"1"
+"Rynore";"case";"Flickering Lantern";"1"
+"Rynore";"case";"Oak Log";"1"
+"Rynore";"case";"Tonberry Coat";"21"
+"Rynore";"case";"Tin Ore";"1"
+"Rynore";"case";"Cockatrice Meat";"2"
+"Rynore";"case";"Arrowwood Lbr.";"15"
+"Rynore";"case";"Delkfutt Key";"1"
+"Rynore";"case";"Sheepskin";"3"
+"Rynore";"case";"Legshard: GEO";"1"
+"Rynore";"case";"Indi-Frailty";"1"
+"Rynore";"case";"Elm Lumber";"1"
+"Rynore";"case";"Lufet Salt";"2"
+"Rynore";"case";"Soil Geode";"1"
+"Rynore";"case";"Rubicund Cell";"1"
+"Rynore";"case";"Beetle Shell";"8"
+"Rynore";"case";"Fenrite";"1"
+"Rynore";"case";"Wamoura Silk";"1"
+"Rynore";"safe2";"Grass Cloth";"2"
+"Rynore";"safe2";"Bronze Rose";"1"
+"Rynore";"safe2";"Bst. Testimony";"1"
+"Rynore";"safe2";"Seasoning Stone";"8"
+"Rynore";"safe2";"Tonko: Ni";"1"
+"Rynore";"safe2";"Flint Stone";"16"
+"Rynore";"safe2";"Argyro Rivet";"1"
+"Rynore";"safe2";"Pld. Testimony";"1"
+"Rynore";"safe2";"Absorb-INT";"1"
+"Rynore";"safe2";"Beeswax";"5"
+"Rynore";"safe2";"Absorb-VIT";"1"
+"Rynore";"safe2";"Indi-Wilt";"1"
+"Rynore";"safe2";"Archer's Prelude";"1"
+"Rynore";"safe2";"Rng. Testimony";"1"
+"Rynore";"safe2";"E.Abjuration: Hd.";"1"
+"Rynore";"safe2";"Mizu-Deppo";"99"
+"Rynore";"safe2";"Fossilized Fang";"6"
+"Rynore";"safe2";"Drk. Testimony";"1"
+"Rynore";"safe2";"P.Abjuration: Ft.";"1"
+"Rynore";"safe2";"Raiton: Ni";"1"
+"Rynore";"safe2";"Riftborn Boulder";"69"
+"Rynore";"safe2";"War. Testimony";"1"
+"Rynore";"safe2";"A.Abjuration: Ft.";"1"
+"Rynore";"safe2";"Silver Ore";"9"
+"Rynore";"safe2";"Mercury";"7"
+"Rynore";"safe2";"Mythril Ore";"12"
+"Rynore";"safe2";"E.Abjuration: Hn.";"1"
+"Rynore";"safe2";"Papaka Grass";"2"
+"Rynore";"safe2";"Sciss. Sphere";"8"
+"Rynore";"safe2";"Drg. Testimony";"1"
+"Rynore";"safe2";"Bird Feather";"14"
+"Rynore";"safe2";"Lindwurm Skin";"2"
+"Rynore";"safe2";"Iron Ore";"15"
+"Rynore";"safe2";"Spect. Goldenrod";"2"
+"Rynore";"safe2";"Ancient Blood";"1"
+"Rynore";"safe2";"Nin. Testimony";"1"
+"Rynore";"safe2";"Saruta Cotton";"7"
+"Rynore";"safe2";"Absorb-AGI";"1"
+"Rynore";"safe2";"Doton: Ni";"1"
+"Rynore";"safe2";"Huton: Ni";"1"
+"Rynore";"safe2";"Yagudo Feather";"7"
+"Rynore";"safe2";"2Leaf Mandra Bud";"4"
+"Rynore";"safe2";"Mnk. Testimony";"1"
+"Rynore";"safe2";"Barrage Turbine";"1"
+"Rynore";"safe2";"Bkn. Taru. Rod";"1"
+"Rynore";"safe2";"Kopparnickel Ore";"7"
+"Rynore";"safe2";"Slv. Arrowheads";"1"
+"Rynore";"safe2";"Whm. Testimony";"1"
+"Rynore";"safe2";"Silk Thread";"9"
+"Rynore";"safe2";"Zinc Ore";"23"
+"Rynore";"safe2";"Rdm. Testimony";"1"
+"Rynore";"safe2";"Pluton";"22"
+"Rynore";"safe2";"Steel Nugget";"1"
+"Rynore";"safe2";"Dokumori: Ichi";"1"
+"Rynore";"safe2";"Blm. Testimony";"1"
+"Rynore";"safe2";"Dresser";"1"
+"Rynore";"wardrobe2";"Wayfarer Clogs";"1"
+"Rynore";"wardrobe2";"Buckler";"1"
+"Rynore";"wardrobe2";"Carapace Mask";"1"
+"Rynore";"wardrobe2";"Almogavar Bow";"1"
+"Rynore";"wardrobe2";"Agile Mantle";"1"
+"Rynore";"wardrobe2";"Shinobi Gi";"1"
+"Rynore";"wardrobe2";"Velvet Hat";"1"
+"Rynore";"wardrobe2";"Iron Mittens";"1"
+"Rynore";"wardrobe2";"Gauntlets";"1"
+"Rynore";"wardrobe2";"Hoplon";"1"
+"Rynore";"wardrobe2";"Channeling Robe";"1"
+"Rynore";"wardrobe2";"Shinobi Hakama";"1"
+"Rynore";"wardrobe2";"Sahip Helm";"1"
+"Rynore";"wardrobe2";"Shinobi Hachigane";"1"
+"Rynore";"wardrobe2";"Wayfarer Circlet";"1"
+"Rynore";"wardrobe2";"Cuir Trousers";"1"
+"Rynore";"wardrobe2";"Wayfarer Slops";"1"
+"Rynore";"wardrobe2";"Ogre Trousers";"1"
+"Rynore";"wardrobe2";"Padded Armor";"1"
+"Rynore";"wardrobe2";"Crow Beret";"1"
+"Rynore";"wardrobe2";"Gambison";"1"
+"Rynore";"wardrobe2";"Sallet";"1"
+"Rynore";"wardrobe2";"Ryl.Sqr. Robe";"1"
+"Rynore";"wardrobe2";"Wool Cuffs";"1"
+"Rynore";"wardrobe2";"Gnd.T.K. Bangles";"1"
+"Rynore";"wardrobe2";"Crow Hose";"1"
+"Rynore";"wardrobe2";"Espial Cap";"1"
+"Rynore";"wardrobe2";"Ryl.Kgt. Aketon";"1"
+"Rynore";"wardrobe2";"Velvet Cuffs";"1"
+"Rynore";"wardrobe2";"Gothic Sabatons";"1"
+"Rynore";"wardrobe2";"Wayfarer Cuffs";"1"
+"Rynore";"wardrobe2";"Wayfarer Robe";"1"
+"Rynore";"wardrobe2";"Carapace Harness";"1"
+"Rynore";"wardrobe2";"Cuir Highboots";"1"
+"Rynore";"wardrobe2";"Carapace Subligar";"1"
+"Rynore";"wardrobe2";"Cuisses";"1"
+"Rynore";"wardrobe2";"Red Cap";"1"
+"Rynore";"wardrobe2";"Crow Gaiters";"1"
+"Rynore";"wardrobe2";"Iron Subligar";"1"
+"Rynore";"wardrobe2";"Shinobi Tekko";"1"
+"Rynore";"wardrobe2";"Garish Pumps";"1"
+"Rynore";"wardrobe2";"Breastplate";"1"
+"Rynore";"wardrobe2";"Espial Bracers";"1"
+"Rynore";"wardrobe2";"Crow Jupon";"1"
+"Rynore";"wardrobe2";"Darksteel Axe";"1"
+"Rynore";"wardrobe2";"Hose";"1"
+"Rynore";"wardrobe2";"Shinobi Kyahan";"1"
+"Rynore";"wardrobe2";"Kacura Cap";"1"
+"Rynore";"wardrobe2";"Cuir Bouilli";"1"
+"Rynore";"wardrobe2";"Alkyoneus's Brc.";"1"
+"Rynore";"wardrobe2";"Plain Pick";"1"
+"Rynore";"wardrobe2";"Coalrake Sabots";"1"
+"Rynore";"wardrobe2";"Banded Mail";"1"
+"Rynore";"wardrobe2";"Plate Leggings";"1"
+"Rynore";"wardrobe2";"Velvet Robe";"1"
+"Rynore";"wardrobe2";"Espial Hose";"1"
+"Rynore";"wardrobe2";"Espial Socks";"1"
+"Rynore";"wardrobe2";"Socks";"1"
+"Rynore";"wardrobe2";"Gothic Gauntlets";"1"
+"Rynore";"wardrobe2";"Padded Cap";"1"
+"Rynore";"wardrobe2";"Pyro Robe";"1"
+"Rynore";"wardrobe2";"Espial Gambison";"1"
+"Rynore";"wardrobe2";"Bracers";"1"
+"Rynore";"wardrobe2";"Garish Mitts";"1"
+"Rynore";"wardrobe2";"Cuir Bandana";"1"
+"Rynore";"wardrobe2";"Ebony Sabots";"1"
+"Rynore";"wardrobe2";"Brigandine";"1"
+"Rynore";"wardrobe2";"Crow Bracers";"1"
+"Rynore";"wardrobe2";"Cpc. Leggings";"1"
+"Rynore";"wardrobe2";"Leggings";"1"
+"Rynore";"wardrobe2";"Velvet Slops";"1"
+"Rynore";"wardrobe3";"Deathbringer";"1"
+"Rynore";"wardrobe3";"Hagoita";"1"
+"Rynore";"wardrobe3";"Holy Mace";"1"
+"Rynore";"wardrobe3";"Custodes";"1"
+"Rynore";"wardrobe3";"Bomb Arm";"3"
+"Rynore";"wardrobe3";"Brass Rod";"1"
+"Rynore";"wardrobe3";"Kite Shield";"1"
+"Rynore";"wardrobe3";"Spear";"1"
+"Rynore";"wardrobe3";"Lilith's Rod";"1"
+"Rynore";"wardrobe3";"Willow Wand";"1"
+"Rynore";"wardrobe3";"Hoplon";"1"
+"Rynore";"wardrobe3";"Em. Baghnakhs";"1"
+"Rynore";"wardrobe3";"Yew Wand";"1"
+"Rynore";"wardrobe3";"Bronze Axe";"1"
+"Rynore";"wardrobe3";"Maple Shield";"1"
+"Rynore";"wardrobe3";"Flame Boomerang";"1"
+"Rynore";"wardrobe3";"Bronze Knife";"1"
+"Rynore";"wardrobe3";"Ceres' Spica";"1"
+"Rynore";"wardrobe3";"Flame Degen";"1"
+"Rynore";"wardrobe3";"Katayama";"1"
+"Rynore";"wardrobe3";"Power Crossbow";"1"
+"Rynore";"wardrobe3";"Brass Xiphos";"1"
+"Rynore";"wardrobe3";"Nymph Shield";"1"
+"Rynore";"wardrobe3";"Rose Wand";"1"
+"Rynore";"wardrobe3";"Misery Staff";"1"
+"Rynore";"wardrobe3";"Shortbow";"1"
+"Rynore";"wardrobe3";"Floral Hagoita";"1"
+"Rynore";"wardrobe3";"Broadsword";"1"
+"Rynore";"wardrobe3";"Pebble";"13"
+"Rynore";"wardrobe3";"Maul";"1"
+"Rynore";"wardrobe3";"Ebony Wand";"1"
+"Rynore";"wardrobe3";"Spellcaster's Ecu";"1"
+"Rynore";"wardrobe3";"Wrapped Bow";"1"
+"Rynore";"wardrobe3";"Eyra Baghnakhs";"1"
+"Rynore";"wardrobe3";"Shell Shield";"1"
+"Rynore";"wardrobe3";"Hard Shield";"1"
+"Rynore";"wardrobe3";"Mahogany Shield";"1"
+"Rynore";"wardrobe3";"Hunting Sword";"1"
+"Rynore";"wardrobe3";"Slime Shield";"1"
+"Rynore";"wardrobe3";"Stone Arrow";"450"
+"Rynore";"wardrobe3";"Firefly";"1"
+"Rynore";"wardrobe3";"Bone Arrow";"1"
+"Rynore";"wardrobe3";"Holly Pole";"1"
+"Rynore";"wardrobe3";"Targe";"1"
+"Rynore";"wardrobe3";"She-Slime Shield";"1"
+"Rynore";"wardrobe3";"Fish Scale Shield";"1"
+"Rynore";"wardrobe3";"Holy Maul";"1"
+"Rynore";"wardrobe3";"Elm Staff";"1"
+"Rynore";"wardrobe3";"Oak Pole";"1"
+"Rynore";"wardrobe3";"Leather Shield";"1"
+"Rynore";"wardrobe3";"Bone Knife";"1"
+"Rynore";"wardrobe3";"Flame Blade";"1"
+"Rynore";"wardrobe3";"Composite Bow";"1"
+"Rynore";"wardrobe3";"San d'Orian Bow";"1"
+"Rynore";"wardrobe3";"Small Sword";"1"
+"Rynore";"wardrobe3";"Othinus' Bow";"1"
+"Rynore";"wardrobe3";"Metal Slime Shield";"1"
+"Rynore";"wardrobe3";"Janus Guard";"1"
+"Rynore";"wardrobe3";"Kukri";"1"
+"Rynore";"wardrobe3";"Bronze Bolt";"190"
+"Rynore";"wardrobe3";"Battleaxe";"1"
+"Rynore";"wardrobe3";"Beetle Knife";"1"
+"Rynore";"wardrobe3";"Aspir Knife";"2"
+"Rynore";"wardrobe3";"Light Crossbow";"1"
+"Rynore";"wardrobe3";"Lohar";"1"
+"Rynore";"wardrobe3";"Gladius";"1"
+"Rynore";"wardrobe3";"Spatha";"1"
+"Rynore";"wardrobe3";"Mammut";"1"
+"Rynore";"wardrobe4";"Brass Ring";"1"
+"Rynore";"wardrobe4";"Gyokuto Obi";"1"
+"Rynore";"wardrobe4";"Corsette";"1"
+"Rynore";"wardrobe4";"Sardonyx Ring";"1"
+"Rynore";"wardrobe4";"Desperado Ring";"1"
+"Rynore";"wardrobe4";"Grand T.K. Collar";"1"
+"Rynore";"wardrobe4";"Brocade Obi";"1"
+"Rynore";"wardrobe4";"Ryl. Army Mantle";"1"
+"Rynore";"wardrobe4";"Silver Ring";"1"
+"Rynore";"wardrobe4";"High Brth. Mantle";"1"
+"Rynore";"wardrobe4";"Green Earring";"1"
+"Rynore";"wardrobe4";"Qiqirn Sash";"1"
+"Rynore";"wardrobe4";"Lleu's Charm";"1"
+"Rynore";"wardrobe4";"Torque";"1"
+"Rynore";"wardrobe4";"Beast Whistle";"1"
+"Rynore";"wardrobe4";"Tiger Stole";"1"
+"Rynore";"wardrobe4";"Rabbit Mantle";"1"
+"Rynore";"wardrobe4";"Arete del Sol";"1"
+"Rynore";"wardrobe4";"Tourmaline Ring";"1"
+"Rynore";"wardrobe4";"Vehemence Ring";"1"
+"Rynore";"wardrobe4";"Warp Ring";"1"
+"Rynore";"wardrobe4";"Sardonyx Earring";"1"
+"Rynore";"wardrobe4";"Peiste Mantle";"1"
+"Rynore";"wardrobe4";"Rancorous Mantle";"1"
+"Rynore";"wardrobe4";"M. Slime Earring";"1"
+"Rynore";"wardrobe4";"Swordbelt";"1"
+"Rynore";"wardrobe4";"Protect Earring";"1"
+"Rynore";"wardrobe4";"Dhalmel Mantle";"1"
+"Rynore";"wardrobe4";"Hard Leather Ring";"1"
+"Rynore";"wardrobe4";"Little Worm Belt";"1"
+"Rynore";"wardrobe4";"Clear Ring";"1"
+"Rynore";"wardrobe4";"Gold Earring";"1"
+"Rynore";"wardrobe4";"Sun Earring";"1"
+"Rynore";"wardrobe4";"Leather Ring";"1"
+"Rynore";"wardrobe4";"Beak Necklace";"1"
+"Rynore";"wardrobe4";"Ranger's Necklace";"1"
+"Rynore";"wardrobe4";"Bushinomimi";"1"
+"Rynore";"wardrobe4";"Invisible Mantle";"1"
+"Rynore";"wardrobe4";"Chocobo Rope";"1"
+"Rynore";"wardrobe4";"Flower Necklace";"1"
+"Rynore";"wardrobe4";"Amethyst Earring";"1"
+"Rynore";"wardrobe4";"Friar's Rope";"1"
+"Rynore";"wardrobe4";"Justice Badge";"1"
+"Rynore";"wardrobe4";"Opo-opo Necklace";"1"
+"Rynore";"wardrobe4";"Flagellant's Rope";"1"
+"Rynore";"wardrobe4";"Black Cape";"1"
+"Rynore";"wardrobe4";"Tortoise Earring";"1"
+"Rynore";"wardrobe4";"Pinwheel Belt";"1"
+"Rynore";"wardrobe4";"Nexus Cape";"1"
+"Rynore";"wardrobe4";"Mythril Earring";"1"
+"Rynore";"wardrobe4";"Cape";"1"
+"Rynore";"wardrobe4";"Coral Gorget";"1"
+"Rynore";"wardrobe4";"Purple Earring";"1"
+"Rynore";"wardrobe4";"Physical Earring";"1"
+"Rynore";"wardrobe4";"Peiste Belt";"1"
+"Rynore";"wardrobe4";"Mohbwa Scarf";"1"
+"Rynore";"wardrobe4";"Hi-Potion Tank";"1"
+"Rynore";"wardrobe4";"She-Slime Earring";"1"
+"Rynore";"wardrobe4";"Twinthread Obi";"1"
+"Rynore";"wardrobe4";"Jester's Cape";"1"
+"Rynore";"wardrobe4";"Pile Chain";"1"
+"Rynore";"wardrobe4";"Amber Ring";"1"
+"Rynore";"wardrobe4";"Gaia Mantle";"1"
+"Rynore";"wardrobe4";"Rainbow Obi";"1"
+"Rynore";"wardrobe4";"White Belt";"1"
+"Rynore";"wardrobe4";"Tenax Strap";"1"
+"Rynore";"wardrobe4";"Medieval Collar";"1"
+"Rynore";"wardrobe4";"Hi-Ether Tank";"1"
+"Rynore";"wardrobe4";"Cotton Cape";"1"
+"Rynore";"wardrobe4";"Homing Ring";"1"
+"Rynore";"wardrobe4";"Slime Earring";"1"
+"Rynore";"wardrobe4";"Wing Pendant";"1"
+"Rynore";"wardrobe4";"Accura Cape";"1"
+"Rynore";"wardrobe4";"Focus Collar";"1"
+"Rynore";"wardrobe4";"Blind Ring";"1"
+"Rynore";"wardrobe4";"Opal Ring";"1"
+"Rynore";"wardrobe4";"Echad Ring";"1"
+"Rynore";"wardrobe4";"Aquamrne. Earring";"1"
+"Rynore";"wardrobe4";"Opal Earring";"1"
+"Rynore";"wardrobe4";"Mythril Ring";"1"
+"Rynore";"wardrobe5";"Dwarf Pugil";"1"
+"Rynore";"wardrobe5";"Drill Calamary";"2"
+"Rynore";"wardrobe5";"Pet Food Beta";"12"
+"Rynore";"wardrobe5";"Svg. Mole Broth";"1"
+"Rynore";"wardrobe5";"Clothespole";"1"
+"Rynore";"wardrobe5";"Carrot Broth";"10"
+"Rynore";"wardrobe5";"Carrion Broth";"1"
+"Rynore";"wardrobe5";"Yew Fishing Rod";"1"
+"Rynore";"wardrobe5";"S. Herbal Broth";"5"
+"Rynore";"wardrobe5";"Bamboo Fish. Rod";"1"
+"Rynore";"wardrobe5";"Sliced Sardine";"90"
+"Rynore";"wardrobe5";"Wormy Broth";"4"
+"Rynore";"wardrobe5";"Crayfish Ball";"10"
+"Rynore";"wardrobe5";"Peeled Crayfish";"6"
+"Rynore";"wardrobe6";"Analgesia Torque";"1"
+"Rynore";"wardrobe6";"Esse Earring";"1"
+"Rynore";"wardrobe6";"Metamorph Ring";"1"
+"Rynore";"key items";"job gesture: black mage";"1"
+"Rynore";"key items";"crimson orb";"1"
+"Rynore";"key items";"map of Pso'Xja";"1"
+"Rynore";"key items";"Ancient Melody: O";"1"
+"Rynore";"key items";"Holla gate crystal";"1"
+"Rynore";"key items";"cerulean crystal";"1"
+"Rynore";"key items";"story of an impatient chocobo";"1"
+"Rynore";"key items";"ring of supernal disjunction";"1"
+"Rynore";"key items";"♪Doll companion";"1"
+"Rynore";"key items";"Delkfutt key";"1"
+"Rynore";"key items";"clear abyssite";"1"
+"Rynore";"key items";"Mog Patio design document";"1"
+"Rynore";"key items";"Mea gate crystal";"1"
+"Rynore";"key items";"Windurst Trust permit";"1"
+"Rynore";"key items";"Shard of Rage";"1"
+"Rynore";"key items";"green invitation card";"1"
+"Rynore";"key items";"scroll of treasure";"1"
+"Rynore";"key items";"map of Bibiki Bay";"1"
+"Rynore";"key items";"Vahzl gate crystal";"1"
+"Rynore";"key items";"Shard of Apathy";"1"
+"Rynore";"key items";"job gesture: dark knight";"1"
+"Rynore";"key items";"map of Ru'Hmet";"1"
+"Rynore";"key items";"story of a diligent chocobo";"1"
+"Rynore";"key items";"map of the Boyahda Tree";"1"
+"Rynore";"key items";"map of Cape Riverne";"1"
+"Rynore";"key items";"♪Raptor companion";"1"
+"Rynore";"key items";"map of the Kuftal Tunnel";"1"
+"Rynore";"key items";"Moghancement: Gardening";"1"
+"Rynore";"key items";"job gesture: red mage";"1"
+"Rynore";"key items";"map of Tavnazia";"1"
+"Rynore";"key items";"map of Carpenters' Landing";"1"
+"Rynore";"key items";"job gesture: paladin";"1"
+"Rynore";"key items";"job gesture: summoner";"1"
+"Rynore";"key items";"Magian learner's log";"1"
+"Rynore";"key items";"♪Buffalo companion";"1"
+"Rynore";"key items";"map of the Elshimo regions";"1"
+"Rynore";"key items";"prospector's pan";"1"
+"Rynore";"key items";"map of the Toraimarai Canal";"1"
+"Rynore";"key items";"Adventurer's Certificate";"1"
+"Rynore";"key items";"map of Bhaflau Thickets";"1"
+"Rynore";"key items";""Adoulin's Topiary Treasures"";"1"
+"Rynore";"key items";"Angelica's autograph";"1"
+"Rynore";"key items";"map of the Horutoto Ruins";"1"
+"Rynore";"key items";"map of Halvung";"1"
+"Rynore";"key items";"map of Ordelle's Caves";"1"
+"Rynore";"key items";"map of Oldton Movalpolos";"1"
+"Rynore";"key items";"map of Temple of Uggalepih";"1"
+"Rynore";"key items";"map of the Gusgen Mines";"1"
+"Rynore";"key items";"airship pass";"1"
+"Rynore";"key items";""A Farewell to Freshwater"";"1"
+"Rynore";"key items";"map of Delkfutt's Tower";"1"
+"Rynore";"key items";"Ambuscade Primer Volume Two";"1"
+"Rynore";"key items";"map of Wajaom Woodlands";"1"
+"Rynore";"key items";"map of Mamook";"1"
+"Rynore";"key items";"map of Ve'Lugannon Palace";"1"
+"Rynore";"key items";"traverser stone";"1"
+"Rynore";"key items";"map of the Vollbow region";"1"
+"Rynore";"key items";"map of the Garlaige Citadel";"1"
+"Rynore";"key items";"map of Sea Serpent Grotto";"1"
+"Rynore";"key items";"blue invitation card";"1"
+"Rynore";"key items";"map of Qufim Island";"1"
+"Rynore";"key items";"map of Al'Taieu";"1"
+"Rynore";"key items";"map of the Crawlers' Nest";"1"
+"Rynore";"key items";"geomagnetron";"1"
+"Rynore";"key items";"tuning fork of fire";"1"
+"Rynore";"key items";"map of Castle Oztroja";"1"
+"Rynore";"key items";"map of Labyrinth of Onzozo";"1"
+"Rynore";"key items";"synergy crucible";"1"
+"Rynore";"key items";"job gesture: beastmaster";"1"
+"Rynore";"key items";"map of the Dangruf Wadi";"1"
+"Rynore";"key items";"job gesture: monk";"1"
+"Rynore";"key items";"black matinee necklace";"1"
+"Rynore";"key items";"prototype attuner";"1"
+"Rynore";"key items";"Ballista License";"1"
+"Rynore";"key items";"map of Castle Zvahl";"1"
+"Rynore";"key items";"GPS crystal";"1"
+"Rynore";"key items";"seal of banishing";"1"
+"Rynore";"key items";"map of the Maze of Shakhrami";"1"
+"Rynore";"key items";"map of Fei'Yin";"1"
+"Rynore";"key items";"trainer's whistle";"1"
+"Rynore";"key items";"map of Bostaunieux Oubliette";"1"
+"Rynore";"key items";"lamb memento";"1"
+"Rynore";"key items";"vial of shrouded sand";"1"
+"Rynore";"key items";"♪Byakko";"1"
+"Rynore";"key items";"map of King Ranperre's Tomb";"1"
+"Rynore";"key items";"map of the Den of Rancor";"1"
+"Rynore";"key items";"♪Iron Giant companion";"1"
+"Rynore";"key items";"Tenshodo Member's Card";"1"
+"Rynore";"key items";"map of the Kuzotz region";"1"
+"Rynore";"key items";"♪Red crab companion";"1"
+"Rynore";"key items";"deed to Purgonorgo Isle";"1"
+"Rynore";"key items";""Grandiloquent Groves"";"1"
+"Rynore";"key items";"map of the Ranguemont Pass";"1"
+"Rynore";"key items";"shimmering invitation";"1"
+"Rynore";"key items";""Susuroon's Biiig Catch"";"1"
+"Rynore";"key items";"map of Vunkerl Inlet";"1"
+"Rynore";"key items";"map of Beadeaux";"1"
+"Rynore";"key items";"archducal audience permit";"1"
+"Rynore";"key items";""Rhapsody in White"";"1"
+"Rynore";"key items";""My First Furrow"";"1"
+"Rynore";"key items";"Reisenjima Sanctorium orb";"1"
+"Rynore";"key items";"map of the Attohwa Chasm";"1"
+"Rynore";"key items";"prismatic fragment";"1"
+"Rynore";"key items";"map of the Palborough Mines";"1"
+"Rynore";"key items";"concordoll";"1"
+"Rynore";"key items";"limit breaker";"1"
+"Rynore";"key items";"map of the Zeruhn Mines";"1"
+"Rynore";"key items";"white handkerchief";"1"
+"Rynore";"key items";"San d'Oria Trust permit";"1"
+"Rynore";"key items";"map of Giddeus";"1"
+"Rynore";"key items";"map of Newton Movalpolos";"1"
+"Rynore";"key items";"memorandoll";"1"
+"Rynore";"key items";"map of the Quicksand Caves";"1"
+"Rynore";"key items";"crab caller";"1"
+"Rynore";"key items";"gil repository";"1"
+"Rynore";"key items";""20,000 Yalms Under the Sea"";"1"
+"Rynore";"key items";"map of the Jeuno area";"1"
+"Rynore";"key items";"map of the Eldieme Necropolis";"1"
+"Rynore";"key items";"map of Grauberg";"1"
+"Rynore";"key items";"map of Ifrit's Cauldron";"1"
+"Rynore";"key items";""The Old Men of the Sea"";"1"
+"Rynore";"key items";"timber survey checklist";"1"
+"Rynore";"key items";"♪Phuabo companion";"1"
+"Rynore";"key items";"Shard of Arrogance";"1"
+"Rynore";"key items";"Yhoator gate crystal";"1"
+"Rynore";"key items";"treasure map";"1"
+"Rynore";"key items";""Dredging's No Drudgery"";"1"
+"Rynore";"key items";""Varicose Mineral Veins"";"1"
+"Rynore";"key items";""Water, Water Everywhere!"";"1"
+"Rynore";"key items";""Take A Lode Off"";"1"
+"Rynore";"key items";""Mythril Marathon Quarterly"";"1"
+"Rynore";"key items";"Altepa gate crystal";"1"
+"Rynore";"key items";"Shard of Cowardice";"1"
+"Rynore";"key items";""Give My Regards to Reodoan"";"1"
+"Rynore";"key items";""Sow★Your★Seed!"";"1"
+"Rynore";"key items";"Dem gate crystal";"1"
+"Rynore";"key items";"traverser stone";"1"
+"Rynore";"key items";"map of the Aqueducts";"1"
+"Rynore";"key items";"loadstone";"1"
+"Rynore";"key items";"map of Nashmau";"1"
+"Rynore";"key items";"mysterious amulet";"1"
+"Rynore";"key items";"map of Fort Karugo-Narugo";"1"
+"Rynore";"key items";"map of Aydeewa Subterrane";"1"
+"Rynore";"key items";""Black Fish of the Family"";"1"
+"Rynore";"key items";"map of Alzadaal Ruins";"1"
+"Rynore";"key items";"map of Arrapago Reef";"1"
+"Rynore";"key items";"map of Mount Zhayolm";"1"
+"Rynore";"key items";"map of Caedarva Mire";"1"
+"Rynore";"key items";"spirit incense";"1"
+"Rynore";"key items";"piece of rugged tree bark";"1"
+"Rynore";"key items";""All the Ways to Skin a Carp"";"1"
+"Rynore";"key items";"Shard of Envy";"1"
+"Rynore";"key items";"crest of Davoi";"1"
+"Rynore";"key items";"silver bell";"1"
+"Rynore";"key items";"pouch of weighted stones";"1"
+"Rynore";"key items";"map of the Li'Telor region";"1"
+"Rynore";"key items";"job gesture: ranger";"1"
+"Rynore";"key items";"squire certificate";"1"
+"Rynore";"key items";"traverser stone";"1"
+"Rynore";"key items";"job gesture: thief";"1"
+"Rynore";"key items";"job gesture: white mage";"1"
+"Rynore";"key items";"chocobo license";"1"
+"Rynore";"key items";"map of the Sacrarium";"1"
+"Rynore";"key items";"magicked astrolabe";"1"
+"Rynore";"key items";"coruscant rosary";"1"
+"Rynore";"key items";"baby rabbit memento";"1"
+"Rynore";"key items";"rabbit memento";"1"
+"Rynore";"key items";"map of Al Zahbi";"1"
+"Rynore";"key items";"map of the Windurst area";"1"
+"Rynore";"key items";"job gesture: warrior";"1"
+"Rynore";"key items";"map of the Uleguerand Range";"1"
+"Rynore";"key items";"map of Norg";"1"
+"Rynore";"key items";"red invitation card";"1"
+"Rynore";"key items";"white invitation card";"1"
+"Rynore";"key items";"♪Warmachine companion";"1"
+"Rynore";"key items";"map of the Ru'Aun Gardens";"1"
+"Rynore";"key items";"map of the Northlands area";"1"
+"Rynore";"key items";"corked ampoule";"1"
+"Rynore";"key items";"map of Davoi";"1"
+"Rynore";"key items";"map of Hu'Xzoi";"1"
+"Rynore";"key items";"heart of the bushin";"1"
+"Rynore";"key items";"Bastok Trust permit";"1"
+"Rynore";"key items";"map of Ghelsba";"1"
+"Rynore";"key items";"Yagudo torch";"1"
+"Rynore";"key items";"map of the Korroloka Tunnel";"1"
+"Rynore";"key items";"map of the San d'Oria area";"1"
+"Rynore";"key items";"map of the Bastok area";"1"
diff --git a/docker-compose.yml b/docker-compose.yml
index 97b0e54..1ccb02f 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -1,16 +1,33 @@
+version: "3.9"
+
services:
api:
build: ./backend
+ container_name: mogapp-api
environment:
- - PSQL_HOST=10.0.0.199
- - PSQL_PORT=5432
- - PSQL_USER=postgres
- - PSQL_PASSWORD=DP3Wv*QM#t8bY*N
- - PSQL_DBNAME=ffxi_items
+ # Database lives on the Docker host, expose via host networking
+ - PSQL_HOST=${PSQL_HOST:-host.docker.internal}
+ - PSQL_PORT=${PSQL_PORT:-5432}
+ - PSQL_USER=${PSQL_USER:-postgres}
+ - PSQL_PASSWORD=${PSQL_PASSWORD:-postgres}
+ - PSQL_DBNAME=${PSQL_DBNAME:-ffxi_items}
ports:
- "8000:8000"
volumes:
- ./backend/app:/app/app
+ networks:
+ - mognet
-volumes:
- pgdata:
+ frontend:
+ build: ./frontend
+ container_name: mogapp-web
+ depends_on:
+ - api
+ ports:
+ - "3050:80"
+ networks:
+ - mognet
+
+networks:
+ mognet:
+ driver: bridge
diff --git a/frontend/Dockerfile b/frontend/Dockerfile
new file mode 100644
index 0000000..711eb42
--- /dev/null
+++ b/frontend/Dockerfile
@@ -0,0 +1,16 @@
+# ---------- Build stage ----------
+FROM node:20-alpine AS builder
+WORKDIR /app
+COPY package.json package-lock.json* pnpm-lock.yaml* ./
+RUN npm ci --ignore-scripts --prefer-offline
+COPY . .
+RUN npm run build
+
+# ---------- Production stage ----------
+FROM nginx:1.25-alpine AS prod
+COPY --from=builder /app/dist /usr/share/nginx/html
+# Remove default config and add minimal one
+RUN rm /etc/nginx/conf.d/default.conf
+COPY nginx.conf /etc/nginx/conf.d
+EXPOSE 80
+CMD ["nginx", "-g", "daemon off;"]
diff --git a/frontend/nginx.conf b/frontend/nginx.conf
new file mode 100644
index 0000000..be98754
--- /dev/null
+++ b/frontend/nginx.conf
@@ -0,0 +1,22 @@
+server {
+ listen 80;
+ server_name _;
+
+ # Serve static assets
+ root /usr/share/nginx/html;
+ index index.html;
+
+ # Forward API requests to backend
+ location /api/ {
+ proxy_pass http://api:8000/;
+ proxy_set_header Host $host;
+ proxy_set_header X-Real-IP $remote_addr;
+ proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+ proxy_set_header X-Forwarded-Proto $scheme;
+ }
+
+ # All other routes – SPA fallback
+ location / {
+ try_files $uri $uri/ /index.html;
+ }
+}
diff --git a/frontend/package-lock.json b/frontend/package-lock.json
index ddd7b32..8ab8890 100644
--- a/frontend/package-lock.json
+++ b/frontend/package-lock.json
@@ -18,13 +18,24 @@
"react-dom": "^18.2.0"
},
"devDependencies": {
+ "@testing-library/jest-dom": "^6.1.5",
+ "@testing-library/react": "^14.1.2",
+ "@testing-library/user-event": "^14.4.3",
"@types/react": "^18.2.50",
"@types/react-dom": "^18.2.17",
"@vitejs/plugin-react": "^4.0.3",
+ "jsdom": "^23.0.0",
"typescript": "^5.3.3",
- "vite": "^5.2.0"
+ "vite": "^5.2.0",
+ "vitest": "^1.5.0"
}
},
+ "node_modules/@adobe/css-tools": {
+ "version": "4.4.3",
+ "resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.4.3.tgz",
+ "integrity": "sha512-VQKMkwriZbaOgVCby1UDY/LDk5fIjhQicCvVPFqfe+69fWaPWydbWJ3wRt59/YzIwda1I81loas3oCoHxnqvdA==",
+ "dev": true
+ },
"node_modules/@ampproject/remapping": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz",
@@ -38,6 +49,36 @@
"node": ">=6.0.0"
}
},
+ "node_modules/@asamuzakjp/css-color": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-3.2.0.tgz",
+ "integrity": "sha512-K1A6z8tS3XsmCMM86xoWdn7Fkdn9m6RSVtocUrJYIwZnFVkng/PvkEoWtOWmP+Scc6saYWHWZYbndEEXxl24jw==",
+ "dev": true,
+ "dependencies": {
+ "@csstools/css-calc": "^2.1.3",
+ "@csstools/css-color-parser": "^3.0.9",
+ "@csstools/css-parser-algorithms": "^3.0.4",
+ "@csstools/css-tokenizer": "^3.0.3",
+ "lru-cache": "^10.4.3"
+ }
+ },
+ "node_modules/@asamuzakjp/css-color/node_modules/lru-cache": {
+ "version": "10.4.3",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz",
+ "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==",
+ "dev": true
+ },
+ "node_modules/@asamuzakjp/dom-selector": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/@asamuzakjp/dom-selector/-/dom-selector-2.0.2.tgz",
+ "integrity": "sha512-x1KXOatwofR6ZAYzXRBL5wrdV0vwNxlTCK9NCuLqAzQYARqGcvFwiJA6A1ERuh+dgeA4Dxm3JBYictIes+SqUQ==",
+ "dev": true,
+ "dependencies": {
+ "bidi-js": "^1.0.3",
+ "css-tree": "^2.3.1",
+ "is-potential-custom-element-name": "^1.0.1"
+ }
+ },
"node_modules/@babel/code-frame": {
"version": "7.27.1",
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz",
@@ -297,6 +338,116 @@
"node": ">=6.9.0"
}
},
+ "node_modules/@csstools/color-helpers": {
+ "version": "5.0.2",
+ "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-5.0.2.tgz",
+ "integrity": "sha512-JqWH1vsgdGcw2RR6VliXXdA0/59LttzlU8UlRT/iUUsEeWfYq8I+K0yhihEUTTHLRm1EXvpsCx3083EU15ecsA==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/csstools"
+ },
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/csstools"
+ }
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@csstools/css-calc": {
+ "version": "2.1.4",
+ "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-2.1.4.tgz",
+ "integrity": "sha512-3N8oaj+0juUw/1H3YwmDDJXCgTB1gKU6Hc/bB502u9zR0q2vd786XJH9QfrKIEgFlZmhZiq6epXl4rHqhzsIgQ==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/csstools"
+ },
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/csstools"
+ }
+ ],
+ "engines": {
+ "node": ">=18"
+ },
+ "peerDependencies": {
+ "@csstools/css-parser-algorithms": "^3.0.5",
+ "@csstools/css-tokenizer": "^3.0.4"
+ }
+ },
+ "node_modules/@csstools/css-color-parser": {
+ "version": "3.0.10",
+ "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-3.0.10.tgz",
+ "integrity": "sha512-TiJ5Ajr6WRd1r8HSiwJvZBiJOqtH86aHpUjq5aEKWHiII2Qfjqd/HCWKPOW8EP4vcspXbHnXrwIDlu5savQipg==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/csstools"
+ },
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/csstools"
+ }
+ ],
+ "dependencies": {
+ "@csstools/color-helpers": "^5.0.2",
+ "@csstools/css-calc": "^2.1.4"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "peerDependencies": {
+ "@csstools/css-parser-algorithms": "^3.0.5",
+ "@csstools/css-tokenizer": "^3.0.4"
+ }
+ },
+ "node_modules/@csstools/css-parser-algorithms": {
+ "version": "3.0.5",
+ "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-3.0.5.tgz",
+ "integrity": "sha512-DaDeUkXZKjdGhgYaHNJTV9pV7Y9B3b644jCLs9Upc3VeNGg6LWARAT6O+Q+/COo+2gg/bM5rhpMAtf70WqfBdQ==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/csstools"
+ },
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/csstools"
+ }
+ ],
+ "engines": {
+ "node": ">=18"
+ },
+ "peerDependencies": {
+ "@csstools/css-tokenizer": "^3.0.4"
+ }
+ },
+ "node_modules/@csstools/css-tokenizer": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-3.0.4.tgz",
+ "integrity": "sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/csstools"
+ },
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/csstools"
+ }
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
"node_modules/@emotion/babel-plugin": {
"version": "11.13.5",
"resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.13.5.tgz",
@@ -798,6 +949,18 @@
"node": ">=12"
}
},
+ "node_modules/@jest/schemas": {
+ "version": "29.6.3",
+ "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz",
+ "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==",
+ "dev": true,
+ "dependencies": {
+ "@sinclair/typebox": "^0.27.8"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
"node_modules/@jridgewell/gen-mapping": {
"version": "0.3.12",
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.12.tgz",
@@ -1320,6 +1483,12 @@
"win32"
]
},
+ "node_modules/@sinclair/typebox": {
+ "version": "0.27.8",
+ "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz",
+ "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==",
+ "dev": true
+ },
"node_modules/@tanstack/query-core": {
"version": "5.81.5",
"resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.81.5.tgz",
@@ -1344,6 +1513,130 @@
"react": "^18 || ^19"
}
},
+ "node_modules/@testing-library/dom": {
+ "version": "10.4.0",
+ "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.0.tgz",
+ "integrity": "sha512-pemlzrSESWbdAloYml3bAJMEfNh1Z7EduzqPKprCH5S341frlpYnUEW0H72dLxa6IsYr+mPno20GiSm+h9dEdQ==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "@babel/code-frame": "^7.10.4",
+ "@babel/runtime": "^7.12.5",
+ "@types/aria-query": "^5.0.1",
+ "aria-query": "5.3.0",
+ "chalk": "^4.1.0",
+ "dom-accessibility-api": "^0.5.9",
+ "lz-string": "^1.5.0",
+ "pretty-format": "^27.0.2"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@testing-library/jest-dom": {
+ "version": "6.6.3",
+ "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-6.6.3.tgz",
+ "integrity": "sha512-IteBhl4XqYNkM54f4ejhLRJiZNqcSCoXUOG2CPK7qbD322KjQozM4kHQOfkG2oln9b9HTYqs+Sae8vBATubxxA==",
+ "dev": true,
+ "dependencies": {
+ "@adobe/css-tools": "^4.4.0",
+ "aria-query": "^5.0.0",
+ "chalk": "^3.0.0",
+ "css.escape": "^1.5.1",
+ "dom-accessibility-api": "^0.6.3",
+ "lodash": "^4.17.21",
+ "redent": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=14",
+ "npm": ">=6",
+ "yarn": ">=1"
+ }
+ },
+ "node_modules/@testing-library/jest-dom/node_modules/chalk": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz",
+ "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==",
+ "dev": true,
+ "dependencies": {
+ "ansi-styles": "^4.1.0",
+ "supports-color": "^7.1.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/@testing-library/jest-dom/node_modules/dom-accessibility-api": {
+ "version": "0.6.3",
+ "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.6.3.tgz",
+ "integrity": "sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w==",
+ "dev": true
+ },
+ "node_modules/@testing-library/react": {
+ "version": "14.3.1",
+ "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-14.3.1.tgz",
+ "integrity": "sha512-H99XjUhWQw0lTgyMN05W3xQG1Nh4lq574D8keFf1dDoNTJgp66VbJozRaczoF+wsiaPJNt/TcnfpLGufGxSrZQ==",
+ "dev": true,
+ "dependencies": {
+ "@babel/runtime": "^7.12.5",
+ "@testing-library/dom": "^9.0.0",
+ "@types/react-dom": "^18.0.0"
+ },
+ "engines": {
+ "node": ">=14"
+ },
+ "peerDependencies": {
+ "react": "^18.0.0",
+ "react-dom": "^18.0.0"
+ }
+ },
+ "node_modules/@testing-library/react/node_modules/@testing-library/dom": {
+ "version": "9.3.4",
+ "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-9.3.4.tgz",
+ "integrity": "sha512-FlS4ZWlp97iiNWig0Muq8p+3rVDjRiYE+YKGbAqXOu9nwJFFOdL00kFpz42M+4huzYi86vAK1sOOfyOG45muIQ==",
+ "dev": true,
+ "dependencies": {
+ "@babel/code-frame": "^7.10.4",
+ "@babel/runtime": "^7.12.5",
+ "@types/aria-query": "^5.0.1",
+ "aria-query": "5.1.3",
+ "chalk": "^4.1.0",
+ "dom-accessibility-api": "^0.5.9",
+ "lz-string": "^1.5.0",
+ "pretty-format": "^27.0.2"
+ },
+ "engines": {
+ "node": ">=14"
+ }
+ },
+ "node_modules/@testing-library/react/node_modules/aria-query": {
+ "version": "5.1.3",
+ "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.1.3.tgz",
+ "integrity": "sha512-R5iJ5lkuHybztUfuOAznmboyjWq8O6sqNqtK7CLOqdydi54VNbORp49mb14KbWgG1QD3JFO9hJdZ+y4KutfdOQ==",
+ "dev": true,
+ "dependencies": {
+ "deep-equal": "^2.0.5"
+ }
+ },
+ "node_modules/@testing-library/user-event": {
+ "version": "14.6.1",
+ "resolved": "https://registry.npmjs.org/@testing-library/user-event/-/user-event-14.6.1.tgz",
+ "integrity": "sha512-vq7fv0rnt+QTXgPxr5Hjc210p6YKq2kmdziLgnsZGgLJ9e6VAShx1pACLuRjd/AS/sr7phAR58OIIpf0LlmQNw==",
+ "dev": true,
+ "engines": {
+ "node": ">=12",
+ "npm": ">=6"
+ },
+ "peerDependencies": {
+ "@testing-library/dom": ">=7.21.4"
+ }
+ },
+ "node_modules/@types/aria-query": {
+ "version": "5.0.4",
+ "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz",
+ "integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==",
+ "dev": true
+ },
"node_modules/@types/babel__core": {
"version": "7.20.5",
"resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz",
@@ -1447,11 +1740,250 @@
"vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0-beta.0"
}
},
+ "node_modules/@vitest/expect": {
+ "version": "1.6.1",
+ "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-1.6.1.tgz",
+ "integrity": "sha512-jXL+9+ZNIJKruofqXuuTClf44eSpcHlgj3CiuNihUF3Ioujtmc0zIa3UJOW5RjDK1YLBJZnWBlPuqhYycLioog==",
+ "dev": true,
+ "dependencies": {
+ "@vitest/spy": "1.6.1",
+ "@vitest/utils": "1.6.1",
+ "chai": "^4.3.10"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/@vitest/runner": {
+ "version": "1.6.1",
+ "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-1.6.1.tgz",
+ "integrity": "sha512-3nSnYXkVkf3mXFfE7vVyPmi3Sazhb/2cfZGGs0JRzFsPFvAMBEcrweV1V1GsrstdXeKCTXlJbvnQwGWgEIHmOA==",
+ "dev": true,
+ "dependencies": {
+ "@vitest/utils": "1.6.1",
+ "p-limit": "^5.0.0",
+ "pathe": "^1.1.1"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/@vitest/snapshot": {
+ "version": "1.6.1",
+ "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-1.6.1.tgz",
+ "integrity": "sha512-WvidQuWAzU2p95u8GAKlRMqMyN1yOJkGHnx3M1PL9Raf7AQ1kwLKg04ADlCa3+OXUZE7BceOhVZiuWAbzCKcUQ==",
+ "dev": true,
+ "dependencies": {
+ "magic-string": "^0.30.5",
+ "pathe": "^1.1.1",
+ "pretty-format": "^29.7.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/@vitest/snapshot/node_modules/ansi-styles": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz",
+ "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==",
+ "dev": true,
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/@vitest/snapshot/node_modules/pretty-format": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz",
+ "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==",
+ "dev": true,
+ "dependencies": {
+ "@jest/schemas": "^29.6.3",
+ "ansi-styles": "^5.0.0",
+ "react-is": "^18.0.0"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/@vitest/snapshot/node_modules/react-is": {
+ "version": "18.3.1",
+ "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz",
+ "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==",
+ "dev": true
+ },
+ "node_modules/@vitest/spy": {
+ "version": "1.6.1",
+ "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-1.6.1.tgz",
+ "integrity": "sha512-MGcMmpGkZebsMZhbQKkAf9CX5zGvjkBTqf8Zx3ApYWXr3wG+QvEu2eXWfnIIWYSJExIp4V9FCKDEeygzkYrXMw==",
+ "dev": true,
+ "dependencies": {
+ "tinyspy": "^2.2.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/@vitest/utils": {
+ "version": "1.6.1",
+ "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-1.6.1.tgz",
+ "integrity": "sha512-jOrrUvXM4Av9ZWiG1EajNto0u96kWAhJ1LmPmJhXXQx/32MecEKd10pOLYgS2BQx1TgkGhloPU1ArDW2vvaY6g==",
+ "dev": true,
+ "dependencies": {
+ "diff-sequences": "^29.6.3",
+ "estree-walker": "^3.0.3",
+ "loupe": "^2.3.7",
+ "pretty-format": "^29.7.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/@vitest/utils/node_modules/ansi-styles": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz",
+ "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==",
+ "dev": true,
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/@vitest/utils/node_modules/pretty-format": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz",
+ "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==",
+ "dev": true,
+ "dependencies": {
+ "@jest/schemas": "^29.6.3",
+ "ansi-styles": "^5.0.0",
+ "react-is": "^18.0.0"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/@vitest/utils/node_modules/react-is": {
+ "version": "18.3.1",
+ "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz",
+ "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==",
+ "dev": true
+ },
+ "node_modules/acorn": {
+ "version": "8.15.0",
+ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz",
+ "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
+ "dev": true,
+ "bin": {
+ "acorn": "bin/acorn"
+ },
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
+ "node_modules/acorn-walk": {
+ "version": "8.3.4",
+ "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz",
+ "integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==",
+ "dev": true,
+ "dependencies": {
+ "acorn": "^8.11.0"
+ },
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
+ "node_modules/agent-base": {
+ "version": "7.1.4",
+ "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz",
+ "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==",
+ "dev": true,
+ "engines": {
+ "node": ">= 14"
+ }
+ },
+ "node_modules/ansi-regex": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
+ "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/ansi-styles": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
+ "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+ "dev": true,
+ "dependencies": {
+ "color-convert": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/aria-query": {
+ "version": "5.3.0",
+ "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz",
+ "integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==",
+ "dev": true,
+ "dependencies": {
+ "dequal": "^2.0.3"
+ }
+ },
+ "node_modules/array-buffer-byte-length": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.2.tgz",
+ "integrity": "sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==",
+ "dev": true,
+ "dependencies": {
+ "call-bound": "^1.0.3",
+ "is-array-buffer": "^3.0.5"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/assertion-error": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz",
+ "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==",
+ "dev": true,
+ "engines": {
+ "node": "*"
+ }
+ },
"node_modules/asynckit": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="
},
+ "node_modules/available-typed-arrays": {
+ "version": "1.0.7",
+ "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz",
+ "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==",
+ "dev": true,
+ "dependencies": {
+ "possible-typed-array-names": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
"node_modules/axios": {
"version": "1.10.0",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.10.0.tgz",
@@ -1476,6 +2008,15 @@
"npm": ">=6"
}
},
+ "node_modules/bidi-js": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/bidi-js/-/bidi-js-1.0.3.tgz",
+ "integrity": "sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw==",
+ "dev": true,
+ "dependencies": {
+ "require-from-string": "^2.0.2"
+ }
+ },
"node_modules/browserslist": {
"version": "4.25.1",
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.25.1.tgz",
@@ -1508,6 +2049,33 @@
"node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
}
},
+ "node_modules/cac": {
+ "version": "6.7.14",
+ "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz",
+ "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/call-bind": {
+ "version": "1.0.8",
+ "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz",
+ "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==",
+ "dev": true,
+ "dependencies": {
+ "call-bind-apply-helpers": "^1.0.0",
+ "es-define-property": "^1.0.0",
+ "get-intrinsic": "^1.2.4",
+ "set-function-length": "^1.2.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
"node_modules/call-bind-apply-helpers": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
@@ -1520,6 +2088,22 @@
"node": ">= 0.4"
}
},
+ "node_modules/call-bound": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz",
+ "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==",
+ "dev": true,
+ "dependencies": {
+ "call-bind-apply-helpers": "^1.0.2",
+ "get-intrinsic": "^1.3.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
"node_modules/callsites": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
@@ -1548,6 +2132,52 @@
}
]
},
+ "node_modules/chai": {
+ "version": "4.5.0",
+ "resolved": "https://registry.npmjs.org/chai/-/chai-4.5.0.tgz",
+ "integrity": "sha512-RITGBfijLkBddZvnn8jdqoTypxvqbOLYQkGGxXzeFjVHvudaPw0HNFD9x928/eUwYWd2dPCugVqspGALTZZQKw==",
+ "dev": true,
+ "dependencies": {
+ "assertion-error": "^1.1.0",
+ "check-error": "^1.0.3",
+ "deep-eql": "^4.1.3",
+ "get-func-name": "^2.0.2",
+ "loupe": "^2.3.6",
+ "pathval": "^1.1.1",
+ "type-detect": "^4.1.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/chalk": {
+ "version": "4.1.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
+ "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
+ "dev": true,
+ "dependencies": {
+ "ansi-styles": "^4.1.0",
+ "supports-color": "^7.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/chalk?sponsor=1"
+ }
+ },
+ "node_modules/check-error": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.3.tgz",
+ "integrity": "sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg==",
+ "dev": true,
+ "dependencies": {
+ "get-func-name": "^2.0.2"
+ },
+ "engines": {
+ "node": "*"
+ }
+ },
"node_modules/clsx": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz",
@@ -1556,6 +2186,24 @@
"node": ">=6"
}
},
+ "node_modules/color-convert": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
+ "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+ "dev": true,
+ "dependencies": {
+ "color-name": "~1.1.4"
+ },
+ "engines": {
+ "node": ">=7.0.0"
+ }
+ },
+ "node_modules/color-name": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
+ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
+ "dev": true
+ },
"node_modules/combined-stream": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
@@ -1567,6 +2215,12 @@
"node": ">= 0.8"
}
},
+ "node_modules/confbox": {
+ "version": "0.1.8",
+ "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.1.8.tgz",
+ "integrity": "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==",
+ "dev": true
+ },
"node_modules/convert-source-map": {
"version": "1.9.0",
"resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz",
@@ -1587,11 +2241,76 @@
"node": ">=10"
}
},
+ "node_modules/cross-spawn": {
+ "version": "7.0.6",
+ "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
+ "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
+ "dev": true,
+ "dependencies": {
+ "path-key": "^3.1.0",
+ "shebang-command": "^2.0.0",
+ "which": "^2.0.1"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/css-tree": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.3.1.tgz",
+ "integrity": "sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw==",
+ "dev": true,
+ "dependencies": {
+ "mdn-data": "2.0.30",
+ "source-map-js": "^1.0.1"
+ },
+ "engines": {
+ "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0"
+ }
+ },
+ "node_modules/css.escape": {
+ "version": "1.5.1",
+ "resolved": "https://registry.npmjs.org/css.escape/-/css.escape-1.5.1.tgz",
+ "integrity": "sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==",
+ "dev": true
+ },
+ "node_modules/cssstyle": {
+ "version": "4.6.0",
+ "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-4.6.0.tgz",
+ "integrity": "sha512-2z+rWdzbbSZv6/rhtvzvqeZQHrBaqgogqt85sqFNbabZOuFbCVFb8kPeEtZjiKkbrm395irpNKiYeFeLiQnFPg==",
+ "dev": true,
+ "dependencies": {
+ "@asamuzakjp/css-color": "^3.2.0",
+ "rrweb-cssom": "^0.8.0"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/cssstyle/node_modules/rrweb-cssom": {
+ "version": "0.8.0",
+ "resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.8.0.tgz",
+ "integrity": "sha512-guoltQEx+9aMf2gDZ0s62EcV8lsXR+0w8915TC3ITdn2YueuNjdAYh/levpU9nFaoChh9RUS5ZdQMrKfVEN9tw==",
+ "dev": true
+ },
"node_modules/csstype": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
"integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="
},
+ "node_modules/data-urls": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-5.0.0.tgz",
+ "integrity": "sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==",
+ "dev": true,
+ "dependencies": {
+ "whatwg-mimetype": "^4.0.0",
+ "whatwg-url": "^14.0.0"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
"node_modules/debug": {
"version": "4.4.1",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz",
@@ -1608,6 +2327,90 @@
}
}
},
+ "node_modules/decimal.js": {
+ "version": "10.6.0",
+ "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.6.0.tgz",
+ "integrity": "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==",
+ "dev": true
+ },
+ "node_modules/deep-eql": {
+ "version": "4.1.4",
+ "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-4.1.4.tgz",
+ "integrity": "sha512-SUwdGfqdKOwxCPeVYjwSyRpJ7Z+fhpwIAtmCUdZIWZ/YP5R9WAsyuSgpLVDi9bjWoN2LXHNss/dk3urXtdQxGg==",
+ "dev": true,
+ "dependencies": {
+ "type-detect": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/deep-equal": {
+ "version": "2.2.3",
+ "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-2.2.3.tgz",
+ "integrity": "sha512-ZIwpnevOurS8bpT4192sqAowWM76JDKSHYzMLty3BZGSswgq6pBaH3DhCSW5xVAZICZyKdOBPjwww5wfgT/6PA==",
+ "dev": true,
+ "dependencies": {
+ "array-buffer-byte-length": "^1.0.0",
+ "call-bind": "^1.0.5",
+ "es-get-iterator": "^1.1.3",
+ "get-intrinsic": "^1.2.2",
+ "is-arguments": "^1.1.1",
+ "is-array-buffer": "^3.0.2",
+ "is-date-object": "^1.0.5",
+ "is-regex": "^1.1.4",
+ "is-shared-array-buffer": "^1.0.2",
+ "isarray": "^2.0.5",
+ "object-is": "^1.1.5",
+ "object-keys": "^1.1.1",
+ "object.assign": "^4.1.4",
+ "regexp.prototype.flags": "^1.5.1",
+ "side-channel": "^1.0.4",
+ "which-boxed-primitive": "^1.0.2",
+ "which-collection": "^1.0.1",
+ "which-typed-array": "^1.1.13"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/define-data-property": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz",
+ "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==",
+ "dev": true,
+ "dependencies": {
+ "es-define-property": "^1.0.0",
+ "es-errors": "^1.3.0",
+ "gopd": "^1.0.1"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/define-properties": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz",
+ "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==",
+ "dev": true,
+ "dependencies": {
+ "define-data-property": "^1.0.1",
+ "has-property-descriptors": "^1.0.0",
+ "object-keys": "^1.1.1"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
"node_modules/delayed-stream": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
@@ -1616,6 +2419,30 @@
"node": ">=0.4.0"
}
},
+ "node_modules/dequal": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz",
+ "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==",
+ "dev": true,
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/diff-sequences": {
+ "version": "29.6.3",
+ "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz",
+ "integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==",
+ "dev": true,
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/dom-accessibility-api": {
+ "version": "0.5.16",
+ "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz",
+ "integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==",
+ "dev": true
+ },
"node_modules/dom-helpers": {
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz",
@@ -1644,6 +2471,18 @@
"integrity": "sha512-wObbz/ar3Bc6e4X5vf0iO8xTN8YAjN/tgiAOJLr7yjYFtP9wAjq8Mb5h0yn6kResir+VYx2DXBj9NNobs0ETSA==",
"dev": true
},
+ "node_modules/entities": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz",
+ "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.12"
+ },
+ "funding": {
+ "url": "https://github.com/fb55/entities?sponsor=1"
+ }
+ },
"node_modules/error-ex": {
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz",
@@ -1668,6 +2507,26 @@
"node": ">= 0.4"
}
},
+ "node_modules/es-get-iterator": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/es-get-iterator/-/es-get-iterator-1.1.3.tgz",
+ "integrity": "sha512-sPZmqHBe6JIiTfN5q2pEi//TwxmAFHwj/XEuYjTuse78i8KxaqMTTzxPoFKuzRpDpTJ+0NAbpfenkmH2rePtuw==",
+ "dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.2",
+ "get-intrinsic": "^1.1.3",
+ "has-symbols": "^1.0.3",
+ "is-arguments": "^1.1.1",
+ "is-map": "^2.0.2",
+ "is-set": "^2.0.2",
+ "is-string": "^1.0.7",
+ "isarray": "^2.0.5",
+ "stop-iteration-iterator": "^1.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
"node_modules/es-object-atoms": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
@@ -1751,6 +2610,38 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/estree-walker": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz",
+ "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==",
+ "dev": true,
+ "dependencies": {
+ "@types/estree": "^1.0.0"
+ }
+ },
+ "node_modules/execa": {
+ "version": "8.0.1",
+ "resolved": "https://registry.npmjs.org/execa/-/execa-8.0.1.tgz",
+ "integrity": "sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==",
+ "dev": true,
+ "dependencies": {
+ "cross-spawn": "^7.0.3",
+ "get-stream": "^8.0.1",
+ "human-signals": "^5.0.0",
+ "is-stream": "^3.0.0",
+ "merge-stream": "^2.0.0",
+ "npm-run-path": "^5.1.0",
+ "onetime": "^6.0.0",
+ "signal-exit": "^4.1.0",
+ "strip-final-newline": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=16.17"
+ },
+ "funding": {
+ "url": "https://github.com/sindresorhus/execa?sponsor=1"
+ }
+ },
"node_modules/find-root": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz",
@@ -1775,6 +2666,21 @@
}
}
},
+ "node_modules/for-each": {
+ "version": "0.3.5",
+ "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz",
+ "integrity": "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==",
+ "dev": true,
+ "dependencies": {
+ "is-callable": "^1.2.7"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
"node_modules/form-data": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.3.tgz",
@@ -1812,6 +2718,15 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/functions-have-names": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz",
+ "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==",
+ "dev": true,
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
"node_modules/gensync": {
"version": "1.0.0-beta.2",
"resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz",
@@ -1821,6 +2736,15 @@
"node": ">=6.9.0"
}
},
+ "node_modules/get-func-name": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.2.tgz",
+ "integrity": "sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==",
+ "dev": true,
+ "engines": {
+ "node": "*"
+ }
+ },
"node_modules/get-intrinsic": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
@@ -1856,6 +2780,18 @@
"node": ">= 0.4"
}
},
+ "node_modules/get-stream": {
+ "version": "8.0.1",
+ "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-8.0.1.tgz",
+ "integrity": "sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==",
+ "dev": true,
+ "engines": {
+ "node": ">=16"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/globals": {
"version": "11.12.0",
"resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz",
@@ -1875,6 +2811,39 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/has-bigints": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.1.0.tgz",
+ "integrity": "sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==",
+ "dev": true,
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/has-flag": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
+ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/has-property-descriptors": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz",
+ "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==",
+ "dev": true,
+ "dependencies": {
+ "es-define-property": "^1.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
"node_modules/has-symbols": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
@@ -1924,6 +2893,65 @@
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
},
+ "node_modules/html-encoding-sniffer": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-4.0.0.tgz",
+ "integrity": "sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==",
+ "dev": true,
+ "dependencies": {
+ "whatwg-encoding": "^3.1.1"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/http-proxy-agent": {
+ "version": "7.0.2",
+ "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz",
+ "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==",
+ "dev": true,
+ "dependencies": {
+ "agent-base": "^7.1.0",
+ "debug": "^4.3.4"
+ },
+ "engines": {
+ "node": ">= 14"
+ }
+ },
+ "node_modules/https-proxy-agent": {
+ "version": "7.0.6",
+ "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz",
+ "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==",
+ "dev": true,
+ "dependencies": {
+ "agent-base": "^7.1.2",
+ "debug": "4"
+ },
+ "engines": {
+ "node": ">= 14"
+ }
+ },
+ "node_modules/human-signals": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-5.0.0.tgz",
+ "integrity": "sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=16.17.0"
+ }
+ },
+ "node_modules/iconv-lite": {
+ "version": "0.6.3",
+ "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
+ "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
+ "dev": true,
+ "dependencies": {
+ "safer-buffer": ">= 2.1.2 < 3.0.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
"node_modules/import-fresh": {
"version": "3.3.1",
"resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz",
@@ -1939,11 +2967,110 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/indent-string": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz",
+ "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/internal-slot": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.1.0.tgz",
+ "integrity": "sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==",
+ "dev": true,
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "hasown": "^2.0.2",
+ "side-channel": "^1.1.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/is-arguments": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.2.0.tgz",
+ "integrity": "sha512-7bVbi0huj/wrIAOzb8U1aszg9kdi3KN/CyU19CTI7tAoZYEZoL9yCDXpbXN+uPsuWnP02cyug1gleqq+TU+YCA==",
+ "dev": true,
+ "dependencies": {
+ "call-bound": "^1.0.2",
+ "has-tostringtag": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-array-buffer": {
+ "version": "3.0.5",
+ "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz",
+ "integrity": "sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==",
+ "dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.8",
+ "call-bound": "^1.0.3",
+ "get-intrinsic": "^1.2.6"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
"node_modules/is-arrayish": {
"version": "0.2.1",
"resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz",
"integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg=="
},
+ "node_modules/is-bigint": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.1.0.tgz",
+ "integrity": "sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==",
+ "dev": true,
+ "dependencies": {
+ "has-bigints": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-boolean-object": {
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.2.2.tgz",
+ "integrity": "sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==",
+ "dev": true,
+ "dependencies": {
+ "call-bound": "^1.0.3",
+ "has-tostringtag": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-callable": {
+ "version": "1.2.7",
+ "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz",
+ "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==",
+ "dev": true,
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
"node_modules/is-core-module": {
"version": "2.16.1",
"resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz",
@@ -1958,11 +3085,231 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/is-date-object": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.1.0.tgz",
+ "integrity": "sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==",
+ "dev": true,
+ "dependencies": {
+ "call-bound": "^1.0.2",
+ "has-tostringtag": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-map": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz",
+ "integrity": "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==",
+ "dev": true,
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-number-object": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.1.1.tgz",
+ "integrity": "sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==",
+ "dev": true,
+ "dependencies": {
+ "call-bound": "^1.0.3",
+ "has-tostringtag": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-potential-custom-element-name": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz",
+ "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==",
+ "dev": true
+ },
+ "node_modules/is-regex": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz",
+ "integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==",
+ "dev": true,
+ "dependencies": {
+ "call-bound": "^1.0.2",
+ "gopd": "^1.2.0",
+ "has-tostringtag": "^1.0.2",
+ "hasown": "^2.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-set": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.3.tgz",
+ "integrity": "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==",
+ "dev": true,
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-shared-array-buffer": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.4.tgz",
+ "integrity": "sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==",
+ "dev": true,
+ "dependencies": {
+ "call-bound": "^1.0.3"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-stream": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz",
+ "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==",
+ "dev": true,
+ "engines": {
+ "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/is-string": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.1.1.tgz",
+ "integrity": "sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==",
+ "dev": true,
+ "dependencies": {
+ "call-bound": "^1.0.3",
+ "has-tostringtag": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-symbol": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.1.1.tgz",
+ "integrity": "sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==",
+ "dev": true,
+ "dependencies": {
+ "call-bound": "^1.0.2",
+ "has-symbols": "^1.1.0",
+ "safe-regex-test": "^1.1.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-weakmap": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz",
+ "integrity": "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==",
+ "dev": true,
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-weakset": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.4.tgz",
+ "integrity": "sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==",
+ "dev": true,
+ "dependencies": {
+ "call-bound": "^1.0.3",
+ "get-intrinsic": "^1.2.6"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/isarray": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz",
+ "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==",
+ "dev": true
+ },
+ "node_modules/isexe": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
+ "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
+ "dev": true
+ },
"node_modules/js-tokens": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="
},
+ "node_modules/jsdom": {
+ "version": "23.2.0",
+ "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-23.2.0.tgz",
+ "integrity": "sha512-L88oL7D/8ufIES+Zjz7v0aes+oBMh2Xnh3ygWvL0OaICOomKEPKuPnIfBJekiXr+BHbbMjrWn/xqrDQuxFTeyA==",
+ "dev": true,
+ "dependencies": {
+ "@asamuzakjp/dom-selector": "^2.0.1",
+ "cssstyle": "^4.0.1",
+ "data-urls": "^5.0.0",
+ "decimal.js": "^10.4.3",
+ "form-data": "^4.0.0",
+ "html-encoding-sniffer": "^4.0.0",
+ "http-proxy-agent": "^7.0.0",
+ "https-proxy-agent": "^7.0.2",
+ "is-potential-custom-element-name": "^1.0.1",
+ "parse5": "^7.1.2",
+ "rrweb-cssom": "^0.6.0",
+ "saxes": "^6.0.0",
+ "symbol-tree": "^3.2.4",
+ "tough-cookie": "^4.1.3",
+ "w3c-xmlserializer": "^5.0.0",
+ "webidl-conversions": "^7.0.0",
+ "whatwg-encoding": "^3.1.1",
+ "whatwg-mimetype": "^4.0.0",
+ "whatwg-url": "^14.0.0",
+ "ws": "^8.16.0",
+ "xml-name-validator": "^5.0.0"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "peerDependencies": {
+ "canvas": "^2.11.2"
+ },
+ "peerDependenciesMeta": {
+ "canvas": {
+ "optional": true
+ }
+ }
+ },
"node_modules/jsesc": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz",
@@ -1996,6 +3343,28 @@
"resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz",
"integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg=="
},
+ "node_modules/local-pkg": {
+ "version": "0.5.1",
+ "resolved": "https://registry.npmjs.org/local-pkg/-/local-pkg-0.5.1.tgz",
+ "integrity": "sha512-9rrA30MRRP3gBD3HTGnC6cDFpaE1kVDWxWgqWJUN0RvDNAo+Nz/9GxB+nHOH0ifbVFy0hSA1V6vFDvnx54lTEQ==",
+ "dev": true,
+ "dependencies": {
+ "mlly": "^1.7.3",
+ "pkg-types": "^1.2.1"
+ },
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/antfu"
+ }
+ },
+ "node_modules/lodash": {
+ "version": "4.17.21",
+ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
+ "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
+ "dev": true
+ },
"node_modules/loose-envify": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
@@ -2007,6 +3376,15 @@
"loose-envify": "cli.js"
}
},
+ "node_modules/loupe": {
+ "version": "2.3.7",
+ "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.7.tgz",
+ "integrity": "sha512-zSMINGVYkdpYSOBmLi0D1Uo7JU9nVdQKrHxC8eYlV+9YKK9WePqAlL7lSlorG/U2Fw1w0hTBmaa/jrQ3UbPHtA==",
+ "dev": true,
+ "dependencies": {
+ "get-func-name": "^2.0.1"
+ }
+ },
"node_modules/lru-cache": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
@@ -2016,6 +3394,24 @@
"yallist": "^3.0.2"
}
},
+ "node_modules/lz-string": {
+ "version": "1.5.0",
+ "resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz",
+ "integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==",
+ "dev": true,
+ "bin": {
+ "lz-string": "bin/bin.js"
+ }
+ },
+ "node_modules/magic-string": {
+ "version": "0.30.17",
+ "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz",
+ "integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==",
+ "dev": true,
+ "dependencies": {
+ "@jridgewell/sourcemap-codec": "^1.5.0"
+ }
+ },
"node_modules/math-intrinsics": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
@@ -2024,6 +3420,18 @@
"node": ">= 0.4"
}
},
+ "node_modules/mdn-data": {
+ "version": "2.0.30",
+ "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.30.tgz",
+ "integrity": "sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==",
+ "dev": true
+ },
+ "node_modules/merge-stream": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz",
+ "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==",
+ "dev": true
+ },
"node_modules/mime-db": {
"version": "1.52.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
@@ -2043,6 +3451,45 @@
"node": ">= 0.6"
}
},
+ "node_modules/mimic-fn": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz",
+ "integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==",
+ "dev": true,
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/min-indent": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz",
+ "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==",
+ "dev": true,
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/mlly": {
+ "version": "1.7.4",
+ "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.7.4.tgz",
+ "integrity": "sha512-qmdSIPC4bDJXgZTCR7XosJiNKySV7O215tsPtDN9iEO/7q/76b/ijtgRu/+epFXSJhijtTCCGp3DWS549P3xKw==",
+ "dev": true,
+ "dependencies": {
+ "acorn": "^8.14.0",
+ "pathe": "^2.0.1",
+ "pkg-types": "^1.3.0",
+ "ufo": "^1.5.4"
+ }
+ },
+ "node_modules/mlly/node_modules/pathe": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz",
+ "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==",
+ "dev": true
+ },
"node_modules/ms": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
@@ -2072,6 +3519,33 @@
"integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==",
"dev": true
},
+ "node_modules/npm-run-path": {
+ "version": "5.3.0",
+ "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.3.0.tgz",
+ "integrity": "sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==",
+ "dev": true,
+ "dependencies": {
+ "path-key": "^4.0.0"
+ },
+ "engines": {
+ "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/npm-run-path/node_modules/path-key": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz",
+ "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/object-assign": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
@@ -2080,6 +3554,93 @@
"node": ">=0.10.0"
}
},
+ "node_modules/object-inspect": {
+ "version": "1.13.4",
+ "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz",
+ "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==",
+ "dev": true,
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/object-is": {
+ "version": "1.1.6",
+ "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.6.tgz",
+ "integrity": "sha512-F8cZ+KfGlSGi09lJT7/Nd6KJZ9ygtvYC0/UYYLI9nmQKLMnydpB9yvbv9K1uSkEu7FU9vYPmVwLg328tX+ot3Q==",
+ "dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.7",
+ "define-properties": "^1.2.1"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/object-keys": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz",
+ "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==",
+ "dev": true,
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/object.assign": {
+ "version": "4.1.7",
+ "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.7.tgz",
+ "integrity": "sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==",
+ "dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.8",
+ "call-bound": "^1.0.3",
+ "define-properties": "^1.2.1",
+ "es-object-atoms": "^1.0.0",
+ "has-symbols": "^1.1.0",
+ "object-keys": "^1.1.1"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/onetime": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz",
+ "integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==",
+ "dev": true,
+ "dependencies": {
+ "mimic-fn": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/p-limit": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-5.0.0.tgz",
+ "integrity": "sha512-/Eaoq+QyLSiXQ4lyYV23f14mZRQcXnxfHrN0vCai+ak9G0pp9iEQukIIZq5NccEvwRB8PUnZT0KsOoDCINS1qQ==",
+ "dev": true,
+ "dependencies": {
+ "yocto-queue": "^1.0.0"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/parent-module": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
@@ -2108,6 +3669,27 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/parse5": {
+ "version": "7.3.0",
+ "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz",
+ "integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==",
+ "dev": true,
+ "dependencies": {
+ "entities": "^6.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/inikulin/parse5?sponsor=1"
+ }
+ },
+ "node_modules/path-key": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
+ "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/path-parse": {
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
@@ -2121,11 +3703,52 @@
"node": ">=8"
}
},
+ "node_modules/pathe": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.2.tgz",
+ "integrity": "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==",
+ "dev": true
+ },
+ "node_modules/pathval": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz",
+ "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==",
+ "dev": true,
+ "engines": {
+ "node": "*"
+ }
+ },
"node_modules/picocolors": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
"integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="
},
+ "node_modules/pkg-types": {
+ "version": "1.3.1",
+ "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.3.1.tgz",
+ "integrity": "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==",
+ "dev": true,
+ "dependencies": {
+ "confbox": "^0.1.8",
+ "mlly": "^1.7.4",
+ "pathe": "^2.0.1"
+ }
+ },
+ "node_modules/pkg-types/node_modules/pathe": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz",
+ "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==",
+ "dev": true
+ },
+ "node_modules/possible-typed-array-names": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz",
+ "integrity": "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==",
+ "dev": true,
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
"node_modules/postcss": {
"version": "8.5.6",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz",
@@ -2154,6 +3777,38 @@
"node": "^10 || ^12 || >=14"
}
},
+ "node_modules/pretty-format": {
+ "version": "27.5.1",
+ "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz",
+ "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==",
+ "dev": true,
+ "dependencies": {
+ "ansi-regex": "^5.0.1",
+ "ansi-styles": "^5.0.0",
+ "react-is": "^17.0.1"
+ },
+ "engines": {
+ "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0"
+ }
+ },
+ "node_modules/pretty-format/node_modules/ansi-styles": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz",
+ "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==",
+ "dev": true,
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/pretty-format/node_modules/react-is": {
+ "version": "17.0.2",
+ "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz",
+ "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==",
+ "dev": true
+ },
"node_modules/prop-types": {
"version": "15.8.1",
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
@@ -2174,6 +3829,33 @@
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="
},
+ "node_modules/psl": {
+ "version": "1.15.0",
+ "resolved": "https://registry.npmjs.org/psl/-/psl-1.15.0.tgz",
+ "integrity": "sha512-JZd3gMVBAVQkSs6HdNZo9Sdo0LNcQeMNP3CozBJb3JYC/QUYZTnKxP+f8oWRX4rHP5EurWxqAHTSwUCjlNKa1w==",
+ "dev": true,
+ "dependencies": {
+ "punycode": "^2.3.1"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/lupomontero"
+ }
+ },
+ "node_modules/punycode": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
+ "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==",
+ "dev": true,
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/querystringify": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz",
+ "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==",
+ "dev": true
+ },
"node_modules/react": {
"version": "18.3.1",
"resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz",
@@ -2226,6 +3908,54 @@
"react-dom": ">=16.6.0"
}
},
+ "node_modules/redent": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz",
+ "integrity": "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==",
+ "dev": true,
+ "dependencies": {
+ "indent-string": "^4.0.0",
+ "strip-indent": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/regexp.prototype.flags": {
+ "version": "1.5.4",
+ "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz",
+ "integrity": "sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==",
+ "dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.8",
+ "define-properties": "^1.2.1",
+ "es-errors": "^1.3.0",
+ "get-proto": "^1.0.1",
+ "gopd": "^1.2.0",
+ "set-function-name": "^2.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/require-from-string": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz",
+ "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/requires-port": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz",
+ "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==",
+ "dev": true
+ },
"node_modules/resolve": {
"version": "1.22.10",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz",
@@ -2292,6 +4022,47 @@
"fsevents": "~2.3.2"
}
},
+ "node_modules/rrweb-cssom": {
+ "version": "0.6.0",
+ "resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.6.0.tgz",
+ "integrity": "sha512-APM0Gt1KoXBz0iIkkdB/kfvGOwC4UuJFeG/c+yV7wSc7q96cG/kJ0HiYCnzivD9SB53cLV1MlHFNfOuPaadYSw==",
+ "dev": true
+ },
+ "node_modules/safe-regex-test": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.1.0.tgz",
+ "integrity": "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==",
+ "dev": true,
+ "dependencies": {
+ "call-bound": "^1.0.2",
+ "es-errors": "^1.3.0",
+ "is-regex": "^1.2.1"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/safer-buffer": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
+ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
+ "dev": true
+ },
+ "node_modules/saxes": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz",
+ "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==",
+ "dev": true,
+ "dependencies": {
+ "xmlchars": "^2.2.0"
+ },
+ "engines": {
+ "node": ">=v12.22.7"
+ }
+ },
"node_modules/scheduler": {
"version": "0.23.2",
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz",
@@ -2309,6 +4080,149 @@
"semver": "bin/semver.js"
}
},
+ "node_modules/set-function-length": {
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz",
+ "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==",
+ "dev": true,
+ "dependencies": {
+ "define-data-property": "^1.1.4",
+ "es-errors": "^1.3.0",
+ "function-bind": "^1.1.2",
+ "get-intrinsic": "^1.2.4",
+ "gopd": "^1.0.1",
+ "has-property-descriptors": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/set-function-name": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz",
+ "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==",
+ "dev": true,
+ "dependencies": {
+ "define-data-property": "^1.1.4",
+ "es-errors": "^1.3.0",
+ "functions-have-names": "^1.2.3",
+ "has-property-descriptors": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/shebang-command": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
+ "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
+ "dev": true,
+ "dependencies": {
+ "shebang-regex": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/shebang-regex": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
+ "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/side-channel": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz",
+ "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==",
+ "dev": true,
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "object-inspect": "^1.13.3",
+ "side-channel-list": "^1.0.0",
+ "side-channel-map": "^1.0.1",
+ "side-channel-weakmap": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/side-channel-list": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz",
+ "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==",
+ "dev": true,
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "object-inspect": "^1.13.3"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/side-channel-map": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz",
+ "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==",
+ "dev": true,
+ "dependencies": {
+ "call-bound": "^1.0.2",
+ "es-errors": "^1.3.0",
+ "get-intrinsic": "^1.2.5",
+ "object-inspect": "^1.13.3"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/side-channel-weakmap": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz",
+ "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==",
+ "dev": true,
+ "dependencies": {
+ "call-bound": "^1.0.2",
+ "es-errors": "^1.3.0",
+ "get-intrinsic": "^1.2.5",
+ "object-inspect": "^1.13.3",
+ "side-channel-map": "^1.0.1"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/siginfo": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz",
+ "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==",
+ "dev": true
+ },
+ "node_modules/signal-exit": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz",
+ "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==",
+ "dev": true,
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
"node_modules/source-map": {
"version": "0.5.7",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
@@ -2326,11 +4240,90 @@
"node": ">=0.10.0"
}
},
+ "node_modules/stackback": {
+ "version": "0.0.2",
+ "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz",
+ "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==",
+ "dev": true
+ },
+ "node_modules/std-env": {
+ "version": "3.9.0",
+ "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.9.0.tgz",
+ "integrity": "sha512-UGvjygr6F6tpH7o2qyqR6QYpwraIjKSdtzyBdyytFOHmPZY917kwdwLG0RbOjWOnKmnm3PeHjaoLLMie7kPLQw==",
+ "dev": true
+ },
+ "node_modules/stop-iteration-iterator": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.1.0.tgz",
+ "integrity": "sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==",
+ "dev": true,
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "internal-slot": "^1.1.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/strip-final-newline": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz",
+ "integrity": "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==",
+ "dev": true,
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/strip-indent": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz",
+ "integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==",
+ "dev": true,
+ "dependencies": {
+ "min-indent": "^1.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/strip-literal": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-2.1.1.tgz",
+ "integrity": "sha512-631UJ6O00eNGfMiWG78ck80dfBab8X6IVFB51jZK5Icd7XAs60Z5y7QdSd/wGIklnWvRbUNloVzhOKKmutxQ6Q==",
+ "dev": true,
+ "dependencies": {
+ "js-tokens": "^9.0.1"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/antfu"
+ }
+ },
+ "node_modules/strip-literal/node_modules/js-tokens": {
+ "version": "9.0.1",
+ "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-9.0.1.tgz",
+ "integrity": "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==",
+ "dev": true
+ },
"node_modules/stylis": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/stylis/-/stylis-4.2.0.tgz",
"integrity": "sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw=="
},
+ "node_modules/supports-color": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
+ "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
+ "dev": true,
+ "dependencies": {
+ "has-flag": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/supports-preserve-symlinks-flag": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
@@ -2342,6 +4335,72 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/symbol-tree": {
+ "version": "3.2.4",
+ "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz",
+ "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==",
+ "dev": true
+ },
+ "node_modules/tinybench": {
+ "version": "2.9.0",
+ "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz",
+ "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==",
+ "dev": true
+ },
+ "node_modules/tinypool": {
+ "version": "0.8.4",
+ "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-0.8.4.tgz",
+ "integrity": "sha512-i11VH5gS6IFeLY3gMBQ00/MmLncVP7JLXOw1vlgkytLmJK7QnEr7NXf0LBdxfmNPAeyetukOk0bOYrJrFGjYJQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/tinyspy": {
+ "version": "2.2.1",
+ "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-2.2.1.tgz",
+ "integrity": "sha512-KYad6Vy5VDWV4GH3fjpseMQ/XU2BhIYP7Vzd0LG44qRWm/Yt2WCOTicFdvmgo6gWaqooMQCawTtILVQJupKu7A==",
+ "dev": true,
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/tough-cookie": {
+ "version": "4.1.4",
+ "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.4.tgz",
+ "integrity": "sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==",
+ "dev": true,
+ "dependencies": {
+ "psl": "^1.1.33",
+ "punycode": "^2.1.1",
+ "universalify": "^0.2.0",
+ "url-parse": "^1.5.3"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/tr46": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/tr46/-/tr46-5.1.1.tgz",
+ "integrity": "sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==",
+ "dev": true,
+ "dependencies": {
+ "punycode": "^2.3.1"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/type-detect": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.1.0.tgz",
+ "integrity": "sha512-Acylog8/luQ8L7il+geoSxhEkazvkslg7PSNKOX59mbB9cOveP5aq9h74Y7YU8yDpJwetzQQrfIwtf4Wp4LKcw==",
+ "dev": true,
+ "engines": {
+ "node": ">=4"
+ }
+ },
"node_modules/typescript": {
"version": "5.8.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz",
@@ -2355,6 +4414,21 @@
"node": ">=14.17"
}
},
+ "node_modules/ufo": {
+ "version": "1.6.1",
+ "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.6.1.tgz",
+ "integrity": "sha512-9a4/uxlTWJ4+a5i0ooc1rU7C7YOw3wT+UGqdeNNHWnOF9qcMBgLRS+4IYUqbczewFx4mLEig6gawh7X6mFlEkA==",
+ "dev": true
+ },
+ "node_modules/universalify": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz",
+ "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==",
+ "dev": true,
+ "engines": {
+ "node": ">= 4.0.0"
+ }
+ },
"node_modules/update-browserslist-db": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz",
@@ -2385,6 +4459,16 @@
"browserslist": ">= 4.21.0"
}
},
+ "node_modules/url-parse": {
+ "version": "1.5.10",
+ "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz",
+ "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==",
+ "dev": true,
+ "dependencies": {
+ "querystringify": "^2.1.1",
+ "requires-port": "^1.0.0"
+ }
+ },
"node_modules/vite": {
"version": "5.4.19",
"resolved": "https://registry.npmjs.org/vite/-/vite-5.4.19.tgz",
@@ -2444,6 +4528,273 @@
}
}
},
+ "node_modules/vite-node": {
+ "version": "1.6.1",
+ "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-1.6.1.tgz",
+ "integrity": "sha512-YAXkfvGtuTzwWbDSACdJSg4A4DZiAqckWe90Zapc/sEX3XvHcw1NdurM/6od8J207tSDqNbSsgdCacBgvJKFuA==",
+ "dev": true,
+ "dependencies": {
+ "cac": "^6.7.14",
+ "debug": "^4.3.4",
+ "pathe": "^1.1.1",
+ "picocolors": "^1.0.0",
+ "vite": "^5.0.0"
+ },
+ "bin": {
+ "vite-node": "vite-node.mjs"
+ },
+ "engines": {
+ "node": "^18.0.0 || >=20.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/vitest": {
+ "version": "1.6.1",
+ "resolved": "https://registry.npmjs.org/vitest/-/vitest-1.6.1.tgz",
+ "integrity": "sha512-Ljb1cnSJSivGN0LqXd/zmDbWEM0RNNg2t1QW/XUhYl/qPqyu7CsqeWtqQXHVaJsecLPuDoak2oJcZN2QoRIOag==",
+ "dev": true,
+ "dependencies": {
+ "@vitest/expect": "1.6.1",
+ "@vitest/runner": "1.6.1",
+ "@vitest/snapshot": "1.6.1",
+ "@vitest/spy": "1.6.1",
+ "@vitest/utils": "1.6.1",
+ "acorn-walk": "^8.3.2",
+ "chai": "^4.3.10",
+ "debug": "^4.3.4",
+ "execa": "^8.0.1",
+ "local-pkg": "^0.5.0",
+ "magic-string": "^0.30.5",
+ "pathe": "^1.1.1",
+ "picocolors": "^1.0.0",
+ "std-env": "^3.5.0",
+ "strip-literal": "^2.0.0",
+ "tinybench": "^2.5.1",
+ "tinypool": "^0.8.3",
+ "vite": "^5.0.0",
+ "vite-node": "1.6.1",
+ "why-is-node-running": "^2.2.2"
+ },
+ "bin": {
+ "vitest": "vitest.mjs"
+ },
+ "engines": {
+ "node": "^18.0.0 || >=20.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ },
+ "peerDependencies": {
+ "@edge-runtime/vm": "*",
+ "@types/node": "^18.0.0 || >=20.0.0",
+ "@vitest/browser": "1.6.1",
+ "@vitest/ui": "1.6.1",
+ "happy-dom": "*",
+ "jsdom": "*"
+ },
+ "peerDependenciesMeta": {
+ "@edge-runtime/vm": {
+ "optional": true
+ },
+ "@types/node": {
+ "optional": true
+ },
+ "@vitest/browser": {
+ "optional": true
+ },
+ "@vitest/ui": {
+ "optional": true
+ },
+ "happy-dom": {
+ "optional": true
+ },
+ "jsdom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/w3c-xmlserializer": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz",
+ "integrity": "sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==",
+ "dev": true,
+ "dependencies": {
+ "xml-name-validator": "^5.0.0"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/webidl-conversions": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz",
+ "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==",
+ "dev": true,
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/whatwg-encoding": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz",
+ "integrity": "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==",
+ "dev": true,
+ "dependencies": {
+ "iconv-lite": "0.6.3"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/whatwg-mimetype": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz",
+ "integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==",
+ "dev": true,
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/whatwg-url": {
+ "version": "14.2.0",
+ "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.2.0.tgz",
+ "integrity": "sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==",
+ "dev": true,
+ "dependencies": {
+ "tr46": "^5.1.0",
+ "webidl-conversions": "^7.0.0"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/which": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
+ "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
+ "dev": true,
+ "dependencies": {
+ "isexe": "^2.0.0"
+ },
+ "bin": {
+ "node-which": "bin/node-which"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/which-boxed-primitive": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.1.1.tgz",
+ "integrity": "sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==",
+ "dev": true,
+ "dependencies": {
+ "is-bigint": "^1.1.0",
+ "is-boolean-object": "^1.2.1",
+ "is-number-object": "^1.1.1",
+ "is-string": "^1.1.1",
+ "is-symbol": "^1.1.1"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/which-collection": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.2.tgz",
+ "integrity": "sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==",
+ "dev": true,
+ "dependencies": {
+ "is-map": "^2.0.3",
+ "is-set": "^2.0.3",
+ "is-weakmap": "^2.0.2",
+ "is-weakset": "^2.0.3"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/which-typed-array": {
+ "version": "1.1.19",
+ "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.19.tgz",
+ "integrity": "sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw==",
+ "dev": true,
+ "dependencies": {
+ "available-typed-arrays": "^1.0.7",
+ "call-bind": "^1.0.8",
+ "call-bound": "^1.0.4",
+ "for-each": "^0.3.5",
+ "get-proto": "^1.0.1",
+ "gopd": "^1.2.0",
+ "has-tostringtag": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/why-is-node-running": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz",
+ "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==",
+ "dev": true,
+ "dependencies": {
+ "siginfo": "^2.0.0",
+ "stackback": "0.0.2"
+ },
+ "bin": {
+ "why-is-node-running": "cli.js"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/ws": {
+ "version": "8.18.3",
+ "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz",
+ "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==",
+ "dev": true,
+ "engines": {
+ "node": ">=10.0.0"
+ },
+ "peerDependencies": {
+ "bufferutil": "^4.0.1",
+ "utf-8-validate": ">=5.0.2"
+ },
+ "peerDependenciesMeta": {
+ "bufferutil": {
+ "optional": true
+ },
+ "utf-8-validate": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/xml-name-validator": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz",
+ "integrity": "sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==",
+ "dev": true,
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/xmlchars": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz",
+ "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==",
+ "dev": true
+ },
"node_modules/yallist": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
@@ -2457,6 +4808,18 @@
"engines": {
"node": ">= 6"
}
+ },
+ "node_modules/yocto-queue": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.2.1.tgz",
+ "integrity": "sha512-AyeEbWOu/TAXdxlV9wmGcR0+yh2j3vYPGOECcIj2S7MkrLyC7ne+oye2BKTItt0ii2PHk4cDy+95+LshzbXnGg==",
+ "dev": true,
+ "engines": {
+ "node": ">=12.20"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
}
}
}
diff --git a/frontend/package.json b/frontend/package.json
index 1af3c1b..4f61048 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -3,6 +3,7 @@
"version": "0.1.0",
"private": true,
"scripts": {
+ "test": "vitest",
"dev": "vite",
"build": "vite build",
"preview": "vite preview"
@@ -18,6 +19,11 @@
"react-dom": "^18.2.0"
},
"devDependencies": {
+ "vitest": "^1.5.0",
+ "@testing-library/react": "^14.1.2",
+ "@testing-library/jest-dom": "^6.1.5",
+ "@testing-library/user-event": "^14.4.3",
+ "jsdom": "^23.0.0",
"@types/react": "^18.2.50",
"@types/react-dom": "^18.2.17",
"typescript": "^5.3.3",
diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx
index b8decaf..178900d 100644
--- a/frontend/src/App.tsx
+++ b/frontend/src/App.tsx
@@ -9,9 +9,15 @@ import InventoryPage from "./pages/Inventory";
import ItemExplorerPage from "./pages/ItemExplorer";
import RecipesPage from "./pages/Recipes";
import Footer from "./components/Footer";
+import { inventoryColor, explorerColor, recipesColor } from "./constants/colors";
+import MobileNav from "./components/MobileNav";
+import useMediaQuery from "@mui/material/useMediaQuery";
+import { useTheme } from "@mui/material/styles";
export default function App() {
const [page, setPage] = useState(0);
+ const theme = useTheme();
+ const isMobile = useMediaQuery(theme.breakpoints.down('sm'));
const { data: metadata } = useQuery({
queryKey: ["metadata"],
@@ -29,8 +35,9 @@ export default function App() {
MOG SQUIRE
- {(() => {
- const tabColors = ['#66bb6a', '#42a5f5', '#ffa726'];
+ {!isMobile && (() => {
+ const tabColors = [inventoryColor, explorerColor, recipesColor];
+ const tabLabels = ['Inventory','Item Explorer','Recipes'];
return (
- {['Inventory','Item Explorer','Recipes'].map((label, idx) => (
-
+ {tabLabels.map((label, idx) => (
+
))}
);
})()}
- {page === 0 && metadata && (
+ {/* Main content */}
+ {/* bottom padding for nav */}
+ {page === 0 && metadata && (
)}
{page === 1 && metadata && (
@@ -58,7 +68,9 @@ export default function App() {
)}
-
+
+
+ {isMobile && }
);
}
diff --git a/frontend/src/__tests__/filterExplorerItems.test.ts b/frontend/src/__tests__/filterExplorerItems.test.ts
new file mode 100644
index 0000000..2575254
--- /dev/null
+++ b/frontend/src/__tests__/filterExplorerItems.test.ts
@@ -0,0 +1,25 @@
+import { describe, it, expect } from 'vitest';
+import { filterExplorerItems, RawItem } from '../utils/filterExplorerItems';
+
+const sampleData: RawItem[] = [
+ { id: 1, name: 'Bronze Sword', type_description: 'WEAPON' },
+ { id: 2, name: 'Leather Armor', type_description: 'ARMOR' },
+ { id: 3, name: 'Antidote', type_description: 'USABLE_ITEM' },
+ { id: 4, name: 'Mystery', type_description: undefined },
+];
+
+const baseTypes = ['WEAPON', 'ARMOR'];
+
+describe('filterExplorerItems', () => {
+ it('excludes baseTypes while on MISC tab with empty search', () => {
+ const result = filterExplorerItems(sampleData, 'MISC', baseTypes, '');
+ const names = result?.map((g) => g.name);
+ expect(names).toEqual(['Antidote', 'Mystery']);
+ });
+
+ it('includes all items when a search term is present', () => {
+ const result = filterExplorerItems(sampleData, 'MISC', baseTypes, 'ant');
+ const names = result?.map((g) => g.name);
+ expect(names).toEqual(['Bronze Sword', 'Leather Armor', 'Antidote', 'Mystery']);
+ });
+});
diff --git a/frontend/src/api.ts b/frontend/src/api.ts
index c34aa0c..93e2fe3 100644
--- a/frontend/src/api.ts
+++ b/frontend/src/api.ts
@@ -3,3 +3,12 @@ import axios from "axios";
export const api = axios.create({
baseURL: "/api",
});
+
+export async function importInventoryCsv(file: File) {
+ const formData = new FormData();
+ formData.append("file", file);
+ const { data } = await api.post("/inventory/import", formData, {
+ headers: { "Content-Type": "multipart/form-data" },
+ });
+ return data as { imported: number };
+}
diff --git a/frontend/src/components/ItemDetailPanel.tsx b/frontend/src/components/ItemDetailPanel.tsx
index b96242b..fefaae6 100644
--- a/frontend/src/components/ItemDetailPanel.tsx
+++ b/frontend/src/components/ItemDetailPanel.tsx
@@ -1,4 +1,6 @@
-import Drawer from "@mui/material/Drawer";
+import Dialog from "@mui/material/Dialog";
+import DialogTitle from "@mui/material/DialogTitle";
+import DialogContent from "@mui/material/DialogContent";
import Box from "@mui/material/Box";
import Typography from "@mui/material/Typography";
import Divider from "@mui/material/Divider";
@@ -7,6 +9,7 @@ import OpenInNewIcon from "@mui/icons-material/OpenInNew";
import { useQuery } from "@tanstack/react-query";
import { api } from "../api";
import CircularProgress from "@mui/material/CircularProgress";
+import { craftColors } from "../constants/colors";
export interface ItemSummary {
id: number;
@@ -14,7 +17,7 @@ export interface ItemSummary {
}
interface Props {
open: boolean;
- item: (ItemSummary & { storages?: string[]; storage_type?: string }) | null;
+ item: (ItemSummary & { storages?: string[]; storage_type?: string; jobs_description?: string[] }) | null;
onClose: () => void;
}
@@ -56,7 +59,7 @@ export default function ItemDetailPanel({ open, item, onClose }: Props) {
});
return (
-
+
+
);
}
diff --git a/frontend/src/components/ItemsGrid.tsx b/frontend/src/components/ItemsGrid.tsx
index 0037243..1673d58 100644
--- a/frontend/src/components/ItemsGrid.tsx
+++ b/frontend/src/components/ItemsGrid.tsx
@@ -10,6 +10,7 @@ export interface GridItem {
iconUrl?: string;
icon_id?: string;
quantity?: number;
+ jobs_description?: string[];
storages?: string[];
storage_type?: string;
isScroll?: boolean;
@@ -24,16 +25,16 @@ interface Props {
}
const IconImg = styled("img")(({ theme }) => ({
- width: 32,
- height: 32,
+ width: 40,
+ height: 40,
[theme.breakpoints.up("sm")]: {
- width: 40,
- height: 40,
- },
- [theme.breakpoints.up("md")]: {
width: 48,
height: 48,
},
+ [theme.breakpoints.up("md")]: {
+ width: 80,
+ height: 80,
+ },
}));
export default function ItemsGrid({ items, onSelect, loading, duplicates, selectedId }: Props) {
@@ -45,15 +46,15 @@ export default function ItemsGrid({ items, onSelect, loading, duplicates, select
display: "grid",
gridTemplateColumns: { xs: 'repeat(4, 1fr)', sm: 'repeat(6, 1fr)', md: 'repeat(8, 1fr)', lg: 'repeat(10, 1fr)' },
gap: 1,
- p: 2,
+ p: 1.5,
}}
>
{cells.map((item, idx) => (
item && onSelect(item)}
@@ -76,7 +77,7 @@ export default function ItemsGrid({ items, onSelect, loading, duplicates, select
{loading || !item ? (
-
+
) : item.iconUrl ? (
1 ? ` x${item.quantity}` : ""}`}
diff --git a/frontend/src/components/MobileNav.tsx b/frontend/src/components/MobileNav.tsx
new file mode 100644
index 0000000..9b79218
--- /dev/null
+++ b/frontend/src/components/MobileNav.tsx
@@ -0,0 +1,30 @@
+import BottomNavigation from "@mui/material/BottomNavigation";
+import BottomNavigationAction from "@mui/material/BottomNavigationAction";
+import Paper from "@mui/material/Paper";
+import InventoryIcon from "@mui/icons-material/Inventory2";
+import SearchIcon from "@mui/icons-material/Search";
+import RestaurantIcon from "@mui/icons-material/Restaurant";
+
+interface Props {
+ value: number;
+ onChange: (value: number) => void;
+}
+
+export default function MobileNav({ value, onChange }: Props) {
+ return (
+
+ onChange(v)}
+ showLabels
+ >
+ } />
+ } />
+ } />
+
+
+ );
+}
diff --git a/frontend/src/constants/colors.ts b/frontend/src/constants/colors.ts
new file mode 100644
index 0000000..f26a9e5
--- /dev/null
+++ b/frontend/src/constants/colors.ts
@@ -0,0 +1,27 @@
+// Central accent colour palette used across navigation tabs.
+// Keep colours distinct and readable on dark background.
+export const inventoryColor = '#66bb6a';
+export const explorerColor = '#42a5f5';
+export const recipesColor = '#ffa726';
+
+export const craftColors: Record = {
+ woodworking: '#ad7e50', // Woodworking – warm earthy brown
+ smithing: '#c94f4f', // Smithing – ember red
+ alchemy: '#b672e8', // Alchemy – mystical purple
+ bonecraft: '#F5F5DC', // Bonecraft – off-white
+ goldsmithing: '#FFD700',// Goldsmithing – gold
+ clothcraft: '#E6C9C9', // Clothcraft – soft beige
+ leathercraft: '#A0522D',// Leathercraft – rich tan
+ cooking: '#E25822', // Cooking – orange-red
+};
+
+export const accentColors = [
+ '#ef5350', // red
+ '#ab47bc', // purple
+ '#5c6bc0', // indigo
+ '#29b6f6', // light blue
+ '#66bb6a', // green
+ '#ffca28', // amber
+ '#ffa726', // orange
+ '#8d6e63', // brown
+];
diff --git a/frontend/src/pages/Inventory.tsx b/frontend/src/pages/Inventory.tsx
index 34f78cf..c61d107 100644
--- a/frontend/src/pages/Inventory.tsx
+++ b/frontend/src/pages/Inventory.tsx
@@ -1,10 +1,16 @@
-import { useState, useEffect, useMemo, useRef } from "react";
+import React, { useState, useEffect, useMemo, useRef } from "react";
import Tabs from "@mui/material/Tabs";
import TextField from "@mui/material/TextField";
import InputAdornment from "@mui/material/InputAdornment";
import SearchIcon from "@mui/icons-material/Search";
import Tab from "@mui/material/Tab";
import Box from "@mui/material/Box";
+import Select from "@mui/material/Select";
+import MenuItem from "@mui/material/MenuItem";
+
+import FormControl from "@mui/material/FormControl";
+import useMediaQuery from "@mui/material/useMediaQuery";
+import { useTheme } from "@mui/material/styles";
import CircularProgress from "@mui/material/CircularProgress";
import { useQuery } from "@tanstack/react-query";
import useDebounce from "../hooks/useDebounce";
@@ -17,6 +23,12 @@ import mountIcon from "../icons/mount.png";
import keyIcon from "../icons/key.png";
import noIcon from "../icons/no-icon.png";
import blankIcon from "../icons/blank.png";
+import { inventoryColor } from "../constants/colors";
+import Button from "@mui/material/Button";
+import UploadFileIcon from "@mui/icons-material/UploadFile";
+import { useMutation, useQueryClient } from "@tanstack/react-query";
+import { importInventoryCsv } from "../api";
+import Snackbar from "@mui/material/Snackbar";
interface Props {
storageTypes: string[];
@@ -25,6 +37,7 @@ interface Props {
export default function InventoryPage({ storageTypes, character = "Rynore" }: Props) {
const [tab, setTab] = useState(0);
+ const [sortKey, setSortKey] = useState<'slot' | 'name' | 'type'>('slot');
const prevTabRef = useRef(0);
// Adjust default selection to "Inventory" once storageTypes are available
@@ -98,28 +111,54 @@ export default function InventoryPage({ storageTypes, character = "Rynore" }: Pr
);
}, [allInv]);
- const filtered = (tab === searchTabIndex ? allInv : data)?.filter(d => debouncedSearch ? d.item_name.toLowerCase().includes(debouncedSearch.toLowerCase()) : true);
+ const baseRows = (tab === searchTabIndex ? allInv : data) ?? [];
+ const filtered = baseRows.filter(d => debouncedSearch ? d.item_name.toLowerCase().includes(debouncedSearch.toLowerCase()) : true);
+
+ const sortedRows = useMemo(()=>{
+ if(sortKey==='slot') return filtered;
+ const copy = [...filtered];
+ if(sortKey==='name') copy.sort((a,b)=>a.item_name.localeCompare(b.item_name));
+ else if(sortKey==='type') copy.sort((a,b)=>((a as any).type_description ?? '').localeCompare(((b as any).type_description ?? '')) || a.item_name.localeCompare(b.item_name));
+ return copy;
+ }, [filtered, sortKey]);
let tempId = -1;
- let gridItems: GridItem[] = (filtered ?? []).map((d: any) => ({
- isScroll: d.type_description === 'SCROLL',
- storages: duplicates.has(d.item_name) ? Array.from((allInv?.filter(i=>i.item_name===d.item_name).map(i=>i.storage_type.toUpperCase())??[])) : undefined,
- id: d.id ?? tempId--,
- storage_type: d.storage_type,
- name: d.item_name,
- quantity: 'quantity' in d ? d.quantity : undefined,
- // Determine icon URL with prioritized fallback logic when missing icon_id
- iconUrl: (d as any).icon_id
- ? `/api/icon/${d.icon_id}`
- : (() => {
- const nameLower = d.item_name.toLowerCase();
- if (nameLower.includes("map")) return mapIcon;
- if (d.item_name.includes("♪")) return mountIcon;
- if ((d.type_description ?? "").toUpperCase().includes("KEY")) return keyIcon;
- return noIcon;
- })(),
- icon_id: d.icon_id,
- }));
+ const seenIds = new Set();
+ let gridItems: GridItem[] = [];
+
+ for (const row of sortedRows) {
+ const realId: number | undefined = (row as any).id;
+ if (realId !== undefined) {
+ if (seenIds.has(realId)) {
+ continue; // skip duplicates
+ }
+ seenIds.add(realId);
+ }
+ const effectiveId = realId ?? tempId--;
+
+ gridItems.push({
+ isScroll: (row as any).type_description === 'SCROLL',
+ storages: duplicates.has(row.item_name)
+ ? Array.from(
+ (allInv?.filter(i => i.item_name === row.item_name).map(i => i.storage_type.toUpperCase()) ?? [])
+ )
+ : undefined,
+ id: effectiveId,
+ storage_type: (row as any).storage_type,
+ name: row.item_name,
+ quantity: 'quantity' in row ? (row as any).quantity : undefined,
+ iconUrl: (row as any).icon_id
+ ? `/api/icon/${(row as any).icon_id}`
+ : (() => {
+ const nameLower = row.item_name.toLowerCase();
+ if (nameLower.includes('map')) return mapIcon;
+ if (row.item_name.includes('♪')) return mountIcon;
+ if (((row as any).type_description ?? '').toUpperCase().includes('KEY')) return keyIcon;
+ return noIcon;
+ })(),
+ icon_id: (row as any).icon_id,
+ });
+ }
// Pad to 80 slots with empty placeholders (skip for Search tab)
if (tab !== searchTabIndex && gridItems.length < 80) {
@@ -133,28 +172,93 @@ export default function InventoryPage({ storageTypes, character = "Rynore" }: Pr
}
}
+ const queryClient = useQueryClient();
+ const importMutation = useMutation({
+ mutationFn: importInventoryCsv,
+ onSuccess: async () => {
+ await queryClient.invalidateQueries({ queryKey: ["inventory"] });
+ setSnack({ open: true, message: "Inventory imported!" });
+ },
+ onError: () => {
+ setSnack({ open: true, message: "Failed to import CSV" });
+ },
+ });
+
+ const fileInputRef = useRef(null);
+ const [snack, setSnack] = useState<{ open: boolean; message: string }>({
+ open: false,
+ message: "",
+ });
+
+ const theme = useTheme();
+ const isNarrow = useMediaQuery(theme.breakpoints.down('md'));
+
return (
-
- {(() => {
- const colors = ['#ef5350','#ab47bc','#5c6bc0','#29b6f6','#66bb6a','#ffca28','#ffa726','#8d6e63'];
- const getColor = (idx:number)=>colors[idx%colors.length];
+
+ {isNarrow ? (
+
+
+
+ ) : (() => {
+ const getColor = () => inventoryColor;
return (
setTab(v)}
variant="scrollable"
- TabIndicatorProps={{ sx:{ backgroundColor:getColor(tab)} }}
+ TabIndicatorProps={{ sx:{ backgroundColor:inventoryColor } }}
sx={{ bgcolor:'background.paper' }}
>
{storageTypes.map((s,idx)=>(
-
+
))}
{searchMode && }
);
})()}
- {
+ const file = e.target.files?.[0];
+ if (file) importMutation.mutate(file);
+ }}
+ />
+ }
+ onClick={() => fileInputRef.current?.click()}
+ disabled={importMutation.isPending}
+ sx={{ bgcolor: inventoryColor, '&:hover':{ bgcolor: inventoryColor }, whiteSpace:'nowrap' }}
+ >
+ {importMutation.isPending ? "Uploading…" : "Import CSV"}
+
+
+
+
+
+
+ setSelected(null)}
/>
+
+ setSnack({ ...snack, open: false })}
+ message={snack.message}
+ />
);
}
diff --git a/frontend/src/pages/ItemExplorer.tsx b/frontend/src/pages/ItemExplorer.tsx
index 229fd45..fa22450 100644
--- a/frontend/src/pages/ItemExplorer.tsx
+++ b/frontend/src/pages/ItemExplorer.tsx
@@ -1,4 +1,5 @@
import { useState, useMemo, useEffect } from "react";
+import { RawItem } from "../utils/filterExplorerItems";
import Tabs from "@mui/material/Tabs";
import TextField from "@mui/material/TextField";
import InputAdornment from "@mui/material/InputAdornment";
@@ -7,6 +8,7 @@ import Tab from "@mui/material/Tab";
import Box from "@mui/material/Box";
import CircularProgress from "@mui/material/CircularProgress";
import Pagination from "@mui/material/Pagination";
+import { explorerColor } from "../constants/colors";
import { useQuery } from "@tanstack/react-query";
import useDebounce from "../hooks/useDebounce";
import ItemsGrid, { GridItem } from "../components/ItemsGrid";
@@ -20,6 +22,7 @@ interface Props {
export default function ItemExplorerPage({ typeDescriptions }: Props) {
const [tab, setTab] = useState(0);
const [subTab, setSubTab] = useState(0);
+ const [selectedJobs, setSelectedJobs] = useState([]);
const [page, setPage] = useState(1);
const [totalPages, setTotalPages] = useState(1);
const [search, setSearch] = useState("");
@@ -52,15 +55,24 @@ export default function ItemExplorerPage({ typeDescriptions }: Props) {
const currentSubType =
currentType === "USABLE_ITEM" ? subTypeValues[subTab] : currentType;
+ const armorJobs = useMemo(
+ () =>
+ "WAR,MNK,WHM,BLM,RDM,THF,PLD,DRK,BST,BRD,RNG,SAM,NIN,DRG,SMN,BLU,COR,PUP,DNC,SCH,GEO,RUN".split(
+ ","
+ ),
+ []
+ );
+
// Reset pagination when type changes
useEffect(() => {
setPage(1);
setTotalPages(1);
setSearch("");
+ setSelectedJobs([]);
}, [currentType, currentSubType]);
const { data, isLoading } = useQuery({
- queryKey: ["items", currentType === "MISC" ? "MISC" : currentSubType, page, debouncedSearch],
+ queryKey: ["items", currentType === "MISC" ? "MISC" : currentSubType, page, debouncedSearch, selectedJobs],
queryFn: async () => {
let url = `/items?page=${page}&page_size=100`;
if (debouncedSearch) url += `&search=${encodeURIComponent(debouncedSearch)}`;
@@ -68,45 +80,92 @@ export default function ItemExplorerPage({ typeDescriptions }: Props) {
url = `/items?type=${encodeURIComponent(currentSubType)}&page=${page}&page_size=100`;
if (debouncedSearch) url += `&search=${encodeURIComponent(debouncedSearch)}`;
}
+ // Add jobs to the query if we're on the ARMOR tab and jobs are selected
+ if (currentType === 'ARMOR' && selectedJobs.length > 0) {
+ const jobsParam = selectedJobs.join(',');
+ url += `&jobs=${jobsParam}`;
+ }
const response = await api.get(url);
const totalCountHeader = response.headers['x-total-count'] ?? response.headers['X-Total-Count'];
if (totalCountHeader) {
const total = parseInt(totalCountHeader, 10);
- if (!isNaN(total)) {
- setTotalPages(Math.max(1, Math.ceil(total / 100)));
- }
+ setTotalPages(Math.ceil(total / 100));
}
- return response.data as { id: number; name: string; icon_id?: string; type_description?: string }[];
+
+
+
+
+
+
+
+
+
+
+
+
+ return response.data;
},
enabled: !!currentType,
});
const gridItems: GridItem[] | undefined = data
- ?.filter((d) => d.name !== '.' && (currentType !== 'MISC' ? true : !baseTypes.includes(d.type_description ?? '')))
+ ?.filter((d: RawItem) => {
+ if (d.name === '.') return false;
+ // When the user is actively searching, don't hide any server results – let the backend decide.
+ if (debouncedSearch.trim() !== '') return true;
+ // Otherwise, apply the MISC filter rules as before.
+ if (currentType !== 'MISC') return true;
+ return !baseTypes.includes(d.type_description ?? '');
+ })
+ // Apply client-side filtering for jobs_description if we're on the ARMOR tab
+ .filter((d: RawItem) => {
+ if (currentType !== 'ARMOR' || selectedJobs.length === 0) return true;
+
+ // Debug: Log the item being filtered when jobs are selected
+ if (selectedJobs.length > 0) {
+ console.log('Filtering item:', d.name, 'jobs_description:', d.jobs_description);
+ }
+
+ // If the item has a jobs_description property, check if any selected job is in the array
+ // or if it contains 'ALL'
+ if (d.jobs_description) {
+ const jobsList = Array.isArray(d.jobs_description) ? d.jobs_description : [];
+ const shouldInclude = jobsList.includes('ALL') || selectedJobs.some(job => jobsList.includes(job));
+
+ if (selectedJobs.length > 0) {
+ console.log(`Item ${d.name}: ${shouldInclude ? 'INCLUDED' : 'EXCLUDED'}, matches: ${selectedJobs.filter(job => jobsList.includes(job))}`);
+ }
+
+ return shouldInclude;
+ }
+
+ // If no jobs_description property exists, we'll still show the item when no specific filter is selected
+ return true;
+ })
.map((d) => ({
- id: d.id,
- name: d.name,
- iconUrl: d.icon_id ? `/api/icon/${d.icon_id}` : undefined,
- icon_id: d.icon_id,
- }));
+ id: d.id,
+ name: d.name,
+ iconUrl: d.icon_id ? `/api/icon/${d.icon_id}` : undefined,
+ icon_id: d.icon_id,
+ jobs_description: d.jobs_description,
+ }));
return (
{(() => {
- const colors = ['#ff7043','#42a5f5','#66bb6a','#ab47bc','#5c6bc0','#ffa726'];
- const getColor=(idx:number)=>colors[idx%colors.length];
+ const getColor = () => explorerColor;
return (
{ setTab(v); setPage(1);} }
variant="scrollable"
- TabIndicatorProps={{ sx:{ backgroundColor:getColor(tab)} }}
+ TabIndicatorProps={{ sx:{ backgroundColor:explorerColor } }}
sx={{ flexGrow:1 }}
>
{displayTypes.map((t,idx)=>{
const label = t.replace('_ITEM','');
- return
+ return
})}
);
@@ -127,23 +186,74 @@ export default function ItemExplorerPage({ typeDescriptions }: Props) {
/>
+ {/* Job filters for Armor items */}
+ {currentType === "ARMOR" && (
+
+
+ setSelectedJobs([])}
+ sx={{
+ cursor: 'pointer',
+ py: 0.5,
+ px: 1,
+ borderRadius: 1,
+ bgcolor: selectedJobs.length === 0 ? `${explorerColor}22` : 'transparent',
+ border: `1px solid ${explorerColor}`,
+ color: explorerColor,
+ '&:hover': {
+ bgcolor: `${explorerColor}11`,
+ },
+ }}
+ >
+ ALL
+
+ {armorJobs.map((jobCode) => (
+ {
+ if (selectedJobs.includes(jobCode)) {
+ setSelectedJobs(selectedJobs.filter((j) => j !== jobCode));
+ } else {
+ setSelectedJobs([...selectedJobs, jobCode]);
+ }
+ }}
+ sx={{
+ cursor: 'pointer',
+ py: 0.5,
+ px: 1,
+ borderRadius: 1,
+ bgcolor: selectedJobs.includes(jobCode) ? `${explorerColor}22` : 'transparent',
+ border: `1px solid ${explorerColor}`,
+ color: explorerColor,
+ '&:hover': {
+ bgcolor: `${explorerColor}11`,
+ },
+ }}
+ >
+ {jobCode}
+
+ ))}
+
+
+
+ )}
+
{/* Sub-tabs for usable types */}
{currentType === "USABLE_ITEM" && (
{(() => {
- const colors = ['#ef5350','#ab47bc','#5c6bc0','#29b6f6','#66bb6a','#ffca28','#ffa726','#8d6e63'];
- const getColor=(idx:number)=>colors[idx%colors.length];
+ const getColor = () => explorerColor;
return (
setSubTab(v)}
variant="scrollable"
- TabIndicatorProps={{ sx:{ backgroundColor:getColor(subTab)} }}
+ TabIndicatorProps={{ sx:{ backgroundColor:explorerColor } }}
sx={{ bgcolor:'background.paper', mt:1 }}
>
{subTypeValues.map((s,idx)=>{
const label = s === 'ITEM' ? 'MISC' : s.replace('_ITEM','');
- return
+ return
})}
);
diff --git a/frontend/src/pages/Recipes.tsx b/frontend/src/pages/Recipes.tsx
index a63016e..c40b263 100644
--- a/frontend/src/pages/Recipes.tsx
+++ b/frontend/src/pages/Recipes.tsx
@@ -5,6 +5,7 @@ import Tab from "@mui/material/Tab";
import CircularProgress from "@mui/material/CircularProgress";
import ToggleButtonGroup from "@mui/material/ToggleButtonGroup";
import ToggleButton from "@mui/material/ToggleButton";
+import { recipesColor, craftColors } from "../constants/colors";
import { useQuery } from "@tanstack/react-query";
import RecipesDetailTable from "../components/RecipesDetailTable";
import { api } from "../api";
@@ -114,17 +115,15 @@ export default function RecipesPage({ crafts }: Props) {
{/* Craft tabs */}
{(() => {
- const colors = ['#66bb6a','#42a5f5','#ffa726','#ab47bc','#5c6bc0','#ff7043','#8d6e63'];
- const getColor=(idx:number)=>colors[idx%colors.length];
return (
{setCraftIdx(v); setCatIdx(0);} }
variant="scrollable"
- TabIndicatorProps={{ sx:{ backgroundColor:getColor(craftIdx)} }}
+ TabIndicatorProps={{ sx:{ backgroundColor: craftColors[currentCraft] || recipesColor } }}
>
{crafts.map((c,idx)=>(
-
+
))}
);
@@ -145,18 +144,16 @@ export default function RecipesPage({ crafts }: Props) {
{/* Category sub-tabs */}
{(() => {
- const colors = ['#ef5350','#ab47bc','#5c6bc0','#29b6f6','#66bb6a','#ffca28','#ffa726','#8d6e63'];
- const getColor=(idx:number)=>colors[idx%colors.length];
return (
setCatIdx(v)}
variant="scrollable"
- TabIndicatorProps={{ sx:{ backgroundColor:getColor(catIdx)} }}
+ TabIndicatorProps={{ sx:{ backgroundColor:craftColors[currentCraft] || recipesColor } }}
sx={{ bgcolor:'background.paper' }}
>
{categories.map((cat,idx)=>(
-
+
))}
);
diff --git a/frontend/src/setupTests.ts b/frontend/src/setupTests.ts
new file mode 100644
index 0000000..7b0828b
--- /dev/null
+++ b/frontend/src/setupTests.ts
@@ -0,0 +1 @@
+import '@testing-library/jest-dom';
diff --git a/frontend/src/utils/filterExplorerItems.ts b/frontend/src/utils/filterExplorerItems.ts
new file mode 100644
index 0000000..7e29725
--- /dev/null
+++ b/frontend/src/utils/filterExplorerItems.ts
@@ -0,0 +1,39 @@
+import type { GridItem } from '../components/ItemsGrid';
+
+export interface RawItem {
+ id: number;
+ name: string;
+ icon_id?: string;
+ type_description?: string;
+ jobs_description?: string[];
+}
+
+/**
+ * Replicates client-side filtering logic in ItemExplorerPage.
+ * @param data list returned from `/items` API
+ * @param currentType current selected high-level type (e.g. "MISC", "USABLE_ITEM")
+ * @param baseTypes array of base types derived from metadata
+ * @param search current (debounced) search string
+ */
+export function filterExplorerItems(
+ data: RawItem[] | undefined,
+ currentType: string,
+ baseTypes: string[],
+ search: string
+): GridItem[] | undefined {
+ if (!data) return undefined;
+
+ return data
+ .filter((d) => {
+ if (d.name === '.') return false;
+ if (search.trim() !== '') return true; // no extra filter while searching
+ if (currentType !== 'MISC') return true;
+ return !baseTypes.includes(d.type_description ?? '');
+ })
+ .map((d) => ({
+ id: d.id,
+ name: d.name,
+ iconUrl: d.icon_id ? `/api/icon/${d.icon_id}` : undefined,
+ icon_id: d.icon_id,
+ }));
+}
diff --git a/frontend/tsconfig.json b/frontend/tsconfig.json
index 3d0a51a..2233e9c 100644
--- a/frontend/tsconfig.json
+++ b/frontend/tsconfig.json
@@ -14,7 +14,8 @@
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
- "jsx": "react-jsx"
+ "jsx": "react-jsx",
+ "types": ["vitest/globals"]
},
"include": ["src"],
"references": [{ "path": "./tsconfig.node.json" }]
diff --git a/frontend/vitest.config.ts b/frontend/vitest.config.ts
new file mode 100644
index 0000000..3484380
--- /dev/null
+++ b/frontend/vitest.config.ts
@@ -0,0 +1,11 @@
+import { defineConfig } from 'vitest/config';
+import react from '@vitejs/plugin-react';
+
+export default defineConfig({
+ plugins: [react()],
+ test: {
+ environment: 'jsdom',
+ setupFiles: './src/setupTests.ts',
+ globals: true,
+ },
+});
diff --git a/scripts/README.md b/scripts/README.md
index 1b4cb64..54642ae 100644
--- a/scripts/README.md
+++ b/scripts/README.md
@@ -13,7 +13,8 @@ PostgreSQL database.
| **recipes_to_csv_v2.py** | Generic parser. `python recipes_to_csv_v2.py ` processes one craft; use `python recipes_to_csv_v2.py --all` **or simply omit the argument** to parse every `.txt` file under `datasets/`, producing `datasets/_v2.csv` for each. |
| **load_woodworking_to_db.py** | Loader for the legacy CSV (kept for reference). |
| **load_woodworking_v2_to_db.py** | Drops & recreates **recipes_woodworking** table and bulk-loads `Woodworking_v2.csv`. |
-| **load_recipes_v2_to_db.py** | Generic loader. `python load_recipes_v2_to_db.py ` loads one craft; omit the argument to load **all** generated CSVs into their respective `recipes_` tables. |
+| **load_recipes_v2_to_db.py** | Generic loader.
+| **load_inventory_to_db.py** | Truncate & load `datasets/inventory.csv` into the `inventory` table. | `python load_recipes_v2_to_db.py ` loads one craft; omit the argument to load **all** generated CSVs into their respective `recipes_` tables. |
| **requirements.txt** | Minimal Python dependencies for the scripts. |
| **venv/** | Local virtual-environment created by the setup steps below. |
diff --git a/scripts/load_inventory_to_db.py b/scripts/load_inventory_to_db.py
new file mode 100644
index 0000000..aaa7dd7
--- /dev/null
+++ b/scripts/load_inventory_to_db.py
@@ -0,0 +1,163 @@
+#!/usr/bin/env python3
+"""Load datasets/inventory.csv into the **inventory** table, replacing any
+existing contents.
+
+Usage:
+ python load_inventory_to_db.py [CSV_PATH]
+
+If ``CSV_PATH`` is omitted the script defaults to ``datasets/inventory.csv``
+relative to the project root.
+
+This script is similar in style to the other ETL helpers in ``scripts/``. It is
+idempotent – it truncates the ``inventory`` table before bulk-inserting the new
+rows.
+
+The database connection details are read from the standard ``db.conf`` file
+located at the project root. The file must define at least the following keys::
+
+ PSQL_HOST
+ PSQL_PORT
+ PSQL_USER
+ PSQL_PASSWORD
+ PSQL_DBNAME
+
+"""
+from __future__ import annotations
+
+import argparse
+import asyncio
+import csv
+import datetime as _dt
+import pathlib
+import re
+from typing import Dict, List, Tuple
+
+import asyncpg
+
+# ---------------------------------------------------------------------------
+# Paths & Constants
+# ---------------------------------------------------------------------------
+PROJECT_ROOT = pathlib.Path(__file__).resolve().parents[1]
+CONF_PATH = PROJECT_ROOT / "db.conf"
+DEFAULT_CSV_PATH = PROJECT_ROOT / "datasets" / "inventory.csv"
+
+RE_CONF = re.compile(r"^([A-Z0-9_]+)=(.*)$")
+
+# ---------------------------------------------------------------------------
+# Helpers
+# ---------------------------------------------------------------------------
+
+def parse_db_conf(path: pathlib.Path) -> Dict[str, str]:
+ """Parse ``db.conf`` (simple KEY=VALUE format) into a dict."""
+ if not path.exists():
+ raise FileNotFoundError("db.conf not found at project root – required for DB credentials")
+
+ conf: Dict[str, str] = {}
+ for line in path.read_text().splitlines():
+ line = line.strip()
+ if not line or line.startswith("#"):
+ continue
+ if (m := RE_CONF.match(line)):
+ key, value = m.group(1), m.group(2).strip().strip("'\"")
+ conf[key] = value
+
+ required = {"PSQL_HOST", "PSQL_PORT", "PSQL_USER", "PSQL_PASSWORD", "PSQL_DBNAME"}
+ missing = required - conf.keys()
+ if missing:
+ raise RuntimeError(f"Missing keys in db.conf: {', '.join(sorted(missing))}")
+
+ return conf
+
+
+async def ensure_inventory_table(conn: asyncpg.Connection) -> None:
+ """Create the ``inventory`` table if it doesn't already exist.
+
+ The schema mirrors the SQLAlchemy model in ``backend/app/models.py``.
+ """
+ await conn.execute(
+ """
+ CREATE TABLE IF NOT EXISTS inventory (
+ id SERIAL PRIMARY KEY,
+ character_name TEXT NOT NULL,
+ storage_type TEXT NOT NULL,
+ item_name TEXT NOT NULL,
+ quantity INT NOT NULL,
+ last_updated TIMESTAMPTZ DEFAULT NOW()
+ );
+ """
+ )
+
+
+async def truncate_inventory(conn: asyncpg.Connection) -> None:
+ """Remove all rows from the inventory table before re-inserting."""
+ await conn.execute("TRUNCATE TABLE inventory;")
+
+
+async def copy_csv_to_db(conn: asyncpg.Connection, rows: List[Tuple[str, str, str, int]]) -> None:
+ """Bulk copy the parsed CSV rows into the DB using ``copy_records_to_table``."""
+ await conn.copy_records_to_table(
+ "inventory",
+ records=rows,
+ columns=[
+ "character_name",
+ "storage_type",
+ "item_name",
+ "quantity",
+ "last_updated",
+ ],
+ )
+
+
+# ---------------------------------------------------------------------------
+# Main logic
+# ---------------------------------------------------------------------------
+
+async def load_inventory(csv_path: pathlib.Path) -> None:
+ if not csv_path.exists():
+ raise SystemExit(f"CSV file not found: {csv_path}")
+
+ conf = parse_db_conf(CONF_PATH)
+
+ conn = await asyncpg.connect(
+ host=conf["PSQL_HOST"],
+ port=int(conf["PSQL_PORT"]),
+ user=conf["PSQL_USER"],
+ password=conf["PSQL_PASSWORD"],
+ database=conf["PSQL_DBNAME"],
+ )
+ try:
+ await ensure_inventory_table(conn)
+ await truncate_inventory(conn)
+
+ # Parse CSV
+ rows: List[Tuple[str, str, str, int]] = []
+ with csv_path.open(newline="", encoding="utf-8") as f:
+ reader = csv.DictReader(f, delimiter=";", quotechar='"')
+ for r in reader:
+ char = r["char"].strip()
+ storage = r["storage"].strip()
+ item = r["item"].strip()
+ qty = int(r["quantity"].strip()) if r["quantity"].strip() else 0
+ rows.append((char, storage, item, qty, _dt.datetime.utcnow()))
+
+ await copy_csv_to_db(conn, rows)
+ print(f"Inserted {len(rows)} inventory rows.")
+ finally:
+ await conn.close()
+
+
+async def main_async(csv_arg: str | None) -> None:
+ csv_path = pathlib.Path(csv_arg).expanduser().resolve() if csv_arg else DEFAULT_CSV_PATH
+ await load_inventory(csv_path)
+
+
+def main() -> None:
+ p = argparse.ArgumentParser(description="Load inventory CSV into DB")
+ p.add_argument("csv", nargs="?", help="Path to CSV; defaults to datasets/inventory.csv")
+ args = p.parse_args()
+
+ asyncio.run(main_async(args.csv))
+
+
+if __name__ == "__main__":
+ main()