commit 1bd131e9d8431c9a353edbd1f64ceeecca289ae1 Author: amirhosein.ashourloo Date: Wed Sep 2 17:03:23 2026 +0330 first commit diff --git a/.claude/launch.json b/.claude/launch.json new file mode 100644 index 0000000..5596531 --- /dev/null +++ b/.claude/launch.json @@ -0,0 +1,11 @@ +{ + "version": "0.0.1", + "configurations": [ + { + "name": "moalem-dev", + "runtimeExecutable": "npm", + "runtimeArgs": ["run", "dev"], + "port": 3000 + } + ] +} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..96301bd --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +node_modules +.next +out +.env*.local +next-env.d.ts +tsconfig.tsbuildinfo diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..9aa3e90 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,179 @@ +# moalem.shop — context for AI agents and tooling + +Storefront for **فروشگاه موبایل معلم** built on the Meshkee CMS contract. +Keep this file current: every prompt that changes structure, data sources or +fallbacks should update it. + +## Stack + +- Next.js 16.3 (App Router, Turbopack) · React 19 · TypeScript +- Tailwind CSS v4 (`@theme inline` tokens in `src/app/globals.css`) +- Swiper 14 for every slider/carousel +- fa / RTL, flat design, no gradients + +## Data — Meshkee Website API + +- Base: `https://api.meshkee.com/api/v1`, docs: +- Tenant: `moalem.shop` (business id 27, `specialProductsSource: store_item`) +- Every public read goes through `src/lib/api.ts`; wrap calls in `safe()` so an + empty or failing module degrades one section instead of the page. +- Never invent CMS/admin endpoints. If OpenAPI and any prose conflict, OpenAPI wins. + +### What the tenant actually returns today (2026-08-31) + +| Endpoint | State | +|---|---| +| `enabledModules` (`GET /tenants/moalem.shop`) | `["products", "blog", "finance"]` — **`workshops` is not enabled.** Do not build a workshops page/nav entry until it is. | +| `/products` | **479 items**, real titles, images, categories | +| `/categories?entityType=product` | **48 items**, 3 levels under «کالای دیجیتال». Products are filed on **leaf** categories only — a parent category page/carousel must query its whole subtree (`subtreeIds`/`subtreeIdsAll` in `src/lib/categoryTree.ts`). | +| `/store-items`, `/store-specials` | **empty** → no prices, no discounts, everything `inStock:false`. `/store-items/by-product/{id}` returns a single `{ storeItem }` (or null) — for a product's full variant list use `/store-items?productId=&pageSize=100` (`{ items }`) instead, see `getStoreItemsByProduct` in `src/lib/api.ts`. | +| `/website/sliders`, `/website/static-images`, `/website/category-groups` | **empty** | +| `/blogs` | **empty** (module enabled) | +| `/website/business-info`, `/website/favicon` | present but blank (no logo, no phone, no socials) | +| `/products/by-id/{id}/technical-info` | `{ form: null, values: [] }` — empty on every product checked so far | +| `/comments` | **empty**, but `POST` works (no bearer required by the API itself) | + +### Fallbacks in place until the CMS is filled + +| Gap | Fallback | Remove when | +|---|---|---| +| No prices | `src/lib/demoPricing.ts`, gated by `NEXT_PUBLIC_DEMO_PRICING=1` — deterministic price per product id | store-items exist | +| No sliders | `src/app/page.tsx`'s local `FALLBACK_SLIDES` (banners downloaded from the live moalem.shop theme into `public/images/banners/`) — used only when the `slider` static-image slot is also empty, see "Static-image slots" below | `/website/sliders` or the `slider` slot returns items | +| No blog posts | `src/data/fallbackBlogs.ts` — the real posts from moalem.shop/articles, shown on the homepage and `/blog` as non-clickable cards (no real detail page exists for them) | `/blogs` returns items | +| No category art | placeholder SVGs in `public/images/categories/{id}.svg`, generated by `scripts/gen-category-placeholders.mjs` — overridden per-slide by the `categories` static-image slot when it has images | real thumbnails are dropped in, or the `categories` slot is filled | +| No branding in API | logo/favicon copied from the live theme into `public/` | `logoUrl` is set on the tenant | +| No banner art (`one/two/three-banner-bg`) | `src/components/PromoBanners.tsx` renders a flat dashed placeholder per slot cell, labelled with the recommended size/ratio | the matching static-image slot is filled | + +## Brand + +Taken from the live moalem.shop theme, not invented: +primary `#E51E22`, dark `#B10123`, ink `#2E2E2E`, body `#7C7C7C`. +Fonts: IRANYekan (fa, local `public/fonts`) + Montserrat (latin, next/font). + +## Layout conventions + +- Content width `--container-boxed: 1200px`, applied with the `boxed` utility. +- Homepage order: promo strip → header → hero slider → category carousel → + hot deals → product carousels (interleaved with promo banners) → magazine + carousel → footer. Exact interleaving is in "Static-image slots" below. +- Section header (title + one-line desc on the right, «مشاهده همه» pill on the + left) is `src/components/ui/SectionHeader.tsx` — reuse it for every carousel. +- Product card: `ui/ProductCard.tsx`, 8px radius, discount badge top-right, + strikethrough old price above the current one. The price block is a fixed + 44px row and the old-price line is always rendered (empty when there is no + discount) so **every card in a carousel is exactly the same height** — keep + that invariant when adding badges or meta lines. +- Carousel arrows: `ui/CarouselNav.tsx`, passed to `SectionHeader` as `nav` so + they sit beside the «مشاهده همه» pill on desktop; the copy under the track is + `lg:hidden` for thumbs. +- Prices are Toman and rendered with Persian digits (`src/lib/format.ts`). + +## Navigation + +- **Desktop (lg+):** full header — logo, search, account/cart, then the product + mega menu and the main nav. +- **Mobile:** no hamburger. The header carries the search box alone; everything + else lives in `MobileBottomNav.tsx`, a fixed bottom bar with خانه / سبد خرید / + دسته‌بندی‌ها / ورود (or the account menu once signed in). «دسته‌بندی‌ها» opens + a bottom sheet with the level-2 categories and an accordion for their + children. Any new mobile entry point belongs in that bar, not in a drawer. + +## Auth / account menu + +Login itself happens off-site, on the customer portal (`customer.{domain}`) — +this storefront never renders a login form. For the header/bottom-nav account +menu to show "نام عزیز" instead of "ورود | ثبت‌نام", the customer portal needs +to set a **non-HttpOnly cookie on the shared parent domain** (`.{domain}`) +named `AUTH_TOKEN_COOKIE` (`src/lib/config.ts`, currently `"meshkee_token"`) +holding the bearer access token. Until that exists, `getSession()` in +`src/lib/auth.ts` always resolves to `null` and the anonymous view is all +anyone will see — this is expected, not a bug, and nothing else breaks. + +- `src/lib/auth.ts` — **server-only** (`next/headers`): `getSession()` reads + the cookie and calls `GET /auth/me`. +- `src/lib/authUser.ts` — pure helpers (`displayName`, `isAdmin`), safe to + import from client components. **Never import `auth.ts` from a client + component** — it pulls in `next/headers` and breaks the build; that's the + entire reason this file is split out. +- Admin detection (`isAdmin()`) reads `user.isAdmin`/`user.role` defensively — + the OpenAPI schema doesn't document a role field on the user object, so + this is a best guess. Update it once the real shape is confirmed. +- Cart ("افزودن به سبد خرید" in `ProductBuyPanel.tsx`) needs a real + `storeItemVariantId`, which only exists once a product has store-items — + today that's none of them, so the button always shows "این محصول هنوز برای + خرید آنلاین فعال نشده است" regardless of login state. That's correct given + the data, not a bug either. + +## Static-image slots + +Slot catalog: `src/app/meshkee/static-image-slots/route.ts`. Keys and where +each is consumed: + +| Key | Kind | Consumed by | +|---|---|---| +| `slider` | list | `HeroSlider.tsx` — wins over `FALLBACK_SLIDES` when it has images | +| `categories` | list | `CategoryCarousel.tsx` — images matched **positionally** to the category list (business fills them in the same order); a category past the end of the slot keeps its SVG placeholder | +| `one-banner-bg` | single | `PromoBanners.tsx` → `OneBanner` | +| `two-banner-bg` | list (2) | `PromoBanners.tsx` → `TwoBanners` | +| `three-banner-bg` | list (3) | `PromoBanners.tsx` → `ThreeBanners` | + +**Rule:** every `aspectRatio`/`recommendedWidth` in that route file must match +what the component actually renders. If you change a banner's size or ratio, +update its slot entry in the same commit — that file is the only thing +telling the dashboard (and the designer) what to upload. + +Homepage banner placement (mirrors the logilook.com reference given for this +layout): hero → categories → hot deals → **three-banner-bg** → موبایل +carousel → **two-banner-bg** → لوازم جانبی → لپ تاپ → **one-banner-bg** → +کنسول بازی → مجله. + +## Product / blog pages + +- `/products` — full catalog, `?name=` search, `?page=`. +- `/products/category/{id}/{slug}` — category listing; merges every leaf in + the subtree (see the leaf-category note above) and paginates in memory. +- `/products/{id}/{slug}` — detail: `ProductGallery.tsx` (click-to-lightbox + + hover-zoom via a background-position overlay, desktop only), + `ProductBuyPanel.tsx` (variants, price, auth-aware cart), `ProductInfoTabs.tsx` + (توضیحات / مشخصات فنی / دیدگاه کاربران), related products from + `relatedProducts` on the by-id response. +- `/blog`, `/blog/{id}/{slug}` — same shape, no variants/tabs; just content + + `CommentSection.tsx`. Fallback posts (see table above) only exist in the + listing grid, not as real detail pages. +- **Technical specs**: `values[]` from `technical-info` carries no label, only + a `fieldId` — never render it directly. Join against `form.fields` first via + `src/lib/technicalInfo.ts#buildSpecRows`. +- **Comments**: `POST /comments` doesn't require a bearer token at the API + level, but `CommentSection.tsx` gates the form behind `getSession()` per the + page brief — anonymous visitors get a login prompt instead of a textarea. + +## URLs (Meshkee canonical shapes) + +- Product `/products/{id}/{nameFaSlug}` +- Category `/products/category/{id}/{nameFaSlug}` +- Blog `/blog/{id}/{titleSlug}` +- Helpers live in `src/lib/slug.ts` — build links with those, never by hand. + +## Published for the platform + +- `GET /meshkee/static-image-slots` — slot catalog for the dashboard Refresh button +- `GET /meshkee/sitemap-config.json` — static pages only, **auto-scanned from + `src/app` at request time** (`src/app/meshkee/sitemap-config.json/route.ts`) + rather than hand-listed, so it can never advertise a page that doesn't exist + yet. Product/category/blog/portfolio detail pages are excluded on purpose — + Meshkee resolves those straight from the API. +- Do **not** add `app/sitemap.ts` or `app/robots.ts`: nginx proxies + `/sitemap.xml` and `/robots.txt` to the API. + +## SEO rules for every new page + +Unique `` + meta description, exactly one `<h1>`, real `<h2>` structure, +non-empty `alt` on every content image. + +## Scripts + +```bash +npm run dev # localhost:3000 +npm run build +node scripts/gen-category-placeholders.mjs # refresh category thumb placeholders +``` diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..43c994c --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1 @@ +@AGENTS.md diff --git a/README.md b/README.md new file mode 100644 index 0000000..76a44e5 --- /dev/null +++ b/README.md @@ -0,0 +1,18 @@ +# moalem.shop + +فروشگاه اینترنتی موبایل معلم — Next.js storefront on the Meshkee CMS API. + +```bash +npm install +npm run dev +``` + +Environment (`.env.local`): + +``` +NEXT_PUBLIC_SITE_DOMAIN=moalem.shop +NEXT_PUBLIC_DEMO_PRICING=1 # temporary: the tenant has no store-items yet +``` + +Architecture, data gaps and the fallbacks currently in place are documented in +[AGENTS.md](AGENTS.md). diff --git a/banners.psd b/banners.psd new file mode 100644 index 0000000..9f6deea Binary files /dev/null and b/banners.psd differ diff --git a/eslint.config.mjs b/eslint.config.mjs new file mode 100644 index 0000000..05e726d --- /dev/null +++ b/eslint.config.mjs @@ -0,0 +1,18 @@ +import { defineConfig, globalIgnores } from "eslint/config"; +import nextVitals from "eslint-config-next/core-web-vitals"; +import nextTs from "eslint-config-next/typescript"; + +const eslintConfig = defineConfig([ + ...nextVitals, + ...nextTs, + // Override default ignores of eslint-config-next. + globalIgnores([ + // Default ignores of eslint-config-next: + ".next/**", + "out/**", + "build/**", + "next-env.d.ts", + ]), +]); + +export default eslintConfig; diff --git a/next.config.ts b/next.config.ts new file mode 100644 index 0000000..289a129 --- /dev/null +++ b/next.config.ts @@ -0,0 +1,13 @@ +import type { NextConfig } from "next"; + +const nextConfig: NextConfig = { + images: { + remotePatterns: [ + { protocol: "https", hostname: "c804387.parspack.net" }, + { protocol: "https", hostname: "meshkee-storage.sas.amin.parminstorage.ir" }, + { protocol: "https", hostname: "meshkee-templates.sas.amin.parminstorage.ir" }, + ], + }, +}; + +export default nextConfig; diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..00781cb --- /dev/null +++ b/package-lock.json @@ -0,0 +1,6802 @@ +{ + "name": "moalem-shop", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "moalem-shop", + "version": "0.1.0", + "dependencies": { + "next": "16.3.1", + "react": "19.2.8", + "react-dom": "19.2.8", + "swiper": "^14.1.0" + }, + "devDependencies": { + "@tailwindcss/postcss": "^4", + "@types/node": "^20", + "@types/react": "^19", + "@types/react-dom": "^19", + "eslint": "^9", + "eslint-config-next": "16.3.1", + "tailwindcss": "^4", + "typescript": "^5" + } + }, + "node_modules/@alloc/quick-lru": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz", + "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz", + "integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.29.7", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.7.tgz", + "integrity": "sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.7.tgz", + "integrity": "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.7", + "@babel/helper-compilation-targets": "^7.29.7", + "@babel/helper-module-transforms": "^7.29.7", + "@babel/helpers": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/template": "^7.29.7", + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7", + "@jridgewell/remapping": "^2.3.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/generator": { + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.8.tgz", + "integrity": "sha512-gZbepsdh3WDtgZKWL+vTPh71LSBrm/Y4/QDZBVCcYfmeTEEuoOYwlSy+G1StfJg+/Zy550u/3TATbm7qDbbMtg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.8", + "@babel/types": "^7.29.8", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.29.7.tgz", + "integrity": "sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.29.7", + "@babel/helper-validator-option": "^7.29.7", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.29.7.tgz", + "integrity": "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.29.7.tgz", + "integrity": "sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.29.7.tgz", + "integrity": "sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7", + "@babel/traverse": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz", + "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", + "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.29.7.tgz", + "integrity": "sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.7.tgz", + "integrity": "sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.8.tgz", + "integrity": "sha512-E8lTAYNB1KW+FH+VGJuZM1ioAx2E6oVlvQFRrf5P8ZZmsiJXYAD9vTFV7yyEURNzgh1dFqMZuO6tUwcARbqFCA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.8" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/template": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.29.7.tgz", + "integrity": "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.8.tgz", + "integrity": "sha512-I5z7H3bf/41ktsNVLtpN0wAa336HkqIHQ5BuPLEhTkt1jVSyZpeNKIzTgEWmlxjdg81R0IgUCcaE+Ok3NvrfZg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.8", + "@babel/helper-globals": "^7.29.7", + "@babel/parser": "^7.29.8", + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.8", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.8.tgz", + "integrity": "sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@emnapi/core": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.10.0.tgz", + "integrity": "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/wasi-threads": "1.2.1", + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.11.3", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.11.3.tgz", + "integrity": "sha512-Xz4Tpyki7XyrpbUK1jR1AhdAdaXyhhY4lZ3neLodmhpuWfy2PAQN5B46sAiU4liOXGLkHypn/qU+jvfWSCYYLA==", + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/wasi-threads": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz", + "integrity": "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.10.1.tgz", + "integrity": "sha512-cuadcxVFE8sDK6iWJbs8Sn0av2Nrh2QSGQhVlBW9AaAHqHwjWsZHT8LJ4hFGPh7ASBV2deFdM7H/DPjulmh8rg==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", + "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/config-array": { + "version": "0.21.2", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.2.tgz", + "integrity": "sha512-nJl2KGTlrf9GjLimgIru+V/mzgSK0ABCDQRvxw5BjURL7WfH5uoWmizbH7QB6MmnMBd8cIC9uceWnezL1VZWWw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/object-schema": "^2.1.7", + "debug": "^4.3.1", + "minimatch": "^3.1.5" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/config-helpers": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.2.tgz", + "integrity": "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.17.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/core": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz", + "integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.6.tgz", + "integrity": "sha512-l2Ul9PrHsPCKcEY/ac7VgFj9D80C7S68sOKc618SyHDPK36s1XcFebXY0iTzUVn4Yq+YbwvSnDmCz9yxjX+QrA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^6.14.0", + "debug": "^4.3.2", + "espree": "^10.0.1", + "globals": "^14.0.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.3.0", + "minimatch": "^3.1.5", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/js": { + "version": "9.39.5", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.5.tgz", + "integrity": "sha512-QywQuszQh77pIXCsq998c8hbhSTI/azTty1Z6N53dmAudKHhy573j3yvRLsX2BSp8YpLtoCEG8E9DJe+8zUh4A==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + } + }, + "node_modules/@eslint/object-schema": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.7.tgz", + "integrity": "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.1.tgz", + "integrity": "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.17.0", + "levn": "^0.4.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@humanfs/core": { + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.2.tgz", + "integrity": "sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/types": "^0.15.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node": { + "version": "0.16.8", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.8.tgz", + "integrity": "sha512-gE1eQNZ3R++kTzFUpdGlpmy8kDZD/MLyHqDwqjkVQI0JMdI1D51sy1H958PNXYkM2rAac7e5/CnIKZrHtPh3BQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/core": "^0.19.2", + "@humanfs/types": "^0.15.0", + "@humanwhocodes/retry": "^0.4.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/types": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/@humanfs/types/-/types-0.15.0.tgz", + "integrity": "sha512-ZZ1w0aoQkwuUuC7Yf+7sdeaNfqQiiLcSRbfI08oAxqLtpXQr9AIVX7Ay7HLDuiLYAaFPu8oBYNq/QIi9URHJ3Q==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/retry": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@img/colour": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@img/colour/-/colour-1.1.0.tgz", + "integrity": "sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@img/sharp-darwin-arm64": { + "version": "0.35.4", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.35.4.tgz", + "integrity": "sha512-Uhfl4V4lhP2nbUVF9+hyH1+luj86f1gUFeo8ALYxFoULoU+G87D43BfeMP8XHsk9boxAnCY/bf2EHwhA7MuGsA==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-arm64": "1.3.3" + } + }, + "node_modules/@img/sharp-darwin-x64": { + "version": "0.35.4", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.35.4.tgz", + "integrity": "sha512-hWniXY3bG5qKpkKrAwPe4y+VTPmf086YQAnkxWh7uA1YrlRouWGa0M0Mxj3ZjnXFkv7/TD1bTy9lGUK26vRvWw==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-x64": "1.3.3" + } + }, + "node_modules/@img/sharp-freebsd-wasm32": { + "version": "0.35.4", + "resolved": "https://registry.npmjs.org/@img/sharp-freebsd-wasm32/-/sharp-freebsd-wasm32-0.35.4.tgz", + "integrity": "sha512-lIsKw/BU+kjB4eZjxrYrZmwOJYi3Ajrv66iAlBmUPyKc3HpnloevB1g3wxGD9P/5BbQ1brBGl65VRRrCvQDEqA==", + "license": "Apache-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "dependencies": { + "@img/sharp-wasm32": "0.35.4" + }, + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-darwin-arm64": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.3.3.tgz", + "integrity": "sha512-suTBPTDGrI9WodccaDdwZItTSaBYASlBk1NSfElSHrUfzu3szG6lvIF58+WiFvnfzuK8ZBFS5zE00PxqxnRiPg==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-darwin-x64": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.3.3.tgz", + "integrity": "sha512-FVJZ5mITMobmXIz/hPDTw0EintTW5H3WfrxwLqEqjiIihlu+hVRyGrFQ60xl0Lxn7Bt3zdpevPaQi0HEzqz9fw==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.3.3.tgz", + "integrity": "sha512-3rbU4vqXXc3hY/OiXdl52xZvT0F1yEngWfvqudtPJg/KkyiaQw2DRsFrNzpmLvfavbwOq3qXn36GP8obHRULQA==", + "cpu": [ + "arm" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm64": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.3.3.tgz", + "integrity": "sha512-0DaL0A6Xu6sQSQFwe4iVCrKWU2cCTItnRsYsCdxAMm9NF6twAA9BKnoqy4hqz4+azQ0JHuA26qiUKsf1XJ/v5A==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-ppc64": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.3.3.tgz", + "integrity": "sha512-cdn1OvUBwsXhbC0zSzJnNzf5MZ/mTrobawDvNXBTxe8VtqKAm0sRuEY2Evzovb/w9JMk4TvRxqt1mekSuJz64w==", + "cpu": [ + "ppc64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-riscv64": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-riscv64/-/sharp-libvips-linux-riscv64-1.3.3.tgz", + "integrity": "sha512-HjPVx7yKz+0lqdhDlTw1tt90wamBoxhiXpvl1XZpJLiHH4RCJ5yDTqH+VlYPv2fwFs89JFw4c1IexYOcQUi4IQ==", + "cpu": [ + "riscv64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-s390x": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.3.3.tgz", + "integrity": "sha512-neWLh+3yCNThxnfy3c4BbVBeGgt9aftno+XbT56iK28RgeDs3UOFWviLWlUu0bArYVYJaFDK+RRohbicUNCm8Q==", + "cpu": [ + "s390x" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-x64": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.3.3.tgz", + "integrity": "sha512-4vKmvAst9nrowcqquKFAyZJUDolUaIp8uRiN0mWFguJ1IplC9/pitXtlnnlU4aa/eJw3J7i67V+pwUL+wZGdsA==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-arm64": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.3.3.tgz", + "integrity": "sha512-Y9kQaLMuNoB0bPYOOdcZMaseNrFpPodIWWMrx+CZyydf2xn68j9WYc6sWWRrDwNkzCQjKYfc68L7jKjGlHMibw==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-x64": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.3.3.tgz", + "integrity": "sha512-fj8Mv0HHfD1Rr+4I68+3agJynxDWtBFgicTbSOb9Bke6pIwzGcJ+RX/yHjmiEGFMCavY/dxvem7MyNaJF+wDiw==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-linux-arm": { + "version": "0.35.4", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.35.4.tgz", + "integrity": "sha512-7OAS8gI0EReKGVN2HssHlM6umJgxF5VI3xN0p9FA91p/YO+ou5hiNghLdZ5BEHztwaaK5+bLKRf8x/o2L2nk9A==", + "cpu": [ + "arm" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm": "1.3.3" + } + }, + "node_modules/@img/sharp-linux-arm64": { + "version": "0.35.4", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.35.4.tgz", + "integrity": "sha512-De4jpEnAU8Hd5oT0j1G3uL4ZvTuipVMn7YC6vPaJhy6/7EwEae0SVAoBrUMYQbkLGDm85taVWwuPc1a44LTzCQ==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm64": "1.3.3" + } + }, + "node_modules/@img/sharp-linux-ppc64": { + "version": "0.35.4", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.35.4.tgz", + "integrity": "sha512-2oYZJeIl4kCcMGk4ouZVjnkCtFrpQFlNEtJ6GbxzhHQchwH0NH/qEb9ykmOl29dqwMq+JhFdZn+1ak2FKhI9fQ==", + "cpu": [ + "ppc64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-ppc64": "1.3.3" + } + }, + "node_modules/@img/sharp-linux-riscv64": { + "version": "0.35.4", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-riscv64/-/sharp-linux-riscv64-0.35.4.tgz", + "integrity": "sha512-cPbNChoRURAWdebDIHSenxRpgEdy7JkPydSnUxRm9VvKD7m0/xVaR/8Fzlu81pk5nHEvHH87UZUA7cTtwnbJSA==", + "cpu": [ + "riscv64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-riscv64": "1.3.3" + } + }, + "node_modules/@img/sharp-linux-s390x": { + "version": "0.35.4", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.35.4.tgz", + "integrity": "sha512-RY0JFY8Fd6RonCBtHz+DvadaPkXDSI1AUn6yWL9TipqkZ1vY8w8evqdgyDFnkm4/K1ve1TvZiaePP5oSd4+WVQ==", + "cpu": [ + "s390x" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-s390x": "1.3.3" + } + }, + "node_modules/@img/sharp-linux-x64": { + "version": "0.35.4", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.35.4.tgz", + "integrity": "sha512-9qvvEAuk8k89TfWUoX2htWjbAMX8p+NxCppjpcg5k6xMsjhBQPTsoIh36h9Qde4WRuGpJeYnOjdosDn/cnv+OA==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-x64": "1.3.3" + } + }, + "node_modules/@img/sharp-linuxmusl-arm64": { + "version": "0.35.4", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.35.4.tgz", + "integrity": "sha512-KB5jxpfWQTr0nc3xdHtWChdbifHrBGsd2SM62Eyxrl8afikm+f5qGBU75SJIZBT/S1MC8XyacdlXBMSWq6OURA==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-arm64": "1.3.3" + } + }, + "node_modules/@img/sharp-linuxmusl-x64": { + "version": "0.35.4", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.35.4.tgz", + "integrity": "sha512-f+eZJZIQNEEd26RPSW+76chwOf1XtA2Y/O+5ocVyLliHkeih3e+jhLVBdNTd2rS3IbNXK8+ug93Vf5ZXtF5Lxg==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-x64": "1.3.3" + } + }, + "node_modules/@img/sharp-wasm32": { + "version": "0.35.4", + "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.35.4.tgz", + "integrity": "sha512-zQnl4Kwp7Q6NHsENtU2T/00Zi+w3AQNwz3+UaTyVBy2FpXrzXzGjndpK61onhZjRtRpQXxCTeqw19bVyXOh7jA==", + "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT", + "optional": true, + "dependencies": { + "@emnapi/runtime": "^1.11.3" + }, + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-webcontainers-wasm32": { + "version": "0.35.4", + "resolved": "https://registry.npmjs.org/@img/sharp-webcontainers-wasm32/-/sharp-webcontainers-wasm32-0.35.4.tgz", + "integrity": "sha512-ESfNkywmCfPNyaZjxooddJQiQ+l/nTpGEOGthxiLnIHXC/CmcBixnfwUleX9mCz9ovrUUvKMap/pm8RYbzfwaA==", + "cpu": [ + "wasm32" + ], + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@img/sharp-wasm32": "0.35.4" + }, + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-arm64": { + "version": "0.35.4", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.35.4.tgz", + "integrity": "sha512-iNdlBX9gLVvqe2I3uIJSIKTq6wckP/DYxZtcqxm09x5Gi24DnFBmPAWZmr60ZyYMG0xlzo6goG3670ar+RXvRw==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-ia32": { + "version": "0.35.4", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.35.4.tgz", + "integrity": "sha512-kqRsbaa5CS6KHlpxnN7WhE6vAAugXyZButpRdvDWetlv6Qv4N9WTcrWzF7tXfB9T7MsoadqdI8hmwLq6UlLvtw==", + "cpu": [ + "ia32" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-x64": { + "version": "0.35.4", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.35.4.tgz", + "integrity": "sha512-XtmnYhBcrORsJ4XJngyzr/EWP0hRZLAZRFaApdKuviyqF78+ylxh2y06ZmtULAMOnObJ3ucpN0AcwSWnMowTRg==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.6.0.tgz", + "integrity": "sha512-T7jf+5zgsZHwNJ4lvQ7/aezbyk0nNX+zJVWpmHA7VYsEx7a7qr5Rg5IbtJFqkgze5Y2sruq1RUY8Q837Od7iFw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@napi-rs/wasm-runtime": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.2.3.tgz", + "integrity": "sha512-UMduMbqO5s5zF2NkNacMT/yK5Y5QiKvWr2+50bzIIxFDwVJ2h49b+oyjaCGPhJxd2/gC2x39EHv/gHVuu36x2Q==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@tybys/wasm-util": "^0.10.3" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=23.5.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + }, + "peerDependencies": { + "@emnapi/core": "^1.7.1 || ^2.0.0-alpha.4", + "@emnapi/runtime": "^1.7.1 || ^2.0.0-alpha.4" + } + }, + "node_modules/@next/env": { + "version": "16.3.1", + "resolved": "https://registry.npmjs.org/@next/env/-/env-16.3.1.tgz", + "integrity": "sha512-35G3xwkQUb2oETSDjFXGrVugknoayLFBh7vSE+yAcl9IP2zT9wyGwq7297AYHR11kJld807t5f8AJBs6WBzXsQ==", + "license": "MIT" + }, + "node_modules/@next/eslint-plugin-next": { + "version": "16.3.1", + "resolved": "https://registry.npmjs.org/@next/eslint-plugin-next/-/eslint-plugin-next-16.3.1.tgz", + "integrity": "sha512-B4SznlXwVpaLDa7Tbi6zLuueria2d/PmFDhXyDymPGrk2r1n/RMJmcn5FZq1L64k+Jsyte1lKvOr7lP9Xo80mQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "4.9.1", + "fast-glob": "3.3.1" + } + }, + "node_modules/@next/eslint-plugin-next/node_modules/@eslint-community/eslint-utils": { + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", + "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@next/eslint-plugin-next/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@next/swc-darwin-arm64": { + "version": "16.3.1", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-16.3.1.tgz", + "integrity": "sha512-ABMIu2zQ7cnNIHm5ivKGwZwUrm0pAai3yiJ/gK/rF1c1VP9UOnj7XECbMKFdVKp9I9eMYq9NoDs1WXOoowxzJw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-darwin-x64": { + "version": "16.3.1", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-16.3.1.tgz", + "integrity": "sha512-gNG21e/UnrroeScbY/QndUEdl0mF1FRibW7BBeYUz/5ABCepjqDdEdgr592vpzMtCn/m7FTjYq3TN4TpyDnutw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-arm64-gnu": { + "version": "16.3.1", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-16.3.1.tgz", + "integrity": "sha512-6B6Lw016iwNUQuaJoraMMTLh6TwHzFUtxipSScD1F3YyymcrRWkobodRS2ftIOkF5vrs4zNlyUrTC5YZQ9Lz5w==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-arm64-musl": { + "version": "16.3.1", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-16.3.1.tgz", + "integrity": "sha512-JUiPXZKK9wOhjf4MgDiH29GZLxfqOesbLtHq2pDxwH/WwscTRV2ToymnOTh1egzaZf0ueUf8T2+CeYTGHjW0Iw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-x64-gnu": { + "version": "16.3.1", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-16.3.1.tgz", + "integrity": "sha512-Uog9jsrmIRIL/lfvIp9htmskSNC7JcQsMVucXL2V2YY1y/D9IUN3LPEafqy0zRJ2cIU1SQ0V6F6TlffQ+pLAGg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-x64-musl": { + "version": "16.3.1", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-16.3.1.tgz", + "integrity": "sha512-6yy3FT13KgUFOj5H8bl8w/6nKiJwHIvbtwh1V+1acsu+7y4tJjnemSa6mhsh53BeoVrlozE+fMgZhXH46WmjMA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-win32-arm64-msvc": { + "version": "16.3.1", + "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-16.3.1.tgz", + "integrity": "sha512-iOoN1QecUoGNZik536U/vtK43YwgyrCsGIkth52yIkl612n+0C9MjSnJbQAikISpb+WYRooBVhaDlUW7iZoKog==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-win32-x64-msvc": { + "version": "16.3.1", + "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-16.3.1.tgz", + "integrity": "sha512-d/k+PpAriUPaeMJJOG7HUSdqfEX46FEPWU1p3/nm2ACmXhj9hFEWdFODUBIpkuijXYkfL90qZzTqVPRp4BW/hw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nolyfill/is-core-module": { + "version": "1.0.39", + "resolved": "https://registry.npmjs.org/@nolyfill/is-core-module/-/is-core-module-1.0.39.tgz", + "integrity": "sha512-nn5ozdjYQpUCZlWGuxcJY/KpxkWQs4DcbMCmKojjyrYDEAGy4Ce19NN4v5MduafTwJlbKc99UA8YhSVqq9yPZA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.4.0" + } + }, + "node_modules/@rtsao/scc": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz", + "integrity": "sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==", + "dev": true, + "license": "MIT" + }, + "node_modules/@swc/helpers": { + "version": "0.5.23", + "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.23.tgz", + "integrity": "sha512-5lSsMOTXURePglDfvuAQUqkGek9Hg2kksOYay2m0+XR++b2NWYL/4sWyuvVBIs8oKnJaxkdi9whaL/sqN13afw==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.8.0" + } + }, + "node_modules/@tailwindcss/node": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.3.3.tgz", + "integrity": "sha512-/T8IKEsf9VTU6tLjgC7+sv2mOPtQxzE2jMw7u4Tt40Tx+QSZxpzh95/H6cMKoja9XuW7iMdLJYBB0o9G1CaAgg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/remapping": "^2.3.5", + "enhanced-resolve": "^5.24.1", + "jiti": "^2.7.0", + "lightningcss": "1.32.0", + "magic-string": "^0.30.21", + "source-map-js": "^1.2.1", + "tailwindcss": "4.3.3" + } + }, + "node_modules/@tailwindcss/oxide": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.3.3.tgz", + "integrity": "sha512-krXjAikiaFSPaK/FkAQT5UTx3VormQaiZ5hBFlJZ9UFQGB/rwg1MZIhHAG9smMQRTdyJxP6Qt5MwMtdyU5FWrA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 20" + }, + "optionalDependencies": { + "@tailwindcss/oxide-android-arm64": "4.3.3", + "@tailwindcss/oxide-darwin-arm64": "4.3.3", + "@tailwindcss/oxide-darwin-x64": "4.3.3", + "@tailwindcss/oxide-freebsd-x64": "4.3.3", + "@tailwindcss/oxide-linux-arm-gnueabihf": "4.3.3", + "@tailwindcss/oxide-linux-arm64-gnu": "4.3.3", + "@tailwindcss/oxide-linux-arm64-musl": "4.3.3", + "@tailwindcss/oxide-linux-x64-gnu": "4.3.3", + "@tailwindcss/oxide-linux-x64-musl": "4.3.3", + "@tailwindcss/oxide-wasm32-wasi": "4.3.3", + "@tailwindcss/oxide-win32-arm64-msvc": "4.3.3", + "@tailwindcss/oxide-win32-x64-msvc": "4.3.3" + } + }, + "node_modules/@tailwindcss/oxide-android-arm64": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.3.3.tgz", + "integrity": "sha512-Y85A2gmPSkl5Ve5qR86GL4HT509cFqQh1aes9p3sSkyTPwt0Pppf3GkwGe4JPACcRYjgJIEhQgM6dBClnr0NYw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-darwin-arm64": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.3.3.tgz", + "integrity": "sha512-BiaWatpBcERQFDlOjRDpIVXuFK5PJez5SA4JMg6VYZdBYU+qKfV/vqjcIs+IYmtitf1xYQZTwXvU/8y4lfZUGw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-darwin-x64": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.3.3.tgz", + "integrity": "sha512-fAeUqfV5ndhxRwai8cXGzdLvul9utWOmeTkv69unv4ZXixjn61Z+p9lCWdwOwA3TYboG3BwdVuN/RDjhBRl0mw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-freebsd-x64": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.3.3.tgz", + "integrity": "sha512-iyf5bV6+wnAlflVeEy7R25dupxTNECZN5QMI0qNT6eT+EgaGdZcKhGkr5SdoaWiLJ3spLqIY9VCeSGrwmtg4kw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.3.3.tgz", + "integrity": "sha512-aAYUprJAJQWWbRrPvtjdroZ56Md+JM8pMiopS6xGEwDfLhqj+2ver2p4nU4Mb3CRqcMmNBjo8KkUgcxhkzVQGQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm64-gnu": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.3.3.tgz", + "integrity": "sha512-nDxldcEENOxZRzC2uu9jrutZdAAQtb+8WWDCSnWL1zvBk1+FN+x6MtDViPB5AJMfttVCUhehGWus3XBPgatM/w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm64-musl": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.3.3.tgz", + "integrity": "sha512-Md44bD6veX/PC5iyF8cDVnw4HBIANZepRZZ7a8DQOvkfo5WUBwcp6iAuCUz23u+4SUkhJlD3eL7hNdW8ezd/kA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-x64-gnu": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.3.3.tgz", + "integrity": "sha512-tx7us1muwOKAKWao2v/GaafFeQboE6aj88vC6ziN2NCGcRm8gWUhwjzg+YdVB1e4boAtdtma4L43onunI6NS4w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-x64-musl": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.3.3.tgz", + "integrity": "sha512-SJxX60smvHgasZoBy11dX6YRjXJFovwWBoedhbQPOBzgFWBHGB+TVPWB9BxzR7TTxU8FQZAI2AyiNCMzFm8Img==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-wasm32-wasi": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.3.3.tgz", + "integrity": "sha512-jx1+rPhY/5Ympkktd656HBWEBLxP7dH06losBLjjf5vgCODXvi9KhtftWcMIwTFIDqBr7cRnQkdLnAG+IOlGvQ==", + "bundleDependencies": [ + "@napi-rs/wasm-runtime", + "@emnapi/core", + "@emnapi/runtime", + "@tybys/wasm-util", + "@emnapi/wasi-threads", + "tslib" + ], + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "^1.11.1", + "@emnapi/runtime": "^1.11.1", + "@emnapi/wasi-threads": "^1.2.2", + "@napi-rs/wasm-runtime": "^1.1.4", + "@tybys/wasm-util": "^0.10.2", + "tslib": "^2.8.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@tailwindcss/oxide-win32-arm64-msvc": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.3.3.tgz", + "integrity": "sha512-3rc292Ca2ceK6Ulcc/bAVnTs/3nDtoPhyEKlgPv+yQJQi/JS/AMJlqzxvlDacL1nekbrcf6bTqp/jV4qgnPxNQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-win32-x64-msvc": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.3.3.tgz", + "integrity": "sha512-yJ0pwIVc/nYeGoV02WtsN8KYyLQv7kyI2wDnkezyJlGGjkd4QLwDGAwl47YpPJeuI0M0ObaXGSPjvWDPeTPggw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/postcss": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/postcss/-/postcss-4.3.3.tgz", + "integrity": "sha512-JTSZZGQi1AyKirbLN3azmjVzef92tcX7h+iSqPdaeStyFpGpDlKvvpxeOE8njhbUanbRwr3z8DyzhICWnMtQeg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@alloc/quick-lru": "^5.2.0", + "@tailwindcss/node": "4.3.3", + "@tailwindcss/oxide": "4.3.3", + "postcss": "^8.5.16", + "tailwindcss": "4.3.3" + } + }, + "node_modules/@tybys/wasm-util": { + "version": "0.10.3", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.3.tgz", + "integrity": "sha512-F3fo1MYrRJYL3zER0OUOmkutjr1Vp23m7OsSgp7nq4SP6OqX6C/56XFIPAl5bt3zaBRjmW7SGz3u/6LwFpYcOg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@types/estree": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/json5": { + "version": "0.0.29", + "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", + "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "20.19.43", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.43.tgz", + "integrity": "sha512-6oYBAi5ikg4Pl+kGsoYtawUMBT2zZMCvPNF7pVLnHZfd1zf38DRiWn/gT01RYCdUqkv7Fhr+C9ot4/tb+2sVvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/@types/react": { + "version": "19.2.18", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.18.tgz", + "integrity": "sha512-AnzbBERsrLKtk2XSfTbYRLjQPdy116Sty4q+T+Bp3IC4l6jNBvreVPAHmpq9qhXQM7CXZPjLVmGMw9sy+hxQ3w==", + "dev": true, + "license": "MIT", + "dependencies": { + "csstype": "^3.2.2" + } + }, + "node_modules/@types/react-dom": { + "version": "19.2.5", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.5.tgz", + "integrity": "sha512-fMPwH9v7r/pp43yUd2/Mbiex5KouJwwR3dzHkhLREUC6764VyDsqxhAxv6OFEYR1RhjOyD1naqba8ECDBe7ZQg==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "^19.2.0" + } + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "8.68.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.68.0.tgz", + "integrity": "sha512-WASHDpCm6qO5jj9g1a+8NiW5+GCkAyLReR56/4VruYmNgfUmqpxOfZ2Yfb8xGfJPWv5Qi6LSD8sXdces3vbp/Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.12.2", + "@typescript-eslint/scope-manager": "8.68.0", + "@typescript-eslint/type-utils": "8.68.0", + "@typescript-eslint/utils": "8.68.0", + "@typescript-eslint/visitor-keys": "8.68.0", + "ignore": "^7.0.5", + "natural-compare": "^1.4.0", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^8.68.0", + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.6.tgz", + "integrity": "sha512-BAg6QkE8W+TuQLrrw0Ugr7HegXduRuuj8/ti2kSOc+jz1dmx8/WNcjr6XGnq5YpDWxFwwaavqD0+jIUOKelTsw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "8.68.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.68.0.tgz", + "integrity": "sha512-fHq2VC1kpyYfvEcbiMjOpySY4WS7voEp89yAThrHRX5sm9j2lzYppCb2umFMEed4fWcyeLjHxrz0mpjNBaBxMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/scope-manager": "8.68.0", + "@typescript-eslint/types": "8.68.0", + "@typescript-eslint/typescript-estree": "8.68.0", + "@typescript-eslint/visitor-keys": "8.68.0", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/project-service": { + "version": "8.68.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.68.0.tgz", + "integrity": "sha512-5GQtWZCXFcFYux955pvoS02WLc49pXNlvIxocKjS0clvwo3in1RdlzVKyiqQH9vE5AKWFLTaUgeQkOrTS+0Qxw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/tsconfig-utils": "^8.68.0", + "@typescript-eslint/types": "^8.68.0", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "8.68.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.68.0.tgz", + "integrity": "sha512-T5eXpcaJNg8bhjHJ8Rjp68Vq/QBteYtTKY8TZqVNPaUbuz0f6jI9t6aDkylwvalpAB9XTTFeFOjrjXAZ3YvmVA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.68.0", + "@typescript-eslint/visitor-keys": "8.68.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/tsconfig-utils": { + "version": "8.68.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.68.0.tgz", + "integrity": "sha512-F7zrGQfiJHojPwi8vhxZQC1tWtJzvL74cK/nqri2lk8YUXvYaYwl263xOJ69jDWPUk1hmcdoayFwk9lX09npVw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "8.68.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.68.0.tgz", + "integrity": "sha512-X77zqoY1EjeWGs/0JNxeaMfp5C5lIz4Tw8y66F1Ne8Faq6g424sBNYM6xBAqElfGZPLpWS+CZAp0DXyKDzWiHg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.68.0", + "@typescript-eslint/typescript-estree": "8.68.0", + "@typescript-eslint/utils": "8.68.0", + "debug": "^4.4.3", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/types": { + "version": "8.68.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.68.0.tgz", + "integrity": "sha512-9RnpsGJjrAllCMefGVVsImJM24YurhC0Q1h4UbvivtvOqXmR/vEJge2OoE++z9m6hyg8T1Q8t5SNT6tHSbrxcg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "8.68.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.68.0.tgz", + "integrity": "sha512-OKKsD0tYmoNiU5PW2zehO1yO56jYOm1ShYlxon/Z0SJNidAkdVg86eg9ruRuoXf8xfnuWZGbwDsStkoXbZtIIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/project-service": "8.68.0", + "@typescript-eslint/tsconfig-utils": "8.68.0", + "@typescript-eslint/types": "8.68.0", + "@typescript-eslint/visitor-keys": "8.68.0", + "debug": "^4.4.3", + "minimatch": "^10.2.2", + "semver": "^7.7.3", + "tinyglobby": "^0.2.15", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { + "version": "5.0.9", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.9.tgz", + "integrity": "sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { + "version": "10.2.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.6.tgz", + "integrity": "sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.8" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "8.68.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.68.0.tgz", + "integrity": "sha512-PB5gJMMOg0Q5P1tsgWtEAqQacJXq0qEqRHDX/YJ4FaTMLfZPpHB3gjl2EJuiZyPABxmj4ZQYiY9m1bdAJ5y7tQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.9.1", + "@typescript-eslint/scope-manager": "8.68.0", + "@typescript-eslint/types": "8.68.0", + "@typescript-eslint/typescript-estree": "8.68.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "8.68.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.68.0.tgz", + "integrity": "sha512-YR65gGdGvTUAWLldC3xLOvOzamdGzB4A5/N8rehEaHs3Zvoe39BhgY+u0SPch1OvrVTfLcc55wsSgK2NcnTS/A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.68.0", + "eslint-visitor-keys": "^5.0.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", + "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@unrs/resolver-binding-android-arm-eabi": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm-eabi/-/resolver-binding-android-arm-eabi-1.12.2.tgz", + "integrity": "sha512-g5T90pqg1bo/7mytQx6F4iBNC0Wsh9cu+z9veDbFjc7HjpesJFWD7QMS0NGStXM075+7dJPPVvBbpZlnrdpi/w==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@unrs/resolver-binding-android-arm64": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm64/-/resolver-binding-android-arm64-1.12.2.tgz", + "integrity": "sha512-YGCRZv/9GLhwmz6mYDeTsm/92BAyR28l6c2ReweVW5pWgfsitWLY8upvfRlGdoyD8HjeTHSYJWyZGD4KJA/nFQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@unrs/resolver-binding-darwin-arm64": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-arm64/-/resolver-binding-darwin-arm64-1.12.2.tgz", + "integrity": "sha512-u9DiNT1auQMO20A9SyTuG3wUgQWB9Z7KjAg0uFuCDR1FsAY8A0CG2S6JpHS1xwm/w1G08bjXZDcyOCjv1WAm2w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@unrs/resolver-binding-darwin-x64": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-x64/-/resolver-binding-darwin-x64-1.12.2.tgz", + "integrity": "sha512-f7rPLi/T1HVKZu/u6t87lroib16n8vrSzcyxI7lg4BGO9UF26KhQL44sd9eOUgrTYhvRXtWOIZT5PejdPyJfUA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@unrs/resolver-binding-freebsd-x64": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-freebsd-x64/-/resolver-binding-freebsd-x64-1.12.2.tgz", + "integrity": "sha512-BpcOjWCJub6nRZUS2zA20pmLvjtqAtGejETaIyRLiZiQf++cbrjltLA5NN/xaXfqeOBOSlMFbemIl5/S5tljmg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm-gnueabihf": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-gnueabihf/-/resolver-binding-linux-arm-gnueabihf-1.12.2.tgz", + "integrity": "sha512-vZTDvdSISZjJx66OzJqtsOhzifbqRjbmI1Mnu49fQDwog5GtDI4QidRiEAYbZCRj9C8YZEW+3ZjqsyS9GR4k2A==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm-musleabihf": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-musleabihf/-/resolver-binding-linux-arm-musleabihf-1.12.2.tgz", + "integrity": "sha512-BiPI+IrIlwcW4nLLMM21+B1dFPzd55yAVgVGrdgDjNef+ch03GdxrcyaIz8X9SsQirh/kCQ7mviyWlMxdh2D7g==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm64-gnu": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-gnu/-/resolver-binding-linux-arm64-gnu-1.12.2.tgz", + "integrity": "sha512-zJc0H99FEPoFfSrNpa91HYfxzfAJCr502oxNK1cfdC9hlaFI43RT+JFCann9JUgZmLzzntChHyn13Sgn9ljHNg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm64-musl": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-musl/-/resolver-binding-linux-arm64-musl-1.12.2.tgz", + "integrity": "sha512-KQ3Lki6l+Pz1k/eBipN41ES+YUK30beLGb9YqcB1O542cyLCNE6GaxrfcY3T6EezmGGk84wb5XyO9loTM9tkcA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-loong64-gnu": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-loong64-gnu/-/resolver-binding-linux-loong64-gnu-1.12.2.tgz", + "integrity": "sha512-3SJGEh1DborhG6pyxvhPzCT4bbSIVihsvgJc13P1bHG7KLdNDaF9T3gsTwFc7Jw/5Y5/iWOjkEx7Zy0NvCGX3Q==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-loong64-musl": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-loong64-musl/-/resolver-binding-linux-loong64-musl-1.12.2.tgz", + "integrity": "sha512-jiuG/Obbel7uw1PwHNFfrkiKhLAF6mnyZ6aWlOAVN9WqKm8v0OFGnciJIHu8+CMvXLQ8AD51LPzAoUfT21D5Ew==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-ppc64-gnu": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-ppc64-gnu/-/resolver-binding-linux-ppc64-gnu-1.12.2.tgz", + "integrity": "sha512-q7xRvVpmcfeL+LlZg8Pbbo6QaTZwDU5BaGZbwfhkEsXJn3Was8xYfE0RBH266xZt0rM6B7i8xAYIvjthuUIWHg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-riscv64-gnu": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-gnu/-/resolver-binding-linux-riscv64-gnu-1.12.2.tgz", + "integrity": "sha512-0CVdx6lcnT3Q9inOH8tsMIOJ6ImndllMjqJHg8RLVdB7Vq4SfkEXl9mCSsVNuNA4MCYycRicCUxPCabVHJRr6A==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-riscv64-musl": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-musl/-/resolver-binding-linux-riscv64-musl-1.12.2.tgz", + "integrity": "sha512-iOwlRo9vnp6R6ohHQS11n0NnfdXx/omhkocmIfaPRpQhKZ+3BDMkkdRVh53qjkFkpPddf+FETA28NwGN7l5l+w==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-s390x-gnu": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-s390x-gnu/-/resolver-binding-linux-s390x-gnu-1.12.2.tgz", + "integrity": "sha512-HYJtLfXq94q8iZNFT1lknx258wlkkWhZeUXJRqzKBBUJ00CvZ+N33zgbCqimLjsyw5Va6uUxhVa12mI+kaveEw==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-x64-gnu": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-gnu/-/resolver-binding-linux-x64-gnu-1.12.2.tgz", + "integrity": "sha512-mPsUhunKKDih5O96Y6enDQyHc1SqBPlY1E/SfMWDM3EdJ95Z9CArPeCVwCCqbP45ljvivdEk8Fxn+SIb1rDAJQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-x64-musl": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-musl/-/resolver-binding-linux-x64-musl-1.12.2.tgz", + "integrity": "sha512-azrt6+5ydLd8Vt210AAFis/lZevSfPw93EJRIJG+xPu4WCJ8K0kppCTpMyLPcKT7H15M4Jnt2tMp5bOvCkRC6A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-openharmony-arm64": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-openharmony-arm64/-/resolver-binding-openharmony-arm64-1.12.2.tgz", + "integrity": "sha512-YZ9hP4O0X9PQb8eO980qmLNGH4zT3I9+SZTdt0Pr0YyuGQhYKoOZkV02VzrzyOZJ5xIJ3UFIenKkUkGg8GjgWQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@unrs/resolver-binding-wasm32-wasi": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-wasm32-wasi/-/resolver-binding-wasm32-wasi-1.12.2.tgz", + "integrity": "sha512-tYFDIkMxSflfEc/h92ZWNsZlHSwgimbNHSO3PL2JWQHfCuC2q316jMyYU9TIWZsFK2bQwyK5VAdYgn8ygPj69A==", + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "1.10.0", + "@emnapi/runtime": "1.10.0", + "@napi-rs/wasm-runtime": "^1.1.4" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@unrs/resolver-binding-wasm32-wasi/node_modules/@emnapi/runtime": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.10.0.tgz", + "integrity": "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@unrs/resolver-binding-win32-arm64-msvc": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-arm64-msvc/-/resolver-binding-win32-arm64-msvc-1.12.2.tgz", + "integrity": "sha512-qzNyg3xL0VPQmCaUh+N5jSitce6k+uCBfMDesWRnlULOZaqUkaJ0ybdT+UqlAWJoQjuqfIU/0Ptx9bteN4D82g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@unrs/resolver-binding-win32-ia32-msvc": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-ia32-msvc/-/resolver-binding-win32-ia32-msvc-1.12.2.tgz", + "integrity": "sha512-WD9sY00OfpHVGfsnHZoA8jVT+esS/Bg8z8jzxp5BnDCjjwsuKsPQrzswwpFy4J1AUJbXPRfkpcX0mXrzeXW79g==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@unrs/resolver-binding-win32-x64-msvc": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-x64-msvc/-/resolver-binding-win32-x64-msvc-1.12.2.tgz", + "integrity": "sha512-nAB74NfSNKknqQ1RrYj6uz8FcXEomu/MATJZxh/x+BArzN2U3JbOYC0APYzUIGhVY3m5hRxA8VPNdPBoG8txlA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/acorn": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.18.0.tgz", + "integrity": "sha512-lGq+9yr1/GuAWaVYIHRjvvySG5/4VfKIvC8EWxStPdcDh/Ka7FG3twP6v4d5BkravUilhIAsG4Qj83t02LWUPQ==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/ajv": { + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz", + "integrity": "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/aria-query": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.2.tgz", + "integrity": "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/array-buffer-byte-length": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.2.tgz", + "integrity": "sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "is-array-buffer": "^3.0.5" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array-includes": { + "version": "3.1.9", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.9.tgz", + "integrity": "sha512-FmeCCAenzH0KH381SPT5FZmiA/TmpndpcaShhfgEN9eCVjnFBqq3l1xrI42y8+PPLI6hypzou4GXw00WHmPBLQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.24.0", + "es-object-atoms": "^1.1.1", + "get-intrinsic": "^1.3.0", + "is-string": "^1.1.1", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.findlast": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/array.prototype.findlast/-/array.prototype.findlast-1.2.5.tgz", + "integrity": "sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.findlastindex": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.6.tgz", + "integrity": "sha512-F/TKATkzseUExPlfvmwQKGITM3DGTK+vkAsCZoDc5daVygbJBnjEUCbgkAvVFsgfXfX4YIqZ/27G3k3tdXrTxQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.9", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "es-shim-unscopables": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.flat": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.3.tgz", + "integrity": "sha512-rwG/ja1neyLqCuGZ5YYrznA62D4mZXg0i1cIskIUKSiqF3Cje9/wXAls9B9s1Wa2fomMsIv8czB8jZcPmxCXFg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.flatmap": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.3.tgz", + "integrity": "sha512-Y7Wt51eKJSyi80hFrJCePGGNo5ktJCslFuboqJsbf57CCPcm5zztluPlc4/aD8sWsKvlwatezpV4U1efk8kpjg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.tosorted": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/array.prototype.tosorted/-/array.prototype.tosorted-1.1.4.tgz", + "integrity": "sha512-p6Fx8B7b7ZhL/gmUsAy0D15WhvDccw3mnGNbZpi3pmeJdxtWsj2jEaI4Y6oo3XiHfzuSgPwKc04MYt6KgvC/wA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.3", + "es-errors": "^1.3.0", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/arraybuffer.prototype.slice": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.4.tgz", + "integrity": "sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-buffer-byte-length": "^1.0.1", + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "is-array-buffer": "^3.0.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/ast-types-flow": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.8.tgz", + "integrity": "sha512-OH/2E5Fg20h2aPrbe+QL8JZQFko0YZaF+j4mnQ7BGhfavO7OpSLa8a0y9sBwomHdSbkhTS8TQNayBfnW5DwbvQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/async-function": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/async-function/-/async-function-1.0.0.tgz", + "integrity": "sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/available-typed-arrays": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", + "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "possible-typed-array-names": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/axe-core": { + "version": "4.13.0", + "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.13.0.tgz", + "integrity": "sha512-UzGt8zg7Ny8djbYMhxl2zuEevVa7r2gJjYY5Lwr1xM7+XU2nd6CkIWFTVcCIbAP63vSz71NaVyyuSk9lHKcy0A==", + "dev": true, + "license": "MPL-2.0", + "engines": { + "node": ">=4" + } + }, + "node_modules/axobject-query": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz", + "integrity": "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/baseline-browser-mapping": { + "version": "2.11.20", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.11.20.tgz", + "integrity": "sha512-H0ulySigv6icDJ1F7SjtdCD6PrhTpdYCmP0CactWy1+ekh0AFd0o1Wn5T8b+hnTmdBx19u9yhL6wvCylXMY7zw==", + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/brace-expansion": { + "version": "1.1.18", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.18.tgz", + "integrity": "sha512-Edep/X9fGqVNmzKBVsDYIOtD+z1tuezV70LBjdCst9Tqu76lsnvRiZ6oTic1n+/BIwX6QDGAO94PN4N2SADvtw==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.28.8", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.8.tgz", + "integrity": "sha512-V2NpofLblG64mfOtSgDhOJESZEGogzDMBv/q+W6oc4LXWP/q75eOXoOaaOu1EOadB9U4Bwx/e0yzbvwKH8zalA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "baseline-browser-mapping": "^2.11.12", + "caniuse-lite": "^1.0.30001809", + "electron-to-chromium": "^1.5.402", + "node-releases": "^2.0.53", + "update-browserslist-db": "^1.3.0" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/call-bind": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.9.tgz", + "integrity": "sha512-a/hy+pNsFUTR+Iz8TCJvXudKVLAnz/DyeSUo10I5yvFDQJBFU2s9uqQpoSrJlroHUKoKqzg+epxyP9lqFdzfBQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "get-intrinsic": "^1.3.0", + "set-function-length": "^1.2.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001810", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001810.tgz", + "integrity": "sha512-TITQPUkaz+aVk5GL6NhOdwk1aEaNTSDPsGFWrTuhKGtjTF70jL/Oht2W4c6rXUe5fu7Ie19VIahAXHIIiWWNeg==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/client-only": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz", + "integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==", + "license": "MIT" + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/damerau-levenshtein": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz", + "integrity": "sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==", + "dev": true, + "license": "BSD-2-Clause" + }, + "node_modules/data-view-buffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.2.tgz", + "integrity": "sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/data-view-byte-length": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/data-view-byte-length/-/data-view-byte-length-1.0.2.tgz", + "integrity": "sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/inspect-js" + } + }, + "node_modules/data-view-byte-offset": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/data-view-byte-offset/-/data-view-byte-offset-1.0.1.tgz", + "integrity": "sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/define-properties": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", + "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.0.1", + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "devOptional": true, + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.416", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.416.tgz", + "integrity": "sha512-K6bvB2BjnNrugtIih6ewlbBI9DXa976jIdiIlRLHhBoEI9a4JaQjjHyF+A1IQI543aQYR4LnmOrT/K5fZj0aPA==", + "dev": true, + "license": "ISC" + }, + "node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true, + "license": "MIT" + }, + "node_modules/enhanced-resolve": { + "version": "5.24.5", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.24.5.tgz", + "integrity": "sha512-L1l8TNvomm6UVW5B253AGxQagSQr+vGwhMlrrfRS2qmhx46AMpMVJKQYLvWYbysTMY8VoicOvzHzoHMbyzB+4A==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.3.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/es-abstract": { + "version": "1.24.2", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.2.tgz", + "integrity": "sha512-2FpH9Q5i2RRwyEP1AylXe6nYLR5OhaJTZwmlcP0dL/+JCbgg7yyEo/sEK6HeGZRf3dFpWwThaRHVApXSkW3xeg==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-buffer-byte-length": "^1.0.2", + "arraybuffer.prototype.slice": "^1.0.4", + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "data-view-buffer": "^1.0.2", + "data-view-byte-length": "^1.0.2", + "data-view-byte-offset": "^1.0.1", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "es-set-tostringtag": "^2.1.0", + "es-to-primitive": "^1.3.0", + "function.prototype.name": "^1.1.8", + "get-intrinsic": "^1.3.0", + "get-proto": "^1.0.1", + "get-symbol-description": "^1.1.0", + "globalthis": "^1.0.4", + "gopd": "^1.2.0", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "internal-slot": "^1.1.0", + "is-array-buffer": "^3.0.5", + "is-callable": "^1.2.7", + "is-data-view": "^1.0.2", + "is-negative-zero": "^2.0.3", + "is-regex": "^1.2.1", + "is-set": "^2.0.3", + "is-shared-array-buffer": "^1.0.4", + "is-string": "^1.1.1", + "is-typed-array": "^1.1.15", + "is-weakref": "^1.1.1", + "math-intrinsics": "^1.1.0", + "object-inspect": "^1.13.4", + "object-keys": "^1.1.1", + "object.assign": "^4.1.7", + "own-keys": "^1.0.1", + "regexp.prototype.flags": "^1.5.4", + "safe-array-concat": "^1.1.3", + "safe-push-apply": "^1.0.0", + "safe-regex-test": "^1.1.0", + "set-proto": "^1.0.0", + "stop-iteration-iterator": "^1.1.0", + "string.prototype.trim": "^1.2.10", + "string.prototype.trimend": "^1.0.9", + "string.prototype.trimstart": "^1.0.8", + "typed-array-buffer": "^1.0.3", + "typed-array-byte-length": "^1.0.3", + "typed-array-byte-offset": "^1.0.4", + "typed-array-length": "^1.0.7", + "unbox-primitive": "^1.1.0", + "which-typed-array": "^1.1.19" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es-abstract-get": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-abstract-get/-/es-abstract-get-1.0.0.tgz", + "integrity": "sha512-6PMWXpdhshVvFp+FoWYs1EvG1Nj0tvk0dZM+XcK0xMEM1czRVcP6ohqPWHy6qPagSpC8j4+p89WXlT+xXJs/fg==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.2", + "is-callable": "^1.2.7", + "object-inspect": "^1.13.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-iterator-helpers": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/es-iterator-helpers/-/es-iterator-helpers-1.4.0.tgz", + "integrity": "sha512-c/A0P0oxkACDc+cKWw8evLXK83oBKgn0qPOqCYT4x9uolpCIJAcYvJC9QYKNDRPsTeGyCrQ326jrvgZWdCdK5Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.9", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.24.2", + "es-errors": "^1.3.0", + "es-set-tostringtag": "^2.1.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.3.0", + "globalthis": "^1.0.4", + "gopd": "^1.2.0", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.2.0", + "has-symbols": "^1.1.0", + "internal-slot": "^1.1.0", + "iterator.prototype": "^1.1.5", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz", + "integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-shim-unscopables": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.1.0.tgz", + "integrity": "sha512-d9T8ucsEhh8Bi1woXCf+TIKDIROLG5WCkxg8geBCbvk22kzwC5G2OnXVMO6FUsvQlgUUXQ2itephWDLqDzbeCw==", + "dev": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-to-primitive": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.3.4.tgz", + "integrity": "sha512-yPDz7wqpg1/mmHLmS3tcfTfbw5f1eryXvyghYBffGdERwe+mV7ZcWzTR8LR17Kvqt3qfPurjlonmnq3MKXIOXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-abstract-get": "^1.0.0", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "is-callable": "^1.2.7", + "is-date-object": "^1.1.0", + "is-symbol": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "9.39.5", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.5.tgz", + "integrity": "sha512-DgZS62aPLXKlnxILS/AYCoRvHaZeXceIzlXPkkGGzJWSow1aEk0lbTlxUSlyjC8jcaKxAdOnTDz+o1JFSBsyjw==", + "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.8.0", + "@eslint-community/regexpp": "^4.12.1", + "@eslint/config-array": "^0.21.2", + "@eslint/config-helpers": "^0.4.2", + "@eslint/core": "^0.17.0", + "@eslint/eslintrc": "^3.3.6", + "@eslint/js": "9.39.5", + "@eslint/plugin-kit": "^0.4.1", + "@humanfs/node": "^0.16.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.4.2", + "@types/estree": "^1.0.6", + "ajv": "^6.14.0", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.6", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^8.4.0", + "eslint-visitor-keys": "^4.2.1", + "espree": "^10.4.0", + "esquery": "^1.5.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.5", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } + } + }, + "node_modules/eslint-config-next": { + "version": "16.3.1", + "resolved": "https://registry.npmjs.org/eslint-config-next/-/eslint-config-next-16.3.1.tgz", + "integrity": "sha512-0vtrpwFVHFEkycUgV/DyrG29OS+HSRdah5Yu8YuZoiBMtlAT6NIiWzaLwDkJZxr2kGfx+9LIvfQ7KHAlEs0VsA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@next/eslint-plugin-next": "16.3.1", + "eslint-import-resolver-node": "^0.3.6", + "eslint-import-resolver-typescript": "^3.5.2", + "eslint-plugin-import": "^2.32.0", + "eslint-plugin-jsx-a11y": "^6.10.0", + "eslint-plugin-react": "^7.37.0", + "eslint-plugin-react-hooks": "^7.0.0", + "globals": "16.4.0", + "typescript-eslint": "^8.46.0" + }, + "peerDependencies": { + "eslint": ">=9.0.0", + "typescript": ">=3.3.1" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/eslint-config-next/node_modules/globals": { + "version": "16.4.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-16.4.0.tgz", + "integrity": "sha512-ob/2LcVVaVGCYN+r14cnwnoDPUufjiYgSqRhiFD0Q1iI4Odora5RE8Iv1D24hAz5oMophRGkGz+yuvQmmUMnMw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint-import-resolver-node": { + "version": "0.3.10", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.10.tgz", + "integrity": "sha512-tRrKqFyCaKict5hOd244sL6EQFNycnMQnBe+j8uqGNXYzsImGbGUU4ibtoaBmv5FLwJwcFJNeg1GeVjQfbMrDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^3.2.7", + "is-core-module": "^2.16.1", + "resolve": "^2.0.0-next.6" + } + }, + "node_modules/eslint-import-resolver-node/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-import-resolver-typescript": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-typescript/-/eslint-import-resolver-typescript-3.10.1.tgz", + "integrity": "sha512-A1rHYb06zjMGAxdLSkN2fXPBwuSaQ0iO5M/hdyS0Ajj1VBaRp0sPD3dn1FhME3c/JluGFbwSxyCfqdSbtQLAHQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "@nolyfill/is-core-module": "1.0.39", + "debug": "^4.4.0", + "get-tsconfig": "^4.10.0", + "is-bun-module": "^2.0.0", + "stable-hash": "^0.0.5", + "tinyglobby": "^0.2.13", + "unrs-resolver": "^1.6.2" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint-import-resolver-typescript" + }, + "peerDependencies": { + "eslint": "*", + "eslint-plugin-import": "*", + "eslint-plugin-import-x": "*" + }, + "peerDependenciesMeta": { + "eslint-plugin-import": { + "optional": true + }, + "eslint-plugin-import-x": { + "optional": true + } + } + }, + "node_modules/eslint-module-utils": { + "version": "2.14.0", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.14.0.tgz", + "integrity": "sha512-W2WCRZ9Dqntd+2u8jJcVMV2PKulc6RdLgUUoh/yQr3uB6lo/ZOeGx11sv60/8S4QFFKNslAlWhr9u0Ef7ZW6Ig==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^3.2.7" + }, + "engines": { + "node": ">=4" + }, + "peerDependenciesMeta": { + "eslint": { + "optional": true + } + } + }, + "node_modules/eslint-module-utils/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-plugin-import": { + "version": "2.32.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.32.0.tgz", + "integrity": "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@rtsao/scc": "^1.1.0", + "array-includes": "^3.1.9", + "array.prototype.findlastindex": "^1.2.6", + "array.prototype.flat": "^1.3.3", + "array.prototype.flatmap": "^1.3.3", + "debug": "^3.2.7", + "doctrine": "^2.1.0", + "eslint-import-resolver-node": "^0.3.9", + "eslint-module-utils": "^2.12.1", + "hasown": "^2.0.2", + "is-core-module": "^2.16.1", + "is-glob": "^4.0.3", + "minimatch": "^3.1.2", + "object.fromentries": "^2.0.8", + "object.groupby": "^1.0.3", + "object.values": "^1.2.1", + "semver": "^6.3.1", + "string.prototype.trimend": "^1.0.9", + "tsconfig-paths": "^3.15.0" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 || ^9" + } + }, + "node_modules/eslint-plugin-import/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-plugin-jsx-a11y": { + "version": "6.10.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.10.2.tgz", + "integrity": "sha512-scB3nz4WmG75pV8+3eRUQOHZlNSUhFNq37xnpgRkCCELU3XMvXAxLk1eqWWyE22Ki4Q01Fnsw9BA3cJHDPgn2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "aria-query": "^5.3.2", + "array-includes": "^3.1.8", + "array.prototype.flatmap": "^1.3.2", + "ast-types-flow": "^0.0.8", + "axe-core": "^4.10.0", + "axobject-query": "^4.1.0", + "damerau-levenshtein": "^1.0.8", + "emoji-regex": "^9.2.2", + "hasown": "^2.0.2", + "jsx-ast-utils": "^3.3.5", + "language-tags": "^1.0.9", + "minimatch": "^3.1.2", + "object.fromentries": "^2.0.8", + "safe-regex-test": "^1.0.3", + "string.prototype.includes": "^2.0.1" + }, + "engines": { + "node": ">=4.0" + }, + "peerDependencies": { + "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9" + } + }, + "node_modules/eslint-plugin-react": { + "version": "7.37.5", + "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.37.5.tgz", + "integrity": "sha512-Qteup0SqU15kdocexFNAJMvCJEfa2xUKNV4CC1xsVMrIIqEy3SQ/rqyxCWNzfrd3/ldy6HMlD2e0JDVpDg2qIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-includes": "^3.1.8", + "array.prototype.findlast": "^1.2.5", + "array.prototype.flatmap": "^1.3.3", + "array.prototype.tosorted": "^1.1.4", + "doctrine": "^2.1.0", + "es-iterator-helpers": "^1.2.1", + "estraverse": "^5.3.0", + "hasown": "^2.0.2", + "jsx-ast-utils": "^2.4.1 || ^3.0.0", + "minimatch": "^3.1.2", + "object.entries": "^1.1.9", + "object.fromentries": "^2.0.8", + "object.values": "^1.2.1", + "prop-types": "^15.8.1", + "resolve": "^2.0.0-next.5", + "semver": "^6.3.1", + "string.prototype.matchall": "^4.0.12", + "string.prototype.repeat": "^1.0.0" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7" + } + }, + "node_modules/eslint-plugin-react-hooks": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-7.1.1.tgz", + "integrity": "sha512-f2I7Gw6JbvCexzIInuSbZpfdQ44D7iqdWX01FKLvrPgqxoE7oMj8clOfto8U6vYiz4yd5oKu39rRSVOe1zRu0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.24.4", + "@babel/parser": "^7.24.4", + "hermes-parser": "^0.25.1", + "zod": "^3.25.0 || ^4.0.0", + "zod-validation-error": "^3.5.0 || ^4.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0 || ^10.0.0" + } + }, + "node_modules/eslint-scope": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", + "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/espree": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", + "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.15.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^4.2.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz", + "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-glob": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.1.tgz", + "integrity": "sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fastq": { + "version": "1.20.3", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.3.tgz", + "integrity": "sha512-XKv5nnLs6nLF71NgiKJLIZFLkPyIEuOselLG7ujZnGrRfQK8HpvY+WqKhAJUAdLomwVHErVS4LfxFlPq0/FTAw==", + "dev": true, + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/file-entry-cache": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "flat-cache": "^4.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.4" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/flatted": { + "version": "3.4.4", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.4.tgz", + "integrity": "sha512-5+ybhBZANEJxaH3X5evAFatUxLfEHSr7n6kYJ+1Qd0mUqr4eu9gIf6GDbWHf8RJijHrjjO8G+la14SlL2SeS1Q==", + "dev": true, + "license": "ISC" + }, + "node_modules/for-each": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz", + "integrity": "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-callable": "^1.2.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/function.prototype.name": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.2.0.tgz", + "integrity": "sha512-jObKIik1P2QjPHP5nz5BaOtUlfgS0fWo8IUByNXkM+o+02sJOi94em77GwJKQSJ3gfPHdgzLNrHc1uokV4P/ew==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.9", + "call-bound": "^1.0.4", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "functions-have-names": "^1.2.3", + "has-property-descriptors": "^1.0.2", + "hasown": "^2.0.4", + "is-callable": "^1.2.7", + "is-document.all": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/functions-have-names": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/generator-function": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/generator-function/-/generator-function-2.0.1.tgz", + "integrity": "sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/get-symbol-description": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.1.0.tgz", + "integrity": "sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-tsconfig": { + "version": "4.14.3", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.14.3.tgz", + "integrity": "sha512-++QEw4DIY7WGoukz+/+A/8dGYPT9l9yIadnmSgZ8Rjr3YVSVDipQSO9CdnJo9ePqFqUUqh+wk9uIaoiAwsiPkA==", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/globals": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globalthis": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz", + "integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-properties": "^1.2.1", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/has-bigints": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.1.0.tgz", + "integrity": "sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-proto": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.2.0.tgz", + "integrity": "sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz", + "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==", + "dev": true, + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/hermes-estree": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.25.1.tgz", + "integrity": "sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw==", + "dev": true, + "license": "MIT" + }, + "node_modules/hermes-parser": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.25.1.tgz", + "integrity": "sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "hermes-estree": "0.25.1" + } + }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/internal-slot": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.1.0.tgz", + "integrity": "sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "hasown": "^2.0.2", + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/is-array-buffer": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz", + "integrity": "sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-async-function": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-async-function/-/is-async-function-2.1.1.tgz", + "integrity": "sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "async-function": "^1.0.0", + "call-bound": "^1.0.3", + "get-proto": "^1.0.1", + "has-tostringtag": "^1.0.2", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-bigint": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.1.0.tgz", + "integrity": "sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-bigints": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-boolean-object": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.2.2.tgz", + "integrity": "sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-bun-module": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-bun-module/-/is-bun-module-2.0.0.tgz", + "integrity": "sha512-gNCGbnnnnFAUGKeZ9PdbyeGYJqewpmc2aKHUEMO5nQPWU9lOmv7jcmQIv+qHD8fXW6W7qfuCwX4rY9LNRjXrkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.7.1" + } + }, + "node_modules/is-bun-module/node_modules/semver": { + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-core-module": { + "version": "2.16.2", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.2.tgz", + "integrity": "sha512-evOr8xfXKxE6qSR0hSXL2r3sd7ALj8+7jQEUvPYcm5sgZFdJ+AYzT6yNmJenvIYQBgIGwfwz08sL8zoL7yq2BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-data-view": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-data-view/-/is-data-view-1.0.2.tgz", + "integrity": "sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "get-intrinsic": "^1.2.6", + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-date-object": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.1.0.tgz", + "integrity": "sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-document.all": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-document.all/-/is-document.all-1.0.0.tgz", + "integrity": "sha512-+XSoyS05OdBbhFuELhgTCpFNHkpBOJqtsZfUFFpe5QTw+9Sjbh8zitxhQkYAo6wV7e1Vb8cAPvpCk9jGam/82g==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-finalizationregistry": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-finalizationregistry/-/is-finalizationregistry-1.1.1.tgz", + "integrity": "sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-generator-function": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.1.2.tgz", + "integrity": "sha512-upqt1SkGkODW9tsGNG5mtXTXtECizwtS2kA161M+gJPc1xdb/Ax629af6YrTwcOeQHbewrPNlE5Dx7kzvXTizA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.4", + "generator-function": "^2.0.0", + "get-proto": "^1.0.1", + "has-tostringtag": "^1.0.2", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-map": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz", + "integrity": "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-negative-zero": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.3.tgz", + "integrity": "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-number-object": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.1.1.tgz", + "integrity": "sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-regex": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", + "integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-set": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.3.tgz", + "integrity": "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-shared-array-buffer": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.4.tgz", + "integrity": "sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-string": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.1.1.tgz", + "integrity": "sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-symbol": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.1.1.tgz", + "integrity": "sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "has-symbols": "^1.1.0", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-typed-array": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.15.tgz", + "integrity": "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "which-typed-array": "^1.1.16" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakmap": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz", + "integrity": "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakref": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.1.1.tgz", + "integrity": "sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakset": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.4.tgz", + "integrity": "sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "dev": true, + "license": "MIT" + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/iterator.prototype": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/iterator.prototype/-/iterator.prototype-1.1.5.tgz", + "integrity": "sha512-H0dkQoCa3b2VEeKQBOxFph+JAbcrQdE7KC0UkqwpLmv2EC4P41QXP+rqo9wYodACiG5/WM5s9oDApTU8utwj9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.6", + "get-proto": "^1.0.0", + "has-symbols": "^1.1.0", + "set-function-name": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/jiti": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.7.0.tgz", + "integrity": "sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==", + "dev": true, + "license": "MIT", + "bin": { + "jiti": "lib/jiti-cli.mjs" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/js-yaml": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.2.tgz", + "integrity": "sha512-SFNOvSJ+Dgf/9An904Yx+CgSlIPCkIpao4qo51lpee25TIRejdH3rhR4EZMGoNx3/TP3O+wzWuiTFl4sqbltzA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/puzrin" + }, + { + "type": "github", + "url": "https://github.com/sponsors/nodeca" + } + ], + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/jsx-ast-utils": { + "version": "3.3.5", + "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz", + "integrity": "sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-includes": "^3.1.6", + "array.prototype.flat": "^1.3.1", + "object.assign": "^4.1.4", + "object.values": "^1.1.6" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/language-subtag-registry": { + "version": "0.3.23", + "resolved": "https://registry.npmjs.org/language-subtag-registry/-/language-subtag-registry-0.3.23.tgz", + "integrity": "sha512-0K65Lea881pHotoGEa5gDlMxt3pctLi2RplBb7Ezh4rRdLEOtgi7n4EwK9lamnUCkKBqaeKRVebTq6BAxSkpXQ==", + "dev": true, + "license": "CC0-1.0" + }, + "node_modules/language-tags": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/language-tags/-/language-tags-1.0.9.tgz", + "integrity": "sha512-MbjN408fEndfiQXbFQ1vnd+1NoLDsnQW41410oQBXiyXDMYH5z505juWa4KUE1LqxRC7DgOgZDbKLxHIwm27hA==", + "dev": true, + "license": "MIT", + "dependencies": { + "language-subtag-registry": "^0.3.20" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/lightningcss": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz", + "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==", + "dev": true, + "license": "MPL-2.0", + "dependencies": { + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-android-arm64": "1.32.0", + "lightningcss-darwin-arm64": "1.32.0", + "lightningcss-darwin-x64": "1.32.0", + "lightningcss-freebsd-x64": "1.32.0", + "lightningcss-linux-arm-gnueabihf": "1.32.0", + "lightningcss-linux-arm64-gnu": "1.32.0", + "lightningcss-linux-arm64-musl": "1.32.0", + "lightningcss-linux-x64-gnu": "1.32.0", + "lightningcss-linux-x64-musl": "1.32.0", + "lightningcss-win32-arm64-msvc": "1.32.0", + "lightningcss-win32-x64-msvc": "1.32.0" + } + }, + "node_modules/lightningcss-android-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz", + "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz", + "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz", + "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-freebsd-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz", + "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz", + "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz", + "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz", + "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz", + "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz", + "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz", + "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz", + "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.18", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.18.tgz", + "integrity": "sha512-DTg4MJbGMWkfi6VZFdNt2/caMbQy4Ou+Op/hJQvGEWcnVfoA1QA+xzRKAzw9jD6+GVOOeYr/mIcuDSdug6F6+w==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/napi-postinstall": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/napi-postinstall/-/napi-postinstall-0.3.4.tgz", + "integrity": "sha512-PHI5f1O0EP5xJ9gQmFGMS6IZcrVvTjpXjz7Na41gTE7eE2hK11lg04CECCYEEjdc17EV4DO+fkGEtt7TpTaTiQ==", + "dev": true, + "license": "MIT", + "bin": { + "napi-postinstall": "lib/cli.js" + }, + "engines": { + "node": "^12.20.0 || ^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/napi-postinstall" + } + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "license": "MIT" + }, + "node_modules/next": { + "version": "16.3.1", + "resolved": "https://registry.npmjs.org/next/-/next-16.3.1.tgz", + "integrity": "sha512-hsAp0i7Rh+/dhe7DGIeN2YlpLM1DP4MNxti9EtDMtqcO612X81MvvEj388/oTce9U1EcEIOWDlGq0zRwrBKvuA==", + "license": "MIT", + "dependencies": { + "@next/env": "16.3.1", + "@swc/helpers": "0.5.23", + "baseline-browser-mapping": "^2.9.19", + "caniuse-lite": "^1.0.30001579", + "postcss": "8.5.23", + "styled-jsx": "5.1.6" + }, + "bin": { + "next": "dist/bin/next" + }, + "engines": { + "node": ">=20.9.0" + }, + "optionalDependencies": { + "@next/swc-darwin-arm64": "16.3.1", + "@next/swc-darwin-x64": "16.3.1", + "@next/swc-linux-arm64-gnu": "16.3.1", + "@next/swc-linux-arm64-musl": "16.3.1", + "@next/swc-linux-x64-gnu": "16.3.1", + "@next/swc-linux-x64-musl": "16.3.1", + "@next/swc-win32-arm64-msvc": "16.3.1", + "@next/swc-win32-x64-msvc": "16.3.1", + "sharp": "^0.35.3" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.1.0", + "@playwright/test": "^1.51.1", + "babel-plugin-react-compiler": "*", + "react": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", + "react-dom": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", + "sass": "^1.3.0" + }, + "peerDependenciesMeta": { + "@opentelemetry/api": { + "optional": true + }, + "@playwright/test": { + "optional": true + }, + "babel-plugin-react-compiler": { + "optional": true + }, + "sass": { + "optional": true + } + } + }, + "node_modules/next/node_modules/postcss": { + "version": "8.5.23", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.23.tgz", + "integrity": "sha512-g50586zr4bZmwFiTlflMu8E0bDTb5I5gertgwAKmsdUlTQIhZtunzUlD1WSzwcVWPoAVpsrA6vlfCD7oXvRwgg==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.16", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/node-exports-info": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/node-exports-info/-/node-exports-info-1.6.2.tgz", + "integrity": "sha512-kXs9Go0cah0qHVV2v389IXQLdLCeE1xfFtjOAF+iobu0OIoG1pje8At2vMHyaPMiPMnG/LWP50twML21eMcAag==", + "dev": true, + "license": "MIT", + "dependencies": { + "array.prototype.flatmap": "^1.3.3", + "es-errors": "^1.3.0", + "object.entries": "^1.1.9", + "semver": "^6.3.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/node-releases": { + "version": "2.0.54", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.54.tgz", + "integrity": "sha512-YHs7BmmcsdAI5Ozuf8JZo6PT0mv2GIWC9vMfvUC3dp65M8hn7Ux8CPL+2oBI7juNuj9d0ndhTcznq2ODBps9cQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.assign": { + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.7.tgz", + "integrity": "sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0", + "has-symbols": "^1.1.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.entries": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.9.tgz", + "integrity": "sha512-8u/hfXFRBD1O0hPUjioLhoWFHRmt6tKA4/vZPyckBr18l1KE9uHrFaFaUi8MDRTpi4uak2goyPTSNJLXX2k2Hw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.fromentries": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.8.tgz", + "integrity": "sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.groupby": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/object.groupby/-/object.groupby-1.0.3.tgz", + "integrity": "sha512-+Lhy3TQTuzXI5hevh8sBGqbmurHbbIjAi0Z4S63nthVLmLxfbj4T54a4CfZrXIrt9iP4mVAPYMo/v99taj3wjQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.values": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.2.1.tgz", + "integrity": "sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/own-keys": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/own-keys/-/own-keys-1.0.2.tgz", + "integrity": "sha512-19YVAg7T+WTrxggPukVq7DjTv6+PJ867TmhCvBsYwmbFCsZd344rq2Ld1p0wo8f8Qrrhgp82c6FJRqdXWtSEhg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.4", + "get-intrinsic": "^1.3.0", + "object-keys": "^1.1.1", + "safe-push-apply": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true, + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/possible-typed-array-names": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", + "integrity": "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/postcss": { + "version": "8.5.26", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.26.tgz", + "integrity": "sha512-u82N74LFzG8ca+dD8puPnplTXoGH4fTPpVGuIbt36G3qvNlkvfD0lEAZSxaly3KX8TS/L1A1gsCEmvKmBcVbkQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.17", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/prop-types": { + "version": "15.8.1", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "dev": true, + "license": "MIT", + "dependencies": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.13.1" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/react": { + "version": "19.2.8", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.8.tgz", + "integrity": "sha512-PWaYA1L/q9u2u7xYQi+Y3L3Yfnie7XyLeaJICV1MGD6LprsBxcAqGjYyr0eY3p+QdsA+x/Irkt4Qif8D63+Sbw==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "19.2.8", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.8.tgz", + "integrity": "sha512-rVprimfGBG3DR+Tq0IQG2DT5PxKth1WIGDmj5yPmlzr4YBe7uyE+Du4oVqTDXZSHGGGXRtTJEGSSePyQCMBglQ==", + "license": "MIT", + "dependencies": { + "scheduler": "^0.27.0" + }, + "peerDependencies": { + "react": "^19.2.8" + } + }, + "node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/reflect.getprototypeof": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz", + "integrity": "sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.9", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.7", + "get-proto": "^1.0.1", + "which-builtin-type": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/regexp.prototype.flags": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz", + "integrity": "sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-errors": "^1.3.0", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "set-function-name": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve": { + "version": "2.0.0-next.7", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.7.tgz", + "integrity": "sha512-tqt+NBWwyaMgw3zDsnygx4CByWjQEJHOPMdslYhppaQSJUtL/D4JO9CcBBlhPoI8lz9oJIDXkwXfhF4aWqP8xQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "is-core-module": "^2.16.2", + "node-exports-info": "^1.6.0", + "object-keys": "^1.1.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/resolve-pkg-maps": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + } + }, + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/safe-array-concat": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.4.tgz", + "integrity": "sha512-wtZlHyOje6OZTGqAoaDKxFkgRtkF9CnHAVnCHKfuj200wAgL+bSJhdsCD2l0Qx/2ekEXjPWcyKkfGb5CPboslg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.9", + "call-bound": "^1.0.4", + "get-intrinsic": "^1.3.0", + "has-symbols": "^1.1.0", + "isarray": "^2.0.5" + }, + "engines": { + "node": ">=0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safe-push-apply": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/safe-push-apply/-/safe-push-apply-1.0.0.tgz", + "integrity": "sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "isarray": "^2.0.5" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safe-regex-test": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.1.0.tgz", + "integrity": "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "is-regex": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/scheduler": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", + "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", + "license": "MIT" + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/set-function-name": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz", + "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "functions-have-names": "^1.2.3", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/set-proto": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/set-proto/-/set-proto-1.0.0.tgz", + "integrity": "sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/sharp": { + "version": "0.35.4", + "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.35.4.tgz", + "integrity": "sha512-n++8XWcj+jCOr2IOl7h8LbKnGBDY4aPbmprMONBNFdn0ImXqpGVv5zliDs0V9HbmbCQLpbuo2ej9rAoOQTvMDA==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@img/colour": "^1.1.0", + "detect-libc": "^2.1.2", + "semver": "^7.8.5" + }, + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-darwin-arm64": "0.35.4", + "@img/sharp-darwin-x64": "0.35.4", + "@img/sharp-freebsd-wasm32": "0.35.4", + "@img/sharp-libvips-darwin-arm64": "1.3.3", + "@img/sharp-libvips-darwin-x64": "1.3.3", + "@img/sharp-libvips-linux-arm": "1.3.3", + "@img/sharp-libvips-linux-arm64": "1.3.3", + "@img/sharp-libvips-linux-ppc64": "1.3.3", + "@img/sharp-libvips-linux-riscv64": "1.3.3", + "@img/sharp-libvips-linux-s390x": "1.3.3", + "@img/sharp-libvips-linux-x64": "1.3.3", + "@img/sharp-libvips-linuxmusl-arm64": "1.3.3", + "@img/sharp-libvips-linuxmusl-x64": "1.3.3", + "@img/sharp-linux-arm": "0.35.4", + "@img/sharp-linux-arm64": "0.35.4", + "@img/sharp-linux-ppc64": "0.35.4", + "@img/sharp-linux-riscv64": "0.35.4", + "@img/sharp-linux-s390x": "0.35.4", + "@img/sharp-linux-x64": "0.35.4", + "@img/sharp-linuxmusl-arm64": "0.35.4", + "@img/sharp-linuxmusl-x64": "0.35.4", + "@img/sharp-webcontainers-wasm32": "0.35.4", + "@img/sharp-win32-arm64": "0.35.4", + "@img/sharp-win32-ia32": "0.35.4", + "@img/sharp-win32-x64": "0.35.4" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/sharp/node_modules/semver": { + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "license": "ISC", + "optional": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/side-channel": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.1.tgz", + "integrity": "sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.4", + "side-channel-list": "^1.0.1", + "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.1", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz", + "integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/stable-hash": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/stable-hash/-/stable-hash-0.0.5.tgz", + "integrity": "sha512-+L3ccpzibovGXFK+Ap/f8LOS0ahMrHTf3xu7mMLSpEGU0EO9ucaysSylKo9eRDFNhWve/y275iPmIZ4z39a9iA==", + "dev": true, + "license": "MIT" + }, + "node_modules/stop-iteration-iterator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.1.0.tgz", + "integrity": "sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "internal-slot": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/string.prototype.includes": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/string.prototype.includes/-/string.prototype.includes-2.0.1.tgz", + "integrity": "sha512-o7+c9bW6zpAdJHTtujeePODAhkuicdAryFsfVKwA+wGw89wJ4GTY484WTucM9hLtDEOpOvI+aHnzqnC5lHp4Rg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.3" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/string.prototype.matchall": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.1.0.tgz", + "integrity": "sha512-tHNHTxInrYLCga9O9YGxWA3G9/nnzQw8UGAyqGx3Ar1pSTTzIuM4woFSq4SowkXCjJIwq5sIiQvEfRI9tCH1qQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.9", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.24.2", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.2", + "get-intrinsic": "^1.3.0", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "internal-slot": "^1.1.0", + "regexp.prototype.flags": "^1.5.4", + "set-function-name": "^2.0.2", + "side-channel": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.repeat": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/string.prototype.repeat/-/string.prototype.repeat-1.0.0.tgz", + "integrity": "sha512-0u/TldDbKD8bFCQ/4f5+mNRrXwZ8hg2w7ZR8wa16e8z9XpePWl3eGEcUD0OXpEH/VJH/2G3gjUtR3ZOiBe2S/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.5" + } + }, + "node_modules/string.prototype.trim": { + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.11.tgz", + "integrity": "sha512-PwvK7BU+CMTJGYQCTZb5RWXIML92lftJLhQz1tBzgKiqGxJaMlBAa48POXaNAC2s4y8jr3EFqrkF9+44neS46w==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.9", + "call-bound": "^1.0.4", + "define-data-property": "^1.1.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.24.2", + "es-object-atoms": "^1.1.2", + "has-property-descriptors": "^1.0.2", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimend": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.10.tgz", + "integrity": "sha512-2+3aDAOmPTmuFwjDnmJG2ctEkQKVki7vOSqaxkv42Mowj1V6PnvuwFCRrR5lChUux1TBskPjfkeTOhqczDMxTw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.9", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimstart": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz", + "integrity": "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/styled-jsx": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.1.6.tgz", + "integrity": "sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA==", + "license": "MIT", + "dependencies": { + "client-only": "0.0.1" + }, + "engines": { + "node": ">= 12.0.0" + }, + "peerDependencies": { + "react": ">= 16.8.0 || 17.x.x || ^18.0.0-0 || ^19.0.0-0" + }, + "peerDependenciesMeta": { + "@babel/core": { + "optional": true + }, + "babel-plugin-macros": { + "optional": true + } + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/swiper": { + "version": "14.2.0", + "resolved": "https://registry.npmjs.org/swiper/-/swiper-14.2.0.tgz", + "integrity": "sha512-GsL4M9Fq7Sg22OyL3SjdNj/0bxTfIxPJJHHW394Li0u5vYtExi0a09iBUc8CuLafJi5pAlH2AHUe1PocxHl45g==", + "funding": [ + { + "type": "custom", + "url": "https://sponsors.nolimits4web.com" + }, + { + "type": "github", + "url": "https://github.com/sponsors/nolimits4web" + } + ], + "license": "MIT", + "engines": { + "node": ">= 4.7.0" + } + }, + "node_modules/tailwindcss": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.3.3.tgz", + "integrity": "sha512-gOhV3P7ufE62QDGg1zVaTgCR+EtPv92k2nIhVcVKcLmxT1sUBsQGhnZj175j+MqRt4zLF7ic+sCYjfhxMxj7YQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/tapable": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.3.tgz", + "integrity": "sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinyglobby/node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/tinyglobby/node_modules/picomatch": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.7.tgz", + "integrity": "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/ts-api-utils": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.5.0.tgz", + "integrity": "sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.12" + }, + "peerDependencies": { + "typescript": ">=4.8.4" + } + }, + "node_modules/tsconfig-paths": { + "version": "3.15.0", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz", + "integrity": "sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/json5": "^0.0.29", + "json5": "^1.0.2", + "minimist": "^1.2.6", + "strip-bom": "^3.0.0" + } + }, + "node_modules/tsconfig-paths/node_modules/json5": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", + "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "minimist": "^1.2.0" + }, + "bin": { + "json5": "lib/cli.js" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/typed-array-buffer": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz", + "integrity": "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/typed-array-byte-length": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.3.tgz", + "integrity": "sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "for-each": "^0.3.3", + "gopd": "^1.2.0", + "has-proto": "^1.2.0", + "is-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-byte-offset": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.4.tgz", + "integrity": "sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "for-each": "^0.3.3", + "gopd": "^1.2.0", + "has-proto": "^1.2.0", + "is-typed-array": "^1.1.15", + "reflect.getprototypeof": "^1.0.9" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-length": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.8.tgz", + "integrity": "sha512-phPGCwqr2+Qo0fwniCE8e4pKnGu/yFb5nD5Y8bf0EEeiI5GklnACYA9GFy/DrAeRrKHXvHn+1SUsOWgJp6RO+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.9", + "for-each": "^0.3.5", + "gopd": "^1.2.0", + "is-typed-array": "^1.1.15", + "possible-typed-array-names": "^1.1.0", + "reflect.getprototypeof": "^1.0.10" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/typescript-eslint": { + "version": "8.68.0", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.68.0.tgz", + "integrity": "sha512-MHy0Y0ynqeEbx/S45+i/bBssdy3X6KNBfmJAP35GrgtNxu2TQ5K5xsFDhAnmsq1jvpdoZOPG1LGtJo0HWqYCrQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/eslint-plugin": "8.68.0", + "@typescript-eslint/parser": "8.68.0", + "@typescript-eslint/typescript-estree": "8.68.0", + "@typescript-eslint/utils": "8.68.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/unbox-primitive": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.1.0.tgz", + "integrity": "sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-bigints": "^1.0.2", + "has-symbols": "^1.1.0", + "which-boxed-primitive": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/unrs-resolver": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/unrs-resolver/-/unrs-resolver-1.12.2.tgz", + "integrity": "sha512-dmlRxBJJayXjqTwC+JtF1HhJmgf3ftQ3YejFcZrf4+KKtJv0qDsK1pjqaaVjG7wJ5NJ6UVP1OqRMQ71Z4C3rxQ==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "napi-postinstall": "^0.3.4" + }, + "funding": { + "url": "https://opencollective.com/unrs-resolver" + }, + "optionalDependencies": { + "@unrs/resolver-binding-android-arm-eabi": "1.12.2", + "@unrs/resolver-binding-android-arm64": "1.12.2", + "@unrs/resolver-binding-darwin-arm64": "1.12.2", + "@unrs/resolver-binding-darwin-x64": "1.12.2", + "@unrs/resolver-binding-freebsd-x64": "1.12.2", + "@unrs/resolver-binding-linux-arm-gnueabihf": "1.12.2", + "@unrs/resolver-binding-linux-arm-musleabihf": "1.12.2", + "@unrs/resolver-binding-linux-arm64-gnu": "1.12.2", + "@unrs/resolver-binding-linux-arm64-musl": "1.12.2", + "@unrs/resolver-binding-linux-loong64-gnu": "1.12.2", + "@unrs/resolver-binding-linux-loong64-musl": "1.12.2", + "@unrs/resolver-binding-linux-ppc64-gnu": "1.12.2", + "@unrs/resolver-binding-linux-riscv64-gnu": "1.12.2", + "@unrs/resolver-binding-linux-riscv64-musl": "1.12.2", + "@unrs/resolver-binding-linux-s390x-gnu": "1.12.2", + "@unrs/resolver-binding-linux-x64-gnu": "1.12.2", + "@unrs/resolver-binding-linux-x64-musl": "1.12.2", + "@unrs/resolver-binding-openharmony-arm64": "1.12.2", + "@unrs/resolver-binding-wasm32-wasi": "1.12.2", + "@unrs/resolver-binding-win32-arm64-msvc": "1.12.2", + "@unrs/resolver-binding-win32-ia32-msvc": "1.12.2", + "@unrs/resolver-binding-win32-x64-msvc": "1.12.2" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.3.2.tgz", + "integrity": "sha512-UQ+MSxlhRm1bzjhU+DcuXfjFO1FzNtqhK5+9Yvlp90ItDLk5vT932A0rFu619nf7RVS+Y/VeaUW1jaRDqZ8VJw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/which-boxed-primitive": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.1.1.tgz", + "integrity": "sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-bigint": "^1.1.0", + "is-boolean-object": "^1.2.1", + "is-number-object": "^1.1.1", + "is-string": "^1.1.1", + "is-symbol": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-builtin-type": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/which-builtin-type/-/which-builtin-type-1.2.1.tgz", + "integrity": "sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "function.prototype.name": "^1.1.6", + "has-tostringtag": "^1.0.2", + "is-async-function": "^2.0.0", + "is-date-object": "^1.1.0", + "is-finalizationregistry": "^1.1.0", + "is-generator-function": "^1.0.10", + "is-regex": "^1.2.1", + "is-weakref": "^1.0.2", + "isarray": "^2.0.5", + "which-boxed-primitive": "^1.1.0", + "which-collection": "^1.0.2", + "which-typed-array": "^1.1.16" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-collection": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.2.tgz", + "integrity": "sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-map": "^2.0.3", + "is-set": "^2.0.3", + "is-weakmap": "^2.0.2", + "is-weakset": "^2.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-typed-array": { + "version": "1.1.22", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.22.tgz", + "integrity": "sha512-fvO4ExWMFsqyhG3AiPAObMuY1lxaqgYcxbc49CNdWDDECOJNgQyvsOWVwbZc+qf3rzRtxojBK+CMEv0Ld5CYpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.9", + "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/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/zod": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.5.4.tgz", + "integrity": "sha512-sC95tT5iHHH9gtpj6A81kh+NEaRAUFN+qlUPDUbRfOMvNf5QCBqsb3WgvnpVtK5Y+4UfA6KqufotuTvMGiTlsA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/zod-validation-error": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/zod-validation-error/-/zod-validation-error-4.0.2.tgz", + "integrity": "sha512-Q6/nZLe6jxuU80qb/4uJ4t5v2VEZ44lzQjPDhYJNztRQ4wyWc6VF3D3Kb/fAuPetZQnhS3hnajCf9CsWesghLQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "zod": "^3.25.0 || ^4.0.0" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..5f6ee10 --- /dev/null +++ b/package.json @@ -0,0 +1,27 @@ +{ + "name": "moalem-shop", + "version": "0.1.0", + "private": true, + "scripts": { + "dev": "next dev", + "build": "next build", + "start": "next start", + "lint": "eslint" + }, + "dependencies": { + "next": "16.3.1", + "react": "19.2.8", + "react-dom": "19.2.8", + "swiper": "^14.1.0" + }, + "devDependencies": { + "@tailwindcss/postcss": "^4", + "@types/node": "^20", + "@types/react": "^19", + "@types/react-dom": "^19", + "eslint": "^9", + "eslint-config-next": "16.3.1", + "tailwindcss": "^4", + "typescript": "^5" + } +} diff --git a/postcss.config.mjs b/postcss.config.mjs new file mode 100644 index 0000000..61e3684 --- /dev/null +++ b/postcss.config.mjs @@ -0,0 +1,7 @@ +const config = { + plugins: { + "@tailwindcss/postcss": {}, + }, +}; + +export default config; diff --git a/public/favicon.png b/public/favicon.png new file mode 100644 index 0000000..d087fd3 Binary files /dev/null and b/public/favicon.png differ diff --git a/public/fonts/iranyekanwebboldfanum.woff b/public/fonts/iranyekanwebboldfanum.woff new file mode 100644 index 0000000..07f9756 Binary files /dev/null and b/public/fonts/iranyekanwebboldfanum.woff differ diff --git a/public/fonts/iranyekanweblightfanum.woff b/public/fonts/iranyekanweblightfanum.woff new file mode 100644 index 0000000..9e6d42c Binary files /dev/null and b/public/fonts/iranyekanweblightfanum.woff differ diff --git a/public/images/banners/06SQinO4sG.jpg b/public/images/banners/06SQinO4sG.jpg new file mode 100644 index 0000000..0c1fc9f Binary files /dev/null and b/public/images/banners/06SQinO4sG.jpg differ diff --git a/public/images/banners/9WYorEOTwn.jpg b/public/images/banners/9WYorEOTwn.jpg new file mode 100644 index 0000000..2654fc3 Binary files /dev/null and b/public/images/banners/9WYorEOTwn.jpg differ diff --git a/public/images/banners/OiNl9v5gTf.jpg b/public/images/banners/OiNl9v5gTf.jpg new file mode 100644 index 0000000..b1522c6 Binary files /dev/null and b/public/images/banners/OiNl9v5gTf.jpg differ diff --git a/public/images/banners/SOwPl0RoPm.jpg b/public/images/banners/SOwPl0RoPm.jpg new file mode 100644 index 0000000..e90bb8b Binary files /dev/null and b/public/images/banners/SOwPl0RoPm.jpg differ diff --git a/public/images/banners/TRm6o2MsRo.jpg b/public/images/banners/TRm6o2MsRo.jpg new file mode 100644 index 0000000..623cf63 Binary files /dev/null and b/public/images/banners/TRm6o2MsRo.jpg differ diff --git a/public/images/banners/UxRql2ljcu.jpg b/public/images/banners/UxRql2ljcu.jpg new file mode 100644 index 0000000..bfec43a Binary files /dev/null and b/public/images/banners/UxRql2ljcu.jpg differ diff --git a/public/images/banners/WRbs72TDPR.jpg b/public/images/banners/WRbs72TDPR.jpg new file mode 100644 index 0000000..e5381c6 Binary files /dev/null and b/public/images/banners/WRbs72TDPR.jpg differ diff --git a/public/images/banners/ieYGotL2qd.jpg b/public/images/banners/ieYGotL2qd.jpg new file mode 100644 index 0000000..e53a9ab Binary files /dev/null and b/public/images/banners/ieYGotL2qd.jpg differ diff --git a/public/images/banners/lAGRh3ks32.jpg b/public/images/banners/lAGRh3ks32.jpg new file mode 100644 index 0000000..6cd766a Binary files /dev/null and b/public/images/banners/lAGRh3ks32.jpg differ diff --git a/public/images/banners/lKmvUEi1qT.jpg b/public/images/banners/lKmvUEi1qT.jpg new file mode 100644 index 0000000..d140e29 Binary files /dev/null and b/public/images/banners/lKmvUEi1qT.jpg differ diff --git a/public/images/banners/otiLVfiUJM.jpg b/public/images/banners/otiLVfiUJM.jpg new file mode 100644 index 0000000..ab9a00f Binary files /dev/null and b/public/images/banners/otiLVfiUJM.jpg differ diff --git a/public/images/banners/xH0doILyNs.jpg b/public/images/banners/xH0doILyNs.jpg new file mode 100644 index 0000000..4407933 Binary files /dev/null and b/public/images/banners/xH0doILyNs.jpg differ diff --git a/public/images/categories/1226.svg b/public/images/categories/1226.svg new file mode 100644 index 0000000..985c4ca --- /dev/null +++ b/public/images/categories/1226.svg @@ -0,0 +1,6 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 120 120" width="120" height="120" role="img" aria-label="لوازم جانبی"> + <circle cx="60" cy="60" r="59" fill="#f6f7f9" stroke="#ececec"/> + <rect x="34" y="34" width="52" height="52" rx="12" fill="#fff" stroke="#e51e22" stroke-opacity="0.35" stroke-dasharray="5 4"/> + <text x="60" y="64" text-anchor="middle" font-family="Tahoma, sans-serif" font-size="11" fill="#7c7c7c">لوازم جان</text> + <text x="60" y="78" text-anchor="middle" font-family="Tahoma, sans-serif" font-size="8" fill="#c9c9c9">1226</text> +</svg> diff --git a/public/images/categories/1251.svg b/public/images/categories/1251.svg new file mode 100644 index 0000000..79f61c2 --- /dev/null +++ b/public/images/categories/1251.svg @@ -0,0 +1,6 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 120 120" width="120" height="120" role="img" aria-label="موبایل"> + <circle cx="60" cy="60" r="59" fill="#f6f7f9" stroke="#ececec"/> + <rect x="34" y="34" width="52" height="52" rx="12" fill="#fff" stroke="#e51e22" stroke-opacity="0.35" stroke-dasharray="5 4"/> + <text x="60" y="64" text-anchor="middle" font-family="Tahoma, sans-serif" font-size="11" fill="#7c7c7c">موبایل</text> + <text x="60" y="78" text-anchor="middle" font-family="Tahoma, sans-serif" font-size="8" fill="#c9c9c9">1251</text> +</svg> diff --git a/public/images/categories/1253.svg b/public/images/categories/1253.svg new file mode 100644 index 0000000..60d0f57 --- /dev/null +++ b/public/images/categories/1253.svg @@ -0,0 +1,6 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 120 120" width="120" height="120" role="img" aria-label="تبلت"> + <circle cx="60" cy="60" r="59" fill="#f6f7f9" stroke="#ececec"/> + <rect x="34" y="34" width="52" height="52" rx="12" fill="#fff" stroke="#e51e22" stroke-opacity="0.35" stroke-dasharray="5 4"/> + <text x="60" y="64" text-anchor="middle" font-family="Tahoma, sans-serif" font-size="11" fill="#7c7c7c">تبلت</text> + <text x="60" y="78" text-anchor="middle" font-family="Tahoma, sans-serif" font-size="8" fill="#c9c9c9">1253</text> +</svg> diff --git a/public/images/categories/1254.svg b/public/images/categories/1254.svg new file mode 100644 index 0000000..78aaf9d --- /dev/null +++ b/public/images/categories/1254.svg @@ -0,0 +1,6 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 120 120" width="120" height="120" role="img" aria-label="لپ تاپ"> + <circle cx="60" cy="60" r="59" fill="#f6f7f9" stroke="#ececec"/> + <rect x="34" y="34" width="52" height="52" rx="12" fill="#fff" stroke="#e51e22" stroke-opacity="0.35" stroke-dasharray="5 4"/> + <text x="60" y="64" text-anchor="middle" font-family="Tahoma, sans-serif" font-size="11" fill="#7c7c7c">لپ تاپ</text> + <text x="60" y="78" text-anchor="middle" font-family="Tahoma, sans-serif" font-size="8" fill="#c9c9c9">1254</text> +</svg> diff --git a/public/images/categories/1255.svg b/public/images/categories/1255.svg new file mode 100644 index 0000000..05a87a0 --- /dev/null +++ b/public/images/categories/1255.svg @@ -0,0 +1,6 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 120 120" width="120" height="120" role="img" aria-label="ساعت و بند هوشمند"> + <circle cx="60" cy="60" r="59" fill="#f6f7f9" stroke="#ececec"/> + <rect x="34" y="34" width="52" height="52" rx="12" fill="#fff" stroke="#e51e22" stroke-opacity="0.35" stroke-dasharray="5 4"/> + <text x="60" y="64" text-anchor="middle" font-family="Tahoma, sans-serif" font-size="11" fill="#7c7c7c">ساعت و بن</text> + <text x="60" y="78" text-anchor="middle" font-family="Tahoma, sans-serif" font-size="8" fill="#c9c9c9">1255</text> +</svg> diff --git a/public/images/categories/1256.svg b/public/images/categories/1256.svg new file mode 100644 index 0000000..c97c2a7 --- /dev/null +++ b/public/images/categories/1256.svg @@ -0,0 +1,6 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 120 120" width="120" height="120" role="img" aria-label="کنسول بازی"> + <circle cx="60" cy="60" r="59" fill="#f6f7f9" stroke="#ececec"/> + <rect x="34" y="34" width="52" height="52" rx="12" fill="#fff" stroke="#e51e22" stroke-opacity="0.35" stroke-dasharray="5 4"/> + <text x="60" y="64" text-anchor="middle" font-family="Tahoma, sans-serif" font-size="11" fill="#7c7c7c">کنسول باز</text> + <text x="60" y="78" text-anchor="middle" font-family="Tahoma, sans-serif" font-size="8" fill="#c9c9c9">1256</text> +</svg> diff --git a/public/images/categories/1258.svg b/public/images/categories/1258.svg new file mode 100644 index 0000000..3114ccb --- /dev/null +++ b/public/images/categories/1258.svg @@ -0,0 +1,6 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 120 120" width="120" height="120" role="img" aria-label="گوشی تلفن"> + <circle cx="60" cy="60" r="59" fill="#f6f7f9" stroke="#ececec"/> + <rect x="34" y="34" width="52" height="52" rx="12" fill="#fff" stroke="#e51e22" stroke-opacity="0.35" stroke-dasharray="5 4"/> + <text x="60" y="64" text-anchor="middle" font-family="Tahoma, sans-serif" font-size="11" fill="#7c7c7c">گوشی تلفن</text> + <text x="60" y="78" text-anchor="middle" font-family="Tahoma, sans-serif" font-size="8" fill="#c9c9c9">1258</text> +</svg> diff --git a/public/images/categories/1259.svg b/public/images/categories/1259.svg new file mode 100644 index 0000000..bf87a6c --- /dev/null +++ b/public/images/categories/1259.svg @@ -0,0 +1,6 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 120 120" width="120" height="120" role="img" aria-label="مانیتور"> + <circle cx="60" cy="60" r="59" fill="#f6f7f9" stroke="#ececec"/> + <rect x="34" y="34" width="52" height="52" rx="12" fill="#fff" stroke="#e51e22" stroke-opacity="0.35" stroke-dasharray="5 4"/> + <text x="60" y="64" text-anchor="middle" font-family="Tahoma, sans-serif" font-size="11" fill="#7c7c7c">مانیتور</text> + <text x="60" y="78" text-anchor="middle" font-family="Tahoma, sans-serif" font-size="8" fill="#c9c9c9">1259</text> +</svg> diff --git a/public/images/categories/1260.svg b/public/images/categories/1260.svg new file mode 100644 index 0000000..b07d2a3 --- /dev/null +++ b/public/images/categories/1260.svg @@ -0,0 +1,6 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 120 120" width="120" height="120" role="img" aria-label="لوازم برقی"> + <circle cx="60" cy="60" r="59" fill="#f6f7f9" stroke="#ececec"/> + <rect x="34" y="34" width="52" height="52" rx="12" fill="#fff" stroke="#e51e22" stroke-opacity="0.35" stroke-dasharray="5 4"/> + <text x="60" y="64" text-anchor="middle" font-family="Tahoma, sans-serif" font-size="11" fill="#7c7c7c">لوازم برق</text> + <text x="60" y="78" text-anchor="middle" font-family="Tahoma, sans-serif" font-size="8" fill="#c9c9c9">1260</text> +</svg> diff --git a/public/images/categories/1261.svg b/public/images/categories/1261.svg new file mode 100644 index 0000000..fc31f59 --- /dev/null +++ b/public/images/categories/1261.svg @@ -0,0 +1,6 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 120 120" width="120" height="120" role="img" aria-label="دوربین عکاسی"> + <circle cx="60" cy="60" r="59" fill="#f6f7f9" stroke="#ececec"/> + <rect x="34" y="34" width="52" height="52" rx="12" fill="#fff" stroke="#e51e22" stroke-opacity="0.35" stroke-dasharray="5 4"/> + <text x="60" y="64" text-anchor="middle" font-family="Tahoma, sans-serif" font-size="11" fill="#7c7c7c">دوربین عک</text> + <text x="60" y="78" text-anchor="middle" font-family="Tahoma, sans-serif" font-size="8" fill="#c9c9c9">1261</text> +</svg> diff --git a/public/images/categories/1263.svg b/public/images/categories/1263.svg new file mode 100644 index 0000000..b252a51 --- /dev/null +++ b/public/images/categories/1263.svg @@ -0,0 +1,6 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 120 120" width="120" height="120" role="img" aria-label="مودم همراه و ثابت"> + <circle cx="60" cy="60" r="59" fill="#f6f7f9" stroke="#ececec"/> + <rect x="34" y="34" width="52" height="52" rx="12" fill="#fff" stroke="#e51e22" stroke-opacity="0.35" stroke-dasharray="5 4"/> + <text x="60" y="64" text-anchor="middle" font-family="Tahoma, sans-serif" font-size="11" fill="#7c7c7c">مودم همرا</text> + <text x="60" y="78" text-anchor="middle" font-family="Tahoma, sans-serif" font-size="8" fill="#c9c9c9">1263</text> +</svg> diff --git a/public/images/categories/1264.svg b/public/images/categories/1264.svg new file mode 100644 index 0000000..afba72d --- /dev/null +++ b/public/images/categories/1264.svg @@ -0,0 +1,6 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 120 120" width="120" height="120" role="img" aria-label="لوازم عکاسی"> + <circle cx="60" cy="60" r="59" fill="#f6f7f9" stroke="#ececec"/> + <rect x="34" y="34" width="52" height="52" rx="12" fill="#fff" stroke="#e51e22" stroke-opacity="0.35" stroke-dasharray="5 4"/> + <text x="60" y="64" text-anchor="middle" font-family="Tahoma, sans-serif" font-size="11" fill="#7c7c7c">لوازم عکا</text> + <text x="60" y="78" text-anchor="middle" font-family="Tahoma, sans-serif" font-size="8" fill="#c9c9c9">1264</text> +</svg> diff --git a/public/images/categories/1268.svg b/public/images/categories/1268.svg new file mode 100644 index 0000000..f5533eb --- /dev/null +++ b/public/images/categories/1268.svg @@ -0,0 +1,6 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 120 120" width="120" height="120" role="img" aria-label="تجهیزات عکس برداری"> + <circle cx="60" cy="60" r="59" fill="#f6f7f9" stroke="#ececec"/> + <rect x="34" y="34" width="52" height="52" rx="12" fill="#fff" stroke="#e51e22" stroke-opacity="0.35" stroke-dasharray="5 4"/> + <text x="60" y="64" text-anchor="middle" font-family="Tahoma, sans-serif" font-size="11" fill="#7c7c7c">تجهیزات ع</text> + <text x="60" y="78" text-anchor="middle" font-family="Tahoma, sans-serif" font-size="8" fill="#c9c9c9">1268</text> +</svg> diff --git a/public/images/categories/1269.svg b/public/images/categories/1269.svg new file mode 100644 index 0000000..42402ac --- /dev/null +++ b/public/images/categories/1269.svg @@ -0,0 +1,6 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 120 120" width="120" height="120" role="img" aria-label="خط0912"> + <circle cx="60" cy="60" r="59" fill="#f6f7f9" stroke="#ececec"/> + <rect x="34" y="34" width="52" height="52" rx="12" fill="#fff" stroke="#e51e22" stroke-opacity="0.35" stroke-dasharray="5 4"/> + <text x="60" y="64" text-anchor="middle" font-family="Tahoma, sans-serif" font-size="11" fill="#7c7c7c">خط0912</text> + <text x="60" y="78" text-anchor="middle" font-family="Tahoma, sans-serif" font-size="8" fill="#c9c9c9">1269</text> +</svg> diff --git a/public/images/categories/1271.svg b/public/images/categories/1271.svg new file mode 100644 index 0000000..6a19006 --- /dev/null +++ b/public/images/categories/1271.svg @@ -0,0 +1,6 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 120 120" width="120" height="120" role="img" aria-label="میکروفون"> + <circle cx="60" cy="60" r="59" fill="#f6f7f9" stroke="#ececec"/> + <rect x="34" y="34" width="52" height="52" rx="12" fill="#fff" stroke="#e51e22" stroke-opacity="0.35" stroke-dasharray="5 4"/> + <text x="60" y="64" text-anchor="middle" font-family="Tahoma, sans-serif" font-size="11" fill="#7c7c7c">میکروفون</text> + <text x="60" y="78" text-anchor="middle" font-family="Tahoma, sans-serif" font-size="8" fill="#c9c9c9">1271</text> +</svg> diff --git a/public/images/logo.png b/public/images/logo.png new file mode 100644 index 0000000..608fca3 Binary files /dev/null and b/public/images/logo.png differ diff --git a/public/images/products/placeholder.svg b/public/images/products/placeholder.svg new file mode 100644 index 0000000..f41a64b --- /dev/null +++ b/public/images/products/placeholder.svg @@ -0,0 +1,7 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 400 400" width="400" height="400" role="img" aria-label="بدون تصویر"> + <rect width="400" height="400" fill="#f6f7f9"/> + <rect x="60" y="60" width="280" height="280" rx="16" fill="#fff" stroke="#ececec" stroke-width="2" stroke-dasharray="8 6"/> + <circle cx="160" cy="160" r="24" fill="#ececec"/> + <path d="M100 300l70-80 60 60 40-50 70 70" fill="none" stroke="#ececec" stroke-width="10" stroke-linecap="round" stroke-linejoin="round"/> + <text x="200" y="350" text-anchor="middle" font-family="Tahoma, sans-serif" font-size="16" fill="#7c7c7c">بدون تصویر</text> +</svg> diff --git a/scripts/gen-category-placeholders.mjs b/scripts/gen-category-placeholders.mjs new file mode 100644 index 0000000..2251efa --- /dev/null +++ b/scripts/gen-category-placeholders.mjs @@ -0,0 +1,34 @@ +// Writes one placeholder thumbnail per top-level product category into +// public/images/categories/{id}.svg. They are meant to be replaced with real +// artwork one file at a time — the carousel reads the same paths either way. +// Run: node scripts/gen-category-placeholders.mjs +import { mkdir, writeFile } from "node:fs/promises"; + +const DOMAIN = process.env.NEXT_PUBLIC_SITE_DOMAIN || "moalem.shop"; +const OUT = new URL("../public/images/categories/", import.meta.url); + +const res = await fetch( + `https://api.meshkee.com/api/v1/tenants/${DOMAIN}/categories?entityType=product` +); +const { items } = await res.json(); + +const roots = items.filter((c) => c.parentId === null).map((c) => c.id); +const level2 = items.filter((c) => c.parentId && roots.includes(c.parentId)); + +await mkdir(OUT, { recursive: true }); + +const escape = (s) => s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">"); + +for (const cat of level2) { + const label = escape(cat.nameFa || cat.name); + const svg = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 120 120" width="120" height="120" role="img" aria-label="${label}"> + <circle cx="60" cy="60" r="59" fill="#f6f7f9" stroke="#ececec"/> + <rect x="34" y="34" width="52" height="52" rx="12" fill="#fff" stroke="#e51e22" stroke-opacity="0.35" stroke-dasharray="5 4"/> + <text x="60" y="64" text-anchor="middle" font-family="Tahoma, sans-serif" font-size="11" fill="#7c7c7c">${label.slice(0, 9)}</text> + <text x="60" y="78" text-anchor="middle" font-family="Tahoma, sans-serif" font-size="8" fill="#c9c9c9">${cat.id}</text> +</svg> +`; + await writeFile(new URL(`${cat.id}.svg`, OUT), svg, "utf8"); +} + +console.log(`wrote ${level2.length} placeholders to public/images/categories/`); diff --git a/src/app/blog/[id]/[slug]/page.tsx b/src/app/blog/[id]/[slug]/page.tsx new file mode 100644 index 0000000..12aa647 --- /dev/null +++ b/src/app/blog/[id]/[slug]/page.tsx @@ -0,0 +1,100 @@ +import type { Metadata } from "next"; +import Image from "next/image"; +import { notFound } from "next/navigation"; +import Breadcrumbs from "@/components/Breadcrumbs"; +import CommentSection from "@/components/CommentSection"; +import BlogCarousel, { type BlogCardData } from "@/components/BlogCarousel"; +import { getBlogById, getBlogs, getComments, safe } from "@/lib/api"; +import { getSession } from "@/lib/auth"; +import { blogHref } from "@/lib/slug"; + +export async function generateMetadata({ + params, +}: { + params: Promise<{ id: string }>; +}): Promise<Metadata> { + const { id } = await params; + try { + const { blog } = await getBlogById(id); + const title = blog.title ?? blog.nameFa ?? ""; + return { + title, + description: (blog.summary || title).slice(0, 160), + openGraph: blog.thumbnailUrl ? { images: [{ url: blog.thumbnailUrl }] } : undefined, + }; + } catch { + return {}; + } +} + +export default async function BlogDetailPage({ params }: { params: Promise<{ id: string }> }) { + const { id } = await params; + + let blog; + try { + ({ blog } = await getBlogById(id)); + } catch { + notFound(); + } + + const title = blog.title ?? blog.nameFa ?? ""; + const image = blog.thumbnailUrl ?? blog.image ?? null; + const category = blog.categoryNameFa ?? blog.categoryName; + + // ApiBlog doesn't carry a categoryId (only category name strings), so + // "more posts" is just the latest others rather than a same-category query. + const [{ items: comments }, moreResult, user] = await Promise.all([ + safe(getComments("blog", blog.id), { items: [] }), + safe(getBlogs({ pageSize: 8 }), { items: [], total: 0, page: 1, pageSize: 8 }), + getSession(), + ]); + + const morePosts: BlogCardData[] = moreResult.items + .filter((p) => p.id !== blog.id) + .slice(0, 4) + .map((p) => ({ + id: p.id, + href: blogHref(p.id, p.title ?? p.nameFa), + title: p.title ?? p.nameFa ?? "", + image: p.thumbnailUrl ?? p.image ?? null, + category: p.categoryNameFa ?? p.categoryName ?? undefined, + })); + + return ( + <main className="boxed py-8 md:py-12"> + <Breadcrumbs + items={[ + { label: "مجله معلم", href: "/blog" }, + ...(category ? [{ label: category }] : []), + ]} + /> + + <article className="mx-auto max-w-3xl"> + {category && <span className="text-[12px] font-bold text-brand">{category}</span>} + <h1 className="mt-2 text-xl font-bold text-ink md:text-2xl">{title}</h1> + + {image && ( + <span className="relative mt-5 block aspect-[16/9] w-full overflow-hidden rounded-xl bg-surface"> + <Image src={image} alt={title} fill sizes="768px" className="object-cover" priority /> + </span> + )} + + {blog.contentHtml || blog.descriptionHtml ? ( + <div + className="rich-text mt-6 text-sm leading-8 text-body" + dangerouslySetInnerHTML={{ __html: blog.contentHtml || blog.descriptionHtml || "" }} + /> + ) : blog.summary ? ( + <p className="mt-6 text-sm leading-8 text-body">{blog.summary}</p> + ) : null} + + <div className="mt-12"> + <h2 className="mb-5 text-lg font-bold text-ink">دیدگاه‌ها{comments.length > 0 ? ` (${comments.length})` : ""}</h2> + <CommentSection entityType="blog" entityId={blog.id} initialComments={comments} user={user} /> + </div> + </article> + + {morePosts.length > 0 && <BlogCarousel posts={morePosts} />} + </main> + ); +} diff --git a/src/app/blog/page.tsx b/src/app/blog/page.tsx new file mode 100644 index 0000000..1425b47 --- /dev/null +++ b/src/app/blog/page.tsx @@ -0,0 +1,89 @@ +import type { Metadata } from "next"; +import Image from "next/image"; +import Link from "next/link"; +import Breadcrumbs from "@/components/Breadcrumbs"; +import Pagination from "@/components/ui/Pagination"; +import { getBlogs, safe } from "@/lib/api"; +import { blogHref } from "@/lib/slug"; +import { FALLBACK_BLOGS } from "@/data/fallbackBlogs"; + +const PAGE_SIZE = 12; + +export const metadata: Metadata = { + title: "مجله معلم", + description: "تازه‌ترین راهنماهای خرید، مقایسه محصولات و اخبار دنیای دیجیتال از مجله فروشگاه موبایل معلم.", +}; + +export default async function BlogListPage({ + searchParams, +}: { + searchParams: Promise<{ page?: string }>; +}) { + const { page: pageParam } = await searchParams; + const page = Math.max(1, Number(pageParam) || 1); + + const result = await safe(getBlogs({ page, pageSize: PAGE_SIZE }), { + items: [], + total: 0, + page, + pageSize: PAGE_SIZE, + }); + + const usingFallback = result.items.length === 0; + + return ( + <main className="boxed py-8 md:py-12"> + <Breadcrumbs items={[{ label: "مجله معلم" }]} /> + <h1 className="mb-1 text-xl font-bold text-ink md:text-2xl">مجله معلم</h1> + <p className="mb-8 text-[13px] text-body">راهنمای خرید، مقایسه محصولات و اخبار دنیای دیجیتال</p> + + <div className="grid grid-cols-1 gap-5 sm:grid-cols-2 lg:grid-cols-3"> + {usingFallback + ? FALLBACK_BLOGS.map((post) => ( + // No real detail page exists yet for these — see AGENTS.md fallback + // table — so the card is informational, not a link. + <div key={post.id} className="overflow-hidden rounded-xl border border-line bg-white"> + <span className="relative block aspect-[16/9] w-full bg-surface"> + <Image src={post.image} alt={post.title} fill sizes="360px" className="object-cover" /> + <span className="absolute right-2 top-2 rounded-full bg-white/90 px-2.5 py-1 text-[11px] font-bold text-brand"> + {post.category} + </span> + </span> + <p className="clamp-2 p-3 text-[13px] leading-6 text-ink-soft">{post.title}</p> + </div> + )) + : result.items.map((post) => { + const title = post.title ?? post.nameFa ?? ""; + const image = post.thumbnailUrl ?? post.image ?? null; + const category = post.categoryNameFa ?? post.categoryName; + return ( + <Link + key={post.id} + href={blogHref(post.id, title)} + className="overflow-hidden rounded-xl border border-line bg-white transition hover:border-brand/40 hover:shadow-[0_6px_20px_rgba(0,0,0,0.07)]" + > + <span className="relative block aspect-[16/9] w-full bg-surface"> + {image && <Image src={image} alt={title} fill sizes="360px" className="object-cover" />} + {category && ( + <span className="absolute right-2 top-2 rounded-full bg-white/90 px-2.5 py-1 text-[11px] font-bold text-brand"> + {category} + </span> + )} + </span> + <p className="clamp-2 p-3 text-[13px] leading-6 text-ink-soft">{title}</p> + </Link> + ); + })} + </div> + + {!usingFallback && ( + <Pagination + page={page} + pageSize={PAGE_SIZE} + total={result.total} + buildHref={(p) => (p > 1 ? `/blog?page=${p}` : "/blog")} + /> + )} + </main> + ); +} diff --git a/src/app/fonts.ts b/src/app/fonts.ts new file mode 100644 index 0000000..f831f48 --- /dev/null +++ b/src/app/fonts.ts @@ -0,0 +1,12 @@ +import localFont from "next/font/local"; + +// IRANYekan is the face the live moalem.shop theme uses; the two weights +// register under one CSS variable so font-weight alone picks the file. +export const iranYekan = localFont({ + src: [ + { path: "../../public/fonts/iranyekanweblightfanum.woff", weight: "300", style: "normal" }, + { path: "../../public/fonts/iranyekanwebboldfanum.woff", weight: "700", style: "normal" }, + ], + variable: "--font-fa", + display: "swap", +}); diff --git a/src/app/globals.css b/src/app/globals.css new file mode 100644 index 0000000..ec240ed --- /dev/null +++ b/src/app/globals.css @@ -0,0 +1,86 @@ +@import "tailwindcss"; + +:root { + --background: #ffffff; + --foreground: #2e2e2e; + + /* Brand palette lifted from the live moalem.shop theme + (--wb-primary-color #E51E22 / --wb-secondary-color #3B3B3B). Flat, no gradients. */ + --color-brand: #e51e22; + --color-brand-dark: #b10123; + --color-brand-soft: #fdecec; + --color-ink: #2e2e2e; + --color-ink-soft: #3b3b3b; + --color-body: #7c7c7c; + --color-surface: #f6f7f9; + --color-line: #ececec; +} + +@theme inline { + --color-background: var(--background); + --color-foreground: var(--foreground); + + --color-brand: var(--color-brand); + --color-brand-dark: var(--color-brand-dark); + --color-brand-soft: var(--color-brand-soft); + --color-ink: var(--color-ink); + --color-ink-soft: var(--color-ink-soft); + --color-body: var(--color-body); + --color-surface: var(--color-surface); + --color-line: var(--color-line); + + --font-fa: var(--font-fa); + --font-en: var(--font-en); + + --container-boxed: 1200px; +} + +/* Base defaults live in Tailwind's base layer so utilities can override them. */ +@layer base { + body { + background: var(--background); + color: var(--color-ink); + font-family: var(--font-fa), Tahoma, sans-serif; + font-weight: 300; + } + + h1, + h2, + h3, + h4 { + color: var(--color-ink); + font-weight: 700; + } +} + +@utility boxed { + width: 100%; + max-width: var(--container-boxed); + margin-inline: auto; + padding-inline: 1rem; +} + +/* Latin runs (model names, prices in EN digits) inside Farsi copy. */ +.en { + font-family: var(--font-en), var(--font-fa), sans-serif; +} + +/* Swiper: the package ships its own CSS, these are the site-level overrides. */ +.swiper-pagination-bullet { + background: #ffffff; + opacity: 0.55; + width: 8px; + height: 8px; +} +.swiper-pagination-bullet-active { + opacity: 1; + width: 22px; + border-radius: 4px; +} + +.clamp-2 { + display: -webkit-box; + -webkit-line-clamp: 2; + -webkit-box-orient: vertical; + overflow: hidden; +} diff --git a/src/app/layout.tsx b/src/app/layout.tsx new file mode 100644 index 0000000..c02e622 --- /dev/null +++ b/src/app/layout.tsx @@ -0,0 +1,64 @@ +import type { Metadata } from "next"; +import { Montserrat } from "next/font/google"; +import "./globals.css"; +import { iranYekan } from "./fonts"; +import Header from "@/components/Header"; +import Footer from "@/components/Footer"; +import TopBar from "@/components/TopBar"; +import MobileBottomNav from "@/components/MobileBottomNav"; +import { getCategories, safe } from "@/lib/api"; +import { buildCategoryTree, carouselCategories } from "@/lib/categoryTree"; +import { getSession } from "@/lib/auth"; +import { SITE_NAME } from "@/data/site"; +import { SITE_URL } from "@/lib/config"; + +// Montserrat is the Latin face the live moalem.shop theme pairs with IRANYekan. +const montserrat = Montserrat({ + variable: "--font-en", + subsets: ["latin"], + weight: ["500", "700"], + display: "swap", +}); + +export const metadata: Metadata = { + metadataBase: new URL(SITE_URL), + title: { + default: `${SITE_NAME} | خرید موبایل، لپ‌تاپ و لوازم جانبی`, + template: `%s | ${SITE_NAME}`, + }, + description: + "خرید اینترنتی گوشی موبایل، لپ‌تاپ، تبلت، کنسول بازی و لوازم جانبی با ضمانت اصالت کالا از فروشگاه موبایل معلم.", + icons: [{ url: "/favicon.png", type: "image/png" }], + openGraph: { + type: "website", + locale: "fa_IR", + siteName: SITE_NAME, + title: `${SITE_NAME} | خرید موبایل، لپ‌تاپ و لوازم جانبی`, + description: + "خرید اینترنتی گوشی موبایل، لپ‌تاپ، تبلت، کنسول بازی و لوازم جانبی با ضمانت اصالت کالا از فروشگاه موبایل معلم.", + url: SITE_URL, + images: [{ url: "/images/logo.png", width: 130, height: 36, alt: SITE_NAME }], + }, +}; + +export default async function RootLayout({ children }: LayoutProps<"/">) { + const [categories, user] = await Promise.all([ + safe(getCategories().then(buildCategoryTree), []), + getSession(), + ]); + const menu = carouselCategories(categories); + + return ( + <html lang="fa" dir="rtl" className={`${iranYekan.variable} ${montserrat.variable} h-full antialiased`}> + <body className="flex min-h-full flex-col"> + <TopBar /> + <Header menu={menu} user={user} /> + <main className="flex-1 pb-6">{children}</main> + <Footer categories={menu} /> + {/* Spacer so the fixed mobile bar never covers the end of the footer. */} + <div className="h-[68px] lg:hidden" /> + <MobileBottomNav categories={menu} user={user} /> + </body> + </html> + ); +} diff --git a/src/app/meshkee/sitemap-config.json/route.ts b/src/app/meshkee/sitemap-config.json/route.ts new file mode 100644 index 0000000..ead3b52 --- /dev/null +++ b/src/app/meshkee/sitemap-config.json/route.ts @@ -0,0 +1,64 @@ +import { readdir } from "node:fs/promises"; +import path from "node:path"; +import { NextResponse } from "next/server"; +import { SITE_URL } from "@/lib/config"; + +// Static routes only, generated from src/app itself rather than +// hand-maintained — a hand list drifts the moment a page is added or removed +// and starts advertising 404s. CMS-backed detail pages (products, categories, +// blog, portfolios) are resolved by Meshkee straight from the API and must +// stay out of this list; so must anything auth/checkout/cart/admin-shaped. +const APP_DIR = path.join(process.cwd(), "src", "app"); + +const EXCLUDED_SEGMENTS = new Set(["meshkee", "api"]); +const EXCLUDED_PREFIXES = ["/products", "/blog", "/orders", "/cart", "/account", "/checkout", "/admin"]; + +const PRIORITY: Record<string, number> = { + "/": 1.0, + "/about": 0.6, + "/contact": 0.5, +}; + +async function collectStaticPages(dir: string, segments: string[] = []): Promise<string[]> { + const entries = await readdir(dir, { withFileTypes: true }); + const pages: string[] = []; + + const hasPage = entries.some( + (e) => e.isFile() && /^page\.(tsx|ts|jsx|js)$/.test(e.name) + ); + if (hasPage) { + const route = "/" + segments.join("/"); + pages.push(route === "/" ? "/" : route.replace(/\/$/, "")); + } + + for (const entry of entries) { + if (!entry.isDirectory()) continue; + const name = entry.name; + // Route groups "(x)" don't affect the URL; dynamic "[x]" and parallel/ + // intercepting segments aren't static; "_x" and reserved dirs are skipped. + if (name.startsWith("_") || EXCLUDED_SEGMENTS.has(name)) continue; + if (name.startsWith("[") || name.startsWith("@") || name.startsWith("(")) { + if (name.startsWith("(")) { + pages.push(...(await collectStaticPages(path.join(dir, name), segments))); + } + continue; + } + pages.push(...(await collectStaticPages(path.join(dir, name), [...segments, name]))); + } + + return pages; +} + +export async function GET() { + const routes = await collectStaticPages(APP_DIR); + const staticPages = routes + .filter((route) => !EXCLUDED_PREFIXES.some((p) => route === p || route.startsWith(`${p}/`))) + .sort((a, b) => (a === "/" ? -1 : b === "/" ? 1 : a.localeCompare(b))) + .map((route) => ({ + path: route, + changefreq: route === "/" ? "daily" : "monthly", + priority: PRIORITY[route] ?? 0.5, + })); + + return NextResponse.json({ baseUrl: SITE_URL, staticPages }); +} diff --git a/src/app/meshkee/static-image-slots/route.ts b/src/app/meshkee/static-image-slots/route.ts new file mode 100644 index 0000000..482014d --- /dev/null +++ b/src/app/meshkee/static-image-slots/route.ts @@ -0,0 +1,63 @@ +import { NextResponse } from "next/server"; + +// Slot catalog the business dashboard imports with its Refresh button. +// +// RULE: every `aspectRatio` / `recommendedWidth` / `itemCount` below must +// match what the matching component actually renders — HeroSlider.tsx +// (FALLBACK_ASPECT_CLASSES), CategoryCarousel.tsx (90px circles) and +// PromoBanners.tsx (defaultAspect/recommendedWidth per banner count). If a +// component's aspect ratio or image size changes during development, update +// its entry here in the same commit — this file is what tells the dashboard, +// and the designer, what to upload; letting it drift makes every future +// upload the wrong shape. +export function GET() { + return NextResponse.json({ + slots: [ + { + key: "slider", + label: "اسلایدر صفحه اصلی", + kind: "list", + pageKey: "home", + aspectRatio: "1296:366", + itemCount: null, + recommendedWidth: 1296, + }, + { + key: "categories", + label: "تصویر دسته‌بندی‌های صفحه اصلی (به ترتیب دسته‌ها)", + kind: "list", + pageKey: "home", + aspectRatio: "1:1", + itemCount: null, + recommendedWidth: 180, + }, + { + key: "one-banner-bg", + label: "بنر تمام‌عرض", + kind: "single", + pageKey: "home", + aspectRatio: "16:5", + itemCount: 1, + recommendedWidth: 1200, + }, + { + key: "two-banner-bg", + label: "ردیف دو بنر", + kind: "list", + pageKey: "home", + aspectRatio: "16:9", + itemCount: 2, + recommendedWidth: 580, + }, + { + key: "three-banner-bg", + label: "ردیف سه بنر", + kind: "list", + pageKey: "home", + aspectRatio: "4:3", + itemCount: 3, + recommendedWidth: 380, + }, + ], + }); +} diff --git a/src/app/page.tsx b/src/app/page.tsx new file mode 100644 index 0000000..76fe88b --- /dev/null +++ b/src/app/page.tsx @@ -0,0 +1,184 @@ +import HeroSlider, { type Slide } from "@/components/HeroSlider"; +import CategoryCarousel from "@/components/CategoryCarousel"; +import HotDeals from "@/components/HotDeals"; +import ProductCarousel from "@/components/ProductCarousel"; +import { OneBanner, ThreeBanners, TwoBanners } from "@/components/PromoBanners"; +import BlogCarousel, { type BlogCardData } from "@/components/BlogCarousel"; +import { + findSlot, + getBlogs, + getCategories, + getProducts, + getSliders, + getStaticImageSlots, + getStoreSpecials, + safe, +} from "@/lib/api"; +import { buildCategoryTree, carouselCategories, findNode, subtreeIds } from "@/lib/categoryTree"; +import { productToCard, specialToCards } from "@/lib/cards"; +import { blogHref, categoryHref } from "@/lib/slug"; +import { HOME_CAROUSELS } from "@/data/site"; +import { FALLBACK_BLOGS } from "@/data/fallbackBlogs"; + +// The slider slot is empty on this tenant, so the homepage falls back to the +// campaign banners already running on moalem.shop (same art, same targets). +const FALLBACK_SLIDES: Slide[] = [ + { + id: "laptop", + image: "/images/banners/9WYorEOTwn.jpg", + mobileImage: "/images/banners/WRbs72TDPR.jpg", + href: categoryHref("1254", "لپ تاپ"), + alt: "لپ تاپ", + }, + { + id: "console", + image: "/images/banners/OiNl9v5gTf.jpg", + mobileImage: "/images/banners/ieYGotL2qd.jpg", + href: categoryHref("1256", "کنسول بازی"), + alt: "کنسول بازی", + }, + { + id: "iphone-17", + image: "/images/banners/06SQinO4sG.jpg", + mobileImage: "/images/banners/lAGRh3ks32.jpg", + href: categoryHref("1251", "موبایل"), + alt: "آیفون ۱۷ پرو مکس", + }, +]; + +const EMPTY_PAGE = { items: [], total: 0, page: 1, pageSize: 12 }; + +export default async function HomePage() { + const [categories, sliders, specials, blogs, imageSlots] = await Promise.all([ + safe(getCategories().then(buildCategoryTree), []), + safe(getSliders(), []), + safe(getStoreSpecials(), []), + safe(getBlogs({ pageSize: 10 }), { items: [], total: 0, page: 1, pageSize: 10 }), + safe(getStaticImageSlots("home"), []), + ]); + + const carouselCats = carouselCategories(categories); + + // Empty or failing slots fall through to each section's own placeholder — + // findSlot() already returns undefined for an empty/missing key. + const sliderSlot = findSlot(imageSlots, "slider"); + const categoriesSlot = findSlot(imageSlots, "categories"); + const oneBannerSlot = findSlot(imageSlots, "one-banner-bg"); + const twoBannerSlot = findSlot(imageSlots, "two-banner-bg"); + const threeBannerSlot = findSlot(imageSlots, "three-banner-bg"); + + const slides: Slide[] = + sliders.length > 0 + ? sliders + .filter((s) => s.imageUrl) + .map((s, i) => ({ + id: s.id ?? `slide-${i}`, + image: s.imageUrl!, + mobileImage: s.mobileImageUrl ?? undefined, + href: s.linkUrl ?? "/products", + alt: s.title ?? "بنر فروشگاه موبایل معلم", + })) + : FALLBACK_SLIDES; + + // "تخفیف‌های داغ" prefers the curated special-discount group; until it is + // filled in the dashboard it falls back to the newest catalog products. + const discountGroup = + specials.find((s) => s.key === "special-discount" && s.isActive) ?? specials[0]; + const dealsFromCms = discountGroup ? specialToCards(discountGroup) : []; + const fallbackDeals = dealsFromCms.length + ? [] + : (await safe(getProducts({ pageSize: 12 }), EMPTY_PAGE)).items.map(productToCard); + const deals = dealsFromCms.length ? dealsFromCms : fallbackDeals; + + const carousels = await Promise.all( + HOME_CAROUSELS.map(async (section) => { + const cat = findNode(categories, section.categoryId); + const ids = cat ? subtreeIds(cat) : [section.categoryId]; + const pages = await Promise.all( + ids.map((categoryId) => safe(getProducts({ categoryId, pageSize: 12 }), EMPTY_PAGE)) + ); + + const seen = new Set<string>(); + const products = pages + .flatMap((page) => page.items) + .filter((product) => !seen.has(product.id) && seen.add(product.id)) + .slice(0, 12) + .map(productToCard); + + return { + ...section, + href: categoryHref(section.categoryId, cat?.nameFa ?? section.title, cat?.name), + products, + }; + }) + ); + + const posts: BlogCardData[] = + blogs.items.length > 0 + ? blogs.items.map((b) => ({ + id: b.id, + href: blogHref(b.id, b.title ?? b.nameFa), + title: b.title ?? b.nameFa ?? "", + image: b.thumbnailUrl ?? b.image ?? null, + category: b.categoryNameFa ?? b.categoryName ?? undefined, + })) + : FALLBACK_BLOGS.map((b) => ({ + id: b.id, + href: "/blog", + title: b.title, + image: b.image, + category: b.category, + })); + + // The deals countdown runs to the end of the current day, recomputed on each + // server render so the timer never shows a stale target. + const endOfDay = new Date(); + endOfDay.setHours(23, 59, 59, 999); + + const [mobileCarousel, accessoriesCarousel, laptopCarousel, consoleCarousel] = carousels; + + return ( + <> + <h1 className="sr-only">فروشگاه موبایل معلم | خرید موبایل، لپ‌تاپ و لوازم جانبی</h1> + <HeroSlider slot={sliderSlot} slides={slides} /> + <CategoryCarousel categories={carouselCats} slot={categoriesSlot} /> + <HotDeals products={deals} endsAt={endOfDay.getTime() + 2 * 86_400_000} /> + <ThreeBanners slot={threeBannerSlot} /> + {mobileCarousel && ( + <ProductCarousel + title={mobileCarousel.title} + desc={mobileCarousel.desc} + href={mobileCarousel.href} + products={mobileCarousel.products} + /> + )} + <TwoBanners slot={twoBannerSlot} /> + {accessoriesCarousel && ( + <ProductCarousel + title={accessoriesCarousel.title} + desc={accessoriesCarousel.desc} + href={accessoriesCarousel.href} + products={accessoriesCarousel.products} + /> + )} + {laptopCarousel && ( + <ProductCarousel + title={laptopCarousel.title} + desc={laptopCarousel.desc} + href={laptopCarousel.href} + products={laptopCarousel.products} + /> + )} + <OneBanner slot={oneBannerSlot} /> + {consoleCarousel && ( + <ProductCarousel + title={consoleCarousel.title} + desc={consoleCarousel.desc} + href={consoleCarousel.href} + products={consoleCarousel.products} + /> + )} + <BlogCarousel posts={posts} /> + </> + ); +} diff --git a/src/app/products/[id]/[slug]/page.tsx b/src/app/products/[id]/[slug]/page.tsx new file mode 100644 index 0000000..4fdd39b --- /dev/null +++ b/src/app/products/[id]/[slug]/page.tsx @@ -0,0 +1,105 @@ +import type { Metadata } from "next"; +import { notFound } from "next/navigation"; +import Breadcrumbs from "@/components/Breadcrumbs"; +import ProductGallery from "@/components/ProductGallery"; +import ProductBuyPanel from "@/components/ProductBuyPanel"; +import ProductInfoTabs from "@/components/ProductInfoTabs"; +import ProductCard from "@/components/ui/ProductCard"; +import SectionHeader from "@/components/ui/SectionHeader"; +import { + getComments, + getProductById, + getProductTechnicalInfo, + getStoreItemsByProduct, + safe, +} from "@/lib/api"; +import { getSession } from "@/lib/auth"; +import { buildSpecRows } from "@/lib/technicalInfo"; +import { productToCard } from "@/lib/cards"; +import { categoryHref } from "@/lib/slug"; + +export async function generateMetadata({ + params, +}: { + params: Promise<{ id: string }>; +}): Promise<Metadata> { + const { id } = await params; + try { + const { product } = await getProductById(id); + const title = product.nameFa || product.title; + return { + title, + description: (product.summary || title).slice(0, 160), + openGraph: product.thumbnailUrl ? { images: [{ url: product.thumbnailUrl }] } : undefined, + }; + } catch { + return {}; + } +} + +export default async function ProductDetailPage({ params }: { params: Promise<{ id: string }> }) { + const { id } = await params; + + let data; + try { + data = await getProductById(id); + } catch { + notFound(); + } + + const { product, relatedProducts } = data; + const title = product.nameFa || product.title; + + const [{ items: storeItems }, technicalInfo, { items: comments }, user] = await Promise.all([ + safe(getStoreItemsByProduct(product.id), { items: [] }), + safe(getProductTechnicalInfo(product.id), { form: null, values: [] }), + safe(getComments("product", product.id), { items: [] }), + getSession(), + ]); + + const images = [ + ...(product.thumbnailUrl ? [product.thumbnailUrl] : []), + ...product.images.map((img) => img.url), + ].filter((url, i, all) => all.indexOf(url) === i); + + const specRows = buildSpecRows(technicalInfo.form, technicalInfo.values); + + return ( + <main className="boxed py-8 md:py-12"> + <Breadcrumbs + items={[ + { label: "محصولات", href: "/products" }, + ...(product.categoryId && product.categoryNameFa + ? [{ label: product.categoryNameFa, href: categoryHref(product.categoryId, product.categoryNameFa) }] + : []), + { label: title }, + ]} + /> + + <div className="grid grid-cols-1 gap-8 md:gap-10 lg:grid-cols-[380px_1fr]"> + <ProductGallery images={images} title={title} /> + <ProductBuyPanel product={product} items={storeItems} user={user} /> + </div> + + <ProductInfoTabs + title={title} + descriptionHtml={product.descriptionHtml} + specRows={specRows} + comments={comments} + productId={product.id} + user={user} + /> + + {relatedProducts.length > 0 && ( + <div className="mt-14 md:mt-20"> + <SectionHeader title="محصولات مشابه" href={product.categoryId ? categoryHref(product.categoryId, product.categoryNameFa) : "/products"} /> + <div className="grid grid-cols-2 gap-4 sm:grid-cols-3 md:gap-5 lg:grid-cols-4"> + {relatedProducts.map((p) => ( + <ProductCard key={p.id} product={productToCard(p)} /> + ))} + </div> + </div> + )} + </main> + ); +} diff --git a/src/app/products/category/[id]/[slug]/page.tsx b/src/app/products/category/[id]/[slug]/page.tsx new file mode 100644 index 0000000..5b3bd1d --- /dev/null +++ b/src/app/products/category/[id]/[slug]/page.tsx @@ -0,0 +1,107 @@ +import type { Metadata } from "next"; +import { notFound } from "next/navigation"; +import Breadcrumbs from "@/components/Breadcrumbs"; +import ProductCard from "@/components/ui/ProductCard"; +import Pagination from "@/components/ui/Pagination"; +import { getCategories, getProducts, safe } from "@/lib/api"; +import { buildCategoryTree, findNode, pathToRoot, subtreeIdsAll } from "@/lib/categoryTree"; +import { productToCard } from "@/lib/cards"; +import { categoryHref } from "@/lib/slug"; + +const PAGE_SIZE = 24; + +export async function generateMetadata({ + params, +}: { + params: Promise<{ id: string }>; +}): Promise<Metadata> { + const { id } = await params; + const categories = buildCategoryTree(await safe(getCategories(), [])); + const category = findNode(categories, id); + if (!category) return {}; + + const name = category.nameFa || category.name; + return { + title: name, + description: `خرید ${name} با بهترین قیمت از فروشگاه موبایل معلم.`, + }; +} + +export default async function CategoryPage({ + params, + searchParams, +}: { + params: Promise<{ id: string }>; + searchParams: Promise<{ page?: string }>; +}) { + const { id } = await params; + const { page: pageParam } = await searchParams; + const page = Math.max(1, Number(pageParam) || 1); + + const roots = buildCategoryTree(await safe(getCategories(), [])); + const category = findNode(roots, id); + if (!category) notFound(); + + const name = category.nameFa || category.name; + const crumbs = pathToRoot(roots, id); + + // A parent category (e.g. «لوازم جانبی») has no products of its own — + // products are filed against leaves — so every id in the subtree is + // queried and the results merged, then paginated in memory. + const leafIds = subtreeIdsAll(category); + const perLeafPageSize = leafIds.length > 1 ? 200 : PAGE_SIZE; + const pages = await Promise.all( + leafIds.map((categoryId) => + safe(getProducts({ categoryId, pageSize: perLeafPageSize }), { + items: [], + total: 0, + page: 1, + pageSize: perLeafPageSize, + }) + ) + ); + + const seen = new Set<string>(); + const allProducts = pages + .flatMap((p) => p.items) + .filter((p) => !seen.has(p.id) && seen.add(p.id)); + + const total = allProducts.length; + const start = (page - 1) * PAGE_SIZE; + const items = allProducts.slice(start, start + PAGE_SIZE); + + return ( + <main className="boxed py-8 md:py-12"> + <Breadcrumbs + items={[ + { label: "محصولات", href: "/products" }, + ...crumbs.map((c, i) => ({ + label: c.nameFa || c.name, + href: i < crumbs.length - 1 ? categoryHref(c.id, c.nameFa, c.name) : undefined, + })), + ]} + /> + <h1 className="mb-1 text-xl font-bold text-ink md:text-2xl">{name}</h1> + <p className="mb-8 text-[13px] text-body">{total.toLocaleString("fa-IR")} محصول</p> + + {items.length > 0 ? ( + <div className="grid grid-cols-2 gap-4 sm:grid-cols-3 md:gap-5 lg:grid-cols-4 xl:grid-cols-5"> + {items.map((p) => ( + <ProductCard key={p.id} product={productToCard(p)} /> + ))} + </div> + ) : ( + <p className="rounded-xl border border-dashed border-line bg-surface py-16 text-center text-sm text-body"> + محصولی در این دسته‌بندی ثبت نشده است. + </p> + )} + + <Pagination + page={page} + pageSize={PAGE_SIZE} + total={total} + buildHref={(p) => (p > 1 ? `${categoryHref(id, category.nameFa, category.name)}?page=${p}` : categoryHref(id, category.nameFa, category.name))} + /> + </main> + ); +} diff --git a/src/app/products/page.tsx b/src/app/products/page.tsx new file mode 100644 index 0000000..73e1c75 --- /dev/null +++ b/src/app/products/page.tsx @@ -0,0 +1,62 @@ +import type { Metadata } from "next"; +import Breadcrumbs from "@/components/Breadcrumbs"; +import ProductCard from "@/components/ui/ProductCard"; +import Pagination from "@/components/ui/Pagination"; +import { getProducts, safe } from "@/lib/api"; +import { productToCard } from "@/lib/cards"; + +const PAGE_SIZE = 24; + +export const metadata: Metadata = { + title: "همه محصولات", + description: "فهرست کامل محصولات فروشگاه موبایل معلم — موبایل، لپ‌تاپ، تبلت، کنسول بازی و لوازم جانبی.", +}; + +export default async function ProductsPage({ + searchParams, +}: { + searchParams: Promise<{ page?: string; name?: string }>; +}) { + const params = await searchParams; + const page = Math.max(1, Number(params.page) || 1); + const name = params.name?.trim(); + + const result = await safe(getProducts({ page, pageSize: PAGE_SIZE, name }), { + items: [], + total: 0, + page, + pageSize: PAGE_SIZE, + }); + + const buildHref = (p: number) => { + const qs = new URLSearchParams(); + if (name) qs.set("name", name); + if (p > 1) qs.set("page", String(p)); + const query = qs.toString(); + return `/products${query ? `?${query}` : ""}`; + }; + + return ( + <main className="boxed py-8 md:py-12"> + <Breadcrumbs items={[{ label: "محصولات" }]} /> + <h1 className="mb-1 text-xl font-bold text-ink md:text-2xl"> + {name ? `نتایج جستجوی «${name}»` : "همه محصولات"} + </h1> + <p className="mb-8 text-[13px] text-body">{result.total.toLocaleString("fa-IR")} محصول</p> + + {result.items.length > 0 ? ( + <div className="grid grid-cols-2 gap-4 sm:grid-cols-3 md:gap-5 lg:grid-cols-4 xl:grid-cols-5"> + {result.items.map((p) => ( + <ProductCard key={p.id} product={productToCard(p)} /> + ))} + </div> + ) : ( + <p className="rounded-xl border border-dashed border-line bg-surface py-16 text-center text-sm text-body"> + محصولی پیدا نشد. + </p> + )} + + <Pagination page={page} pageSize={PAGE_SIZE} total={result.total} buildHref={buildHref} /> + </main> + ); +} diff --git a/src/components/AccountMenu.tsx b/src/components/AccountMenu.tsx new file mode 100644 index 0000000..b3cb670 --- /dev/null +++ b/src/components/AccountMenu.tsx @@ -0,0 +1,75 @@ +"use client"; + +import Link from "next/link"; +import { useEffect, useRef, useState } from "react"; +import { ChevronDown, UserIcon } from "./icons"; +import { BUSINESS_PORTAL_URL, CUSTOMER_PORTAL_URL } from "@/lib/config"; +import { displayName, isAdmin } from "@/lib/authUser"; +import type { AuthUser } from "@/lib/types"; + +// Desktop header account control. Anonymous: a plain link to the customer +// portal's login/register. Signed in: "نام عزیز" + a dropdown with پروفایل +// من / خروج, plus مدیریت وبسایت when the session looks like an admin's. +export default function AccountMenu({ user }: { user: AuthUser | null }) { + const [open, setOpen] = useState(false); + const ref = useRef<HTMLDivElement>(null); + + useEffect(() => { + const onClick = (e: MouseEvent) => { + if (!ref.current?.contains(e.target as Node)) setOpen(false); + }; + document.addEventListener("mousedown", onClick); + return () => document.removeEventListener("mousedown", onClick); + }, []); + + if (!user) { + return ( + <Link + href={CUSTOMER_PORTAL_URL} + className="flex h-11 items-center gap-2 rounded-xl border border-line px-3 text-[13px] transition hover:border-brand hover:text-brand" + > + <UserIcon className="size-5" /> + <span className="hidden sm:inline">ورود | ثبت نام</span> + </Link> + ); + } + + return ( + <div ref={ref} className="relative h-11"> + <button + onClick={() => setOpen((v) => !v)} + aria-expanded={open} + className="flex h-11 items-center gap-2 rounded-xl border border-line px-3 text-[13px] text-ink transition hover:border-brand hover:text-brand" + > + <UserIcon className="size-5" /> + <span className="hidden max-w-[120px] truncate sm:inline">{displayName(user)}</span> + <ChevronDown className={`size-4 transition ${open ? "rotate-180" : ""}`} /> + </button> + + {open && ( + <div className="absolute left-0 top-full z-50 mt-2 w-44 overflow-hidden rounded-xl border border-line bg-white py-1 shadow-xl"> + <Link + href={`${CUSTOMER_PORTAL_URL}/profile`} + className="block px-4 py-2.5 text-[13px] text-ink-soft transition hover:bg-surface hover:text-brand" + > + پروفایل من + </Link> + {isAdmin(user) && ( + <Link + href={BUSINESS_PORTAL_URL} + className="block px-4 py-2.5 text-[13px] text-ink-soft transition hover:bg-surface hover:text-brand" + > + مدیریت وبسایت + </Link> + )} + <Link + href={`${CUSTOMER_PORTAL_URL}/logout`} + className="block px-4 py-2.5 text-[13px] text-ink-soft transition hover:bg-surface hover:text-brand" + > + خروج + </Link> + </div> + )} + </div> + ); +} diff --git a/src/components/BlogCarousel.tsx b/src/components/BlogCarousel.tsx new file mode 100644 index 0000000..28c324e --- /dev/null +++ b/src/components/BlogCarousel.tsx @@ -0,0 +1,80 @@ +"use client"; + +import Image from "next/image"; +import Link from "next/link"; +import { useState } from "react"; +import type { Swiper as SwiperType } from "swiper"; +import { FreeMode } from "swiper/modules"; +import { Swiper, SwiperSlide } from "swiper/react"; +import "swiper/css"; +import "swiper/css/free-mode"; +import SectionHeader from "./ui/SectionHeader"; +import CarouselNav from "./ui/CarouselNav"; + +export interface BlogCardData { + id: string; + href: string; + title: string; + image: string | null; + category?: string; +} + +export default function BlogCarousel({ posts }: { posts: BlogCardData[] }) { + const [swiper, setSwiper] = useState<SwiperType | null>(null); + if (posts.length === 0) return null; + + return ( + <section className="boxed mt-10"> + <SectionHeader + title="مجله معلم" + desc="تازه‌ترین راهنماهای خرید و اخبار دنیای دیجیتال" + href="/blog" + nav={<CarouselNav swiper={swiper} />} + /> + + <Swiper + modules={[FreeMode]} + dir="rtl" + freeMode + onSwiper={setSwiper} + slidesPerView={1.3} + spaceBetween={16} + breakpoints={{ + 640: { slidesPerView: 2.3 }, + 1024: { slidesPerView: 4 }, + }} + > + {posts.map((post) => ( + <SwiperSlide key={post.id} className="h-auto"> + <Link + href={post.href} + className="flex h-full flex-col overflow-hidden rounded-xl border border-line bg-white transition hover:border-brand/40 hover:shadow-[0_6px_20px_rgba(0,0,0,0.07)]" + > + <span className="relative block aspect-[16/9] w-full bg-surface"> + {post.image && ( + <Image + src={post.image} + alt={post.title} + fill + sizes="(max-width: 1024px) 60vw, 280px" + className="object-cover" + /> + )} + {post.category && ( + <span className="absolute right-2 top-2 rounded-full bg-white/90 px-2.5 py-1 text-[11px] font-bold text-brand"> + {post.category} + </span> + )} + </span> + <span className="clamp-2 p-3 text-[13px] leading-6 text-ink-soft">{post.title}</span> + </Link> + </SwiperSlide> + ))} + </Swiper> + + <div className="mt-4 flex justify-center lg:hidden"> + <CarouselNav swiper={swiper} /> + </div> + </section> + ); +} diff --git a/src/components/Breadcrumbs.tsx b/src/components/Breadcrumbs.tsx new file mode 100644 index 0000000..b2c604c --- /dev/null +++ b/src/components/Breadcrumbs.tsx @@ -0,0 +1,26 @@ +import Link from "next/link"; +import { ChevronLeft } from "./icons"; + +export interface Crumb { + label: string; + href?: string; +} + +export default function Breadcrumbs({ items }: { items: Crumb[] }) { + return ( + <nav className="mb-5 flex flex-wrap items-center gap-1.5 text-[12.5px] text-body" aria-label="مسیر صفحه"> + {items.map((item, i) => ( + <span key={i} className="flex items-center gap-1.5"> + {i > 0 && <ChevronLeft className="size-3.5 opacity-60" />} + {item.href ? ( + <Link href={item.href} className="transition hover:text-brand"> + {item.label} + </Link> + ) : ( + <span className="text-ink-soft">{item.label}</span> + )} + </span> + ))} + </nav> + ); +} diff --git a/src/components/CategoryCarousel.tsx b/src/components/CategoryCarousel.tsx new file mode 100644 index 0000000..a035b5d --- /dev/null +++ b/src/components/CategoryCarousel.tsx @@ -0,0 +1,61 @@ +"use client"; + +import Image from "next/image"; +import Link from "next/link"; +import { FreeMode } from "swiper/modules"; +import { Swiper, SwiperSlide } from "swiper/react"; +import "swiper/css"; +import "swiper/css/free-mode"; +import { categoryHref } from "@/lib/slug"; +import type { CategoryNode, StaticImageSlot } from "@/lib/types"; + +// Circular category thumbnails. The "categories" static-image slot has no +// per-category id, so its images (kind: list, duplicatable) are matched +// positionally to the category list, in the same order the business fills +// them in — each still links to its real category page, only the art swaps. +// A category past the end of the slot, or the whole thing when the slot is +// empty, keeps its placeholder at public/images/categories/{id}.svg. +export default function CategoryCarousel({ + categories, + slot, +}: { + categories: CategoryNode[]; + slot?: StaticImageSlot; +}) { + if (categories.length === 0) return null; + const slotImages = slot?.images ?? []; + + return ( + <section className="boxed mt-8" aria-label="دسته‌بندی محصولات"> + <Swiper + modules={[FreeMode]} + dir="rtl" + freeMode + slidesPerView={3.5} + spaceBetween={12} + breakpoints={{ + 480: { slidesPerView: 4.5 }, + 768: { slidesPerView: 6.5 }, + 1024: { slidesPerView: 9 }, + }} + > + {categories.map((cat, i) => { + const label = cat.nameFa || cat.name; + const art = slotImages[i]?.url || `/images/categories/${cat.id}.svg`; + return ( + <SwiperSlide key={cat.id}> + <Link href={categoryHref(cat.id, cat.nameFa, cat.name)} className="group flex flex-col items-center gap-2"> + <span className="relative size-[90px] overflow-hidden rounded-full bg-surface transition group-hover:bg-brand-soft"> + <Image src={art} alt={label} fill sizes="90px" className="object-contain p-2" /> + </span> + <span className="text-center text-[12.5px] text-ink transition group-hover:text-brand"> + {label} + </span> + </Link> + </SwiperSlide> + ); + })} + </Swiper> + </section> + ); +} diff --git a/src/components/CommentSection.tsx b/src/components/CommentSection.tsx new file mode 100644 index 0000000..bffb05f --- /dev/null +++ b/src/components/CommentSection.tsx @@ -0,0 +1,104 @@ +"use client"; + +import Link from "next/link"; +import { useState } from "react"; +import { postComment } from "@/lib/api"; +import { CUSTOMER_PORTAL_URL } from "@/lib/config"; +import { displayName } from "@/lib/authUser"; +import type { AuthUser, EntityComment } from "@/lib/types"; + +// Comment list + form. The API's POST /comments doesn't itself require +// auth, but the brief asks the storefront to gate the form: signed-in +// visitors get a name-less textarea, anonymous visitors get a login prompt +// instead of the form. +export default function CommentSection({ + entityType, + entityId, + initialComments, + user, +}: { + entityType: "product" | "blog"; + entityId: string; + initialComments: EntityComment[]; + user: AuthUser | null; +}) { + const [comments, setComments] = useState(initialComments); + const [text, setText] = useState(""); + const [submitting, setSubmitting] = useState(false); + const [error, setError] = useState<string | null>(null); + + async function handleSubmit(e: React.FormEvent) { + e.preventDefault(); + if (!user || !text.trim()) return; + setSubmitting(true); + setError(null); + try { + const { comment } = await postComment({ + entityType, + entityId, + authorName: displayName(user).replace(/ عزیز$/, ""), + text: text.trim(), + }); + setComments((prev) => [comment, ...prev]); + setText(""); + } catch { + setError("ثبت دیدگاه ناموفق بود. لطفاً دوباره تلاش کنید."); + } finally { + setSubmitting(false); + } + } + + return ( + <div> + {user ? ( + <form onSubmit={handleSubmit} className="mb-8"> + <textarea + value={text} + onChange={(e) => setText(e.target.value)} + placeholder="دیدگاه خود را بنویسید…" + rows={4} + className="w-full rounded-xl border border-line p-3 text-sm outline-none transition focus:border-brand" + /> + <div className="mt-2 flex items-center justify-between"> + {error && <span className="text-[12px] text-brand">{error}</span>} + <button + type="submit" + disabled={submitting || !text.trim()} + className="mr-auto flex h-10 items-center rounded-lg bg-brand px-5 text-[13px] font-bold text-white transition hover:bg-brand-dark disabled:cursor-not-allowed disabled:opacity-40" + > + {submitting ? "در حال ارسال…" : "ثبت دیدگاه"} + </button> + </div> + </form> + ) : ( + <div className="mb-8 flex flex-col items-center gap-3 rounded-xl border border-dashed border-line bg-surface p-6 text-center"> + <p className="text-sm text-body">برای ثبت دیدگاه ابتدا وارد حساب کاربری خود شوید.</p> + <Link + href={CUSTOMER_PORTAL_URL} + className="rounded-lg bg-brand px-5 py-2 text-[13px] font-bold text-white transition hover:bg-brand-dark" + > + ورود | ثبت‌نام + </Link> + </div> + )} + + {comments.length > 0 ? ( + <ul className="space-y-4"> + {comments.map((c) => ( + <li key={c.id} className="rounded-xl border border-line p-4"> + <div className="flex items-center justify-between"> + <span className="text-sm font-bold text-ink">{c.authorName || "کاربر"}</span> + {c.createdAt && ( + <span className="en text-xs text-body/60">{c.createdAt.slice(0, 10)}</span> + )} + </div> + {c.text && <p className="mt-2 text-sm leading-7 text-body">{c.text}</p>} + </li> + ))} + </ul> + ) : ( + <p className="text-sm text-body">هنوز دیدگاهی ثبت نشده است.</p> + )} + </div> + ); +} diff --git a/src/components/Footer.tsx b/src/components/Footer.tsx new file mode 100644 index 0000000..7612825 --- /dev/null +++ b/src/components/Footer.tsx @@ -0,0 +1,60 @@ +import Link from "next/link"; +import Image from "next/image"; +import { MAIN_NAV, PHONE, PHONE_HREF, SITE_NAME, SITE_TAGLINE } from "@/data/site"; +import { categoryHref } from "@/lib/slug"; +import type { CategoryNode } from "@/lib/types"; +import { PhoneIcon } from "./icons"; +import { toFaDigits } from "@/lib/format"; + +export default function Footer({ categories }: { categories: CategoryNode[] }) { + return ( + <footer className="mt-14 border-t border-line bg-surface"> + <div className="boxed grid gap-8 py-10 md:grid-cols-4"> + <div className="md:col-span-2"> + <Image src="/images/logo.png" alt={SITE_NAME} width={130} height={36} /> + <p className="mt-3 max-w-md text-[13px] leading-7 text-body">{SITE_TAGLINE}</p> + <a + href={PHONE_HREF} + className="mt-4 inline-flex items-center gap-2 text-[13px] font-bold text-ink transition hover:text-brand" + > + <PhoneIcon className="size-4" /> + {PHONE} + </a> + </div> + + <div> + <h3 className="mb-3 text-[14px]">دسته‌بندی‌ها</h3> + <ul className="space-y-2"> + {categories.slice(0, 6).map((cat) => ( + <li key={cat.id}> + <Link + href={categoryHref(cat.id, cat.nameFa, cat.name)} + className="text-[13px] text-body transition hover:text-brand" + > + {cat.nameFa || cat.name} + </Link> + </li> + ))} + </ul> + </div> + + <div> + <h3 className="mb-3 text-[14px]">دسترسی سریع</h3> + <ul className="space-y-2"> + {MAIN_NAV.map((item) => ( + <li key={item.href}> + <Link href={item.href} className="text-[13px] text-body transition hover:text-brand"> + {item.label} + </Link> + </li> + ))} + </ul> + </div> + </div> + + <div className="border-t border-line py-4 text-center text-[12px] text-body"> + © {toFaDigits(1404)} {SITE_NAME} — تمامی حقوق محفوظ است. + </div> + </footer> + ); +} diff --git a/src/components/Header.tsx b/src/components/Header.tsx new file mode 100644 index 0000000..301518f --- /dev/null +++ b/src/components/Header.tsx @@ -0,0 +1,114 @@ +"use client"; + +import Image from "next/image"; +import Link from "next/link"; +import { useState } from "react"; +import SearchBox from "./SearchBox"; +import AccountMenu from "./AccountMenu"; +import { CartIcon, ChevronDown, GridIcon, PhoneIcon } from "./icons"; +import { MAIN_NAV, PHONE, PHONE_HREF, SITE_NAME } from "@/data/site"; +import { CUSTOMER_PORTAL_URL } from "@/lib/config"; +import { categoryHref } from "@/lib/slug"; +import type { AuthUser, CategoryNode } from "@/lib/types"; + +// Two rows: logo + search + account/cart, then the product mega menu and the +// main nav — the same split the reference storefront uses. +export default function Header({ menu, user }: { menu: CategoryNode[]; user: AuthUser | null }) { + const [megaOpen, setMegaOpen] = useState(false); + + return ( + <header className="sticky top-0 z-40 w-full border-b border-line bg-white"> + <div className="boxed"> + {/* Mobile keeps the header down to the search box alone — account, + cart and browsing live in the fixed bottom bar instead. */} + <div className="flex h-[70px] items-center gap-4"> + <Link href="/" className="hidden shrink-0 lg:block" aria-label={SITE_NAME}> + <Image src="/images/logo.png" alt={SITE_NAME} width={130} height={36} priority /> + </Link> + + <div className="flex-1"> + <SearchBox /> + </div> + + <div className="mr-auto hidden items-center gap-2 lg:flex"> + <AccountMenu user={user} /> + <Link + href={`${CUSTOMER_PORTAL_URL}/cart`} + className="flex h-11 items-center gap-2 rounded-xl bg-brand px-3 text-[13px] text-white transition hover:bg-brand-dark" + aria-label="سبد خرید" + > + <CartIcon className="size-5" /> + <span className="font-bold">۰</span> + </Link> + </div> + </div> + + <nav className="hidden h-12 items-center gap-6 text-[13px] lg:flex"> + <div + className="relative h-full" + onMouseEnter={() => setMegaOpen(true)} + onMouseLeave={() => setMegaOpen(false)} + > + <button + className="flex h-full items-center gap-2 font-bold text-ink transition hover:text-brand" + aria-expanded={megaOpen} + > + <GridIcon className="size-5" /> + منو محصولات + <ChevronDown className="size-4" /> + </button> + + {megaOpen && menu.length > 0 && ( + <div className="absolute right-0 top-full z-50 w-[900px] rounded-b-2xl border border-line bg-white p-6 shadow-xl"> + <div className="grid grid-cols-4 gap-x-6 gap-y-5"> + {menu.map((cat) => ( + <div key={cat.id}> + <Link + href={categoryHref(cat.id, cat.nameFa, cat.name)} + className="mb-2 block font-bold text-ink transition hover:text-brand" + > + {cat.nameFa || cat.name} + </Link> + <ul className="space-y-1.5"> + {cat.children.slice(0, 6).map((child) => ( + <li key={child.id}> + <Link + href={categoryHref(child.id, child.nameFa, child.name)} + className="text-[12.5px] text-body transition hover:text-brand" + > + {child.nameFa || child.name} + </Link> + </li> + ))} + </ul> + </div> + ))} + </div> + </div> + )} + </div> + + {MAIN_NAV.map((item) => ( + <Link + key={item.href} + href={item.href} + className={`transition hover:text-brand ${ + item.highlight ? "font-bold text-brand" : "text-ink-soft" + }`} + > + {item.label} + </Link> + ))} + + <a + href={PHONE_HREF} + className="mr-auto flex items-center gap-2 text-ink-soft transition hover:text-brand" + > + <PhoneIcon className="size-4" /> + <span className="font-bold">{PHONE}</span> + </a> + </nav> + </div> + </header> + ); +} diff --git a/src/components/HeroSlider.tsx b/src/components/HeroSlider.tsx new file mode 100644 index 0000000..950af96 --- /dev/null +++ b/src/components/HeroSlider.tsx @@ -0,0 +1,114 @@ +"use client"; + +import Image from "next/image"; +import Link from "next/link"; +import type { CSSProperties } from "react"; +import { Autoplay, Navigation, Pagination } from "swiper/modules"; +import { Swiper, SwiperSlide } from "swiper/react"; +import "swiper/css"; +import "swiper/css/pagination"; +import { ChevronLeft, ChevronRight } from "./icons"; +import { toCssAspectRatio } from "@/lib/format"; +import type { StaticImageSlot } from "@/lib/types"; + +export interface Slide { + id: string; + image: string; + mobileImage?: string; + href: string; + alt: string; +} + +// Default aspect ratios match the fallback banner assets: 700x400 mobile art, +// 1296x366 desktop, so neither crop loses the headline. Kept in sync with the +// "slider" entry in src/app/meshkee/static-image-slots/route.ts — see the +// note there: if this ratio ever changes, that entry must change with it. +const FALLBACK_ASPECT_CLASSES = "aspect-[7/4] md:aspect-[1296/366]"; + +// `slot` is the "slider" static-image slot (kind: list, duplicatable) — when +// it has images they win over the `slides` fallback prop entirely. A CMS +// slide is one image for every breakpoint (not a separate mobile crop), and +// slot.aspectRatio (if set) drives the frame instead of the built-in default. +export default function HeroSlider({ slot, slides = [] }: { slot?: StaticImageSlot; slides?: Slide[] }) { + const fromCms = (slot?.images.length ?? 0) > 0; + + const items: Slide[] = fromCms + ? slot!.images.map((img, i) => ({ + id: img.id, + image: img.url, + href: img.linkUrl || "/products", + alt: img.titleFa || img.titleEn || `بنر فروشگاه موبایل معلم ${i + 1}`, + })) + : slides; + + if (items.length === 0) return null; + + const cssAspect = fromCms ? toCssAspectRatio(slot?.aspectRatio) : undefined; + const aspectStyle = cssAspect ? ({ aspectRatio: cssAspect } as CSSProperties) : undefined; + + return ( + <section className="boxed mt-5" aria-label="بنرهای اصلی"> + <div className="relative overflow-hidden rounded-2xl"> + <Swiper + modules={[Pagination, Navigation, Autoplay]} + dir="rtl" + loop={items.length > 1} + autoplay={{ delay: 5000, disableOnInteraction: false }} + pagination={{ clickable: true, el: ".hero-dots" }} + navigation={{ prevEl: ".hero-prev", nextEl: ".hero-next" }} + className={`w-full ${aspectStyle ? "" : FALLBACK_ASPECT_CLASSES}`} + style={aspectStyle} + > + {items.map((slide) => ( + <SwiperSlide key={slide.id}> + <Link href={slide.href} className="relative block size-full"> + {fromCms ? ( + <Image src={slide.image} alt={slide.alt} fill priority sizes="100vw" className="object-cover" /> + ) : ( + <> + <Image + src={slide.mobileImage || slide.image} + alt={slide.alt} + fill + priority + sizes="100vw" + className="object-cover md:hidden" + /> + <Image + src={slide.image} + alt={slide.alt} + fill + priority + sizes="1200px" + className="hidden object-cover md:block" + /> + </> + )} + </Link> + </SwiperSlide> + ))} + </Swiper> + + <div className="absolute bottom-4 left-4 z-10 flex items-center gap-2"> + <button + className="hero-prev flex size-9 items-center justify-center rounded-full bg-white/90 text-ink shadow transition hover:bg-white" + aria-label="اسلاید قبلی" + > + <ChevronRight className="size-5" /> + </button> + <button + className="hero-next flex size-9 items-center justify-center rounded-full bg-white/90 text-ink shadow transition hover:bg-white" + aria-label="اسلاید بعدی" + > + <ChevronLeft className="size-5" /> + </button> + </div> + + {/* Swiper's own .swiper-pagination rules set left:0/width:100% on this + element, so it is centred with justify-content rather than a + translate — a translate would fight those and push it off-canvas. */} + <div className="hero-dots absolute bottom-5 z-10 flex w-full justify-center gap-1.5" /> + </div> + </section> + ); +} diff --git a/src/components/HotDeals.tsx b/src/components/HotDeals.tsx new file mode 100644 index 0000000..2943611 --- /dev/null +++ b/src/components/HotDeals.tsx @@ -0,0 +1,151 @@ +"use client"; + +import Link from "next/link"; +import { useEffect, useState } from "react"; +import type { Swiper as SwiperType } from "swiper"; +import { FreeMode } from "swiper/modules"; +import { Swiper, SwiperSlide } from "swiper/react"; +import "swiper/css"; +import "swiper/css/free-mode"; +import ProductCard from "./ui/ProductCard"; +import CarouselNav from "./ui/CarouselNav"; +import { ChevronLeft, FireIcon } from "./icons"; +import { toFaDigits } from "@/lib/format"; +import type { ProductCardData } from "@/lib/types"; + +const UNITS: { key: keyof Remaining; label: string }[] = [ + { key: "seconds", label: "ثانیه" }, + { key: "minutes", label: "دقیقه" }, + { key: "hours", label: "ساعت" }, + { key: "days", label: "روز" }, +]; + +interface Remaining { + days: number; + hours: number; + minutes: number; + seconds: number; +} + +function remainingUntil(target: number): Remaining { + const diff = Math.max(0, target - Date.now()); + const total = Math.floor(diff / 1000); + return { + days: Math.floor(total / 86400), + hours: Math.floor((total % 86400) / 3600), + minutes: Math.floor((total % 3600) / 60), + seconds: total % 60, + }; +} + +// The countdown starts only after mount: rendering a live clock on the server +// would hydrate against a different second every time. The first value is set +// from a frame callback rather than the effect body so the state update stays +// out of the render pass. +function Countdown({ endsAt }: { endsAt: number }) { + const [left, setLeft] = useState<Remaining | null>(null); + + useEffect(() => { + const tick = () => setLeft(remainingUntil(endsAt)); + const frame = requestAnimationFrame(tick); + const id = setInterval(tick, 1000); + return () => { + cancelAnimationFrame(frame); + clearInterval(id); + }; + }, [endsAt]); + + return ( + <div className="flex items-center gap-1.5" dir="ltr"> + {UNITS.map(({ key, label }) => ( + <div + key={key} + className="flex size-12 flex-col items-center justify-center rounded-lg bg-white/15 text-white" + > + <span className="text-[15px] font-bold leading-none"> + {left ? toFaDigits(String(left[key]).padStart(2, "0")) : "--"} + </span> + <span className="mt-0.5 text-[9px] opacity-80">{label}</span> + </div> + ))} + </div> + ); +} + +export default function HotDeals({ + products, + endsAt, + href = "/products?special=1", +}: { + products: ProductCardData[]; + endsAt: number; + href?: string; +}) { + const [swiper, setSwiper] = useState<SwiperType | null>(null); + if (products.length === 0) return null; + + return ( + <section className="boxed mt-10"> + <div className="relative overflow-hidden rounded-2xl bg-brand p-5 lg:p-8"> + {/* Faint dot grid so the flat brand block does not read as a plain box. */} + <div + className="pointer-events-none absolute inset-0 opacity-[0.13]" + style={{ + backgroundImage: "radial-gradient(#fff 1.5px, transparent 1.5px)", + backgroundSize: "22px 22px", + }} + aria-hidden + /> + + <div className="relative flex flex-col gap-5 lg:flex-row lg:items-stretch"> + <div className="flex shrink-0 flex-col justify-between gap-4 lg:w-[230px]"> + <div> + <h2 className="flex items-center gap-2 text-[26px] font-black leading-tight text-white lg:text-[32px]"> + <FireIcon className="size-7 shrink-0" /> + تخفیف‌های داغ + </h2> + <p className="mt-1 text-[13px] text-white/85">تا پایان تایمر، بعدش قیمت‌ها برمی‌گرده</p> + </div> + + <Countdown endsAt={endsAt} /> + + <div className="flex items-center justify-between gap-2"> + <Link + href={href} + className="flex items-center gap-1 rounded-full bg-white px-4 py-2 text-[13px] font-bold text-brand transition hover:bg-white/90" + > + مشاهده همه + <ChevronLeft className="size-4" /> + </Link> + <div className="hidden lg:block"> + <CarouselNav swiper={swiper} tone="onBrand" /> + </div> + </div> + </div> + + <div className="min-w-0 flex-1"> + <Swiper + modules={[FreeMode]} + dir="rtl" + freeMode + onSwiper={setSwiper} + slidesPerView={2.1} + spaceBetween={12} + breakpoints={{ + 480: { slidesPerView: 2.6 }, + 768: { slidesPerView: 3.5 }, + 1024: { slidesPerView: 5 }, + }} + > + {products.map((product) => ( + <SwiperSlide key={product.id} className="h-auto"> + <ProductCard product={product} /> + </SwiperSlide> + ))} + </Swiper> + </div> + </div> + </div> + </section> + ); +} diff --git a/src/components/MobileBottomNav.tsx b/src/components/MobileBottomNav.tsx new file mode 100644 index 0000000..272f509 --- /dev/null +++ b/src/components/MobileBottomNav.tsx @@ -0,0 +1,195 @@ +"use client"; + +import Link from "next/link"; +import { useEffect, useRef, useState } from "react"; +import { CartIcon, ChevronDown, GridIcon, UserIcon } from "./icons"; +import { BUSINESS_PORTAL_URL, CUSTOMER_PORTAL_URL } from "@/lib/config"; +import { categoryHref } from "@/lib/slug"; +import { displayName, isAdmin } from "@/lib/authUser"; +import type { AuthUser, CategoryNode } from "@/lib/types"; + +const HomeIcon = ({ className }: { className?: string }) => ( + <svg + viewBox="0 0 24 24" + fill="none" + stroke="currentColor" + strokeWidth={1.6} + strokeLinecap="round" + strokeLinejoin="round" + className={className} + aria-hidden + > + <path d="M3 10.2 12 3l9 7.2V20a1 1 0 0 1-1 1h-5v-6H9v6H4a1 1 0 0 1-1-1z" /> + </svg> +); + +// Mobile navigation lives here instead of a hamburger in the header: a fixed +// bar with the four destinations that matter, and the category tree as a sheet +// so browsing never leaves the page. +export default function MobileBottomNav({ + categories, + user, +}: { + categories: CategoryNode[]; + user: AuthUser | null; +}) { + const [sheetOpen, setSheetOpen] = useState(false); + const [openId, setOpenId] = useState<string | null>(null); + const [accountOpen, setAccountOpen] = useState(false); + const accountRef = useRef<HTMLDivElement>(null); + + useEffect(() => { + document.body.style.overflow = sheetOpen ? "hidden" : ""; + return () => { + document.body.style.overflow = ""; + }; + }, [sheetOpen]); + + useEffect(() => { + const onClick = (e: MouseEvent) => { + if (!accountRef.current?.contains(e.target as Node)) setAccountOpen(false); + }; + document.addEventListener("mousedown", onClick); + return () => document.removeEventListener("mousedown", onClick); + }, []); + + const item = "flex flex-1 flex-col items-center justify-center gap-1 py-2 text-[11px]"; + + return ( + <> + {sheetOpen && ( + <div className="fixed inset-0 z-50 lg:hidden" role="dialog" aria-label="دسته‌بندی محصولات"> + <button + className="absolute inset-0 bg-black/40" + onClick={() => setSheetOpen(false)} + aria-label="بستن" + /> + <div className="absolute inset-x-0 bottom-0 max-h-[75vh] overflow-auto rounded-t-2xl bg-white pb-[76px]"> + <div className="sticky top-0 flex items-center justify-between border-b border-line bg-white px-4 py-3"> + <h2 className="text-[15px] font-bold">دسته‌بندی محصولات</h2> + <button + onClick={() => setSheetOpen(false)} + className="text-[13px] text-body" + aria-label="بستن" + > + بستن + </button> + </div> + + <ul className="divide-y divide-line"> + {categories.map((cat) => ( + <li key={cat.id}> + <div className="flex items-center"> + <Link + href={categoryHref(cat.id, cat.nameFa, cat.name)} + onClick={() => setSheetOpen(false)} + className="flex-1 px-4 py-3 text-[14px] text-ink" + > + {cat.nameFa || cat.name} + </Link> + {cat.children.length > 0 && ( + <button + onClick={() => setOpenId(openId === cat.id ? null : cat.id)} + className="px-4 py-3 text-body" + aria-expanded={openId === cat.id} + aria-label={`زیرشاخه‌های ${cat.nameFa || cat.name}`} + > + <ChevronDown + className={`size-5 transition ${openId === cat.id ? "rotate-180" : ""}`} + /> + </button> + )} + </div> + + {openId === cat.id && cat.children.length > 0 && ( + <ul className="bg-surface px-4 py-2"> + {cat.children.map((child) => ( + <li key={child.id}> + <Link + href={categoryHref(child.id, child.nameFa, child.name)} + onClick={() => setSheetOpen(false)} + className="block py-2 text-[13px] text-body" + > + {child.nameFa || child.name} + </Link> + </li> + ))} + </ul> + )} + </li> + ))} + </ul> + </div> + </div> + )} + + <nav className="fixed inset-x-0 bottom-0 z-50 border-t border-line bg-white lg:hidden"> + <div className="flex items-stretch"> + <Link href="/" className={`${item} text-ink`}> + <HomeIcon className="size-6" /> + خانه + </Link> + + <Link href={`${CUSTOMER_PORTAL_URL}/cart`} className={`${item} text-ink`}> + <CartIcon className="size-6" /> + سبد خرید + </Link> + + <button + onClick={() => setSheetOpen((v) => !v)} + className={`${item} ${sheetOpen ? "text-brand" : "text-ink"}`} + aria-expanded={sheetOpen} + > + <GridIcon className="size-6" /> + دسته‌بندی‌ها + </button> + + {user ? ( + <div ref={accountRef} className="relative flex-1"> + {accountOpen && ( + <div className="absolute inset-x-2 bottom-full mb-2 overflow-hidden rounded-xl border border-line bg-white py-1 shadow-xl"> + <Link + href={`${CUSTOMER_PORTAL_URL}/profile`} + className="block px-4 py-2.5 text-[13px] text-ink-soft" + onClick={() => setAccountOpen(false)} + > + پروفایل من + </Link> + {isAdmin(user) && ( + <Link + href={BUSINESS_PORTAL_URL} + className="block px-4 py-2.5 text-[13px] text-ink-soft" + onClick={() => setAccountOpen(false)} + > + مدیریت وبسایت + </Link> + )} + <Link + href={`${CUSTOMER_PORTAL_URL}/logout`} + className="block px-4 py-2.5 text-[13px] text-ink-soft" + onClick={() => setAccountOpen(false)} + > + خروج + </Link> + </div> + )} + <button + onClick={() => setAccountOpen((v) => !v)} + className={`${item} w-full ${accountOpen ? "text-brand" : "text-ink"}`} + aria-expanded={accountOpen} + > + <UserIcon className="size-6" /> + <span className="max-w-[64px] truncate">{displayName(user)}</span> + </button> + </div> + ) : ( + <Link href={CUSTOMER_PORTAL_URL} className={`${item} text-ink`}> + <UserIcon className="size-6" /> + ورود + </Link> + )} + </div> + </nav> + </> + ); +} diff --git a/src/components/ProductBuyPanel.tsx b/src/components/ProductBuyPanel.tsx new file mode 100644 index 0000000..d241d93 --- /dev/null +++ b/src/components/ProductBuyPanel.tsx @@ -0,0 +1,232 @@ +"use client"; + +import Link from "next/link"; +import { useState } from "react"; +import { CartIcon, CheckIcon, RotateIcon, ShieldIcon, TruckIcon } from "./icons"; +import { formatPrice } from "@/lib/format"; +import { AUTH_TOKEN_COOKIE, CUSTOMER_PORTAL_URL } from "@/lib/config"; +import { DEMO_PRICING, demoPrice } from "@/lib/demoPricing"; +import type { ApiProduct, AuthUser, StoreItem } from "@/lib/types"; + +const trustPoints = [ + { icon: ShieldIcon, title: "ضمانت اصالت کالا" }, + { icon: TruckIcon, title: "ارسال سریع به سراسر کشور" }, + { icon: RotateIcon, title: "تا ۷ روز امکان مرجوعی" }, +]; + +export default function ProductBuyPanel({ + product, + items = [], + user, +}: { + product: ApiProduct; + items?: StoreItem[]; + user: AuthUser | null; +}) { + const [selectedId, setSelectedId] = useState(items[0]?.id); + const [adding, setAdding] = useState(false); + const [added, setAdded] = useState(false); + const [error, setError] = useState<string | null>(null); + const selected = items.find((i) => i.id === selectedId) ?? items[0]; + + const title = product.nameFa || product.title; + const variationName = selected?.selections[0]?.variationName; + + let price: number | null = null; + let oldPrice: number | null = null; + + if (selected) { + price = selected.discountedPrice ?? selected.price; + oldPrice = selected.discountedPrice != null ? selected.price : null; + } else if (product.store.minPrice !== null) { + price = product.store.minPrice; + } else if (DEMO_PRICING) { + const demo = demoPrice(product); + price = demo.price; + oldPrice = demo.oldPrice; + } + + const percent = price !== null && oldPrice !== null && oldPrice > price + ? Math.round(((oldPrice - price) / oldPrice) * 100) + : 0; + + // Purchasing needs a real store-item variant (cart items are added by + // storeItemVariantId, not productId) — until this product has one, there + // is nothing a bearer token could add to a cart, logged in or not. + const purchasable = items.length > 0 && Boolean(selected); + const inStock = selected ? selected.stockQuantity > 0 : product.store.inStock; + + async function handleAddToCart() { + if (!selected || !user) return; + setAdding(true); + setError(null); + try { + const token = document.cookie + .split("; ") + .find((c) => c.startsWith(`${AUTH_TOKEN_COOKIE}=`)) + ?.split("=")[1]; + if (!token) throw new Error("no-token"); + + const res = await fetch( + `https://api.meshkee.com/api/v1/businesses/${product.businessId}/cart/items`, + { + method: "POST", + headers: { "Content-Type": "application/json", Authorization: `Bearer ${token}` }, + body: JSON.stringify({ storeItemVariantId: selected.storeItemId, quantity: 1 }), + } + ); + if (!res.ok) throw new Error(String(res.status)); + setAdded(true); + setTimeout(() => setAdded(false), 1800); + } catch { + setError("افزودن به سبد خرید ناموفق بود. لطفاً دوباره تلاش کنید."); + } finally { + setAdding(false); + } + } + + return ( + <div className="grid grid-cols-1 gap-8 lg:grid-cols-[1fr_300px] md:gap-10"> + <div> + <h1 className="text-xl font-bold text-ink md:text-2xl">{title}</h1> + {product.brand && <p className="mt-1.5 text-sm text-body">برند: {product.brand}</p>} + + {product.summary && ( + <div className="mt-5"> + <p className="mb-1.5 text-sm font-bold text-ink">توضیح کوتاه:</p> + <p className="text-sm leading-7 text-body">{product.summary}</p> + </div> + )} + + {items.length > 0 && ( + <div className="mt-5"> + <p className="mb-2.5 text-sm font-bold text-ink">{variationName || "انتخاب گزینه"}</p> + <div className="flex flex-wrap gap-2"> + {items.map((item) => { + const rawValue = item.selections[0]?.value; + const colorHex = rawValue?.startsWith("#") ? rawValue : null; + return ( + <button + key={item.id} + type="button" + onClick={() => setSelectedId(item.id)} + disabled={item.stockQuantity <= 0} + title={item.label} + className={`flex items-center justify-center transition disabled:cursor-not-allowed disabled:opacity-40 ${ + colorHex + ? `size-9 rounded-full border-2 ${item.id === selectedId ? "border-brand" : "border-line"}` + : `h-10 rounded-lg border px-4 text-sm font-bold ${ + item.id === selectedId + ? "border-brand bg-brand text-white" + : "border-line text-ink hover:border-brand hover:text-brand" + }` + }`} + > + {colorHex ? ( + <span className="block size-6 rounded-full" style={{ background: colorHex }} /> + ) : ( + item.label + )} + </button> + ); + })} + </div> + </div> + )} + + <div className="mt-5 space-y-2 text-sm"> + {product.categoryNameFa && ( + <p className="text-body"> + دسته‌بندی: <span className="font-bold text-ink">{product.categoryNameFa}</span> + </p> + )} + {product.tags.length > 0 && <p className="text-body">تگ‌ها: {product.tags.join("، ")}</p>} + </div> + </div> + + <div className="h-fit space-y-4 lg:sticky lg:top-24"> + <div className="rounded-2xl border border-line bg-white p-5"> + <div className="mb-4 flex items-center justify-between border-b border-line pb-4"> + <span + className={`inline-flex items-center gap-1.5 rounded-full px-3 py-1.5 text-xs font-bold ${ + inStock ? "bg-brand-soft text-brand" : "bg-surface text-body" + }`} + > + {inStock ? "آماده ارسال" : "ناموجود"} + </span> + <TruckIcon className="size-5 text-body" /> + </div> + + <div className="mb-1.5 flex items-center justify-between text-sm text-body"> + <span>قیمت:</span> + {percent > 0 && ( + <span className="rounded-md bg-brand-soft px-1.5 py-0.5 text-xs font-bold text-brand"> + %{percent} + </span> + )} + </div> + + <div className="mb-5 flex items-baseline justify-between gap-2"> + {oldPrice !== null && oldPrice > (price ?? 0) && ( + <span className="text-xs text-body line-through">{formatPrice(oldPrice)}</span> + )} + <span className="mr-auto text-xl font-bold text-ink md:text-2xl"> + {price !== null ? ( + <> + {formatPrice(price)} + <span className="mr-1 text-xs font-normal text-body">تومان</span> + </> + ) : ( + "تماس بگیرید" + )} + </span> + </div> + + {!purchasable ? ( + <p className="rounded-lg bg-surface px-3 py-2.5 text-center text-[12.5px] text-body"> + این محصول هنوز برای خرید آنلاین فعال نشده است. + </p> + ) : !user ? ( + <Link + href={CUSTOMER_PORTAL_URL} + className="flex h-12 w-full items-center justify-center gap-2 rounded-lg bg-brand text-sm font-bold text-white transition hover:bg-brand-dark" + > + برای خرید وارد شوید + </Link> + ) : ( + <button + type="button" + onClick={handleAddToCart} + disabled={!inStock || adding} + className={`flex h-12 w-full items-center justify-center gap-2 rounded-lg text-sm font-bold transition disabled:cursor-not-allowed disabled:opacity-40 ${ + added ? "bg-brand-dark text-white" : "bg-brand text-white hover:bg-brand-dark" + }`} + > + {added ? ( + <> + <CheckIcon className="size-4.5" /> + افزوده شد + </> + ) : ( + <> + <CartIcon className="size-4.5" /> + {adding ? "در حال افزودن…" : "افزودن به سبد خرید"} + </> + )} + </button> + )} + {error && <p className="mt-2 text-center text-[12px] text-brand">{error}</p>} + </div> + + <div className="grid grid-cols-3 gap-4 rounded-2xl border border-dashed border-line p-5"> + {trustPoints.map((p) => ( + <div key={p.title} className="flex flex-col items-center gap-2 text-center"> + <p.icon className="size-6 text-brand" /> + <span className="text-[11px] leading-5 text-body">{p.title}</span> + </div> + ))} + </div> + </div> + </div> + ); +} diff --git a/src/components/ProductCarousel.tsx b/src/components/ProductCarousel.tsx new file mode 100644 index 0000000..9236c88 --- /dev/null +++ b/src/components/ProductCarousel.tsx @@ -0,0 +1,59 @@ +"use client"; + +import { useState } from "react"; +import type { Swiper as SwiperType } from "swiper"; +import { FreeMode } from "swiper/modules"; +import { Swiper, SwiperSlide } from "swiper/react"; +import "swiper/css"; +import "swiper/css/free-mode"; +import ProductCard from "./ui/ProductCard"; +import SectionHeader from "./ui/SectionHeader"; +import CarouselNav from "./ui/CarouselNav"; +import type { ProductCardData } from "@/lib/types"; + +export default function ProductCarousel({ + title, + desc, + href, + products, +}: { + title: string; + desc?: string; + href: string; + products: ProductCardData[]; +}) { + const [swiper, setSwiper] = useState<SwiperType | null>(null); + if (products.length === 0) return null; + + return ( + <section className="boxed mt-10"> + <SectionHeader title={title} desc={desc} href={href} nav={<CarouselNav swiper={swiper} />} /> + + <div className="relative"> + <Swiper + modules={[FreeMode]} + dir="rtl" + freeMode + onSwiper={setSwiper} + slidesPerView={2.2} + spaceBetween={12} + breakpoints={{ + 480: { slidesPerView: 3.2 }, + 768: { slidesPerView: 4.2 }, + 1024: { slidesPerView: 6 }, + }} + > + {products.map((product) => ( + <SwiperSlide key={product.id} className="h-auto"> + <ProductCard product={product} /> + </SwiperSlide> + ))} + </Swiper> + + <div className="mt-4 flex justify-center lg:hidden"> + <CarouselNav swiper={swiper} /> + </div> + </div> + </section> + ); +} diff --git a/src/components/ProductGallery.tsx b/src/components/ProductGallery.tsx new file mode 100644 index 0000000..63703ec --- /dev/null +++ b/src/components/ProductGallery.tsx @@ -0,0 +1,175 @@ +"use client"; + +import Image from "next/image"; +import { useEffect, useState } from "react"; +import { ChevronLeft, ChevronRight, CloseIcon, ZoomIcon } from "./icons"; + +// Product/workshop-style media gallery: a square main image that magnifies +// under the cursor on hover (desktop only — this is a hover interaction, it +// simply does nothing on touch), plus a click-to-open full-screen lightbox +// with its own prev/next and thumbnail strip. +export default function ProductGallery({ images, title }: { images: string[]; title: string }) { + const gallery = images.length > 0 ? images : ["/images/products/placeholder.svg"]; + const [active, setActive] = useState(0); + const [zoomPos, setZoomPos] = useState<{ x: number; y: number } | null>(null); + const [lightboxOpen, setLightboxOpen] = useState(false); + + const prev = () => setActive((i) => (i - 1 + gallery.length) % gallery.length); + const next = () => setActive((i) => (i + 1) % gallery.length); + + useEffect(() => { + if (!lightboxOpen) return; + const onKey = (e: KeyboardEvent) => { + if (e.key === "Escape") setLightboxOpen(false); + if (e.key === "ArrowLeft") next(); // RTL: visual left = next + if (e.key === "ArrowRight") prev(); + }; + document.addEventListener("keydown", onKey); + document.body.style.overflow = "hidden"; + return () => { + document.removeEventListener("keydown", onKey); + document.body.style.overflow = ""; + }; + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [lightboxOpen]); + + return ( + <div> + <div + className="group relative aspect-square w-full cursor-zoom-in overflow-hidden rounded-xl border border-line bg-white" + onMouseMove={(e) => { + const rect = e.currentTarget.getBoundingClientRect(); + setZoomPos({ + x: ((e.clientX - rect.left) / rect.width) * 100, + y: ((e.clientY - rect.top) / rect.height) * 100, + }); + }} + onMouseLeave={() => setZoomPos(null)} + onClick={() => setLightboxOpen(true)} + > + <Image src={gallery[active]} alt={title} fill sizes="500px" className="object-contain p-4" /> + + {zoomPos && ( + <div + className="pointer-events-none absolute inset-0 hidden bg-no-repeat lg:block" + style={{ + backgroundImage: `url(${gallery[active]})`, + backgroundSize: "220%", + backgroundPosition: `${zoomPos.x}% ${zoomPos.y}%`, + }} + aria-hidden + /> + )} + + <span className="absolute left-3 top-3 flex size-9 items-center justify-center rounded-full bg-white/90 text-ink opacity-0 shadow transition group-hover:opacity-100"> + <ZoomIcon className="size-5" /> + </span> + + {gallery.length > 1 && ( + <> + <button + type="button" + onClick={(e) => { + e.stopPropagation(); + prev(); + }} + aria-label="تصویر قبلی" + className="absolute top-1/2 start-3 flex size-9 -translate-y-1/2 items-center justify-center rounded-full bg-white/90 text-ink shadow transition hover:bg-white" + > + <ChevronRight className="size-4.5" /> + </button> + <button + type="button" + onClick={(e) => { + e.stopPropagation(); + next(); + }} + aria-label="تصویر بعدی" + className="absolute top-1/2 end-3 flex size-9 -translate-y-1/2 items-center justify-center rounded-full bg-white/90 text-ink shadow transition hover:bg-white" + > + <ChevronLeft className="size-4.5" /> + </button> + </> + )} + </div> + + {gallery.length > 1 && ( + <div className="mt-3 flex flex-wrap gap-2.5"> + {gallery.map((src, i) => ( + <button + key={src + i} + type="button" + onClick={() => setActive(i)} + className={`relative size-16 shrink-0 overflow-hidden rounded-lg border-2 transition md:size-20 ${ + i === active ? "border-brand" : "border-line" + }`} + > + <Image src={src} alt="" fill sizes="80px" className="object-contain p-1" /> + </button> + ))} + </div> + )} + + {lightboxOpen && ( + <div + className="fixed inset-0 z-[60] flex flex-col bg-black/95" + role="dialog" + aria-modal + aria-label={`گالری تصاویر ${title}`} + > + <div className="flex items-center justify-between p-4"> + <span className="text-[13px] text-white/70"> + {active + 1} / {gallery.length} + </span> + <button + onClick={() => setLightboxOpen(false)} + className="flex size-10 items-center justify-center rounded-full bg-white/10 text-white transition hover:bg-white/20" + aria-label="بستن" + > + <CloseIcon className="size-5" /> + </button> + </div> + + <div className="relative flex-1"> + <Image src={gallery[active]} alt={title} fill sizes="90vw" className="object-contain" /> + + {gallery.length > 1 && ( + <> + <button + onClick={prev} + aria-label="تصویر قبلی" + className="absolute top-1/2 start-4 flex size-11 -translate-y-1/2 items-center justify-center rounded-full bg-white/10 text-white transition hover:bg-white/20" + > + <ChevronRight className="size-5" /> + </button> + <button + onClick={next} + aria-label="تصویر بعدی" + className="absolute top-1/2 end-4 flex size-11 -translate-y-1/2 items-center justify-center rounded-full bg-white/10 text-white transition hover:bg-white/20" + > + <ChevronLeft className="size-5" /> + </button> + </> + )} + </div> + + {gallery.length > 1 && ( + <div className="flex justify-center gap-2 overflow-x-auto p-4"> + {gallery.map((src, i) => ( + <button + key={src + i} + onClick={() => setActive(i)} + className={`relative size-14 shrink-0 overflow-hidden rounded-lg border-2 transition ${ + i === active ? "border-brand" : "border-white/20" + }`} + > + <Image src={src} alt="" fill sizes="56px" className="object-contain" /> + </button> + ))} + </div> + )} + </div> + )} + </div> + ); +} diff --git a/src/components/ProductInfoTabs.tsx b/src/components/ProductInfoTabs.tsx new file mode 100644 index 0000000..c291cfc --- /dev/null +++ b/src/components/ProductInfoTabs.tsx @@ -0,0 +1,87 @@ +"use client"; + +import { useState } from "react"; +import CommentSection from "./CommentSection"; +import type { AuthUser, EntityComment, SpecRow } from "@/lib/types"; + +type Tab = "description" | "technical" | "reviews"; + +export default function ProductInfoTabs({ + title, + descriptionHtml, + specRows, + comments, + productId, + user, +}: { + title: string; + descriptionHtml?: string; + specRows: SpecRow[]; + comments: EntityComment[]; + productId: string; + user: AuthUser | null; +}) { + const [tab, setTab] = useState<Tab>("description"); + + const tabs: { key: Tab; label: string }[] = [ + { key: "description", label: "توضیحات" }, + { key: "technical", label: "مشخصات فنی" }, + { key: "reviews", label: `دیدگاه کاربران${comments.length > 0 ? ` (${comments.length})` : ""}` }, + ]; + + return ( + <div className="mt-14 md:mt-20"> + <div className="flex flex-wrap gap-2 border-b border-line pb-px"> + {tabs.map((t) => ( + <button + key={t.key} + type="button" + onClick={() => setTab(t.key)} + className={`-mb-px h-11 rounded-t-lg border-b-2 px-5 text-sm font-bold transition ${ + tab === t.key ? "border-brand text-brand" : "border-transparent text-body hover:text-ink" + }`} + > + {t.label} + </button> + ))} + </div> + + <div className="pt-8"> + {tab === "description" && ( + <div> + <h2 className="mb-4 text-lg font-bold text-ink md:text-xl">معرفی {title}</h2> + {descriptionHtml ? ( + <div + className="rich-text text-sm leading-8 text-body" + dangerouslySetInnerHTML={{ __html: descriptionHtml }} + /> + ) : ( + <p className="text-sm text-body">توضیحی برای این محصول ثبت نشده است.</p> + )} + </div> + )} + + {tab === "technical" && ( + <div> + {specRows.length > 0 ? ( + <dl className="divide-y divide-line overflow-hidden rounded-xl border border-line"> + {specRows.map((row) => ( + <div key={row.key} className="flex justify-between gap-4 px-4 py-3 text-sm odd:bg-surface"> + <dt className="text-body">{row.label}</dt> + <dd className="font-bold text-ink">{row.value}</dd> + </div> + ))} + </dl> + ) : ( + <p className="text-sm text-body">مشخصات فنی این محصول ثبت نشده است.</p> + )} + </div> + )} + + {tab === "reviews" && ( + <CommentSection entityType="product" entityId={productId} initialComments={comments} user={user} /> + )} + </div> + </div> + ); +} diff --git a/src/components/PromoBanners.tsx b/src/components/PromoBanners.tsx new file mode 100644 index 0000000..fa11c39 --- /dev/null +++ b/src/components/PromoBanners.tsx @@ -0,0 +1,92 @@ +import Image from "next/image"; +import Link from "next/link"; +import type { CSSProperties } from "react"; +import { toCssAspectRatio } from "@/lib/format"; +import type { StaticImageSlot } from "@/lib/types"; + +// Shared renderer for the "one-banner-bg" / "two-banner-bg" / "three-banner-bg" +// static-image slots: url -> <img src>, linkUrl -> wrap in <a>, slot.aspectRatio +// drives the frame. An empty or failed slot keeps a flat, labelled placeholder +// (this site's usual stand-in for undesigned art, see +// public/images/categories/{id}.svg) instead of a fake photo, sized so the +// business/designer can see exactly what to fill in. +function BannerGrid({ + slot, + count, + defaultAspect, + recommendedWidth, + cols, +}: { + slot?: StaticImageSlot; + count: number; + defaultAspect: string; + recommendedWidth: number; + cols: string; +}) { + const images = (slot?.images ?? []).slice(0, count); + const cssAspect = toCssAspectRatio(slot?.aspectRatio) ?? defaultAspect; + const aspectStyle = { aspectRatio: cssAspect } as CSSProperties; + const cells = Array.from({ length: count }, (_, i) => images[i]); + + return ( + <section className={`boxed mt-10`} aria-label="بنرهای تبلیغاتی"> + <div className={`grid gap-3 ${cols}`}> + {cells.map((img, i) => { + if (!img) { + return ( + <div + key={i} + className="flex flex-col items-center justify-center gap-1 rounded-xl border border-dashed border-line bg-surface text-body" + style={aspectStyle} + > + <span className="text-[12px]">بنر تبلیغاتی</span> + <span className="text-[11px] opacity-70" dir="ltr"> + {recommendedWidth}px · {cssAspect.replace("/", ":")} + </span> + </div> + ); + } + + const media = ( + <Image + src={img.url} + alt={img.titleFa || img.titleEn || "بنر تبلیغاتی"} + fill + sizes={`(max-width: 768px) 100vw, ${recommendedWidth}px`} + className="object-cover" + /> + ); + const frameClass = "relative block overflow-hidden rounded-xl bg-surface"; + + // Only wrap in <a> when the slot actually carries a link — an + // undesignated banner shouldn't become a dead click target. + return img.linkUrl ? ( + <Link key={img.id} href={img.linkUrl} className={frameClass} style={aspectStyle}> + {media} + </Link> + ) : ( + <div key={img.id} className={frameClass} style={aspectStyle}> + {media} + </div> + ); + })} + </div> + </section> + ); +} + +export function OneBanner({ slot }: { slot?: StaticImageSlot }) { + return <BannerGrid slot={slot} count={1} defaultAspect="16/5" recommendedWidth={1200} cols="grid-cols-1" />; +} + +export function TwoBanners({ slot }: { slot?: StaticImageSlot }) { + return ( + <BannerGrid slot={slot} count={2} defaultAspect="16/9" recommendedWidth={580} cols="grid-cols-1 md:grid-cols-2" /> + ); +} + +export function ThreeBanners({ slot }: { slot?: StaticImageSlot }) { + return ( + <BannerGrid slot={slot} count={3} defaultAspect="4/3" recommendedWidth={380} cols="grid-cols-1 sm:grid-cols-3" /> + ); +} diff --git a/src/components/SearchBox.tsx b/src/components/SearchBox.tsx new file mode 100644 index 0000000..2f48318 --- /dev/null +++ b/src/components/SearchBox.tsx @@ -0,0 +1,129 @@ +"use client"; + +import Image from "next/image"; +import Link from "next/link"; +import { useEffect, useRef, useState } from "react"; +import { SearchIcon } from "./icons"; +import { formatPrice } from "@/lib/format"; +import { productHref } from "@/lib/slug"; +import type { ApiProduct } from "@/lib/types"; + +const API = "https://api.meshkee.com/api/v1"; +const DOMAIN = process.env.NEXT_PUBLIC_SITE_DOMAIN || "moalem.shop"; + +// Instant search: fires once the query passes 2 characters, debounced, and +// every in-flight request is abortable so a fast typist never sees an older +// response overwrite a newer one. +export default function SearchBox() { + const [query, setQuery] = useState(""); + const [items, setItems] = useState<ApiProduct[]>([]); + const [open, setOpen] = useState(false); + const [loading, setLoading] = useState(false); + const boxRef = useRef<HTMLDivElement>(null); + + // Clearing on a short query happens in the change handler, not here: the + // effect body must not call setState synchronously. + const onChange = (value: string) => { + setQuery(value); + if (value.trim().length < 3) { + setItems([]); + setLoading(false); + setOpen(false); + } + }; + + useEffect(() => { + const term = query.trim(); + if (term.length < 3) return; + + const controller = new AbortController(); + const timer = setTimeout(async () => { + setLoading(true); + try { + const url = new URL(`${API}/tenants/${DOMAIN}/products`); + url.searchParams.set("name", term); + url.searchParams.set("pageSize", "6"); + const res = await fetch(url, { signal: controller.signal }); + const data = (await res.json()) as { items: ApiProduct[] }; + setItems(data.items ?? []); + setOpen(true); + } catch { + /* aborted or offline — keep the previous list */ + } finally { + setLoading(false); + } + }, 300); + + return () => { + clearTimeout(timer); + controller.abort(); + }; + }, [query]); + + useEffect(() => { + const onClick = (e: MouseEvent) => { + if (!boxRef.current?.contains(e.target as Node)) setOpen(false); + }; + document.addEventListener("mousedown", onClick); + return () => document.removeEventListener("mousedown", onClick); + }, []); + + return ( + <div ref={boxRef} className="relative w-full"> + <div className="flex h-12 items-center gap-2 rounded-xl bg-surface px-4 text-sm"> + <SearchIcon className="size-5 shrink-0 text-body" /> + <input + value={query} + onChange={(e) => onChange(e.target.value)} + onFocus={() => items.length > 0 && setOpen(true)} + placeholder="جستجو براساس نام یا کد محصول" + className="h-full w-full bg-transparent outline-none placeholder:text-body" + aria-label="جستجوی محصولات" + /> + {loading && ( + <span className="size-4 shrink-0 animate-spin rounded-full border-2 border-line border-t-brand" /> + )} + </div> + + {open && query.trim().length >= 3 && ( + <div className="absolute inset-x-0 top-14 z-50 overflow-hidden rounded-xl border border-line bg-white shadow-xl"> + {items.length === 0 ? ( + <p className="px-4 py-6 text-center text-sm text-body">محصولی پیدا نشد.</p> + ) : ( + <ul className="max-h-96 overflow-auto"> + {items.map((p) => ( + <li key={p.id}> + <Link + href={productHref(p.id, p.nameFa, p.title)} + onClick={() => setOpen(false)} + className="flex items-center gap-3 px-3 py-2.5 transition hover:bg-surface" + > + <span className="relative size-12 shrink-0 overflow-hidden rounded-lg bg-surface"> + {p.thumbnailUrl && ( + <Image + src={p.thumbnailUrl} + alt={p.nameFa || p.title} + fill + sizes="48px" + className="object-contain" + /> + )} + </span> + <span className="min-w-0 flex-1"> + <span className="clamp-2 text-[13px] leading-6">{p.nameFa || p.title}</span> + </span> + {p.store.minPrice !== null && ( + <span className="shrink-0 text-[12px] font-bold text-ink"> + {formatPrice(p.store.minPrice)} + </span> + )} + </Link> + </li> + ))} + </ul> + )} + </div> + )} + </div> + ); +} diff --git a/src/components/TopBar.tsx b/src/components/TopBar.tsx new file mode 100644 index 0000000..a754970 --- /dev/null +++ b/src/components/TopBar.tsx @@ -0,0 +1,21 @@ +import Link from "next/link"; +import { PROMO } from "@/data/site"; + +// Slim promo strip above the header — the reference storefront's +// `.header_banner_wrapper`, rebuilt as type instead of a baked GIF so the +// copy stays editable. +export default function TopBar() { + return ( + <div className="w-full bg-brand text-white"> + <div className="boxed flex h-10 items-center justify-center gap-3 text-[13px]"> + <span className="truncate">{PROMO.text}</span> + <Link + href={PROMO.href} + className="hidden shrink-0 rounded-full bg-white/15 px-3 py-1 text-[12px] font-bold transition hover:bg-white/25 sm:inline-block" + > + {PROMO.cta} + </Link> + </div> + </div> + ); +} diff --git a/src/components/icons.tsx b/src/components/icons.tsx new file mode 100644 index 0000000..0ff5fd1 --- /dev/null +++ b/src/components/icons.tsx @@ -0,0 +1,112 @@ +// Single-stroke line icons in the Iconsax style the reference storefront uses. +type P = { className?: string }; +const base = { + fill: "none", + stroke: "currentColor", + strokeWidth: 1.6, + strokeLinecap: "round" as const, + strokeLinejoin: "round" as const, + viewBox: "0 0 24 24", +}; + +export const SearchIcon = ({ className }: P) => ( + <svg {...base} className={className} aria-hidden> + <circle cx="11" cy="11" r="7" /> + <path d="M20 20l-3-3" /> + </svg> +); + +export const UserIcon = ({ className }: P) => ( + <svg {...base} className={className} aria-hidden> + <circle cx="12" cy="8" r="4" /> + <path d="M4 20c0-3.3 3.6-5.5 8-5.5s8 2.2 8 5.5" /> + </svg> +); + +export const CartIcon = ({ className }: P) => ( + <svg {...base} className={className} aria-hidden> + <path d="M3 4h2l2.2 10.2A2 2 0 0 0 9.2 16h7.7a2 2 0 0 0 2-1.6L20.5 7H6" /> + <circle cx="9.5" cy="20" r="1.4" /> + <circle cx="17.5" cy="20" r="1.4" /> + </svg> +); + +export const GridIcon = ({ className }: P) => ( + <svg {...base} className={className} aria-hidden> + <rect x="3" y="3" width="7.5" height="7.5" rx="2" /> + <rect x="13.5" y="3" width="7.5" height="7.5" rx="2" /> + <rect x="3" y="13.5" width="7.5" height="7.5" rx="2" /> + <rect x="13.5" y="13.5" width="7.5" height="7.5" rx="2" /> + </svg> +); + +export const PhoneIcon = ({ className }: P) => ( + <svg {...base} className={className} aria-hidden> + <path d="M21 16.5v2.2a2 2 0 0 1-2.2 2 18.6 18.6 0 0 1-8.1-2.9 18.3 18.3 0 0 1-5.6-5.6A18.6 18.6 0 0 1 2.2 4.1 2 2 0 0 1 4.2 2h2.2a2 2 0 0 1 2 1.7c.1.9.3 1.8.7 2.6a2 2 0 0 1-.5 2.1l-1 1a15 15 0 0 0 5.6 5.6l1-1a2 2 0 0 1 2.1-.5c.8.3 1.7.6 2.6.7a2 2 0 0 1 1.7 2z" /> + </svg> +); + +export const ChevronLeft = ({ className }: P) => ( + <svg {...base} className={className} aria-hidden> + <path d="M14.5 6l-6 6 6 6" /> + </svg> +); + +export const ChevronRight = ({ className }: P) => ( + <svg {...base} className={className} aria-hidden> + <path d="M9.5 6l6 6-6 6" /> + </svg> +); + +export const ChevronDown = ({ className }: P) => ( + <svg {...base} className={className} aria-hidden> + <path d="M6 9.5l6 6 6-6" /> + </svg> +); + +export const FireIcon = ({ className }: P) => ( + <svg {...base} className={className} aria-hidden> + <path d="M12 3s5 3.6 5 8.4A5 5 0 0 1 12 21a5 5 0 0 1-5-4.9c0-2 1-3.4 1-3.4s.4 1.4 1.6 1.9c0-2.6 1-5.4 2.4-6.6 0 0-.3 2.4 1.3 3.5.5-1.6-.3-3.5-.3-3.5z" /> + </svg> +); + +export const CheckIcon = ({ className }: P) => ( + <svg {...base} className={className} aria-hidden> + <path d="M4.5 12.5l5 5 10-11" /> + </svg> +); + +export const CloseIcon = ({ className }: P) => ( + <svg {...base} className={className} aria-hidden> + <path d="M5 5l14 14M19 5L5 19" /> + </svg> +); + +export const ZoomIcon = ({ className }: P) => ( + <svg {...base} className={className} aria-hidden> + <circle cx="10.5" cy="10.5" r="6.5" /> + <path d="M15.2 15.2L20 20M8 10.5h5M10.5 8v5" /> + </svg> +); + +export const ShieldIcon = ({ className }: P) => ( + <svg {...base} className={className} aria-hidden> + <path d="M12 3l7 3v5.2c0 4.6-3 7.7-7 9.8-4-2.1-7-5.2-7-9.8V6z" /> + <path d="M9 12l2 2 4-4" /> + </svg> +); + +export const TruckIcon = ({ className }: P) => ( + <svg {...base} className={className} aria-hidden> + <path d="M3 6h10v9H3zM13 10h4l3 3v2h-7z" /> + <circle cx="7" cy="17.5" r="1.7" /> + <circle cx="17" cy="17.5" r="1.7" /> + </svg> +); + +export const RotateIcon = ({ className }: P) => ( + <svg {...base} className={className} aria-hidden> + <path d="M4 12a8 8 0 0 1 14-5.3M20 12a8 8 0 0 1-14 5.3" /> + <path d="M17.5 3.5V7H14M6.5 20.5V17H10" /> + </svg> +); diff --git a/src/components/ui/CarouselNav.tsx b/src/components/ui/CarouselNav.tsx new file mode 100644 index 0000000..beb64ac --- /dev/null +++ b/src/components/ui/CarouselNav.tsx @@ -0,0 +1,38 @@ +"use client"; + +import type { Swiper as SwiperType } from "swiper"; +import { ChevronLeft, ChevronRight } from "../icons"; + +// Prev/next controls that drive a Swiper instance held by the parent, so a +// section can place them wherever its layout needs them. +export default function CarouselNav({ + swiper, + tone = "light", +}: { + swiper: SwiperType | null; + tone?: "light" | "onBrand"; +}) { + const style = + tone === "onBrand" + ? "bg-white/20 text-white hover:bg-white/35" + : "border border-line bg-white text-ink hover:border-brand hover:text-brand"; + + return ( + <div className="flex items-center gap-2"> + <button + onClick={() => swiper?.slidePrev()} + className={`flex size-9 items-center justify-center rounded-full transition ${style}`} + aria-label="قبلی" + > + <ChevronRight className="size-5" /> + </button> + <button + onClick={() => swiper?.slideNext()} + className={`flex size-9 items-center justify-center rounded-full transition ${style}`} + aria-label="بعدی" + > + <ChevronLeft className="size-5" /> + </button> + </div> + ); +} diff --git a/src/components/ui/Pagination.tsx b/src/components/ui/Pagination.tsx new file mode 100644 index 0000000..842729b --- /dev/null +++ b/src/components/ui/Pagination.tsx @@ -0,0 +1,61 @@ +import Link from "next/link"; +import { toFaDigits } from "@/lib/format"; +import { ChevronLeft, ChevronRight } from "../icons"; + +// Simple numbered pager. `buildHref` turns a page number into the URL for +// that page — callers own how query params are preserved (search, filters). +export default function Pagination({ + page, + pageSize, + total, + buildHref, +}: { + page: number; + pageSize: number; + total: number; + buildHref: (page: number) => string; +}) { + const pageCount = Math.max(1, Math.ceil(total / pageSize)); + if (pageCount <= 1) return null; + + // A short window around the current page, plus first/last, keeps the + // control usable even with a few hundred pages. + const pages = new Set<number>([1, pageCount, page - 1, page, page + 1].filter((p) => p >= 1 && p <= pageCount)); + const sorted = [...pages].sort((a, b) => a - b); + + const linkClass = (active: boolean) => + `flex h-9 min-w-9 items-center justify-center rounded-lg px-2 text-[13px] transition ${ + active ? "bg-brand text-white" : "border border-line text-ink-soft hover:border-brand hover:text-brand" + }`; + + return ( + <nav className="mt-8 flex items-center justify-center gap-1.5" aria-label="صفحه‌بندی"> + <Link + href={buildHref(Math.max(1, page - 1))} + aria-disabled={page <= 1} + className={`${linkClass(false)} ${page <= 1 ? "pointer-events-none opacity-40" : ""}`} + aria-label="صفحه قبل" + > + <ChevronRight className="size-4" /> + </Link> + + {sorted.map((p, i) => ( + <span key={p} className="flex items-center gap-1.5"> + {i > 0 && p - sorted[i - 1] > 1 && <span className="px-1 text-body">…</span>} + <Link href={buildHref(p)} className={linkClass(p === page)}> + {toFaDigits(p)} + </Link> + </span> + ))} + + <Link + href={buildHref(Math.min(pageCount, page + 1))} + aria-disabled={page >= pageCount} + className={`${linkClass(false)} ${page >= pageCount ? "pointer-events-none opacity-40" : ""}`} + aria-label="صفحه بعد" + > + <ChevronLeft className="size-4" /> + </Link> + </nav> + ); +} diff --git a/src/components/ui/ProductCard.tsx b/src/components/ui/ProductCard.tsx new file mode 100644 index 0000000..6ed8665 --- /dev/null +++ b/src/components/ui/ProductCard.tsx @@ -0,0 +1,65 @@ +import Image from "next/image"; +import Link from "next/link"; +import { formatPrice, toFaDigits } from "@/lib/format"; +import type { ProductCardData } from "@/lib/types"; + +export default function ProductCard({ product }: { product: ProductCardData }) { + const { href, title, image, price, oldPrice, percent, inStock } = product; + const hasOldPrice = price !== null && oldPrice !== null && oldPrice > price; + + return ( + <Link + href={href} + className="flex h-full flex-col rounded-lg border border-line bg-white p-2 transition hover:border-brand/40 hover:shadow-[0_6px_20px_rgba(0,0,0,0.07)]" + > + <span className="relative mb-2 block aspect-square w-full overflow-hidden rounded-md bg-white"> + {image ? ( + <Image + src={image} + alt={title} + fill + sizes="(max-width: 768px) 45vw, 180px" + className="object-contain p-1" + /> + ) : ( + <span className="flex size-full items-center justify-center text-[11px] text-body"> + بدون تصویر + </span> + )} + {percent > 0 && ( + <span className="absolute right-1.5 top-1.5 rounded-full bg-brand px-2 py-0.5 text-[11px] font-bold text-white"> + {toFaDigits(percent)}٪ + </span> + )} + </span> + + <span className="clamp-2 mb-3 min-h-[42px] px-1 text-[12.5px] leading-[21px] text-ink-soft"> + {title} + </span> + + {/* The old-price line always occupies its row — rendered empty when there + is no discount — so a discounted card is exactly as tall as the rest + and every card in a carousel lines up. */} + <span className="mt-auto flex h-[44px] flex-col items-start justify-end px-1 pb-1"> + {price !== null && inStock ? ( + <> + <span + className={`h-[17px] text-[11px] text-body ${hasOldPrice ? "line-through" : ""}`} + > + {hasOldPrice ? formatPrice(oldPrice) : ""} + </span> + <span className="text-[15px] font-bold text-ink"> + {formatPrice(price)} + <span className="mr-1 text-[11px] font-normal text-body">تومان</span> + </span> + </> + ) : ( + <> + <span className="h-[17px]" /> + <span className="text-[12px] text-body">ناموجود</span> + </> + )} + </span> + </Link> + ); +} diff --git a/src/components/ui/SectionHeader.tsx b/src/components/ui/SectionHeader.tsx new file mode 100644 index 0000000..d08d0ad --- /dev/null +++ b/src/components/ui/SectionHeader.tsx @@ -0,0 +1,38 @@ +import type { ReactNode } from "react"; +import Link from "next/link"; +import { ChevronLeft } from "../icons"; + +// Title + one-line description on the right; on the left the "مشاهده همه" pill +// with the carousel arrows beside it (desktop only — on mobile the arrows move +// under the track, where the thumb is). +export default function SectionHeader({ + title, + desc, + href, + nav, +}: { + title: string; + desc?: string; + href: string; + nav?: ReactNode; +}) { + return ( + <div className="mb-4 flex items-end justify-between gap-4"> + <div> + <h2 className="text-xl font-bold text-ink">{title}</h2> + {desc && <p className="mt-1 text-[13px] text-body">{desc}</p>} + </div> + + <div className="flex shrink-0 items-center gap-2"> + {nav && <div className="hidden lg:flex">{nav}</div>} + <Link + href={href} + className="flex items-center gap-1 rounded-full border border-line px-4 py-2 text-[13px] text-ink-soft transition hover:border-brand hover:text-brand" + > + مشاهده همه + <ChevronLeft className="size-4" /> + </Link> + </div> + </div> + ); +} diff --git a/src/data/fallbackBlogs.ts b/src/data/fallbackBlogs.ts new file mode 100644 index 0000000..5ec61a7 --- /dev/null +++ b/src/data/fallbackBlogs.ts @@ -0,0 +1,75 @@ +// The blog module is enabled on this tenant but GET /blogs is still empty. +// These posts are the real ones published on moalem.shop/articles, kept as a +// fallback so the magazine section has content until the posts are imported. +// Delete this file once /tenants/moalem.shop/blogs returns items. +export interface FallbackBlog { + id: string; + title: string; + image: string; + category: string; +} + +const S = "https://meshkee-storage.sas.amin.parminstorage.ir/moallem_store_2467"; + +export const FALLBACK_BLOGS: FallbackBlog[] = [ + { + id: "ps5-games", + title: "لیست بهترین بازی های PS5 | از God of War تا FIFA و Call of Duty", + image: `${S}/1c626735dbcac8ba9eb4870abbf6db52/conversions/Best-ps5-games-2-thumbnail.jpg`, + category: "کنسول بازی", + }, + { + id: "laptop-programming", + title: "بهترین لپتاپ برای برنامه نویسی | بررسی تخصصی مشخصات و مدل‌های پیشنهادی", + image: `${S}/53cefd2e98c16a12132c9a9291ba2eff/conversions/Best-Laptop-for-Programming-2-thumbnail.jpg`, + category: "لپ تاپ", + }, + { + id: "iphone-apps", + title: "بهترین اپلیکیشن‌های آیفون ۲۰۲۶ | معرفی برنامه‌ها، بازی‌ها و راهنمای خرید اشتراک", + image: `${S}/271fd508674da9facca8c01c112d11e9/conversions/Best-iPhone-Apps-(Cover)-2-thumbnail.jpg`, + category: "موبایل", + }, + { + id: "apple-id", + title: "تفاوت اپل آیدی شخصی و کارتی | مزایا، معایب و خطرات برای کاربران ایرانی", + image: `${S}/27a88efe1ad4a5af32b19e07472d3da9/conversions/Difference-between-personal-and-card-Apple-ID-2-thumbnail.jpg`, + category: "آموزش", + }, + { + id: "best-smartwatch", + title: "راهنمای نهایی انتخاب بهترین ساعت هوشمند برای هر فرد", + image: `${S}/c0b028dcad0c8724b66f42af3c2799ea/conversions/Best-Smartwatch-2-thumbnail.jpg`, + category: "ساعت هوشمند", + }, + { + id: "flagship-vs-midrange", + title: "گوشی پرچم‌دار یا میان‌رده؟ راهنمای انتخاب هوشمندانه بر اساس نیاز و بودجه", + image: `${S}/1a0fb6a0a25999aaa2ba5e44f1976b37/conversions/Difference-between-mid-range-and-flagship-phones-2-thumbnail.jpg`, + category: "راهنمای خرید", + }, + { + id: "macbook-vs-windows", + title: "مقایسه واقعی مک بوک و ویندوز با بررسی تجربه کاربران و کاربردهای روزمره", + image: `${S}/de0311e65e2b183381c3a76a9a46da51/conversions/MacBook-vs-Windows-2-thumbnail.jpg`, + category: "لپ تاپ", + }, + { + id: "ios-26", + title: "تحول واقعی iOS آغاز شد؛ چرا iOS 26 فقط یک آپدیت ساده نیست؟", + image: `${S}/6cd651d586d171994dc4b0867c74a499/conversions/iOS-26-(Cover)-2-thumbnail.jpg`, + category: "اخبار", + }, + { + id: "iphone-17-pro-max", + title: "بررسی جامع آیفون ۱۷ پرو مکس؛ پرچم‌دار جدید اپل با جهشی بزرگ در فناوری", + image: `${S}/ca0aa580c6a0d73def3646e50cb6cf4f/conversions/%D8%A2%DB%8C%D9%81%D9%88%D9%86-17-%D9%BE%D8%B1%D9%88%D9%85%DA%A9%D8%B3-2-thumbnail.jpg`, + category: "موبایل", + }, + { + id: "used-iphone-guide", + title: "راهنمای کامل خرید گوشی دست دوم اپل: نکاتی که قبل از خرید نباید از دست بدهید", + image: `${S}/76794f3bb9964508228367586e7c5ec3/conversions/%D8%AE%D8%B1%DB%8C%D8%AF-%DA%AF%D9%88%D8%B4%DB%8C-%D8%AF%D8%B3%D8%AA-%D8%AF%D9%88%D9%85-%D8%A7%D9%BE%D9%84-2-thumbnail.jpg`, + category: "راهنمای خرید", + }, +]; diff --git a/src/data/site.ts b/src/data/site.ts new file mode 100644 index 0000000..664a3f2 --- /dev/null +++ b/src/data/site.ts @@ -0,0 +1,31 @@ +// Content that is not (yet) in the CMS: nav labels, the promo strip and the +// contact block. Everything product-shaped comes from the Meshkee API. +export const SITE_NAME = "فروشگاه موبایل معلم"; +export const SITE_TAGLINE = "مرجع خرید موبایل، لپ‌تاپ و لوازم جانبی"; + +export const PROMO = { + text: "ارسال رایگان برای خریدهای بالای ۵ میلیون تومان + پرداخت در محل", + cta: "مشاهده شرایط", + href: "/installments", +}; + +export const PHONE = "۰۲۱-۳۳۹۰۰۰۰۰"; +export const PHONE_HREF = "tel:+982133900000"; + +export const MAIN_NAV = [ + { label: "برندها", href: "/brands" }, + { label: "تخفیف ویژه", href: "/products?special=1", highlight: true }, + { label: "مجله معلم", href: "/blog" }, + { label: "پیگیری سفارشات", href: "/orders" }, + { label: "تماس با ما", href: "/contact" }, + { label: "درباره ما", href: "/about" }, +]; + +// Homepage product carousels. Each one is a real category id from +// GET /tenants/moalem.shop/categories?entityType=product. +export const HOME_CAROUSELS = [ + { categoryId: "1251", title: "موبایل", desc: "جدیدترین گوشی‌های روز بازار" }, + { categoryId: "1226", title: "لوازم جانبی", desc: "هر چیزی که گوشی و لپ‌تاپت نیاز داره" }, + { categoryId: "1254", title: "لپ تاپ", desc: "برای کار، بازی و دانشجویی" }, + { categoryId: "1256", title: "کنسول بازی", desc: "پلی‌استیشن، ایکس‌باکس و لوازم جانبی" }, +]; diff --git a/src/lib/api.ts b/src/lib/api.ts new file mode 100644 index 0000000..ad5192a --- /dev/null +++ b/src/lib/api.ts @@ -0,0 +1,208 @@ +import { API_BASE, SITE_DOMAIN } from "./config"; +import type { + ApiBlog, + ApiCategory, + ApiProduct, + AuthUser, + BusinessInfo, + ContactFormInput, + EntityComment, + PagedResult, + ProductVariation, + SliderItem, + StaticImageSlot, + StoreItem, + StoreSpecial, + Tenant, + TechnicalInfoForm, + TechnicalInfoValue, +} from "./types"; + +type QueryParams = Record<string, string | number | boolean | undefined>; + +async function apiGet<T>(path: string, params?: QueryParams): Promise<T> { + const url = new URL(`${API_BASE}${path}`); + if (params) { + for (const [key, value] of Object.entries(params)) { + if (value !== undefined && value !== null && value !== "") { + url.searchParams.set(key, String(value)); + } + } + } + + const res = await fetch(url.toString(), { next: { revalidate: 60 } }); + if (!res.ok) { + throw new Error(`API request failed (${res.status}): ${path}`); + } + return res.json() as Promise<T>; +} + +async function apiPost<T>(path: string, body: unknown, token?: string): Promise<T> { + const res = await fetch(`${API_BASE}${path}`, { + method: "POST", + headers: { + "Content-Type": "application/json", + ...(token ? { Authorization: `Bearer ${token}` } : {}), + }, + body: JSON.stringify(body), + }); + if (!res.ok) { + throw new Error(`API request failed (${res.status}): ${path}`); + } + return res.json() as Promise<T>; +} + +// /auth/me is the one call in this file that needs a bearer token, so it +// bypasses apiGet's no-auth default rather than growing every call site an +// optional header param it will never use. +async function apiGetAuthed<T>(path: string, token: string): Promise<T> { + const res = await fetch(`${API_BASE}${path}`, { + headers: { Authorization: `Bearer ${token}` }, + cache: "no-store", + }); + if (!res.ok) { + throw new Error(`API request failed (${res.status}): ${path}`); + } + return res.json() as Promise<T>; +} + +// Swallows errors so one empty/broken section degrades instead of taking +// the whole page down — several modules on this tenant are still unfilled. +export async function safe<T>(promise: Promise<T>, fallback: T): Promise<T> { + try { + return await promise; + } catch { + return fallback; + } +} + +const t = (path: string) => `/tenants/${SITE_DOMAIN}${path}`; + +export function getTenant(): Promise<Tenant> { + return apiGet<Tenant>(t("")); +} + +export function getBusinessInfo(): Promise<BusinessInfo> { + return apiGet<BusinessInfo>(t("/website/business-info")); +} + +export function getSliders(): Promise<SliderItem[]> { + return apiGet<{ items: SliderItem[] }>(t("/website/sliders")).then((r) => r.items); +} + +export function getStaticImageSlots(pageKey?: string): Promise<StaticImageSlot[]> { + return apiGet<{ items: StaticImageSlot[] }>(t("/website/static-images"), { pageKey }).then( + (r) => r.items + ); +} + +export function getCategories( + entityType: "product" | "blog" | "portfolio" | "video" = "product" +): Promise<ApiCategory[]> { + return apiGet<{ items: ApiCategory[] }>(t("/categories"), { entityType }).then((r) => r.items); +} + +export function getCategoryById(id: string): Promise<ApiCategory> { + return apiGet<{ category: ApiCategory }>(t(`/categories/by-id/${id}`)).then((r) => r.category); +} + +export function getProducts( + params: { + page?: number; + pageSize?: number; + name?: string; + categoryId?: string; + brandId?: string; + tag?: string; + inStore?: boolean; + } = {} +): Promise<PagedResult<ApiProduct>> { + return apiGet<PagedResult<ApiProduct>>(t("/products"), params); +} + +export function getProductById( + id: string +): Promise<{ product: ApiProduct; relatedProducts: ApiProduct[] }> { + return apiGet(t(`/products/by-id/${id}`)); +} + +// Curated merchandising groups managed from the business dashboard +// ("special-discount", "top-selling", …). `source` repeats the tenant's +// specialProductsSource — store_item for this tenant. +export function getStoreSpecials(): Promise<StoreSpecial[]> { + return apiGet<{ source: string; items: StoreSpecial[] }>(t("/store-specials")).then( + (r) => r.items + ); +} + +export function getBlogs( + params: { page?: number; pageSize?: number; categoryId?: string; title?: string } = {} +): Promise<PagedResult<ApiBlog>> { + return apiGet<PagedResult<ApiBlog>>(t("/blogs"), params); +} + +export function getBlogById(id: string): Promise<{ blog: ApiBlog }> { + return apiGet(t(`/blogs/by-id/${id}`)); +} + +export function getProductVariations( + productId: string +): Promise<{ variations: ProductVariation[] }> { + return apiGet(t(`/products/by-id/${productId}/variations`)); +} + +// Labels live in form.fields, values in values[] — join by fieldId. Never +// render values[] alone (see AGENTS.md "Technical specs"). +export function getProductTechnicalInfo( + productId: string +): Promise<{ form: TechnicalInfoForm | null; values: TechnicalInfoValue[] }> { + return apiGet(t(`/products/by-id/${productId}/technical-info`)); +} + +// Not /store-items/by-product/{id} — that one returns a single { storeItem } +// (or null), not the full variant list a buy panel needs. The paginated +// /store-items list, filtered by productId, is the one that returns every +// purchasable variant. +export function getStoreItemsByProduct(productId: string): Promise<{ items: StoreItem[] }> { + return apiGet(t("/store-items"), { productId, pageSize: 100 }); +} + +export function getComments( + entityType: "product" | "blog" | "portfolio" | "video", + entityId: string +): Promise<{ items: EntityComment[] }> { + return apiGet(t("/comments"), { entityType, entityId }); +} + +// Public endpoint (no bearer required by the API), but the storefront gates +// the form behind login per the product-page brief — see ProductInfoTabs. +export function postComment(input: { + entityType: "product" | "blog" | "portfolio" | "video"; + entityId: string; + authorName: string; + authorEmail?: string; + text: string; +}): Promise<{ comment: EntityComment }> { + return apiPost(t("/comments"), input); +} + +// { user } from a bearer token — see src/lib/auth.ts for where the token +// comes from (a cookie the customer portal is expected to set). +export function getMe(token: string): Promise<{ user: AuthUser }> { + return apiGetAuthed("/auth/me", token); +} + +export function submitContactForm( + data: ContactFormInput +): Promise<{ submission: unknown; message: string }> { + return apiPost(t("/contact-submissions"), data); +} + +export function findSlot(slots: StaticImageSlot[], key: string): StaticImageSlot | undefined { + const slot = slots.find((s) => s.key === key); + if (!slot || slot.images.length === 0) return undefined; + return { + ...slot, + images: [...slot.images].sort((a, b) => (a.sortOrder ?? 0) - (b.sortOrder ?? 0)), + }; +} diff --git a/src/lib/auth.ts b/src/lib/auth.ts new file mode 100644 index 0000000..c8dba89 --- /dev/null +++ b/src/lib/auth.ts @@ -0,0 +1,19 @@ +import { cookies } from "next/headers"; +import { getMe, safe } from "./api"; +import { AUTH_TOKEN_COOKIE } from "./config"; +import type { AuthUser } from "./types"; + +export { displayName, isAdmin } from "./authUser"; + +// Server-side "am I logged in" check for the header/account menu. See the +// AUTH_TOKEN_COOKIE comment in config.ts for the one external dependency +// this relies on. Resolves to null whenever the cookie is absent or /auth/me +// fails — never throws, so a missing session degrades to the anonymous view. +export async function getSession(): Promise<AuthUser | null> { + const store = await cookies(); + const token = store.get(AUTH_TOKEN_COOKIE)?.value; + if (!token) return null; + + const result = await safe(getMe(token), null); + return result?.user ?? null; +} diff --git a/src/lib/authUser.ts b/src/lib/authUser.ts new file mode 100644 index 0000000..73f0008 --- /dev/null +++ b/src/lib/authUser.ts @@ -0,0 +1,16 @@ +// Pure AuthUser helpers with no server-only dependency (unlike auth.ts, +// which reads next/headers) — safe to import from client components. +import type { AuthUser } from "./types"; + +// "علی عزیز" — first name (or the fullest name field available) + عزیز. +export function displayName(user: AuthUser): string { + const name = user.firstName || user.nameFa || user.name || "کاربر"; + return `${name} عزیز`; +} + +// Best-effort: the OpenAPI schema doesn't document a role/admin field on the +// user object, so this reads every plausible shape defensively instead of +// assuming one. Absent all of them, the admin menu item is simply hidden. +export function isAdmin(user: AuthUser): boolean { + return Boolean(user.isAdmin) || user.role === "admin" || user.role === "owner"; +} diff --git a/src/lib/cards.ts b/src/lib/cards.ts new file mode 100644 index 0000000..35db36f --- /dev/null +++ b/src/lib/cards.ts @@ -0,0 +1,52 @@ +import { discountPercent } from "./format"; +import { productHref } from "./slug"; +import { DEMO_PRICING, demoPrice } from "./demoPricing"; +import type { ApiProduct, ProductCardData, StoreSpecial } from "./types"; + +export function productToCard(product: ApiProduct): ProductCardData { + const base = { + id: product.id, + href: productHref(product.id, product.nameFa, product.title), + title: product.nameFa || product.title, + image: product.thumbnailUrl || product.image || product.thumbnail, + }; + + if (product.store.minPrice !== null) { + return { ...base, price: product.store.minPrice, oldPrice: null, percent: 0, inStock: product.store.inStock }; + } + + if (DEMO_PRICING) { + const { price, oldPrice } = demoPrice(product); + return { + ...base, + price, + oldPrice, + percent: oldPrice ? discountPercent(price, oldPrice) : 0, + inStock: true, + }; + } + + return { ...base, price: null, oldPrice: null, percent: 0, inStock: false }; +} + +// A curated store-special group carries its own variant pricing, so the +// discount shown there is the real one rather than a product-level guess. +export function specialToCards(special: StoreSpecial): ProductCardData[] { + return special.items + .filter((item) => item.isActive) + .map((item) => { + const variant = item.variants.find((v) => v.discountedPrice !== null) ?? item.variants[0]; + const price = variant?.discountedPrice ?? variant?.price ?? null; + const oldPrice = variant?.discountedPrice !== null ? variant?.price ?? null : null; + return { + id: item.id, + href: productHref(item.productId, item.productNameFa, item.productTitle), + title: item.productNameFa || item.productTitle, + image: item.productImage, + price, + oldPrice, + percent: price !== null && oldPrice !== null ? discountPercent(price, oldPrice) : 0, + inStock: (variant?.stockQuantity ?? 0) > 0, + }; + }); +} diff --git a/src/lib/categoryTree.ts b/src/lib/categoryTree.ts new file mode 100644 index 0000000..0b56603 --- /dev/null +++ b/src/lib/categoryTree.ts @@ -0,0 +1,75 @@ +import type { ApiCategory, CategoryNode } from "./types"; + +export function buildCategoryTree(categories: ApiCategory[]): CategoryNode[] { + const byId = new Map<string, CategoryNode>(); + for (const c of categories) byId.set(c.id, { ...c, children: [] }); + + const roots: CategoryNode[] = []; + for (const node of byId.values()) { + const parent = node.parentId ? byId.get(node.parentId) : undefined; + if (parent) parent.children.push(node); + else roots.push(node); + } + + const sort = (nodes: CategoryNode[]) => { + nodes.sort((a, b) => a.sortOrder - b.sortOrder || a.nameFa.localeCompare(b.nameFa, "fa")); + nodes.forEach((n) => sort(n.children)); + }; + sort(roots); + return roots; +} + +// The category carousel shows level 2 (children of the single root +// "کالای دیجیتال"), which is what shoppers actually browse by. +export function carouselCategories(roots: CategoryNode[]): CategoryNode[] { + const level2 = roots.flatMap((r) => r.children); + return level2.length > 0 ? level2 : roots; +} + +export function findNode(nodes: CategoryNode[], id: string): CategoryNode | undefined { + for (const node of nodes) { + if (node.id === id) return node; + const hit = findNode(node.children, id); + if (hit) return hit; + } + return undefined; +} + +// Products are filed against leaf categories, so a parent like "لوازم جانبی" +// matches almost nothing on its own — a carousel for it has to ask for the +// whole subtree. Capped for the homepage's small carousels; a full category +// listing page needs every id, so use subtreeIdsAll there instead. +export function subtreeIds(node: CategoryNode, limit = 8): string[] { + const ids: string[] = []; + const walk = (n: CategoryNode) => { + if (ids.length >= limit) return; + ids.push(n.id); + n.children.forEach(walk); + }; + walk(node); + return ids; +} + +export function subtreeIdsAll(node: CategoryNode): string[] { + const ids: string[] = []; + const walk = (n: CategoryNode) => { + ids.push(n.id); + n.children.forEach(walk); + }; + walk(node); + return ids; +} + +// Root-first breadcrumb chain, e.g. [کالای دیجیتال, لوازم جانبی, هدفون]. +export function pathToRoot(roots: CategoryNode[], id: string): CategoryNode[] { + const walk = (nodes: CategoryNode[], trail: CategoryNode[]): CategoryNode[] | null => { + for (const node of nodes) { + const nextTrail = [...trail, node]; + if (node.id === id) return nextTrail; + const hit = walk(node.children, nextTrail); + if (hit) return hit; + } + return null; + }; + return walk(roots, []) ?? []; +} diff --git a/src/lib/config.ts b/src/lib/config.ts new file mode 100644 index 0000000..66cda90 --- /dev/null +++ b/src/lib/config.ts @@ -0,0 +1,24 @@ +export const SITE_DOMAIN = process.env.NEXT_PUBLIC_SITE_DOMAIN || "moalem.shop"; +export const API_BASE = "https://api.meshkee.com/api/v1"; +export const SITE_URL = `https://${SITE_DOMAIN}`; +export const CUSTOMER_PORTAL_URL = `https://customer.${SITE_DOMAIN}`; +// Business dashboard — the AI brief reserves "business." alongside "api." +// and "customer." as a subdomain on every Meshkee site. Used only for the +// "مدیریت وبسایت" account-menu item shown to admins. +export const BUSINESS_PORTAL_URL = `https://business.${SITE_DOMAIN}`; + +// Login happens on the customer portal (a separate app), not on this site. +// For the account menu to know a visitor is signed in, that portal needs to +// set this cookie on the shared parent domain (".{SITE_DOMAIN}", not +// HttpOnly) after login, so requests to this site carry it too. Until it +// does, getSession() in src/lib/auth.ts always resolves to null and the +// header simply shows the anonymous "ورود | ثبت‌نام" link — nothing breaks, +// the logged-in state just never activates. +export const AUTH_TOKEN_COOKIE = "meshkee_token"; + +// Pulled from the live moalem.shop theme (--wb-primary-color / --wb-secondary-color). +export const BRAND = { + primary: "#E51E22", + primaryDark: "#B10123", + secondary: "#3B3B3B", +} as const; diff --git a/src/lib/demoPricing.ts b/src/lib/demoPricing.ts new file mode 100644 index 0000000..7d7a09b --- /dev/null +++ b/src/lib/demoPricing.ts @@ -0,0 +1,48 @@ +// TEMPORARY. This tenant has 479 products but zero store-items, so the API +// reports every product as priceless and out of stock and the homepage +// carousels would render as a wall of "ناموجود". +// +// While NEXT_PUBLIC_DEMO_PRICING=1 the cards fall back to a deterministic +// price derived from the product id (same product -> same number on server +// and client, so hydration stays stable). Real store-items always win. +// Delete this file and the env flag once pricing is entered in the dashboard. +import type { ApiProduct } from "./types"; + +export const DEMO_PRICING = process.env.NEXT_PUBLIC_DEMO_PRICING === "1"; + +// Sequential product ids ("4216", "4217") differ by one character, so the +// hash needs an avalanche step or neighbouring products land on the same price. +function hash(id: string): number { + let h = 2166136261; + for (let i = 0; i < id.length; i++) { + h ^= id.charCodeAt(i); + h = Math.imul(h, 16777619); + } + h ^= h >>> 15; + h = Math.imul(h, 2246822507); + h ^= h >>> 13; + return h >>> 0; +} + +const BANDS: { match: RegExp; min: number; max: number }[] = [ + { match: /موبایل|گوشی|آیفون/, min: 12_000_000, max: 95_000_000 }, + { match: /لپ ?تاپ|مک ?بوک/, min: 35_000_000, max: 180_000_000 }, + { match: /تبلت/, min: 9_000_000, max: 60_000_000 }, + { match: /کنسول|پلی ?استیشن/, min: 25_000_000, max: 70_000_000 }, + { match: /ساعت|بند/, min: 1_500_000, max: 18_000_000 }, + { match: /هدفون|هندزفری|اسپیکر/, min: 800_000, max: 12_000_000 }, +]; + +export function demoPrice(product: ApiProduct): { price: number; oldPrice: number | null } { + const name = product.nameFa || product.title; + const band = BANDS.find((b) => b.match.test(name)) ?? { min: 300_000, max: 4_000_000 }; + const h = hash(product.id); + const span = band.max - band.min; + const raw = band.min + (h % span); + const price = Math.round(raw / 10_000) * 10_000; + // Roughly one product in three carries a discount, 5–25%. + const hasDiscount = h % 3 === 0; + const percent = 5 + (h % 21); + const oldPrice = hasDiscount ? Math.round((price / (1 - percent / 100)) / 10_000) * 10_000 : null; + return { price, oldPrice }; +} diff --git a/src/lib/format.ts b/src/lib/format.ts new file mode 100644 index 0000000..8343c22 --- /dev/null +++ b/src/lib/format.ts @@ -0,0 +1,23 @@ +const FA_DIGITS = ["۰", "۱", "۲", "۳", "۴", "۵", "۶", "۷", "۸", "۹"]; + +export function toFaDigits(input: string | number): string { + return String(input).replace(/\d/g, (d) => FA_DIGITS[Number(d)]); +} + +// Prices come from the API in Toman. +export function formatPrice(value: number | null | undefined): string { + if (value === null || value === undefined) return ""; + return toFaDigits(value.toLocaleString("en-US")); +} + +export function discountPercent(price: number, oldPrice: number): number { + if (!oldPrice || oldPrice <= price) return 0; + return Math.round(((oldPrice - price) / oldPrice) * 100); +} + +// The static-image-slot API documents aspectRatio as "16:9", but CSS's +// aspect-ratio property needs a slash ("16/9"), not a colon. +export function toCssAspectRatio(value: string | number | null | undefined): string | undefined { + if (value === null || value === undefined || value === "") return undefined; + return String(value).replace(":", "/"); +} diff --git a/src/lib/slug.ts b/src/lib/slug.ts new file mode 100644 index 0000000..7c88be5 --- /dev/null +++ b/src/lib/slug.ts @@ -0,0 +1,24 @@ +// Canonical Meshkee detail URLs are /{id}/{slug} — the slug segment is +// SEO-only, so it is built from the Farsi name rather than the DB slug. +export function faSlug(input: string | null | undefined): string { + if (!input) return "-"; + return ( + input + .trim() + .replace(/[\/\?%*:|"'<>.,()]/g, "") + .replace(/\s+/g, "-") + .slice(0, 80) || "-" + ); +} + +export function productHref(id: string, nameFa?: string | null, title?: string | null): string { + return `/products/${id}/${faSlug(nameFa || title)}`; +} + +export function categoryHref(id: string, nameFa?: string | null, name?: string | null): string { + return `/products/category/${id}/${faSlug(nameFa || name)}`; +} + +export function blogHref(id: string, title?: string | null): string { + return `/blog/${id}/${faSlug(title)}`; +} diff --git a/src/lib/technicalInfo.ts b/src/lib/technicalInfo.ts new file mode 100644 index 0000000..f979d4a --- /dev/null +++ b/src/lib/technicalInfo.ts @@ -0,0 +1,42 @@ +import type { SpecRow, TechnicalInfoForm, TechnicalInfoValue } from "./types"; + +// technical-info values carry no label — only fieldId + a raw value. This +// joins them against form.fields (by id) to build the label→value rows the +// specs tab actually renders. See AGENTS.md "Technical specs" for why this +// join is required and what NOT to do (never read a label off values[]). +export function buildSpecRows( + form: TechnicalInfoForm | null | undefined, + values: TechnicalInfoValue[] +): SpecRow[] { + if (!form || values.length === 0) return []; + + const fieldsById = new Map(form.fields.map((f) => [f.id, f])); + const rows: SpecRow[] = []; + + for (const value of values) { + const field = fieldsById.get(value.fieldId); + if (!field) continue; + + const label = field.labelFa || field.label || field.key || value.fieldId; + let display = ""; + + if (value.textValue) { + display = value.textValue; + } else if (value.optionId && field.options) { + const opt = field.options.find((o) => o.id === value.optionId); + display = opt ? opt.labelFa || opt.label || opt.id : ""; + } else if (value.optionIds && field.options) { + display = value.optionIds + .map((id) => field.options?.find((o) => o.id === id)) + .filter((o): o is NonNullable<typeof o> => Boolean(o)) + .map((o) => o.labelFa || o.label || o.id) + .join("، "); + } + + if (display) rows.push({ key: field.id, label, value: display }); + } + + return rows.sort( + (a, b) => (fieldsById.get(a.key)?.sortOrder ?? 0) - (fieldsById.get(b.key)?.sortOrder ?? 0) + ); +} diff --git a/src/lib/types.ts b/src/lib/types.ts new file mode 100644 index 0000000..dd4b906 --- /dev/null +++ b/src/lib/types.ts @@ -0,0 +1,302 @@ +export interface SocialMediaLinks { + whatsapp?: string; + telegram?: string; + instagram?: string; + linkedin?: string; + youtube?: string; + aparat?: string; +} + +export interface Tenant { + id: number; + name: string; + nameFa: string; + slug: string; + domain: string; + primaryColor: string | null; + defaultLocale: string; + themeMode: string; + enabledModules: string[]; + specialProductsSource: "product" | "store_item"; + logoUrl: string | null; + logoDarkUrl: string | null; + faviconUrl: string | null; +} + +// This tenant has no emails/phones/addresses filled in yet, so the loose +// shapes are read defensively at render time. +export interface BusinessInfo { + id: string; + name: string; + nameFa: string; + about: string; + vision: string; + logoUrl: string | null; + logoDarkUrl: string | null; + faviconUrl: string | null; + emails: unknown[]; + phoneNumbers: unknown[]; + socialMedia: SocialMediaLinks; + addresses: unknown[]; +} + +export interface ApiCategory { + id: string; + businessId: string; + entityType: string; + parentId: string | null; + name: string; + nameFa: string; + slug: string; + description: string | null; + sortOrder: number; + isActive: boolean; + variationCount: number; +} + +export interface CategoryNode extends ApiCategory { + children: CategoryNode[]; +} + +export interface ProductImage { + mediaId: string; + url: string; +} + +export interface ProductStoreSummary { + variantCount: number; + minPrice: number | null; + maxPrice: number | null; + inStock: boolean; +} + +export interface ApiProduct { + id: string; + businessId: string; + title: string; + nameFa: string; + summary: string; + descriptionHtml: string; + slug: string; + status: string; + categoryId: string | null; + categoryName: string | null; + categoryNameFa: string | null; + brandId: string | null; + brand: string | null; + tags: string[]; + thumbnailUrl: string | null; + image: string | null; + thumbnail: string | null; + images: ProductImage[]; + commentCount: number; + variantCount: number; + createdAt: string; + updatedAt: string; + store: ProductStoreSummary; +} + +export interface PagedResult<T> { + items: T[]; + total: number; + page: number; + pageSize: number; +} + +// The blog module is enabled but empty on this tenant, so every field is +// optional and consumers must read defensively. +export interface ApiBlog { + id: string; + title?: string; + nameFa?: string; + summary?: string; + descriptionHtml?: string; + contentHtml?: string; + slug: string; + thumbnailUrl?: string | null; + image?: string | null; + categoryName?: string | null; + categoryNameFa?: string | null; + createdAt?: string; + publishedAt?: string; +} + +export interface SliderItem { + id?: string; + title?: string | null; + subtitle?: string | null; + imageUrl?: string | null; + mobileImageUrl?: string | null; + linkUrl?: string | null; + sortOrder?: number; +} + +export interface StaticImageItem { + id: string; + url: string; + titleFa?: string | null; + titleEn?: string | null; + subtext?: string | null; + linkUrl?: string | null; + sortOrder?: number; +} + +export interface StaticImageSlot { + key: string; + pageKey: string; + kind: "single" | "list"; + label: string; + aspectRatio?: string | number | null; + recommendedWidth?: number | null; + itemCount?: number | null; + images: StaticImageItem[]; +} + +export interface StoreItemSelection { + variationId: string; + variationName: string; + optionId: string; + value: string; +} + +export interface StoreSpecialVariant { + id: string; + label: string; + selections: StoreItemSelection[]; + price: number | null; + discountedPrice: number | null; + stockQuantity: number; + isFestival: boolean; + sortOrder: number; +} + +export interface StoreSpecialItem { + id: string; + productId: string; + productTitle: string; + productNameFa: string; + productImage: string | null; + isActive: boolean; + sortOrder: number; + variants: StoreSpecialVariant[]; +} + +export interface StoreSpecial { + id: string; + key: string; + title: string; + sortOrder: number; + isActive: boolean; + items: StoreSpecialItem[]; +} + +// A card the carousels render: a product plus the pricing actually shown. +export interface ProductCardData { + id: string; + href: string; + title: string; + image: string | null; + price: number | null; + oldPrice: number | null; + percent: number; + inStock: boolean; +} + +export interface ContactFormInput { + title: string; + name: string; + text: string; + email?: string; + cellNumber?: string; +} + +export interface VariationOption { + id: string; + label: string; + value: string; + colorHex: string | null; + sortOrder: number; +} + +export interface ProductVariation { + id: string; + name: string; + type: string; + sortOrder: number; + options: VariationOption[]; +} + +// A concrete, purchasable variant of a product (one combination of +// variation options), as listed under /store-items. +export interface StoreItem { + id: string; + storeItemId: string; + productId: string; + productTitle: string; + productNameFa: string; + productImage: string | null; + productTotalStock: number; + selections: StoreItemSelection[]; + label: string; + price: number | null; + discountedPrice: number | null; + stockQuantity: number; + rewardPoints: number | null; + isFestival: boolean; + sortOrder: number; +} + +// Technical-info values carry no label (only fieldId + a value) — the label +// comes from the matching technical-info() form.fields entry. See +// AGENTS.md "Technical specs" for the required join. +export interface TechnicalInfoField { + id: string; + label?: string; + labelFa?: string; + key?: string; + type?: string; + sortOrder?: number; + options?: { id: string; label?: string; labelFa?: string }[]; +} + +export interface TechnicalInfoForm { + fields: TechnicalInfoField[]; +} + +export interface TechnicalInfoValue { + fieldId: string; + textValue?: string | null; + optionId?: string | null; + optionIds?: string[] | null; +} + +// A joined, display-ready spec row — built by src/lib/technicalInfo.ts from +// the raw form + values, never rendered from values[] alone. +export interface SpecRow { + key: string; + label: string; + value: string; +} + +// Comment/user shapes aren't documented beyond a few fields, so every +// optional field is read defensively at render time. +export interface EntityComment { + id: string; + authorName?: string; + text?: string; + createdAt?: string; +} + +// GET /auth/me → { user }. Field names beyond id/name aren't in the OpenAPI +// schema, so this is intentionally loose and every field but id is read +// defensively (see src/lib/auth.ts). +export interface AuthUser { + id: string; + name?: string; + nameFa?: string; + firstName?: string; + lastName?: string; + cellNumber?: string; + isAdmin?: boolean; + role?: string; +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..cf9c65d --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,34 @@ +{ + "compilerOptions": { + "target": "ES2017", + "lib": ["dom", "dom.iterable", "esnext"], + "allowJs": true, + "skipLibCheck": true, + "strict": true, + "noEmit": true, + "esModuleInterop": true, + "module": "esnext", + "moduleResolution": "bundler", + "resolveJsonModule": true, + "isolatedModules": true, + "jsx": "react-jsx", + "incremental": true, + "plugins": [ + { + "name": "next" + } + ], + "paths": { + "@/*": ["./src/*"] + } + }, + "include": [ + "next-env.d.ts", + "**/*.ts", + "**/*.tsx", + ".next/types/**/*.ts", + ".next/dev/types/**/*.ts", + "**/*.mts" + ], + "exclude": ["node_modules"] +}