commit 51fb9f6a26299e2f3ae729ccb5dfec7ac8a7603c Author: Alireza Hassani Date: Mon Aug 31 17:01:01 2026 +0330 Add the Kaman Cable Next.js storefront with Meshkee catalog, blog, auth, and static-image slots. Co-authored-by: Cursor diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..abf07ab --- /dev/null +++ b/.gitignore @@ -0,0 +1,37 @@ +# dependencies +/node_modules +/.pnp +.pnp.* +.yarn/* +!.yarn/patches +!.yarn/plugins +!.yarn/releases +!.yarn/versions + +# testing +/coverage + +# next.js +/.next/ +/out/ + +# production +/build + +# misc +.DS_Store +*.pem + +# debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* +.pnpm-debug.log* + +# env files +.env*.local +.env + +# typescript +*.tsbuildinfo +next-env.d.ts diff --git a/CONTEXT.md b/CONTEXT.md new file mode 100644 index 0000000..04fe2a8 --- /dev/null +++ b/CONTEXT.md @@ -0,0 +1,160 @@ +# Kaman Cable — AI / Project Context + +## Source of truth +- Live reference: https://www.kamancable.ir/ +- Backend / Meshkee Website API: https://api.meshkee.com/docs/website +- Domain: `kamancable.ir` (`NEXT_PUBLIC_WEBSITE_DOMAIN`) +- Tenant business id: `24` (`GET /tenants/kamancable.ir`) +- Customer portal: `https://customer.kamancable.ir` +- Business dashboard: `https://business.kamancable.ir` +- Catalog and blog content come from Meshkee (`enabledModules`: products, blog). Business-info / logos in the API may be empty — brand copy and some homepage photos still live in `src/data/site.ts` and `public/images/`. + +## Stack +- Next.js 15 (App Router) + React 19 + TypeScript +- Tailwind CSS v4 (utility import) + custom CSS in `src/app/globals.css` +- Language: `fa`, direction: `rtl` +- Fonts: + - `iran-yekan` from Meshkee CDN (Persian UI / body) + - `Rajdhani` via `next/font/google` (English UI, class `.en`) + - `Oswald` via `next/font/google` (`--font-en-display` for section titles / phone) + +## Color palette +- Dark navy: `#1C1E40` +- White: `#FFFFFF` +- Gold accent: `#E1A34C` +- Highlight yellow: `#FFDE30` +- Muted text: `#C8CCE0` + +## Pages +| Route | Purpose | +|-------|---------| +| `/` | Homepage (hero, app CTA, about teaser, contact bar, category cards, blog carousel, clients) | +| `/products` | Product catalog from `GET /tenants/{domain}/products` | +| `/products/{id}/{nameFaSlug}` | Product detail (`GET .../products/by-id/{id}`) | +| `/products/category/{id}/{nameFaSlug}` | Category (`GET .../categories/by-id/{id}`, then products with `categoryId`) | +| `/blog` | Blog index from `GET /tenants/{domain}/blogs` | +| `/blog/{id}/{titleSlug}` | Blog detail (`GET .../blogs/by-id/{id}`) | +| `/about` | درباره ما | +| `/contact` | تماس با ما | + +Header (RTL): logo + menu on the right; search, language (FA/EN), and account on the left. +- Signed out: icon links to `https://customer.kamancable.ir` +- Signed in: `{firstName} عزیز` dropdown — پروفایل من, خروج, and مدیریت وبسایت if the user is a website admin +- Session: shared cookies `meshkee_customer_access_token` / refresh, hydrated into localStorage; `/api/auth/me` proxies `GET /auth/me` + +Homepage product cards are Meshkee `categories` slot teasers (duplicatable; empty API keeps `src/data/site.ts`). The full catalog is `/products`. +Homepage blog carousel prefers Meshkee blogs, with `src/data/site.ts` as fallback. + +## Meshkee API integration +Base: `{NEXT_PUBLIC_MESHKEE_API_BASE}/tenants/{domain}/...` +Helpers: `src/lib/meshkee.ts` → `meshkeeGet`, `meshkeeGetOrNull`, `tenantPath`, `getTenant`, `getBusinessId` + +### Products +- List: `GET /tenants/{domain}/products?page&pageSize&categoryId` +- Detail: `GET /tenants/{domain}/products/by-id/{id}` → `{ product, relatedProducts }` +- Category: `GET /tenants/{domain}/categories?entityType=product` and `GET .../categories/by-id/{id}` +- Specs: `GET /tenants/{domain}/products/by-id/{id}/technical-info` — join `form.fields[].id` ↔ `values[].fieldId`. Do not invent field labels from the detail payload. +- Helpers: `src/lib/products.ts`, `src/lib/categories.ts`, `src/lib/technical.ts` +- Detail UI: lightbox gallery, hover zoom, tabs (توضیحات / مشخصات فنی / نظرات), similar products row +- Public slugs are built from `nameFa` / title (Farsi kept, spaces → `-`). Never use the legacy DB `slug` in the URL. + +### Blog +- List: `GET /tenants/{domain}/blogs?page&pageSize` +- Detail: `GET /tenants/{domain}/blogs/by-id/{id}` → `{ blog }` +- Helpers: `src/lib/blog.ts` +- Empty API → local posts in `src/data/site.ts` + +### Comments +- List: `GET /tenants/{domain}/comments?entityType=product|blog&entityId=` +- Submit: website proxy `POST /api/comments` → Meshkee comments +- UI: authenticated users get a form; guests see a login box to the customer portal (`src/components/CommentBox.tsx`) + +### Contact +- Public POST: `/tenants/kamancable.ir/contact-submissions` +- Website proxy: `POST /api/contact` +- Body: `{ title, name, text, email?, cellNumber? }` +- Homepage callback strip also posts through this route + +### Auth proxies +- `GET /api/auth/me` → `GET /auth/me` +- `POST /api/auth/handoff` → `POST /auth/handoff` (admin dashboard) + +## Static image slots +API: `GET /tenants/{domain}/website/static-images?pageKey=home|about|contact|products|blog` +Docs: https://api.meshkee.com/docs/website (not http://meshkee.com/docs/website) +Catalog: `GET /meshkee/static-image-slots` → `STATIC_IMAGE_SLOTS` in `src/lib/static-images.ts` +Helpers: `fetchStaticImageSlots`, `pickSlot` (aliases for renamed keys), `overlaySlotImages`, `slotRatioStyle` + +Homepage placeholders (match Meshkee dashboard keys exactly): + +| key | kind | itemCount | aspectRatio | recommendedWidth | section | +| --- | --- | --- | --- | --- | --- | +| `slider` | list (duplicatable) | null | `1920:830` | 1440 | Homepage hero (72vh cover crop) | +| `about-us-image` | single | 1 | `440:609` | 480 | Homepage about photo | +| `categories` | list (duplicatable) | null | `3:4` | 480 | Homepage product cards (`.product-card .thumb`) | +| `partners` | list (duplicatable) | null | `3:2` | 280 | Homepage partner logos | +| `home-blog` | list | 6 | `16:10` | 720 | Homepage blog carousel | +| `about-hero` | single | 1 | `1920:640` | 1440 | About hero | +| `about-gallery` | list | 6 | `4:3` | 720 | About gallery | +| `contact-hero` | single | 1 | `1920:480` | 1440 | Contact hero | +| `products-hero` | single | 1 | `1920:480` | 1440 | Products page hero | +| `blog-hero` | single | 1 | `1920:480` | 1440 | Blog page hero | + +Legacy aliases (still resolved by `pickSlot` if the API has not been re-synced): `home-about-image` → `about-us-image`, `home-products` → `categories`, `home-clients` → `partners`. + +Rules: +- Use `slot.images` (all if `kind=list`, first if `kind=single`) +- `url` → image src; `linkUrl` → wrap in `SlotLink` +- Empty / fail → keep local fallbacks from `src/data/site.ts` / `public/images/` +- Apply `slot.aspectRatio` as CSS `--slot-ratio` (`slotRatioStyle`); do not invent upload APIs +- **If a slot’s on-page size or aspect ratio changes in CSS, update `STATIC_IMAGE_SLOTS` in the same change** (Meshkee’s static-image reader uses this catalog) + +## Content & assets +- Site copy, phones, address, category blurbs: `src/data/site.ts` +- Logo: `public/images/logo/logo.png` +- Hero banners: `public/images/slider/bg1.jpg` … `bg3.jpg` +- Favicon: `/favicon.png` +- Contact: `021-242624` · دفتر تهران، خیابان کریم‌خان زند · کارخانه شهر صنعتی شماره یک زنجان +- App: https://kamancable.com/applanding/ + +## Important paths +- Layout / shell: `src/app/layout.tsx`, `src/components/Header.tsx`, `src/components/AccountMenu.tsx`, `src/components/Footer.tsx` +- Home: `src/app/page.tsx`, `src/components/HeroSlider.tsx`, `src/components/ContactBar.tsx` +- Products: `src/app/products/page.tsx`, `src/app/products/[id]/[slug]/page.tsx` +- Blog: `src/app/blog/page.tsx`, `src/app/blog/[id]/[slug]/page.tsx` +- Config: `src/lib/config.ts` + +## Env +```bash +NEXT_PUBLIC_WEBSITE_DOMAIN=kamancable.ir +NEXT_PUBLIC_MESHKEE_API_BASE=https://api.meshkee.com/api/v1 +``` + +## Commands +```bash +npm install +npm run dev +npm run build +``` + +## Sitemap + robots +- **Do not** add `public/sitemap.xml` or `public/robots.txt` (nginx proxies both to the Meshkee API) +- Live sitemap is an index: `/sitemap.xml` → `/sitemap-main.xml` + `/sitemap-products.xml` +- Publish `GET /meshkee/sitemap-config.json` (generated at build from app routes) +- Includes public static routes automatically; excludes `api` / `meshkee` / `auth` / `checkout` / `cart` / `admin` / `account` / `login` +- Generated at build: `scripts/generate-sitemap-config.mjs` → `src/lib/sitemap-config.generated.json` +- Run manually: `npm run generate:sitemap-config` +- After deploy: business dashboard → Website → Settings → Sync sitemap config (needed for static pages) + +### Canonical CMS URLs (id is canonical; slug is decorative and redirects if stale) +- Product: `/products/{id}/{nameFaSlug}` → `GET /tenants/{domain}/products/by-id/{id}` +- Category: `/products/category/{id}/{nameFaSlug}` → `GET /tenants/{domain}/categories/by-id/{id}` then list products with `categoryId` +- Blog: `/blog/{id}/{titleSlug}` → `GET /tenants/{domain}/blogs/by-id/{id}` +- Portfolio is not enabled for this tenant (`enabledModules`: products, blog, finance) + +### On-page SEO rules +- Unique `` + meta description on every crawlable page (`src/lib/seo.ts` → `pageMetadata`); paginated list pages include the page number +- Prefer CMS `nameFa` / `title` / `abstract` / `summary` / `descriptionHtml`; never leave Next.js defaults +- Exactly one `<h1>` per page; section headings use `<h2>` / `<h3>` +- Content images have meaningful `alt` (CMS title when available) +- Slugify: Farsi letters kept, spaces → `-`, other punctuation stripped (`src/lib/slug.ts`) diff --git a/eslint.config.mjs b/eslint.config.mjs new file mode 100644 index 0000000..bbd17c7 --- /dev/null +++ b/eslint.config.mjs @@ -0,0 +1,14 @@ +import { dirname } from "path"; +import { fileURLToPath } from "url"; +import { FlatCompat } from "@eslint/eslintrc"; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); + +const compat = new FlatCompat({ + baseDirectory: __dirname, +}); + +const eslintConfig = [...compat.extends("next/core-web-vitals", "next/typescript")]; + +export default eslintConfig; diff --git a/next.config.ts b/next.config.ts new file mode 100644 index 0000000..e777a1f --- /dev/null +++ b/next.config.ts @@ -0,0 +1,26 @@ +import type { NextConfig } from "next"; + +const nextConfig: NextConfig = { + images: { + remotePatterns: [ + { + protocol: "https", + hostname: "meshkee-templates.sas.amin.parminstorage.ir", + }, + { + protocol: "https", + hostname: "meshkee-storage.sas.amin.parminstorage.ir", + }, + { + protocol: "https", + hostname: "c804387.parspack.net", + }, + { + protocol: "https", + hostname: "cdn.meshkee.com", + }, + ], + }, +}; + +export default nextConfig; diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..a65b38e --- /dev/null +++ b/package-lock.json @@ -0,0 +1,6263 @@ +{ + "name": "kaman-cable", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "kaman-cable", + "version": "0.1.0", + "dependencies": { + "next": "^15.3.3", + "react": "^19.1.0", + "react-dom": "^19.1.0" + }, + "devDependencies": { + "@eslint/eslintrc": "^3.3.1", + "@tailwindcss/postcss": "^4.1.8", + "@types/node": "^22.15.21", + "@types/react": "^19.1.6", + "@types/react-dom": "^19.1.5", + "eslint": "^9.28.0", + "eslint-config-next": "^15.3.3", + "tailwindcss": "^4.1.8", + "typescript": "^5.8.3" + } + }, + "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/@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": "15.5.24", + "resolved": "https://registry.npmjs.org/@next/env/-/env-15.5.24.tgz", + "integrity": "sha512-mBDF7T0XKZjs9SpUAl0buizVO+O02ULjOvWX8o/AZo/5AGw/UAS1Zzcylmd4pqbftzmKQi+L/nB4jgBYKEAl5Q==", + "license": "MIT" + }, + "node_modules/@next/eslint-plugin-next": { + "version": "15.5.24", + "resolved": "https://registry.npmjs.org/@next/eslint-plugin-next/-/eslint-plugin-next-15.5.24.tgz", + "integrity": "sha512-K6jcYhwMY73qpeJ5SP+rrXs7Fuvv94HEvOCEen/zjwG7aq83utZY8n+8YdKHZR1lv0n5cB4z9C6QqwpE9acN+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-glob": "3.3.1" + } + }, + "node_modules/@next/swc-darwin-arm64": { + "version": "15.5.24", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-15.5.24.tgz", + "integrity": "sha512-AGdNLvxZNY6eR2iSnV+6wUa8CiHTMr4F7g3uHH7fT4ICIJBE00R9u4tzN/Vuwsw0cOi8MTD2HJcTCb6siMH88Q==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-darwin-x64": { + "version": "15.5.24", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-15.5.24.tgz", + "integrity": "sha512-9HrQajBMmGcrrrvDfRimiCrbAPh3E6uHJmwBovYr6Yrmi9p9PZqI876BrXX280wICh3o2XwUlp4blkB0NNBqFg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-arm64-gnu": { + "version": "15.5.24", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-15.5.24.tgz", + "integrity": "sha512-rl9LSfE75si0WT3cDgdUC1XYCKS+TgxC+/IjitmeycrAG18X/plIP1/vy8dd/HPycYcIvE688PD7FuvEAiEAew==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-arm64-musl": { + "version": "15.5.24", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-15.5.24.tgz", + "integrity": "sha512-TlNAnpsjxSF3aAUtqnfmtXXf8m9sIDBlmF3c7bTAlnshUYu2U0OxN2uf5d0gcFwqHVEdivJNBcCaqNOwPGNimw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-x64-gnu": { + "version": "15.5.24", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-15.5.24.tgz", + "integrity": "sha512-7dwtlhr0SLndqTG1z9ncRkbJswDZiKWlxzFyXDvJ2RDZRDRHp8zyMJ4D9UH/FgnQeXxxB6gZy2pMcIUoNKQ4pA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-x64-musl": { + "version": "15.5.24", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-15.5.24.tgz", + "integrity": "sha512-kGZxM+WhkYs0276lFrMkj7PRtXT3Btp6cwvfSO/cCVLxJJttB5Ccnl2niaCgUja8HgSbEVnMHpg3FJWoOJ9e/g==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-win32-arm64-msvc": { + "version": "15.5.24", + "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-15.5.24.tgz", + "integrity": "sha512-jBDDkZ/qKAqkWivWDMkJSXUzbzV0QKRBKJjEHUAvSB97Hzw7NLzJ6yV56Lts/wjir7s4P31GYgpbS6ZL+hasAA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-win32-x64-msvc": { + "version": "15.5.24", + "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-15.5.24.tgz", + "integrity": "sha512-JqtwjvvorjacQ0spgjmUJoxySoYgPwdT1sFdQ0/zmW4iMlP2hjYlCoJIyS7o6Epb4Fug8eco3HXFoBDUCDeH7Q==", + "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/@rushstack/eslint-patch": { + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/@rushstack/eslint-patch/-/eslint-patch-1.16.1.tgz", + "integrity": "sha512-TvZbIpeKqGQQ7X0zSCvPH9riMSFQFSggnfBjFZ1mEoILW+UuXCKwOoPcgjMwiUtRqFZ8jWhPJc4um14vC6I4ag==", + "dev": true, + "license": "MIT" + }, + "node_modules/@swc/helpers": { + "version": "0.5.15", + "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.15.tgz", + "integrity": "sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.8.0" + } + }, + "node_modules/@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": "22.20.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.20.1.tgz", + "integrity": "sha512-EANqOCF9QFyra+4pfxUcX9STKJpCLjMbObVzljIJomAWSnuSIEAvyzEU53GaajbXJEgdh0iEcPL+DGvpUd4k1Q==", + "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/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/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/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/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/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/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": "15.5.24", + "resolved": "https://registry.npmjs.org/eslint-config-next/-/eslint-config-next-15.5.24.tgz", + "integrity": "sha512-RXne8azyj9DKV9N15elyRsnh98BtKINYHCG7bmVhpIhaOtaYvrNJ37XwWpjY9Z09f5RHYOf9vn5uU0MgV1iLrw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@next/eslint-plugin-next": "15.5.24", + "@rushstack/eslint-patch": "^1.10.3", + "@typescript-eslint/eslint-plugin": "^5.4.2 || ^6.0.0 || ^7.0.0 || ^8.0.0", + "@typescript-eslint/parser": "^5.4.2 || ^6.0.0 || ^7.0.0 || ^8.0.0", + "eslint-import-resolver-node": "^0.3.6", + "eslint-import-resolver-typescript": "^3.5.2", + "eslint-plugin-import": "^2.31.0", + "eslint-plugin-jsx-a11y": "^6.10.0", + "eslint-plugin-react": "^7.37.0", + "eslint-plugin-react-hooks": "^5.0.0" + }, + "peerDependencies": { + "eslint": "^7.23.0 || ^8.0.0 || ^9.0.0", + "typescript": ">=3.3.1" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "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-import/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/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": "5.2.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-5.2.0.tgz", + "integrity": "sha512-+f15FfK64YQwZdJNELETdn5ibXEUQmW1DZL6KXhNnc2heoy/sg9VJJeT7n8TlMWouzWqSWavFkIhHyIbIAEapg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0" + } + }, + "node_modules/eslint-plugin-react/node_modules/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/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/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/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-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/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": "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/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/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": "15.5.24", + "resolved": "https://registry.npmjs.org/next/-/next-15.5.24.tgz", + "integrity": "sha512-Y+xn8EQCoC3ZbsFPyzE+tE8XOdrWeUdUF7NeXbmg9DsgAxl5UYxlsrvgVESHTyTGigoTa1bCUrxn70F5bqt0Gw==", + "license": "MIT", + "dependencies": { + "@next/env": "15.5.24", + "@swc/helpers": "0.5.15", + "caniuse-lite": "^1.0.30001579", + "postcss": "8.4.31", + "styled-jsx": "5.1.6" + }, + "bin": { + "next": "dist/bin/next" + }, + "engines": { + "node": "^18.18.0 || ^19.8.0 || >= 20.0.0" + }, + "optionalDependencies": { + "@next/swc-darwin-arm64": "15.5.24", + "@next/swc-darwin-x64": "15.5.24", + "@next/swc-linux-arm64-gnu": "15.5.24", + "@next/swc-linux-arm64-musl": "15.5.24", + "@next/swc-linux-x64-gnu": "15.5.24", + "@next/swc-linux-x64-musl": "15.5.24", + "@next/swc-win32-arm64-msvc": "15.5.24", + "@next/swc-win32-x64-msvc": "15.5.24", + "sharp": "^0.34.3 || ^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.4.31", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", + "integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.6", + "picocolors": "^1.0.0", + "source-map-js": "^1.0.2" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/node-exports-info": { + "version": "1.6.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-exports-info/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/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": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "devOptional": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "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/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/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/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/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/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/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" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..de67318 --- /dev/null +++ b/package.json @@ -0,0 +1,29 @@ +{ + "name": "kaman-cable", + "version": "0.1.0", + "private": true, + "scripts": { + "prebuild": "node scripts/generate-sitemap-config.mjs", + "dev": "next dev", + "build": "next build", + "start": "next start", + "lint": "next lint", + "generate:sitemap-config": "node scripts/generate-sitemap-config.mjs" + }, + "dependencies": { + "next": "^15.3.3", + "react": "^19.1.0", + "react-dom": "^19.1.0" + }, + "devDependencies": { + "@eslint/eslintrc": "^3.3.1", + "@tailwindcss/postcss": "^4.1.8", + "@types/node": "^22.15.21", + "@types/react": "^19.1.6", + "@types/react-dom": "^19.1.5", + "eslint": "^9.28.0", + "eslint-config-next": "^15.3.3", + "tailwindcss": "^4.1.8", + "typescript": "^5.8.3" + } +} 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..7371d8d Binary files /dev/null and b/public/favicon.png differ diff --git a/public/images/about/factory.jpg b/public/images/about/factory.jpg new file mode 100644 index 0000000..6914b23 Binary files /dev/null and b/public/images/about/factory.jpg differ diff --git a/public/images/clients/client-1.png b/public/images/clients/client-1.png new file mode 100644 index 0000000..4e2b022 Binary files /dev/null and b/public/images/clients/client-1.png differ diff --git a/public/images/clients/client-2.png b/public/images/clients/client-2.png new file mode 100644 index 0000000..d86df41 Binary files /dev/null and b/public/images/clients/client-2.png differ diff --git a/public/images/clients/client-3.png b/public/images/clients/client-3.png new file mode 100644 index 0000000..f2e7b83 Binary files /dev/null and b/public/images/clients/client-3.png differ diff --git a/public/images/clients/client-4.png b/public/images/clients/client-4.png new file mode 100644 index 0000000..2b7eecc Binary files /dev/null and b/public/images/clients/client-4.png differ diff --git a/public/images/clients/client-5.png b/public/images/clients/client-5.png new file mode 100644 index 0000000..c7a6722 Binary files /dev/null and b/public/images/clients/client-5.png differ diff --git a/public/images/footer/MeshkeeLogo-White.png b/public/images/footer/MeshkeeLogo-White.png new file mode 100755 index 0000000..a10ecd3 Binary files /dev/null and b/public/images/footer/MeshkeeLogo-White.png differ diff --git a/public/images/logo/logo.png b/public/images/logo/logo.png new file mode 100644 index 0000000..7371d8d Binary files /dev/null and b/public/images/logo/logo.png differ diff --git a/public/images/products/icon-1.png b/public/images/products/icon-1.png new file mode 100644 index 0000000..70eb736 Binary files /dev/null and b/public/images/products/icon-1.png differ diff --git a/public/images/products/icon-2.png b/public/images/products/icon-2.png new file mode 100644 index 0000000..d04111e Binary files /dev/null and b/public/images/products/icon-2.png differ diff --git a/public/images/products/icon-3.png b/public/images/products/icon-3.png new file mode 100644 index 0000000..a10f67b Binary files /dev/null and b/public/images/products/icon-3.png differ diff --git a/public/images/products/icon-4.png b/public/images/products/icon-4.png new file mode 100644 index 0000000..321b36b Binary files /dev/null and b/public/images/products/icon-4.png differ diff --git a/public/images/products/icon-5.png b/public/images/products/icon-5.png new file mode 100644 index 0000000..23d449f Binary files /dev/null and b/public/images/products/icon-5.png differ diff --git a/public/images/products/icon-6.png b/public/images/products/icon-6.png new file mode 100644 index 0000000..61800e9 Binary files /dev/null and b/public/images/products/icon-6.png differ diff --git a/public/images/products/icon-7.png b/public/images/products/icon-7.png new file mode 100644 index 0000000..f875143 Binary files /dev/null and b/public/images/products/icon-7.png differ diff --git a/public/images/samples/cable-closeup.jpg b/public/images/samples/cable-closeup.jpg new file mode 100644 index 0000000..ec2448c Binary files /dev/null and b/public/images/samples/cable-closeup.jpg differ diff --git a/public/images/samples/cables-coils.jpg b/public/images/samples/cables-coils.jpg new file mode 100644 index 0000000..ee6a94e Binary files /dev/null and b/public/images/samples/cables-coils.jpg differ diff --git a/public/images/samples/copper-coils.avif b/public/images/samples/copper-coils.avif new file mode 100644 index 0000000..cec541d Binary files /dev/null and b/public/images/samples/copper-coils.avif differ diff --git a/public/images/samples/factory-floor.avif b/public/images/samples/factory-floor.avif new file mode 100644 index 0000000..53996cd Binary files /dev/null and b/public/images/samples/factory-floor.avif differ diff --git a/public/images/samples/production-line.avif b/public/images/samples/production-line.avif new file mode 100644 index 0000000..80d76f5 Binary files /dev/null and b/public/images/samples/production-line.avif differ diff --git a/public/images/samples/wire-spools.avif b/public/images/samples/wire-spools.avif new file mode 100644 index 0000000..aeefa5a Binary files /dev/null and b/public/images/samples/wire-spools.avif differ diff --git a/public/images/slider/bg1.jpg b/public/images/slider/bg1.jpg new file mode 100644 index 0000000..0628c12 Binary files /dev/null and b/public/images/slider/bg1.jpg differ diff --git a/public/images/slider/bg2.jpg b/public/images/slider/bg2.jpg new file mode 100644 index 0000000..939bae1 Binary files /dev/null and b/public/images/slider/bg2.jpg differ diff --git a/public/images/slider/bg3.jpg b/public/images/slider/bg3.jpg new file mode 100644 index 0000000..cc4c850 Binary files /dev/null and b/public/images/slider/bg3.jpg differ diff --git a/scripts/generate-sitemap-config.mjs b/scripts/generate-sitemap-config.mjs new file mode 100644 index 0000000..cc0cc2e --- /dev/null +++ b/scripts/generate-sitemap-config.mjs @@ -0,0 +1,98 @@ +/** + * Scans app page routes and writes src/lib/sitemap-config.generated.json + * for GET /meshkee/sitemap-config.json (Meshkee sitemap sync). + */ +import fs from "node:fs"; +import path from "node:path"; +import { fileURLToPath } from "node:url"; + +const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), ".."); +const appDir = path.join(root, "src", "app"); +const outFile = path.join(root, "src", "lib", "sitemap-config.generated.json"); + +const EXCLUDE_SEGMENTS = new Set([ + "api", + "meshkee", + "admin", + "auth", + "login", + "checkout", + "cart", + "account", +]); + +const PRIORITY = { + "/": { changefreq: "daily", priority: 1.0 }, + "/about": { changefreq: "monthly", priority: 0.6 }, + "/contact": { changefreq: "monthly", priority: 0.5 }, + "/products": { changefreq: "weekly", priority: 0.8 }, + "/blog": { changefreq: "weekly", priority: 0.7 }, +}; + +function websiteDomain() { + return ( + process.env.NEXT_PUBLIC_WEBSITE_DOMAIN || + process.env.WEBSITE_DOMAIN || + process.env.NEXT_PUBLIC_MESHKEE_DOMAIN || + "kamancable.ir" + ); +} + +function walkPages(dir, segments = []) { + /** @type {string[]} */ + const pages = []; + if (!fs.existsSync(dir)) return pages; + + for (const entry of fs.readdirSync(dir, { withFileTypes: true })) { + if (entry.name.startsWith(".") || entry.name === "node_modules") continue; + const full = path.join(dir, entry.name); + + if (entry.isDirectory()) { + if (EXCLUDE_SEGMENTS.has(entry.name)) continue; + if (/^\[.+\]$/.test(entry.name) || /^\(.+\)$/.test(entry.name)) { + if (/^\(.+\)$/.test(entry.name)) { + pages.push(...walkPages(full, segments)); + } + continue; + } + pages.push(...walkPages(full, [...segments, entry.name])); + continue; + } + + if ( + entry.name === "page.tsx" || + entry.name === "page.ts" || + entry.name === "page.jsx" || + entry.name === "page.js" + ) { + const routePath = segments.length ? `/${segments.join("/")}` : "/"; + pages.push(routePath); + } + } + + return pages; +} + +function main() { + const paths = [...new Set(walkPages(appDir))].sort((a, b) => { + if (a === "/") return -1; + if (b === "/") return 1; + return a.localeCompare(b); + }); + + const staticPages = paths.map((routePath) => { + const meta = PRIORITY[routePath] ?? { changefreq: "monthly", priority: 0.5 }; + return { path: routePath, ...meta }; + }); + + const config = { + baseUrl: `https://${websiteDomain()}`, + staticPages, + }; + + fs.mkdirSync(path.dirname(outFile), { recursive: true }); + fs.writeFileSync(outFile, `${JSON.stringify(config, null, 2)}\n`, "utf8"); + console.log(`Wrote ${path.relative(root, outFile)} (${staticPages.length} static pages)`); +} + +main(); diff --git a/src/app/about/page.tsx b/src/app/about/page.tsx new file mode 100644 index 0000000..b23193a --- /dev/null +++ b/src/app/about/page.tsx @@ -0,0 +1,110 @@ +import SlotImage from "@/components/SlotImage"; +import { + aboutGallery, + aboutIntro, + laboratoryText, + machineryText, + qualityPolicy, + site, + stats, +} from "@/data/site"; +import { pageMetadata } from "@/lib/seo"; +import { fetchStaticImageSlots, overlaySlotImages, pickSlot } from "@/lib/static-images"; + +export const metadata = pageMetadata({ + title: "درباره ما", + description: + "آشنایی با شرکت صنایع کابل کمان؛ تولیدکننده سیم و کابل فشار ضعیف، مخابراتی و ابزار دقیق از سال ۱۳۷۰ در زنجان.", + path: "/about", + image: "/images/samples/cables-coils.jpg", +}); + +export default async function AboutPage() { + const slots = await fetchStaticImageSlots("about"); + const hero = overlaySlotImages( + pickSlot(slots, "about-hero"), + [{ id: "about-hero", image: "/images/samples/cables-coils.jpg", title: "درباره ما" }], + "about-hero", + ); + const gallery = overlaySlotImages( + pickSlot(slots, "about-gallery"), + aboutGallery.map((item) => ({ id: item.id, image: item.image, title: item.title })), + "about-gallery", + ); + + return ( + <> + <section className="page-hero"> + <SlotImage src={hero.items[0]?.image || "/images/samples/cables-coils.jpg"} alt={hero.items[0]?.title || "درباره صنایع کابل کمان"} fill /> + <div className="container"> + <h1>درباره ما</h1> + <p className="en">About Us</p> + </div> + </section> + + <section className="section"> + <div className="container about-blocks"> + <div className="stats"> + {stats.map((stat) => ( + <article key={stat.label} className="stat"> + <strong>{stat.value}</strong> + <div>{stat.label}</div> + <p className="en">{stat.labelEn}</p> + </article> + ))} + </div> + + <article className="about-block"> + <h2>شرکت کابل کمان</h2> + <p className="muted">{aboutIntro}</p> + </article> + + <article className="about-block"> + <h2>اهداف</h2> + <p className="en">Quality Policy</p> + <p className="muted">صداقت بهترین روش است.</p> + <p className="muted"> + شرکت صنایع کابل کمان به‌عنوان یکی از تولیدکنندگان سیم و کابل، رضایت مشتریان را از طریق ارتقاء کیفیت و + رعایت کلیه قوانین و الزامات حاکم بر محصول خود، به‌عنوان برترین هدف برگزیده است. + </p> + <ol className="muted"> + {qualityPolicy.map((item) => ( + <li key={item}>{item}</li> + ))} + </ol> + </article> + + <article className="about-block"> + <h2>آزمایشگاه صنایع کابل کمان</h2> + <p className="en">Laboratory</p> + <p className="muted">{laboratoryText}</p> + </article> + + <article className="about-block"> + <h2>تجهیزات و ماشین‌آلات</h2> + <p className="en">Equipment & Machinery</p> + <p className="muted">{machineryText}</p> + </article> + + <article className="about-block"> + <h2>ضمانت‌نامه شرکت</h2> + <p className="en">Guarantee</p> + <p className="muted">{machineryText}</p> + </article> + + <article className="about-block"> + <h2>گالری</h2> + <p className="en">Gallery</p> + <div className="gallery"> + {gallery.items.map((item) => ( + <div key={item.id} className="gallery-item"> + <SlotImage src={item.image} alt={item.title || site.shortName} /> + </div> + ))} + </div> + </article> + </div> + </section> + </> + ); +} diff --git a/src/app/api/auth/handoff/route.ts b/src/app/api/auth/handoff/route.ts new file mode 100644 index 0000000..93365f5 --- /dev/null +++ b/src/app/api/auth/handoff/route.ts @@ -0,0 +1,17 @@ +import { getBearerToken, jsonResponse, meshkeeFetch, unauthorizedResponse } from "@/lib/meshkee-server"; + +export async function POST(request: Request) { + const token = getBearerToken(request); + if (!token) return unauthorizedResponse(); + + const res = await meshkeeFetch("/auth/handoff", { + method: "POST", + token, + }); + + if (!res.ok) { + return jsonResponse({ error: "امکان باز کردن پنل مدیریت نیست." }, res.status); + } + + return jsonResponse(res.data); +} diff --git a/src/app/api/auth/me/route.ts b/src/app/api/auth/me/route.ts new file mode 100644 index 0000000..53435fb --- /dev/null +++ b/src/app/api/auth/me/route.ts @@ -0,0 +1,10 @@ +import { getBearerToken, jsonResponse, meshkeeFetch, unauthorizedResponse } from "@/lib/meshkee-server"; + +export async function GET(request: Request) { + const token = getBearerToken(request); + if (!token) return unauthorizedResponse(); + + const res = await meshkeeFetch("/auth/me", { token }); + if (!res.ok) return jsonResponse({ error: "Unauthorized" }, res.status); + return jsonResponse(res.data); +} diff --git a/src/app/api/comments/route.ts b/src/app/api/comments/route.ts new file mode 100644 index 0000000..adf3b80 --- /dev/null +++ b/src/app/api/comments/route.ts @@ -0,0 +1,33 @@ +import { NextResponse } from "next/server"; + +import { meshkeeApiBase, websiteDomain } from "@/lib/config"; + +export async function POST(request: Request) { + const body = (await request.json()) as { + entityType?: string; + entityId?: string; + authorName?: string; + authorEmail?: string; + text?: string; + }; + + if (!body.entityType?.trim() || !body.entityId?.trim() || !body.authorName?.trim() || !body.text?.trim()) { + return NextResponse.json({ message: "نام و متن نظر الزامی است." }, { status: 400 }); + } + + const response = await fetch(`${meshkeeApiBase}/tenants/${encodeURIComponent(websiteDomain)}/comments`, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + entityType: body.entityType.trim(), + entityId: body.entityId.trim(), + authorName: body.authorName.trim(), + authorEmail: body.authorEmail?.trim() || undefined, + text: body.text.trim(), + }), + cache: "no-store", + }); + + const data = await response.json().catch(() => ({})); + return NextResponse.json(data, { status: response.status }); +} diff --git a/src/app/api/contact/route.ts b/src/app/api/contact/route.ts new file mode 100644 index 0000000..61ae4ae --- /dev/null +++ b/src/app/api/contact/route.ts @@ -0,0 +1,36 @@ +import { NextResponse } from "next/server"; + +import { meshkeeApiBase, websiteDomain } from "@/lib/config"; + +export async function POST(request: Request) { + const body = (await request.json()) as { + title?: string; + name?: string; + email?: string; + cellNumber?: string; + text?: string; + }; + + if (!body.title?.trim() || !body.name?.trim() || !body.text?.trim()) { + return NextResponse.json({ message: "نام، موضوع و متن پیام الزامی است." }, { status: 400 }); + } + + const response = await fetch( + `${meshkeeApiBase}/tenants/${encodeURIComponent(websiteDomain)}/contact-submissions`, + { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + title: body.title.trim(), + name: body.name.trim(), + email: body.email?.trim() || undefined, + cellNumber: body.cellNumber?.trim() || undefined, + text: body.text.trim(), + }), + cache: "no-store", + }, + ); + + const data = await response.json().catch(() => ({})); + return NextResponse.json(data, { status: response.status }); +} diff --git a/src/app/blog/[id]/[slug]/page.tsx b/src/app/blog/[id]/[slug]/page.tsx new file mode 100644 index 0000000..d6d69b6 --- /dev/null +++ b/src/app/blog/[id]/[slug]/page.tsx @@ -0,0 +1,90 @@ +import type { Metadata } from "next"; +import Link from "next/link"; +import { notFound, redirect } from "next/navigation"; + +import CommentBox from "@/components/CommentBox"; +import SlotImage from "@/components/SlotImage"; +import { + blogDate, + blogHref, + blogImage, + blogTitleSlug, + fetchBlogById, + getLocalBlogPost, +} from "@/lib/blog"; +import { fetchComments } from "@/lib/comments"; +import { sanitizeHtml } from "@/lib/format"; +import { pageMetadata } from "@/lib/seo"; + +export const revalidate = 60; + +type Params = { id: string; slug: string }; + +export async function generateMetadata({ + params, +}: { + params: Promise<Params>; +}): Promise<Metadata> { + const { id } = await params; + const post = (await fetchBlogById(id)) ?? getLocalBlogPost(id); + if (!post) { + return { + title: "مقاله یافت نشد", + description: "این مطلب در وبلاگ صنایع کابل کمان موجود نیست.", + robots: { index: false }, + }; + } + return pageMetadata({ + title: post.title, + description: post.abstract?.trim() || `${post.title} | وبلاگ صنایع سیم و کابل کمان`, + path: blogHref(post), + image: blogImage(post), + }); +} + +export default async function BlogDetailPage({ params }: { params: Promise<Params> }) { + const { id, slug } = await params; + const apiPost = await fetchBlogById(id); + const post = apiPost ?? getLocalBlogPost(id); + if (!post) notFound(); + + const canonical = blogTitleSlug(post.title); + if (decodeURIComponent(slug) !== canonical) { + redirect(blogHref(post)); + } + + const comments = apiPost ? await fetchComments("blog", post.id) : []; + const html = sanitizeHtml(post.mainTextHtml); + + return ( + <> + <section className="page-hero"> + <SlotImage src={blogImage(post)} alt={post.title} fill /> + <div className="container"> + <h1>{post.title}</h1> + {blogDate(post) ? <p className="en">{blogDate(post)}</p> : <p className="en">Blog</p>} + </div> + </section> + + <section className="section"> + <div className="container detail-article"> + {post.abstract ? <p className="detail-lead">{post.abstract}</p> : null} + {html ? <div className="rich-text" dangerouslySetInnerHTML={{ __html: html }} /> : null} + + <section className="detail-tabs"> + <h2>نظرات</h2> + {apiPost ? ( + <CommentBox entityType="blog" entityId={post.id} comments={comments} /> + ) : ( + <p className="comment-empty">ثبت نظر برای این مطلب در دسترس نیست.</p> + )} + </section> + + <Link href="/blog" className="about-contact-link"> + بازگشت به وبلاگ + </Link> + </div> + </section> + </> + ); +} diff --git a/src/app/blog/page.tsx b/src/app/blog/page.tsx new file mode 100644 index 0000000..2215973 --- /dev/null +++ b/src/app/blog/page.tsx @@ -0,0 +1,107 @@ +import type { Metadata } from "next"; +import Link from "next/link"; + +import SlotImage from "@/components/SlotImage"; +import SlotLink from "@/components/SlotLink"; +import { blogDate, blogHref, blogImage, fetchBlogs, getLocalBlogPosts } from "@/lib/blog"; +import { pageMetadata } from "@/lib/seo"; +import { fetchStaticImageSlots, overlaySlotImages, pickSlot } from "@/lib/static-images"; + +export const revalidate = 60; + +export async function generateMetadata({ + searchParams, +}: { + searchParams: Promise<{ page?: string }>; +}): Promise<Metadata> { + const { page: pageParam } = await searchParams; + const page = Math.max(1, Number(pageParam) || 1); + return pageMetadata({ + title: page > 1 ? `وبلاگ — صفحه ${page}` : "وبلاگ", + description: "آخرین اخبار و مقالات فنی صنایع سیم و کابل کمان.", + path: page > 1 ? `/blog?page=${page}` : "/blog", + image: "/images/samples/production-line.avif", + }); +} + +export default async function BlogPage({ + searchParams, +}: { + searchParams: Promise<{ page?: string }>; +}) { + const { page: pageParam } = await searchParams; + const page = Math.max(1, Number(pageParam) || 1); + const [result, slots] = await Promise.all([fetchBlogs({ page, pageSize: 12 }), fetchStaticImageSlots("blog")]); + const hero = overlaySlotImages( + pickSlot(slots, "blog-hero"), + [{ id: "blog-hero", image: "/images/samples/production-line.avif", title: "وبلاگ" }], + "blog-hero", + ); + const usingApi = result.items.length > 0; + const posts = usingApi ? result.items : page === 1 ? getLocalBlogPosts() : []; + const pageCount = usingApi + ? Math.max(1, Math.ceil((result.total || result.items.length) / (result.pageSize || 12))) + : 1; + + return ( + <> + <section className="page-hero"> + <SlotImage src={hero.items[0]?.image || "/images/samples/production-line.avif"} alt={hero.items[0]?.title || "وبلاگ کابل کمان"} fill /> + <div className="container"> + <h1>وبلاگ</h1> + <p className="en">Blog</p> + </div> + </section> + + <section className="section"> + <div className="container"> + {posts.length === 0 ? ( + <p className="comment-empty">مقاله‌ای برای نمایش وجود ندارد.</p> + ) : ( + <div className="blog-grid"> + {posts.map((post) => ( + <article key={post.id} className="blog-card"> + <div className="blog-card-media"> + <SlotLink href={blogHref(post)} className="blog-card-hit"> + <SlotImage src={blogImage(post)} alt={post.title} fill /> + </SlotLink> + </div> + <div className="blog-card-body"> + {blogDate(post) ? <p className="blog-date">{blogDate(post)}</p> : null} + <h2> + <SlotLink href={blogHref(post)}>{post.title}</SlotLink> + </h2> + {post.abstract ? <p className="blog-excerpt">{post.abstract}</p> : null} + <SlotLink href={blogHref(post)} className="blog-more"> + ادامه مطلب + </SlotLink> + </div> + </article> + ))} + </div> + )} + + {pageCount > 1 ? ( + <nav className="pagination" aria-label="صفحه‌بندی وبلاگ"> + {Array.from({ length: pageCount }, (_, index) => { + const number = index + 1; + if (pageCount > 12 && Math.abs(number - page) > 3 && number !== 1 && number !== pageCount) { + return number === 2 || number === pageCount - 1 ? <span key={number}>…</span> : null; + } + return ( + <Link + key={number} + href={number === 1 ? "/blog" : `/blog?page=${number}`} + className={number === page ? "active" : undefined} + > + {number} + </Link> + ); + })} + </nav> + ) : null} + </div> + </section> + </> + ); +} diff --git a/src/app/contact/page.tsx b/src/app/contact/page.tsx new file mode 100644 index 0000000..fdced0a --- /dev/null +++ b/src/app/contact/page.tsx @@ -0,0 +1,63 @@ +import ContactForm from "@/components/ContactForm"; +import SlotImage from "@/components/SlotImage"; +import { site } from "@/data/site"; +import { pageMetadata } from "@/lib/seo"; +import { fetchStaticImageSlots, overlaySlotImages, pickSlot } from "@/lib/static-images"; + +export const metadata = pageMetadata({ + title: "تماس با ما", + description: `ارتباط با صنایع کابل کمان. دفتر تهران: ${site.address}. تلفن ${site.phoneFa}.`, + path: "/contact", + image: "/images/slider/bg3.jpg", +}); + +export default async function ContactPage() { + const slots = await fetchStaticImageSlots("contact"); + const hero = overlaySlotImages( + pickSlot(slots, "contact-hero"), + [{ id: "contact-hero", image: "/images/slider/bg3.jpg", title: "تماس با ما" }], + "contact-hero", + ); + + return ( + <> + <section className="page-hero"> + <SlotImage src={hero.items[0]?.image || "/images/slider/bg3.jpg"} alt={hero.items[0]?.title || "تماس با صنایع کابل کمان"} fill /> + <div className="container"> + <h1>با ما در ارتباط باشید</h1> + <p className="en">Contact Us</p> + </div> + </section> + + <section className="section"> + <div className="container contact-grid"> + <div className="info-card"> + <h2>اطلاعات تماس</h2> + <div className="info-row"> + <span className="kicker en">Address</span> + <p>{site.address}</p> + </div> + <div className="info-row"> + <span className="kicker en">Factory</span> + <p>{site.factory}</p> + </div> + <div className="info-row"> + <span className="kicker en">Phone</span> + <p> + <a href={`tel:${site.phone.replace(/-/g, "")}`}>{site.phoneFa}</a> + </p> + </div> + </div> + <ContactForm /> + </div> + <div className="container map-wrap"> + <iframe + title="موقعیت دفتر صنایع کابل کمان" + src="https://www.openstreetmap.org/export/embed.html?bbox=51.412%2C35.705%2C51.425%2C35.713&layer=mapnik&marker=35.709%2C51.418" + loading="lazy" + /> + </div> + </section> + </> + ); +} diff --git a/src/app/globals.css b/src/app/globals.css new file mode 100644 index 0000000..b5ec854 --- /dev/null +++ b/src/app/globals.css @@ -0,0 +1,2285 @@ +@import "tailwindcss"; + +@font-face { + font-family: "iran-yekan"; + src: url("https://cdn.meshkee.com/web-builder/fonts/iran-yekan/iran-yekan-light.ttf"); + font-weight: 300; + font-style: normal; + font-display: swap; +} + +@font-face { + font-family: "iran-yekan"; + src: url("https://cdn.meshkee.com/web-builder/fonts/iran-yekan/iran-yekan-regular.ttf"); + font-weight: 400; + font-style: normal; + font-display: swap; +} + +@font-face { + font-family: "iran-yekan"; + src: url("https://cdn.meshkee.com/web-builder/fonts/iran-yekan/iran-yekan-medium.ttf"); + font-weight: 500; + font-style: normal; + font-display: swap; +} + +@font-face { + font-family: "iran-yekan"; + src: url("https://cdn.meshkee.com/web-builder/fonts/iran-yekan/iran-yekan-bold.ttf"); + font-weight: 700; + font-style: normal; + font-display: swap; +} + +@font-face { + font-family: "iran-yekan"; + src: url("https://cdn.meshkee.com/web-builder/fonts/iran-yekan/iran-yekan-extrabold.ttf"); + font-weight: 800; + font-style: normal; + font-display: swap; +} + +:root { + --navy: #1c1e40; + --navy-2: #151732; + --navy-3: #26285a; + --gold: #e1a34c; + --gold-2: #ffde30; + --white: #ffffff; + --muted: #c8cce0; + --line: rgba(255, 255, 255, 0.12); + --font-fa: "iran-yekan", Tahoma, sans-serif; +} + +* { + box-sizing: border-box; +} + +html, +body { + margin: 0; + padding: 0; + background: var(--navy); + color: var(--white); + font-family: var(--font-fa); + font-weight: 300; + direction: rtl; +} + +a { + color: inherit; + text-decoration: none; +} + +img { + max-width: 100%; + display: block; +} + +button, +input, +textarea { + font-family: inherit; +} + +h1, +h2, +h3, +h4 { + font-weight: 700; +} + +.wrapper { + min-height: 100vh; + display: flex; + flex-direction: column; +} + +main { + flex: 1; +} + +.container { + width: min(1180px, calc(100% - 40px)); + margin-inline: auto; +} + +.en { + font-family: var(--font-en), "Rajdhani", sans-serif; + letter-spacing: 0.08em; + text-transform: uppercase; +} + +.sr-only { + position: absolute; + width: 1px; + height: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + clip: rect(0, 0, 0, 0); + white-space: nowrap; + border: 0; +} + +.site-header { + position: fixed; + top: 0; + left: 0; + right: 0; + z-index: 50; + width: 100%; + background: rgba(28, 30, 64, 0.22); + backdrop-filter: blur(24px) saturate(150%); + -webkit-backdrop-filter: blur(24px) saturate(150%); + border-bottom: 1px solid rgba(255, 255, 255, 0.08); +} + +.header-inner { + display: flex; + align-items: center; + justify-content: space-between; + gap: 16px; + min-height: 72px; + padding-inline-start: 12px; +} + +.header-start { + display: flex; + align-items: center; + gap: 4px 14px; + min-width: 0; +} + +.header-tools { + display: flex; + align-items: center; + gap: 8px; + flex-shrink: 0; +} + +.header-tool { + display: inline-flex; + align-items: center; + justify-content: center; + gap: 6px; + min-height: 32px; + padding: 0 12px; + color: #fff; + background: transparent; + border: 1px solid var(--line); + border-radius: 999px; + cursor: pointer; + white-space: nowrap; + font-size: 12px; +} + +.header-tool:hover, +.header-login:hover { + border-color: var(--gold); + color: var(--gold); +} + +.header-login { + text-decoration: none; +} + +.header-lang { + display: inline-flex; + align-items: center; +} + +.header-lang select { + min-height: 32px; + padding: 0 12px; + color: #fff; + background: transparent; + border: 1px solid var(--line); + border-radius: 999px; + cursor: pointer; + font-size: 12px; + font-family: var(--font-en), "Rajdhani", sans-serif; +} + +.header-lang select:hover, +.header-lang select:focus { + border-color: var(--gold); + outline: none; +} + +.header-search { + position: relative; +} + +.header-search form { + display: flex; + align-items: center; + overflow: hidden; + min-height: 32px; + background: transparent; + border: 1px solid var(--line); + border-radius: 999px; + transition: background 0.2s ease, border-color 0.2s ease; +} + +.header-search.open form, +.header-search form:focus-within { + background: rgba(28, 30, 64, 0.45); + border-color: var(--gold); +} + +.header-search input { + width: 0; + min-width: 0; + min-height: 30px; + padding: 0; + opacity: 0; + color: #fff; + background: transparent; + border: 0; + outline: none; + font-size: 12px; + pointer-events: none; + transition: width 0.22s ease, padding 0.22s ease, opacity 0.15s ease; +} + +.header-search.open input { + width: 168px; + padding-inline: 12px 0; + opacity: 1; + pointer-events: auto; +} + +.header-search .header-tool { + border: 0; + min-width: 32px; + min-height: 30px; + padding: 0 8px; +} + +.header-search-results { + position: absolute; + top: calc(100% + 8px); + inset-inline-end: 0; + z-index: 60; + min-width: 220px; + margin: 0; + padding: 6px 0; + list-style: none; + background: var(--navy-2); + border: 1px solid var(--line); + border-radius: 12px; +} + +.header-search-results a { + display: block; + padding: 8px 14px; + color: #fff; +} + +.header-search-results a:hover { + color: var(--gold); + background: var(--navy-3); +} + +.brand { + display: flex; + align-items: center; + gap: 8px; + margin-inline-end: 40px; +} + +.brand img { + width: 32px; + height: 32px; + object-fit: contain; + background: #fff; + border-radius: 8px; +} + +.brand-text { + display: flex; + flex-direction: column; + justify-content: center; + gap: 1px; + line-height: 1.15; +} + +.brand-text strong { + font-size: 13px; + font-weight: 700; +} + +.brand-text span { + color: var(--gold); + font-size: 9px; +} + +.nav { + display: flex; + gap: 2px; + align-items: center; +} + +.nav > a, +.nav-dropdown > a { + display: inline-flex; + align-items: center; + gap: 5px; + padding: 6px 10px; + font-size: 13px; + color: var(--white); + border-radius: 0; + border-bottom: 2px solid transparent; + line-height: 1.2; +} + +.nav > a:hover, +.nav > a.active, +.nav-dropdown > a:hover, +.nav-dropdown > a.active { + color: var(--gold); + border-bottom-color: var(--gold); +} + +.nav-caret { + display: inline-block; + transition: transform 0.18s ease; +} + +.nav-dropdown:hover .nav-caret, +.nav-dropdown:focus-within .nav-caret { + transform: rotate(180deg); +} + +.nav-dropdown { + position: relative; +} + +.nav-dropdown:hover::after, +.nav-dropdown.open::after, +.nav-dropdown:focus-within::after { + content: ""; + position: absolute; + top: 100%; + inset-inline: 0; + height: 10px; +} + +.nav-sub { + display: none; + position: absolute; + top: calc(100% + 6px); + inset-inline-start: 0; + z-index: 70; + min-width: 220px; + margin: 0; + padding: 6px 0; + list-style: none; + background: rgba(28, 30, 64, 0.92); + backdrop-filter: blur(16px); + -webkit-backdrop-filter: blur(16px); + border: 1px solid var(--line); + border-radius: 12px; +} + +.nav-dropdown:hover .nav-sub, +.nav-dropdown:focus-within .nav-sub, +.nav-dropdown.open .nav-sub { + display: block; +} + +.nav-sub a { + display: block; + padding: 8px 14px; + font-size: 13px; + color: #fff; + border-bottom: 0; +} + +.nav-sub a:hover { + color: var(--gold); + background: rgba(255, 255, 255, 0.06); +} + +.menu-toggle { + display: none; + background: transparent; + border: 1px solid var(--line); + border-radius: 999px; + color: #fff; + padding: 6px 12px; + font-size: 12px; + cursor: pointer; +} + +.hero { + position: relative; + min-height: 72vh; + overflow: hidden; + background: var(--navy-2); +} + +.hero-slide { + position: absolute; + inset: 0; + opacity: 0; + transition: opacity 0.6s ease; +} + +.hero-slide.active { + opacity: 1; +} + +.hero-slide a { + position: absolute; + inset: 0; + z-index: 1; +} + +.hero-slide img, +.hero-slide .slot-fill { + width: 100%; + height: 100%; + object-fit: cover; +} + +.hero-overlay { + position: absolute; + inset: 0; + pointer-events: none; + background: linear-gradient(180deg, rgba(28, 30, 64, 0.08) 0%, rgba(28, 30, 64, 0.72) 100%); +} + +.hero-copy { + position: relative; + z-index: 2; + pointer-events: none; + min-height: 72vh; + display: flex; + flex-direction: column; + justify-content: flex-end; + padding: 140px 0 140px; +} + +.hero-copy .en { + color: var(--gold); + font-size: clamp(10px, 1.15vw, 13px); + margin: 0 0 10px; + align-self: flex-start; + max-width: 100%; + letter-spacing: 0.14em; +} + +.hero-copy h1, +.hero-copy h2.hero-title { + margin: 0; + font-size: clamp(16px, 2.4vw, 32px); + font-weight: 700; + line-height: 1.2; + align-self: flex-start; + max-width: 100%; +} + +.hero-reveal { + position: relative; + display: inline-block; + overflow: hidden; + max-width: 100%; + vertical-align: bottom; + white-space: nowrap; +} + +.hero-reveal__text { + display: inline-block; + opacity: 0; + white-space: nowrap; + animation: hero-reveal-text 0.05s linear 0.55s forwards; +} + +.hero-reveal__mask { + position: absolute; + inset: 0; + pointer-events: none; + transform: scaleX(0); + animation: hero-reveal-rtl 1.15s cubic-bezier(0.87, 0, 0.13, 1) forwards; +} + +.hero-reveal--white .hero-reveal__mask { + background: #fff; +} + +.hero-reveal--gold .hero-reveal__mask { + background: var(--gold); +} + +html[dir="ltr"] .hero-reveal__mask { + animation-name: hero-reveal-ltr; +} + +@keyframes hero-reveal-rtl { + 0% { + transform: scaleX(0); + transform-origin: right center; + } + 48% { + transform: scaleX(1); + transform-origin: right center; + } + 49% { + transform: scaleX(1); + transform-origin: left center; + } + 100% { + transform: scaleX(0); + transform-origin: left center; + } +} + +@keyframes hero-reveal-ltr { + 0% { + transform: scaleX(0); + transform-origin: left center; + } + 48% { + transform: scaleX(1); + transform-origin: left center; + } + 49% { + transform: scaleX(1); + transform-origin: right center; + } + 100% { + transform: scaleX(0); + transform-origin: right center; + } +} + +@keyframes hero-reveal-text { + to { + opacity: 1; + } +} + +.highlights { + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: 1px; + background: var(--line); + position: relative; + z-index: 4; + margin-top: -72px; +} + +.highlight { + background: var(--navy); + padding: 22px 24px; + border: 0; + color: inherit; + font: inherit; + text-align: inherit; + cursor: pointer; + opacity: 0.58; + transition: opacity 0.25s ease, background 0.25s ease, box-shadow 0.25s ease; +} + +.highlight:hover { + opacity: 0.85; +} + +.highlight.active { + opacity: 1; + background: var(--navy-3); + box-shadow: inset 0 3px 0 var(--gold); +} + +.highlight .n { + color: var(--gold-2); + font-size: 22px; + margin-bottom: 6px; +} + +.highlight h3 { + margin: 0 0 4px; + font-size: 18px; +} + +.highlight p { + margin: 0; + color: var(--muted); + font-size: 12px; +} + +.section { + padding: 88px 0; +} + +#products, +#blog, +#contact { + scroll-margin-top: 72px; +} + +#products { + padding-top: 48px; +} + +.section-head { + position: relative; + margin-bottom: 20px; + overflow: hidden; + padding-top: 18px; +} + +#products .section-head, +#blog .section-head { + padding-top: 36px; +} + +.section-head-bg { + position: absolute; + inset-inline-end: -0.06em; + top: -0.18em; + z-index: 0; + color: transparent; + font-family: var(--font-en-display), "Oswald", sans-serif; + font-size: clamp(64px, 11vw, 128px); + font-weight: 700; + letter-spacing: 0.08em; + line-height: 0.75; + pointer-events: none; + text-transform: uppercase; + user-select: none; + white-space: nowrap; + -webkit-text-stroke: 1px rgba(225, 163, 76, 0.2); +} + +.section-head-meta { + display: flex; + align-items: center; + gap: 12px; + margin-bottom: 4px; + position: relative; + z-index: 1; +} + +.section-head-index { + color: var(--gold); + font-family: var(--font-en-display), "Oswald", sans-serif; + font-size: 13px; + font-weight: 500; + letter-spacing: 0.22em; +} + +.section-head-rule { + width: 42px; + height: 1px; + background: var(--gold); + flex-shrink: 0; +} + +.section-head-en { + margin: 0; + color: var(--gold); + font-family: var(--font-en-display), "Oswald", sans-serif; + font-size: 12px; + font-weight: 500; + letter-spacing: 0.32em; + text-transform: uppercase; +} + +.section-head h2 { + margin: 0; + position: relative; + z-index: 1; + font-size: clamp(20px, 2.4vw, 28px); + font-weight: 300; + line-height: 1.25; +} + +.section-head-text { + max-width: none; + margin: 6px 0 0; + position: relative; + z-index: 1; + color: var(--muted); + font-size: 14px; + font-weight: 300; + line-height: 1.6; +} + +#products .section-head-text, +#blog .section-head-text { + white-space: nowrap; +} + +.muted { + color: var(--muted); + font-weight: 300; + line-height: 2; + text-align: justify; + text-justify: inter-word; +} + +.muted li { + font-weight: 300; + text-align: justify; +} + +.about-grid { + display: grid; + grid-template-columns: 0.9fr 1.1fr; + gap: 48px; + align-items: start; +} + +.about-photo { + position: relative; + aspect-ratio: var(--slot-ratio, 440 / 609); + overflow: hidden; + background: var(--navy-3); +} + +.about-photo a { + position: absolute; + inset: 0; + z-index: 1; +} + +.about-photo img, +.about-photo .slot-fill { + width: 100%; + height: 100%; + object-fit: cover; +} + +.tabs { + display: flex; + gap: 8px; + margin-bottom: 20px; +} + +.tabs button { + background: transparent; + border: 1px solid var(--line); + color: #fff; + padding: 10px 18px; + cursor: pointer; +} + +.tabs button.active { + background: var(--gold); + border-color: var(--gold); + color: var(--navy); + font-weight: 700; +} + +.btn { + display: inline-flex; + align-items: center; + justify-content: center; + gap: 8px; + min-height: 48px; + padding: 0 22px; + border: 0; + cursor: pointer; + font-weight: 700; +} + +.btn-gold { + background: var(--gold); + color: var(--navy); +} + +.btn-ghost { + background: transparent; + color: #fff; + border: 1px solid var(--line); +} + +.about-cta { + display: flex; + justify-content: flex-end; + margin: 28px 0 0; +} + +.about-contact-link { + display: inline-flex; + align-items: center; + gap: 8px; + color: var(--gold); + font-size: 14px; + font-weight: 300; + padding: 0 0 6px; + border-bottom: 1px solid rgba(225, 163, 76, 0.45); +} + +.about-contact-link:hover { + color: #fff; + border-bottom-color: #fff; +} + +.contact-bar { + background: var(--navy-2); + border-block: 1px solid var(--line); + padding: 36px 0; +} + +.contact-bar-frame { + display: grid; + gap: 0; +} + +.contact-bar-inner { + display: flex; + align-items: center; + justify-content: space-between; + gap: 20px; + min-height: 72px; + padding-bottom: 26px; +} + +.contact-bar-subscribe { + display: flex; + align-items: center; + justify-content: space-between; + gap: 24px; + min-height: 72px; + padding-top: 26px; + border-top: 1px solid var(--line); +} + +.contact-bar-subscribe-text { + margin: 0; + max-width: 28em; + color: var(--muted); + font-size: 15px; + font-weight: 300; + line-height: 1.9; +} + +.callback-form { + display: flex; + align-items: center; + flex-wrap: wrap; + gap: 10px; + flex-shrink: 0; +} + +.callback-form input { + min-height: 44px; + width: 168px; + padding: 0 14px; + color: #fff; + background: var(--navy); + border: 1px solid var(--line); + outline: none; + font-size: 13px; + font-weight: 300; +} + +.callback-form input:focus { + border-color: var(--gold); +} + +.callback-form .btn { + min-height: 44px; + padding: 0 18px; + font-size: 13px; +} + +.callback-form .ok, +.callback-form .err { + width: 100%; + margin: 0; + font-size: 12px; + font-weight: 300; +} + +.callback-form .ok { + color: var(--gold); +} + +.callback-form .err { + color: #ff8b8b; +} + +.contact-bar-phone { + display: flex; + align-items: center; + gap: 16px; + min-width: 0; +} + +.contact-bar-kicker { + color: var(--gold); + font-size: 13px; + font-weight: 300; + white-space: nowrap; +} + +.contact-bar-number { + display: inline-flex; + align-items: center; + gap: 10px; + color: #fff; + font-family: var(--font-en-display), "Oswald", sans-serif; + font-size: clamp(20px, 2.4vw, 30px); + font-weight: 500; + letter-spacing: 0.04em; +} + +.contact-bar-number:hover { + color: var(--gold); +} + +.contact-socials { + display: flex; + align-items: center; + gap: 10px; + flex-shrink: 0; +} + +.contact-social { + width: 40px; + height: 40px; + display: grid; + place-items: center; + color: #fff; + border: 1px solid var(--line); + border-radius: 999px; +} + +.contact-social:hover { + color: var(--gold); + border-color: var(--gold); +} + +.app-strip { + background: var(--navy-2); + border-block: 1px solid var(--line); + padding: 28px 0; +} + +.app-strip-inner { + display: flex; + align-items: center; + justify-content: space-between; + gap: 16px; +} + +.app-strip h2 { + margin: 0; + font-size: 28px; +} + +.product-grid { + display: grid; + grid-template-columns: repeat(4, 1fr); + gap: 16px; +} + +.product-card { + position: relative; + scroll-margin-top: 88px; + overflow: hidden; + background: var(--navy-2); + border: 1px solid var(--line); +} + +.product-card .thumb { + position: relative; + aspect-ratio: var(--slot-ratio, 3 / 4); + overflow: hidden; + background: var(--navy-3); +} + +.product-card .thumb a { + position: absolute; + inset: 0; + z-index: 1; +} + +.product-card .thumb img, +.product-card .thumb .slot-fill { + width: 100%; + height: 100%; + object-fit: cover; + transition: transform 0.7s cubic-bezier(0.16, 1, 0.3, 1); +} + +.product-card:hover .thumb img, +.product-card:hover .thumb .slot-fill, +.product-card.is-hover .thumb img, +.product-card:focus-within .thumb img { + transform: scale(1.08); +} + +.product-card .body { + position: absolute; + inset-inline: 0; + bottom: 0; + z-index: 2; + padding: 56px 16px 16px; + background: linear-gradient(to top, rgba(16, 18, 40, 0.94) 0%, rgba(16, 18, 40, 0.55) 48%, transparent 100%); + pointer-events: none; +} + +.product-card h3 { + margin: 0 0 4px; + font-size: 18px; + font-weight: 700; + text-shadow: 0 1px 10px rgba(0, 0, 0, 0.4); +} + +.product-card .en { + color: var(--gold); + font-size: 11px; + margin: 0; +} + +.product-excerpt { + max-height: 0; + margin: 0; + overflow: hidden; + opacity: 0; + color: var(--muted); + font-size: 13px; + font-weight: 300; + line-height: 1.9; + text-align: justify; + text-justify: inter-word; + transform: translateY(8px); + transition: + max-height 0.45s ease, + opacity 0.35s ease, + margin 0.35s ease, + transform 0.35s ease; +} + +.product-card:hover .product-excerpt, +.product-card.is-hover .product-excerpt, +.product-card:focus-within .product-excerpt { + max-height: 8.2em; + margin-top: 10px; + opacity: 1; + transform: none; +} + +@media (hover: none) { + .product-excerpt { + max-height: 6.4em; + margin-top: 10px; + opacity: 1; + transform: none; + } +} + +.clients { + display: grid; + grid-template-columns: repeat(5, 1fr); + gap: 12px; +} + +.client { + background: #fff; + min-height: 110px; + aspect-ratio: var(--slot-ratio, 3 / 2); + display: grid; + place-items: center; + padding: 16px; +} + +.client a { + display: grid; + place-items: center; + width: 100%; + height: 100%; +} + +.client img { + max-height: 72px; + width: auto; + object-fit: contain; +} + +.blog-carousel { + --blog-index: 0; + --blog-per: 3; +} + +.blog-toolbar { + display: flex; + align-items: center; + justify-content: space-between; + gap: 16px; + margin-bottom: 22px; +} + +.blog-dots { + display: flex; + gap: 8px; +} + +.blog-dot { + width: 8px; + height: 8px; + padding: 0; + border: 0; + border-radius: 999px; + background: rgba(255, 255, 255, 0.28); + cursor: pointer; +} + +.blog-dot.active { + width: 22px; + background: var(--gold); +} + +.blog-arrows { + display: flex; + gap: 8px; +} + +.blog-arrow { + width: 36px; + height: 36px; + display: grid; + place-items: center; + color: #fff; + background: transparent; + border: 1px solid var(--line); + border-radius: 999px; + cursor: pointer; + font-size: 22px; + line-height: 1; +} + +.blog-arrow:hover:not(:disabled) { + border-color: var(--gold); + color: var(--gold); +} + +.blog-arrow:disabled { + opacity: 0.35; + cursor: default; +} + +.blog-viewport { + container-type: inline-size; + overflow: hidden; +} + +.blog-track { + display: flex; + gap: 16px; + transition: transform 0.6s cubic-bezier(0.16, 1, 0.3, 1); + transform: translateX(calc(var(--blog-index) * (100cqi + 16px) / var(--blog-per))); +} + +html[dir="ltr"] .blog-track { + transform: translateX(calc(var(--blog-index) * -1 * (100cqi + 16px) / var(--blog-per))); +} + +.blog-card { + flex: 0 0 calc((100cqi - (var(--blog-per) - 1) * 16px) / var(--blog-per)); + min-width: 0; + overflow: hidden; + background: var(--navy-2); + border: 1px solid var(--line); +} + +.blog-card-media { + position: relative; + aspect-ratio: var(--slot-ratio, 16 / 10); + overflow: hidden; + background: var(--navy-3); +} + +.blog-card-hit { + position: absolute; + inset: 0; +} + +.blog-card-media img, +.blog-card-media .slot-fill { + width: 100%; + height: 100%; + object-fit: cover; + transition: transform 0.7s cubic-bezier(0.16, 1, 0.3, 1); +} + +.blog-card:hover .blog-card-media img, +.blog-card:hover .blog-card-media .slot-fill { + transform: scale(1.06); +} + +.blog-card-body { + padding: 18px 16px 20px; +} + +.blog-date { + margin: 0 0 8px; + color: var(--gold); + font-size: 12px; + font-weight: 300; +} + +.blog-card h3 { + margin: 0 0 10px; + font-size: 17px; + line-height: 1.55; +} + +.blog-card h3 a:hover { + color: var(--gold); +} + +.blog-excerpt { + display: -webkit-box; + margin: 0 0 14px; + overflow: hidden; + color: var(--muted); + font-size: 13px; + font-weight: 300; + line-height: 1.9; + text-align: justify; + text-justify: inter-word; + -webkit-box-orient: vertical; + -webkit-line-clamp: 3; +} + +.blog-more { + color: var(--gold); + font-size: 13px; +} + +.blog-more:hover { + color: #fff; +} + +.stats { + display: grid; + grid-template-columns: repeat(4, 1fr); + gap: 1px; + background: var(--line); +} + +.stat { + background: var(--navy-2); + padding: 28px 20px; + text-align: center; +} + +.stat strong { + display: block; + color: var(--gold); + font-size: 28px; + margin-bottom: 6px; +} + +.page-hero { + position: relative; + min-height: 280px; + display: flex; + align-items: flex-end; + padding: 120px 0 40px; + overflow: hidden; +} + +.page-hero img, +.page-hero .slot-fill { + position: absolute; + inset: 0; + width: 100%; + height: 100%; + object-fit: cover; +} + +.page-hero::after { + content: ""; + position: absolute; + inset: 0; + background: rgba(28, 30, 64, 0.72); +} + +.page-hero .container { + position: relative; + z-index: 1; +} + +.page-hero .en { + color: var(--gold); + margin: 8px 0 0; +} + +.page-hero h1 { + margin: 0; + font-size: clamp(32px, 4vw, 48px); +} + +.account-menu { + position: relative; +} + +.account-menu__trigger { + max-width: 220px; +} + +.account-menu__caret { + transition: transform 0.2s ease; +} + +.account-menu__caret.open { + transform: rotate(180deg); +} + +.account-menu__dropdown { + position: absolute; + inset-inline-end: 0; + top: calc(100% + 8px); + min-width: 180px; + z-index: 60; + padding: 8px 0; + background: var(--navy); + border: 1px solid var(--line); +} + +.account-menu__dropdown a, +.account-menu__dropdown button { + display: block; + width: 100%; + padding: 10px 16px; + color: #fff; + background: transparent; + border: 0; + text-align: right; + font-size: 13px; + font-weight: 300; + cursor: pointer; +} + +.account-menu__dropdown a:hover, +.account-menu__dropdown button:hover { + color: var(--gold); + background: var(--navy-2); +} + +.catalog-grid { + display: grid; + grid-template-columns: repeat(4, 1fr); + gap: 16px; +} + +.category-pills { + display: flex; + flex-wrap: wrap; + gap: 8px; + margin-bottom: 28px; +} + +.category-pills a { + padding: 8px 14px; + color: #fff; + border: 1px solid var(--line); + font-size: 13px; + font-weight: 300; +} + +.category-pills a:hover { + border-color: var(--gold); + color: var(--gold); +} + +.catalog-card { + background: var(--navy-2); + border: 1px solid var(--line); +} + +.catalog-card-media { + position: relative; + display: block; + aspect-ratio: 4 / 3; + overflow: hidden; + background: var(--navy-3); +} + +.catalog-card-media img, +.catalog-card-media .slot-fill { + width: 100%; + height: 100%; + object-fit: cover; + transition: transform 0.6s ease; +} + +.catalog-card:hover .catalog-card-media img, +.catalog-card:hover .catalog-card-media .slot-fill { + transform: scale(1.06); +} + +.catalog-card-body { + padding: 14px 16px 18px; +} + +.catalog-card-body h3 { + margin: 0; + font-size: 16px; + font-weight: 300; +} + +.catalog-card-body .en { + margin: 6px 0 0; + color: var(--gold); + font-size: 11px; +} + +.crumbs { + display: flex; + flex-wrap: wrap; + gap: 8px; + margin-bottom: 28px; + color: var(--muted); + font-size: 13px; + font-weight: 300; +} + +.crumbs a:hover { + color: var(--gold); +} + +.product-detail-grid { + display: grid; + grid-template-columns: 0.95fr 1.05fr; + gap: 40px; + align-items: start; + margin-bottom: 48px; +} + +.product-detail-copy h1 { + margin: 8px 0 10px; + font-size: clamp(24px, 3vw, 36px); + font-weight: 300; +} + +.product-category { + margin: 8px 0 0; + color: var(--gold); + font-size: 13px; + font-weight: 300; +} + +.product-gallery { + display: grid; + gap: 12px; +} + +.product-gallery-main { + position: relative; + aspect-ratio: 4 / 3; + overflow: hidden; + padding: 0; + border: 1px solid var(--line); + background: var(--navy-3); + cursor: zoom-in; +} + +.product-gallery-main img { + width: 100%; + height: 100%; + object-fit: cover; + transition: transform 0.18s ease-out; +} + +.product-gallery-main:hover img { + transform: scale(2); +} + +.product-gallery-thumbs { + display: grid; + grid-template-columns: repeat(5, 1fr); + gap: 8px; +} + +.product-gallery-thumbs button { + aspect-ratio: 1; + overflow: hidden; + padding: 0; + border: 1px solid var(--line); + background: var(--navy-3); + cursor: pointer; +} + +.product-gallery-thumbs button.active, +.product-gallery-thumbs button:hover { + border-color: var(--gold); +} + +.product-gallery-thumbs img { + width: 100%; + height: 100%; + object-fit: cover; +} + +.product-gallery-empty { + aspect-ratio: 4 / 3; + display: grid; + place-items: center; + border: 1px solid var(--line); + color: var(--muted); +} + +.product-lightbox { + position: fixed; + inset: 0; + z-index: 80; + display: grid; + place-items: center; + padding: 40px 24px; + background: rgba(12, 14, 32, 0.94); +} + +.product-lightbox img { + max-width: min(1100px, 100%); + max-height: 82vh; + object-fit: contain; +} + +.product-lightbox p { + position: absolute; + bottom: 18px; + margin: 0; + color: var(--muted); +} + +.product-lightbox-close, +.product-lightbox-nav { + position: absolute; + z-index: 2; + width: 44px; + height: 44px; + color: #fff; + background: rgba(255, 255, 255, 0.08); + border: 1px solid var(--line); + border-radius: 999px; + cursor: pointer; + font-size: 22px; +} + +.product-lightbox-close { + top: 20px; + inset-inline-start: 20px; +} + +.product-lightbox-nav.next { + top: 50%; + inset-inline-end: 20px; + transform: translateY(-50%); +} + +.product-lightbox-nav.prev { + top: 50%; + inset-inline-start: 20px; + transform: translateY(-50%); +} + +.detail-tabs { + margin-top: 16px; + border: 1px solid var(--line); + background: var(--navy-2); +} + +.detail-tabs-nav { + display: flex; + flex-wrap: wrap; + gap: 0; + border-bottom: 1px solid var(--line); +} + +.detail-tabs-nav button { + min-height: 48px; + padding: 0 22px; + color: #fff; + background: transparent; + border: 0; + border-bottom: 2px solid transparent; + cursor: pointer; + font-weight: 300; +} + +.detail-tabs-nav button.active { + color: var(--gold); + border-bottom-color: var(--gold); +} + +.detail-tabs-body { + padding: 24px; +} + +.tech-table { + width: 100%; + border-collapse: collapse; +} + +.tech-table th, +.tech-table td { + padding: 12px 14px; + border-bottom: 1px solid var(--line); + font-weight: 300; + text-align: right; +} + +.tech-table th { + width: 36%; + color: var(--gold); +} + +.comment-box { + display: grid; + gap: 20px; +} + +.comment-list { + display: grid; + gap: 16px; +} + +.comment-item { + padding-bottom: 16px; + border-bottom: 1px solid var(--line); +} + +.comment-item strong { + display: block; + font-weight: 500; +} + +.comment-item time, +.comment-empty { + color: var(--muted); + font-size: 13px; + font-weight: 300; +} + +.comment-item p { + margin: 8px 0 0; +} + +.comment-login { + display: flex; + flex-wrap: wrap; + align-items: center; + justify-content: space-between; + gap: 16px; + padding: 18px; + border: 1px solid var(--line); +} + +.comment-form { + display: grid; + gap: 12px; +} + +.comment-form textarea { + width: 100%; + min-height: 120px; + padding: 12px 14px; + color: #fff; + background: var(--navy); + border: 1px solid var(--line); + font: inherit; + font-weight: 300; +} + +.comment-form textarea:focus { + border-color: var(--gold); + outline: none; +} + +.related-section { + margin-top: 56px; +} + +.related-section h2 { + margin: 0; + font-size: clamp(20px, 2.4vw, 28px); + font-weight: 300; +} + +.related-section .en { + margin: 6px 0 22px; + color: var(--gold); +} + +.blog-grid { + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: 16px; +} + +.detail-article { + max-width: 820px; +} + +.detail-lead { + margin: 0 0 24px; + color: var(--muted); + font-size: 16px; + font-weight: 300; + line-height: 2; +} + +.rich-text { + font-weight: 300; + line-height: 2; +} + +.rich-text p, +.rich-text li { + margin: 0 0 14px; +} + +.rich-text img { + max-width: 100%; + height: auto; + margin: 16px 0; +} + +.pagination { + display: flex; + flex-wrap: wrap; + justify-content: center; + gap: 8px; + margin-top: 36px; +} + +.pagination a, +.pagination span { + min-width: 36px; + min-height: 36px; + display: grid; + place-items: center; + padding: 0 8px; + color: #fff; + border: 1px solid var(--line); + font-size: 13px; +} + +.pagination a.active, +.pagination a:hover { + border-color: var(--gold); + color: var(--gold); +} + +.detail-page { + padding: 120px 0 88px; +} + +.about-blocks { + display: grid; + gap: 24px; +} + +.about-block { + background: var(--navy-2); + border: 1px solid var(--line); + padding: 32px; +} + +.about-block h2 { + margin: 0 0 8px; +} + +.about-block .en { + color: var(--gold); + font-size: 12px; + margin: 0 0 16px; +} + +.gallery { + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: 12px; +} + +.gallery-item { + aspect-ratio: 4 / 3; + overflow: hidden; + background: var(--navy-3); +} + +.gallery-item img, +.gallery-item .slot-fill { + width: 100%; + height: 100%; + object-fit: cover; +} + +.contact-grid { + display: grid; + grid-template-columns: 0.9fr 1.1fr; + gap: 32px; +} + +.info-card, +.contact-form { + background: var(--navy-2); + border: 1px solid var(--line); + padding: 28px; +} + +.info-card h2, +.contact-form h2 { + margin: 0 0 20px; +} + +.info-row { + margin-bottom: 22px; +} + +.info-row .en, +.info-row span.kicker { + display: block; + color: var(--gold); + font-size: 12px; + margin-bottom: 6px; +} + +.info-row p, +.info-row a { + margin: 0; + line-height: 1.9; +} + +.contact-form { + display: grid; + gap: 14px; +} + +.contact-form label { + display: grid; + gap: 8px; + font-size: 14px; +} + +.contact-form input, +.contact-form textarea { + background: var(--navy); + border: 1px solid var(--line); + color: #fff; + padding: 12px 14px; + outline: none; +} + +.contact-form textarea { + min-height: 140px; + resize: vertical; +} + +.contact-form .ok { + color: var(--gold); + margin: 0; +} + +.contact-form .err { + color: #ff8b8b; + margin: 0; +} + +.map-wrap { + margin-top: 32px; + border: 1px solid var(--line); + min-height: 320px; + background: var(--navy-2); +} + +.map-wrap iframe { + width: 100%; + height: 360px; + border: 0; + filter: grayscale(0.3); +} + +.site-footer { + background: var(--navy-2); + border-top: 1px solid var(--line); +} + +.footer-grid { + display: grid; + grid-template-columns: 1.4fr 1fr 1fr; + gap: 32px; + padding: 48px 0; +} + +.footer-grid h3 { + margin: 0 0 14px; + font-size: 16px; +} + +.footer-grid p, +.footer-grid li, +.footer-grid a { + color: var(--muted); + line-height: 2; + font-size: 14px; +} + +.footer-grid ul { + margin: 0; + padding: 0; + list-style: none; +} + +.footer-bottom { + border-top: 1px solid var(--line); + padding: 12px 0 18px; +} + +.footer-bottom-bar { + align-items: center; + display: flex; + flex-wrap: wrap; + gap: 12px; + justify-content: space-between; +} + +.footer-bottom-site { + color: #9c9c9c; + font-size: 12px; +} + +.meshkee-credit { + align-items: center; + color: #9c9c9c; + display: inline-flex; + gap: 10px; + padding: 6px 10px; + position: relative; + text-decoration: none; + transition: color 0.3s cubic-bezier(0.22, 1, 0.36, 1); +} + +.meshkee-credit:hover { + color: #e0e0e0; +} + +.meshkee-credit__frame { + inset: 0; + pointer-events: none; + position: absolute; +} + +.meshkee-credit__line { + background: #777; + position: absolute; + transition: transform 0.28s cubic-bezier(0.22, 1, 0.36, 1); +} + +.meshkee-credit__line--l { + height: 100%; + left: 0; + top: 0; + transform: scaleY(0); + transform-origin: top; + width: 1px; +} + +.meshkee-credit__line--b { + bottom: 0; + height: 1px; + left: 0; + transform: scaleX(0); + transform-origin: left; + transition-delay: 0.14s; + width: 100%; +} + +.meshkee-credit__line--r { + height: 100%; + right: 0; + top: 0; + transform: scaleY(0); + transform-origin: bottom; + transition-delay: 0.28s; + width: 1px; +} + +.meshkee-credit__line--t { + height: 1px; + left: 0; + top: 0; + transform: scaleX(0); + transform-origin: right; + transition-delay: 0.42s; + width: 100%; +} + +.meshkee-credit:hover .meshkee-credit__line--l, +.meshkee-credit:hover .meshkee-credit__line--r { + transform: scaleY(1); +} + +.meshkee-credit:hover .meshkee-credit__line--t, +.meshkee-credit:hover .meshkee-credit__line--b { + transform: scaleX(1); +} + +.meshkee-credit:not(:hover) .meshkee-credit__line--l { + transition-delay: 0.42s; +} + +.meshkee-credit:not(:hover) .meshkee-credit__line--b { + transition-delay: 0.28s; +} + +.meshkee-credit:not(:hover) .meshkee-credit__line--r { + transition-delay: 0.14s; +} + +.meshkee-credit:not(:hover) .meshkee-credit__line--t { + transition-delay: 0s; +} + +.meshkee-credit__logo { + display: block; + flex-shrink: 0; + height: 42px; + width: auto; + aspect-ratio: 1 / 1; + object-fit: contain; + opacity: 0.7; +} + +.meshkee-credit:hover .meshkee-credit__logo { + opacity: 1; +} + +.meshkee-credit__text { + display: flex; + flex-direction: column; + justify-content: center; + line-height: 1.25; + min-height: 42px; +} + +.meshkee-credit__label { + font-size: 11px; +} + +.meshkee-credit__name { + color: #cfcfcf; + font-size: 13px; + font-weight: 600; +} + +.meshkee-credit:hover .meshkee-credit__name { + color: #fff; +} + +.reveal { + --reveal-delay: 0ms; + width: 100%; +} + +.reveal:not(.stagger) { + opacity: 0; + filter: blur(8px); + transition: + opacity 0.8s cubic-bezier(0.16, 1, 0.3, 1), + transform 0.8s cubic-bezier(0.16, 1, 0.3, 1), + filter 0.8s ease; + transition-delay: var(--reveal-delay); +} + +.reveal--up:not(.stagger) { + transform: translateY(36px); +} + +.reveal--left:not(.stagger) { + transform: translateX(-40px); +} + +.reveal--right:not(.stagger) { + transform: translateX(40px); +} + +.reveal:not(.stagger).is-in { + opacity: 1; + filter: none; + transform: none; +} + +.stagger > * { + opacity: 0; + filter: blur(8px); + transform: translateY(28px) scale(0.96); +} + +.stagger.is-in > * { + animation: home-in 0.78s cubic-bezier(0.16, 1, 0.3, 1) forwards; + animation-delay: calc(var(--d, 0) * 85ms); +} + +@keyframes home-in { + from { + opacity: 0; + filter: blur(8px); + transform: translateY(28px) scale(0.96); + } + to { + opacity: 1; + filter: none; + transform: none; + } +} + +@media (prefers-reduced-motion: reduce) { + .reveal:not(.stagger), + .stagger > * { + opacity: 1; + filter: none; + transform: none; + animation: none; + transition: none; + } + + .blog-track { + transition: none; + } +} + +@media (max-width: 980px) { + .about-grid, + .contact-grid, + .footer-grid, + .product-grid, + .catalog-grid, + .blog-grid { + grid-template-columns: 1fr 1fr; + } + + .product-detail-grid { + grid-template-columns: 1fr; + } + + .blog-carousel { + --blog-per: 2; + } + + .clients, + .stats, + .gallery { + grid-template-columns: 1fr 1fr; + } +} + +@media (max-width: 760px) { + .section-head-bg { + font-size: 56px; + -webkit-text-stroke-color: rgba(225, 163, 76, 0.16); + } + + .section-head-text, + #products .section-head-text, + #blog .section-head-text { + max-width: none; + white-space: normal; + } + + .menu-toggle { + display: block; + } + + .brand-text { + display: none; + } + + .brand { + margin-inline-end: 0; + } + + .header-login span, + .account-menu__trigger span { + display: none; + } + + .header-search.open input { + width: min(42vw, 160px); + } + + .nav { + display: none; + position: absolute; + inset-inline: 0; + top: 72px; + background: var(--navy); + flex-direction: column; + padding: 12px 20px 20px; + border-bottom: 1px solid var(--line); + } + + .nav.open { + display: flex; + } + + .nav-dropdown { + width: 100%; + } + + .nav-dropdown > a { + width: 100%; + } + + .nav-sub { + position: static; + display: flex; + flex-direction: column; + min-width: 0; + margin-top: 4px; + padding: 0; + background: transparent; + border: 0; + border-radius: 0; + backdrop-filter: none; + } + + .nav-sub a { + padding-inline-start: 18px; + font-size: 12px; + color: rgba(255, 255, 255, 0.82); + } + + .highlights, + .about-grid, + .contact-grid, + .footer-grid, + .product-grid, + .catalog-grid, + .blog-grid, + .clients, + .stats, + .gallery { + grid-template-columns: 1fr; + } + + .blog-carousel { + --blog-per: 1; + } + + .app-strip-inner, + .contact-bar-inner, + .contact-bar-subscribe { + flex-direction: column; + align-items: stretch; + } + + .contact-socials { + justify-content: flex-start; + } + + .callback-form input { + width: 100%; + flex: 1; + } +} diff --git a/src/app/layout.tsx b/src/app/layout.tsx new file mode 100644 index 0000000..ae7ee82 --- /dev/null +++ b/src/app/layout.tsx @@ -0,0 +1,64 @@ +import type { Metadata } from "next"; +import { Oswald, Rajdhani } from "next/font/google"; + +import Footer from "@/components/Footer"; +import Header from "@/components/Header"; +import Providers from "@/components/Providers"; +import { site } from "@/data/site"; +import { websiteDomain } from "@/lib/config"; +import { getBusinessId } from "@/lib/meshkee"; + +import "./globals.css"; + +const rajdhani = Rajdhani({ + subsets: ["latin"], + weight: ["500", "600", "700"], + variable: "--font-en", + display: "swap", +}); + +const oswald = Oswald({ + subsets: ["latin"], + weight: ["400", "500", "600", "700"], + variable: "--font-en-display", + display: "swap", +}); + +export const metadata: Metadata = { + metadataBase: new URL(`https://${websiteDomain}`), + title: { + default: `${site.shortName} | ${site.name}`, + template: `%s | ${site.shortName}`, + }, + description: site.description, + icons: { + icon: "/favicon.png", + }, + openGraph: { + locale: "fa_IR", + siteName: site.shortName, + type: "website", + }, +}; + +export default async function RootLayout({ + children, +}: Readonly<{ + children: React.ReactNode; +}>) { + const businessId = await getBusinessId(); + + return ( + <html lang="fa" dir="rtl" className={`${rajdhani.variable} ${oswald.variable}`}> + <body> + <Providers businessId={businessId || "24"}> + <div className="wrapper"> + <Header /> + <main>{children}</main> + <Footer /> + </div> + </Providers> + </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..998558b --- /dev/null +++ b/src/app/meshkee/sitemap-config.json/route.ts @@ -0,0 +1,17 @@ +import sitemapConfig from "@/lib/sitemap-config.generated.json"; + +const headers = { + "Access-Control-Allow-Origin": "*", + "Access-Control-Allow-Methods": "GET, OPTIONS", + "Access-Control-Allow-Headers": "Content-Type", + "Cache-Control": "public, max-age=60", + "Content-Type": "application/json; charset=utf-8", +}; + +export async function OPTIONS() { + return new Response(null, { status: 204, headers }); +} + +export async function GET() { + return Response.json(sitemapConfig, { headers }); +} 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..0f648ec --- /dev/null +++ b/src/app/meshkee/static-image-slots/route.ts @@ -0,0 +1,16 @@ +import { STATIC_IMAGE_SLOTS } from "@/lib/static-images"; + +const headers = { + "Access-Control-Allow-Origin": "*", + "Access-Control-Allow-Methods": "GET, OPTIONS", + "Access-Control-Allow-Headers": "Content-Type", + "Cache-Control": "public, max-age=60", +}; + +export async function OPTIONS() { + return new Response(null, { status: 204, headers }); +} + +export async function GET() { + return Response.json({ slots: STATIC_IMAGE_SLOTS }, { headers }); +} diff --git a/src/app/page.tsx b/src/app/page.tsx new file mode 100644 index 0000000..8de5dc6 --- /dev/null +++ b/src/app/page.tsx @@ -0,0 +1,213 @@ +import type { CSSProperties } from "react"; + +import AboutTabs from "@/components/AboutTabs"; +import BlogCarousel from "@/components/BlogCarousel"; +import ContactBar from "@/components/ContactBar"; +import HeroSlider from "@/components/HeroSlider"; +import Reveal from "@/components/Reveal"; +import SectionTitle from "@/components/SectionTitle"; +import SlotImage from "@/components/SlotImage"; +import SlotLink from "@/components/SlotLink"; +import { blogHref, blogImage, fetchBlogs } from "@/lib/blog"; +import { blogPosts, clients, products, site, slides } from "@/data/site"; +import { pageMetadata } from "@/lib/seo"; +import { fetchStaticImageSlots, overlaySlotImages, pickSlot, slotRatioStyle } from "@/lib/static-images"; + +export const metadata = pageMetadata({ + title: `${site.shortName} | ${site.name}`, + description: site.description, + path: "/", + image: "/images/slider/bg1.jpg", + absolute: true, +}); + +export default async function HomePage() { + const slots = await fetchStaticImageSlots("home"); + const slider = overlaySlotImages( + pickSlot(slots, "slider"), + slides.map((slide) => ({ + id: slide.id, + image: slide.image, + title: slide.title, + kicker: slide.kicker, + })), + "slider", + ); + const aboutImage = overlaySlotImages( + pickSlot(slots, "about-us-image"), + [{ id: "about", image: "/images/about/factory.jpg", title: "کارخانه صنایع کابل کمان" }], + "about-us-image", + ); + const productItems = overlaySlotImages( + pickSlot(slots, "categories"), + products.map((product) => ({ + id: product.id, + image: product.image, + title: product.title, + kicker: product.titleEn, + text: product.text, + })), + "categories", + ); + const clientItems = overlaySlotImages( + pickSlot(slots, "partners"), + clients.map((client) => ({ + id: client.id, + image: client.image, + title: client.title, + })), + "partners", + ); + const apiBlogs = await fetchBlogs({ pageSize: 6 }); + const blogFallbacks = apiBlogs.items.length + ? apiBlogs.items.map((post) => ({ + id: post.id, + image: blogImage(post), + title: post.title, + text: post.abstract || undefined, + href: blogHref(post), + })) + : blogPosts.map((post) => ({ + id: post.id, + image: post.image, + title: post.title, + kicker: post.date, + text: post.text, + href: blogHref(post), + })); + const blogItems = overlaySlotImages(pickSlot(slots, "home-blog"), blogFallbacks, "home-blog"); + + return ( + <> + <h1 className="sr-only">{site.name}</h1> + <HeroSlider slides={slider.items} aspectRatio={slider.aspectRatio} /> + + <section className="app-strip"> + <Reveal> + <div className="container app-strip-inner"> + <h2>اپلیکیشن اختصاصی</h2> + <a className="btn btn-gold" href={site.appUrl} target="_blank" rel="noopener noreferrer"> + دانلود اپلیکیشن + </a> + </div> + </Reveal> + </section> + + <section className="section"> + <div className="container about-grid"> + <Reveal variant="right"> + <div className="about-photo" style={slotRatioStyle(aboutImage.aspectRatio)}> + <SlotLink href={aboutImage.items[0]?.href}> + <SlotImage + src={aboutImage.items[0]?.image || "/images/about/factory.jpg"} + alt={aboutImage.items[0]?.title || "کارخانه صنایع کابل کمان"} + fill + /> + </SlotLink> + </div> + </Reveal> + <Reveal variant="left" delay={140}> + <div> + <SectionTitle + index="01" + en="About" + title="صنایع کابل کمان" + text={site.tagline} + /> + <AboutTabs /> + <p className="about-cta"> + <a className="about-contact-link" href="#contact"> + تماس با ما + <svg width="16" height="16" viewBox="0 0 24 24" fill="none" aria-hidden> + <path + d="M7.2 3.8h2.6l1.2 3-1.6 1.1a12.5 12.5 0 0 0 5.7 5.7l1.1-1.6 3 1.2v2.6c0 .7-.6 1.4-1.3 1.5-2 .3-6.3-.2-10.1-4S5.4 6.9 5.7 4.9c.1-.7.8-1.1 1.5-1.1Z" + stroke="currentColor" + strokeWidth="1.6" + strokeLinejoin="round" + /> + </svg> + </a> + </p> + </div> + </Reveal> + </div> + </section> + + <Reveal> + <ContactBar /> + </Reveal> + + <section className="section" id="products"> + <div className="container"> + <Reveal> + <SectionTitle + index="02" + en="Products" + title="محصولات کابل و سیم" + text="تنوع کامل سیم و کابل فشار ضعیف، مخابراتی، شبکه و ابزار دقیق، تولیدشده مطابق با استانداردهای ملی و بین‌المللی." + /> + </Reveal> + <Reveal stagger className="product-grid" style={slotRatioStyle(productItems.aspectRatio)}> + {productItems.items.map((item, index) => ( + <article + key={item.id} + className="product-card" + id={`product-${item.id}`} + style={{ "--d": index } as CSSProperties} + > + <div className="thumb"> + <SlotLink href={item.href}> + <SlotImage src={item.image} alt={item.title || "محصول کابل کمان"} fill /> + </SlotLink> + </div> + <div className="body"> + <h3>{item.title}</h3> + {item.kicker ? <p className="en">{item.kicker}</p> : null} + {item.text ? <p className="product-excerpt">{item.text}</p> : null} + </div> + </article> + ))} + </Reveal> + </div> + </section> + + <section className="section" id="blog" style={{ paddingTop: 0 }}> + <div className="container"> + <Reveal> + <SectionTitle + index="03" + en="Blog" + title="آخرین اخبار و مقالات" + text="تازه‌ترین دانستنی‌های فنی و اخبار صنایع سیم و کابل کمان را در این بخش دنبال کنید." + /> + </Reveal> + <Reveal delay={80} style={slotRatioStyle(blogItems.aspectRatio)}> + <BlogCarousel posts={blogItems.items} /> + </Reveal> + </div> + </section> + + <section className="section" style={{ paddingTop: 0 }}> + <div className="container"> + <Reveal> + <SectionTitle + index="04" + en="Partners" + title="همکاران و مشتریان" + text="برندها و مجموعه‌هایی که کیفیت تولید کابل کمان را برای پروژه‌های خود انتخاب کرده‌اند." + /> + </Reveal> + <Reveal stagger className="clients" style={slotRatioStyle(clientItems.aspectRatio)}> + {clientItems.items.map((item, index) => ( + <div key={item.id} className="client" style={{ "--d": index } as CSSProperties}> + <SlotLink href={item.href}> + <SlotImage src={item.image} alt={item.title || "همکار کابل کمان"} /> + </SlotLink> + </div> + ))} + </Reveal> + </div> + </section> + </> + ); +} diff --git a/src/app/products/[id]/[slug]/page.tsx b/src/app/products/[id]/[slug]/page.tsx new file mode 100644 index 0000000..f110b14 --- /dev/null +++ b/src/app/products/[id]/[slug]/page.tsx @@ -0,0 +1,149 @@ +import type { Metadata } from "next"; +import Link from "next/link"; +import { notFound, redirect } from "next/navigation"; + +import CatalogCard from "@/components/CatalogCard"; +import ProductGallery from "@/components/ProductGallery"; +import ProductTabs from "@/components/ProductTabs"; +import { categoryHref, categoryTitle } from "@/lib/categories"; +import { fetchComments } from "@/lib/comments"; +import { plainText, sanitizeHtml } from "@/lib/format"; +import { + fetchProductById, + fetchProductTechnicalInfo, + localProductText, + productGallery, + productHref, + productTitle, +} from "@/lib/products"; +import { pageMetadata } from "@/lib/seo"; +import { slugifyForUrl } from "@/lib/slug"; +import { rowsFromTechnicalInfo } from "@/lib/technical"; + +export const revalidate = 60; + +type Params = { id: string; slug: string }; + +export async function generateMetadata({ + params, +}: { + params: Promise<Params>; +}): Promise<Metadata> { + const { id } = await params; + const data = await fetchProductById(id); + if (!data?.product) { + return { + title: "محصول یافت نشد", + description: "این محصول در کاتالوگ صنایع کابل کمان موجود نیست.", + robots: { index: false }, + }; + } + const title = productTitle(data.product); + const description = + plainText(data.product.summary || data.product.descriptionHtml).slice(0, 160) || + `${title} | محصولات صنایع سیم و کابل کمان`; + return pageMetadata({ + title, + description, + path: productHref(data.product), + image: productGallery(data.product)[0], + }); +} + +export default async function ProductDetailPage({ params }: { params: Promise<Params> }) { + const { id, slug } = await params; + const data = await fetchProductById(id); + if (!data?.product) notFound(); + + const product = data.product; + const title = productTitle(product); + const canonicalSlug = slugifyForUrl(title); + if (decodeURIComponent(slug) !== canonicalSlug) { + redirect(productHref(product)); + } + + const [technicalInfo, comments] = await Promise.all([ + fetchProductTechnicalInfo(product.id), + fetchComments("product", product.id), + ]); + + const descriptionHtml = sanitizeHtml(product.descriptionHtml); + const descriptionText = product.summary?.trim() || localProductText(product); + const related = (data.relatedProducts ?? []).filter((item) => item.id !== product.id).slice(0, 4); + + return ( + <section className="detail-page"> + <div className="container"> + <nav className="crumbs" aria-label="مسیر صفحه"> + <Link href="/">صفحه اصلی</Link> + <span>/</span> + <Link href="/products">محصولات</Link> + {product.categoryId && (product.categoryNameFa || product.categoryName) ? ( + <> + <span>/</span> + <Link + href={categoryHref({ + id: product.categoryId, + nameFa: product.categoryNameFa, + name: product.categoryName, + })} + > + {categoryTitle({ nameFa: product.categoryNameFa, name: product.categoryName })} + </Link> + </> + ) : null} + <span>/</span> + <b>{title}</b> + </nav> + + <div className="product-detail-grid"> + <ProductGallery images={productGallery(product)} alt={title} /> + <div className="product-detail-copy"> + <p className="en">Product Details</p> + {product.categoryId && (product.categoryNameFa || product.categoryName) ? ( + <p className="product-category"> + <Link + href={categoryHref({ + id: product.categoryId, + nameFa: product.categoryNameFa, + name: product.categoryName, + })} + > + {categoryTitle({ nameFa: product.categoryNameFa, name: product.categoryName })} + </Link> + </p> + ) : product.categoryNameFa || product.categoryName ? ( + <p className="product-category">{product.categoryNameFa || product.categoryName}</p> + ) : null} + <h1>{title}</h1> + {product.title && product.nameFa ? <p className="en">{product.title}</p> : null} + {descriptionText && !descriptionHtml ? <p>{descriptionText}</p> : null} + <a className="about-contact-link" href="/#contact"> + تماس با ما + </a> + </div> + </div> + + <ProductTabs + descriptionHtml={descriptionHtml || undefined} + descriptionText={descriptionHtml ? undefined : descriptionText} + technicalRows={rowsFromTechnicalInfo(technicalInfo)} + comments={comments} + entityId={product.id} + /> + + {related.length > 0 ? ( + <section className="related-section"> + <h2>محصولات مشابه</h2> + <p className="en">Similar Products</p> + <div className="catalog-grid"> + {related.map((item) => ( + <CatalogCard key={item.id} product={item} /> + ))} + </div> + </section> + ) : null} + </div> + </section> + ); +} 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..bfd00d6 --- /dev/null +++ b/src/app/products/category/[id]/[slug]/page.tsx @@ -0,0 +1,147 @@ +import type { Metadata } from "next"; +import Link from "next/link"; +import { notFound, redirect } from "next/navigation"; + +import CatalogCard from "@/components/CatalogCard"; +import SlotImage from "@/components/SlotImage"; +import { + categoryHref, + categoryTitle, + fetchCategories, + fetchCategoryById, +} from "@/lib/categories"; +import { fetchProducts } from "@/lib/products"; +import { pageMetadata } from "@/lib/seo"; +import { slugifyForUrl } from "@/lib/slug"; +import { fetchStaticImageSlots, overlaySlotImages, pickSlot } from "@/lib/static-images"; + +export const revalidate = 60; + +type Params = { id: string; slug: string }; + +export async function generateMetadata({ + params, + searchParams, +}: { + params: Promise<Params>; + searchParams: Promise<{ page?: string }>; +}): Promise<Metadata> { + const { id } = await params; + const { page: pageParam } = await searchParams; + const page = Math.max(1, Number(pageParam) || 1); + const category = await fetchCategoryById(id); + if (!category) { + return { title: "دسته‌بندی یافت نشد", description: "این دسته‌بندی در کاتالوگ کابل کمان موجود نیست.", robots: { index: false } }; + } + const title = categoryTitle(category); + const description = + category.description?.trim() || + `محصولات دسته ${title} در صنایع سیم و کابل کمان.`; + return pageMetadata({ + title: page > 1 ? `${title} — صفحه ${page}` : title, + description, + path: categoryHref(category), + image: "/images/samples/cables-coils.jpg", + }); +} + +export default async function ProductCategoryPage({ + params, + searchParams, +}: { + params: Promise<Params>; + searchParams: Promise<{ page?: string }>; +}) { + const { id, slug } = await params; + const { page: pageParam } = await searchParams; + const page = Math.max(1, Number(pageParam) || 1); + const category = await fetchCategoryById(id); + if (!category) notFound(); + + const canonicalSlug = slugifyForUrl(categoryTitle(category)); + if (decodeURIComponent(slug) !== canonicalSlug) { + redirect(categoryHref(category)); + } + + const [result, allCategories, slots] = await Promise.all([ + fetchProducts({ page, pageSize: 12, categoryId: category.id }), + fetchCategories("product"), + fetchStaticImageSlots("products"), + ]); + const hero = overlaySlotImages( + pickSlot(slots, "products-hero"), + [{ id: "products-hero", image: "/images/samples/cables-coils.jpg", title: categoryTitle(category) }], + "products-hero", + ); + const children = allCategories.filter((item) => item.parentId === category.id); + const title = categoryTitle(category); + const pageCount = Math.max(1, Math.ceil((result.total || result.items.length) / (result.pageSize || 12))); + const listHref = (number: number) => + number === 1 ? categoryHref(category) : `${categoryHref(category)}?page=${number}`; + + return ( + <> + <section className="page-hero"> + <SlotImage + src={hero.items[0]?.image || "/images/samples/cables-coils.jpg"} + alt={hero.items[0]?.title || title} + fill + /> + <div className="container"> + <h1>{title}</h1> + {category.name && category.nameFa ? <p className="en">{category.name}</p> : <p className="en">Products</p>} + </div> + </section> + + <section className="section"> + <div className="container"> + <nav className="crumbs" aria-label="مسیر صفحه"> + <Link href="/">صفحه اصلی</Link> + <span>/</span> + <Link href="/products">محصولات</Link> + <span>/</span> + <b>{title}</b> + </nav> + + {category.description ? <p className="detail-lead">{category.description}</p> : null} + + {children.length > 0 ? ( + <div className="category-pills"> + {children.map((item) => ( + <Link key={item.id} href={categoryHref(item)}> + {categoryTitle(item)} + </Link> + ))} + </div> + ) : null} + + {result.items.length === 0 ? ( + <p className="comment-empty">محصولی در این دسته ثبت نشده است.</p> + ) : ( + <div className="catalog-grid"> + {result.items.map((product) => ( + <CatalogCard key={product.id} product={product} /> + ))} + </div> + )} + + {pageCount > 1 ? ( + <nav className="pagination" aria-label="صفحه‌بندی دسته"> + {Array.from({ length: pageCount }, (_, index) => { + const number = index + 1; + if (pageCount > 12 && Math.abs(number - page) > 3 && number !== 1 && number !== pageCount) { + return number === 2 || number === pageCount - 1 ? <span key={number}>…</span> : null; + } + return ( + <Link key={number} href={listHref(number)} className={number === page ? "active" : undefined}> + {number} + </Link> + ); + })} + </nav> + ) : null} + </div> + </section> + </> + ); +} diff --git a/src/app/products/page.tsx b/src/app/products/page.tsx new file mode 100644 index 0000000..31966c5 --- /dev/null +++ b/src/app/products/page.tsx @@ -0,0 +1,109 @@ +import type { Metadata } from "next"; +import Link from "next/link"; + +import CatalogCard from "@/components/CatalogCard"; +import SlotImage from "@/components/SlotImage"; +import { categoryHref, categoryTitle, fetchCategories } from "@/lib/categories"; +import { fetchProducts } from "@/lib/products"; +import { pageMetadata } from "@/lib/seo"; +import { fetchStaticImageSlots, overlaySlotImages, pickSlot } from "@/lib/static-images"; + +export const revalidate = 60; + +export async function generateMetadata({ + searchParams, +}: { + searchParams: Promise<{ page?: string }>; +}): Promise<Metadata> { + const { page: pageParam } = await searchParams; + const page = Math.max(1, Number(pageParam) || 1); + return pageMetadata({ + title: page > 1 ? `محصولات — صفحه ${page}` : "محصولات", + description: "کاتالوگ سیم و کابل فشار ضعیف، مخابراتی، شبکه و ابزار دقیق صنایع کابل کمان.", + path: page > 1 ? `/products?page=${page}` : "/products", + image: "/images/samples/cables-coils.jpg", + }); +} + +export default async function ProductsPage({ + searchParams, +}: { + searchParams: Promise<{ page?: string }>; +}) { + const { page: pageParam } = await searchParams; + const page = Math.max(1, Number(pageParam) || 1); + const [result, categories, slots] = await Promise.all([ + fetchProducts({ page, pageSize: 12 }), + fetchCategories("product"), + fetchStaticImageSlots("products"), + ]); + const hero = overlaySlotImages( + pickSlot(slots, "products-hero"), + [{ id: "products-hero", image: "/images/samples/cables-coils.jpg", title: "محصولات" }], + "products-hero", + ); + const pageCount = Math.max(1, Math.ceil((result.total || result.items.length) / (result.pageSize || 12))); + const topCategories = categories.filter((item) => !item.parentId); + + return ( + <> + <section className="page-hero"> + <SlotImage + src={hero.items[0]?.image || "/images/samples/cables-coils.jpg"} + alt={hero.items[0]?.title || "محصولات کابل کمان"} + fill + /> + <div className="container"> + <h1>محصولات</h1> + <p className="en">Products</p> + </div> + </section> + + <section className="section"> + <div className="container"> + {topCategories.length > 0 ? ( + <div className="category-pills"> + {topCategories.map((item) => ( + <Link key={item.id} href={categoryHref(item)}> + {categoryTitle(item)} + </Link> + ))} + </div> + ) : null} + + {result.items.length === 0 ? ( + <p className="comment-empty">محصولی برای نمایش وجود ندارد.</p> + ) : ( + <div className="catalog-grid"> + {result.items.map((product) => ( + <CatalogCard key={product.id} product={product} /> + ))} + </div> + )} + + {pageCount > 1 ? ( + <nav className="pagination" aria-label="صفحه‌بندی محصولات"> + {Array.from({ length: pageCount }, (_, index) => { + const number = index + 1; + if (pageCount > 12 && Math.abs(number - page) > 3 && number !== 1 && number !== pageCount) { + return number === 2 || number === pageCount - 1 ? ( + <span key={number}>…</span> + ) : null; + } + return ( + <Link + key={number} + href={number === 1 ? "/products" : `/products?page=${number}`} + className={number === page ? "active" : undefined} + > + {number} + </Link> + ); + })} + </nav> + ) : null} + </div> + </section> + </> + ); +} diff --git a/src/components/AboutTabs.tsx b/src/components/AboutTabs.tsx new file mode 100644 index 0000000..5a4524d --- /dev/null +++ b/src/components/AboutTabs.tsx @@ -0,0 +1,39 @@ +"use client"; + +import { useState } from "react"; + +import { goalsText, qualityPolicy, resumeText } from "@/data/site"; + +export default function AboutTabs() { + const [tab, setTab] = useState<"resume" | "goals">("resume"); + + return ( + <div> + <div className="tabs" role="tablist"> + <button type="button" className={tab === "resume" ? "active" : undefined} onClick={() => setTab("resume")}> + رزومه + </button> + <button type="button" className={tab === "goals" ? "active" : undefined} onClick={() => setTab("goals")}> + اهداف + </button> + </div> + {tab === "resume" ? ( + <p className="muted">{resumeText}</p> + ) : ( + <div> + {goalsText.map((paragraph) => ( + <p key={paragraph.slice(0, 24)} className="muted"> + {paragraph} + </p> + ))} + <p className="muted">خط مشی کیفیت عبارت است از:</p> + <ol className="muted"> + {qualityPolicy.map((item) => ( + <li key={item}>{item}</li> + ))} + </ol> + </div> + )} + </div> + ); +} diff --git a/src/components/AccountMenu.tsx b/src/components/AccountMenu.tsx new file mode 100644 index 0000000..d000a50 --- /dev/null +++ b/src/components/AccountMenu.tsx @@ -0,0 +1,139 @@ +"use client"; + +import { useEffect, useRef, useState, type MouseEvent } from "react"; + +import { businessDashboardUrl, customerPortalUrl } from "@/lib/config"; +import { useSession, userDisplayName } from "@/lib/session"; + +function UserIcon() { + return ( + <svg width="15" height="15" viewBox="0 0 24 24" fill="none" aria-hidden> + <circle cx="12" cy="8" r="3.25" stroke="currentColor" strokeWidth="1.75" /> + <path + d="M5.5 19.25c1.4-3 3.8-4.5 6.5-4.5s5.1 1.5 6.5 4.5" + stroke="currentColor" + strokeWidth="1.75" + strokeLinecap="round" + /> + </svg> + ); +} + +function ChevronIcon({ open }: { open: boolean }) { + return ( + <svg + width="10" + height="10" + viewBox="0 0 12 12" + fill="none" + aria-hidden + className={open ? "account-menu__caret open" : "account-menu__caret"} + > + <path d="M2.5 4.5 6 8l3.5-3.5" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" /> + </svg> + ); +} + +export default function AccountMenu() { + const { user, isAuthenticated, isWebsiteAdmin, logout, getAccessToken } = useSession(); + const [open, setOpen] = useState(false); + const [handoffBusy, setHandoffBusy] = useState(false); + const menuRef = useRef<HTMLDivElement>(null); + + useEffect(() => { + if (!open) return; + const onClickOutside = (event: Event) => { + if (menuRef.current && event.target instanceof Node && !menuRef.current.contains(event.target)) { + setOpen(false); + } + }; + const onKeyDown = (event: KeyboardEvent) => { + if (event.key === "Escape") setOpen(false); + }; + document.addEventListener("mousedown", onClickOutside); + document.addEventListener("keydown", onKeyDown); + return () => { + document.removeEventListener("mousedown", onClickOutside); + document.removeEventListener("keydown", onKeyDown); + }; + }, [open]); + + async function openBusinessDashboard(event: MouseEvent<HTMLAnchorElement>) { + if (event.metaKey || event.ctrlKey || event.shiftKey || event.altKey || event.button !== 0) return; + event.preventDefault(); + if (handoffBusy) return; + + const popup = window.open("about:blank", "_blank"); + setHandoffBusy(true); + try { + const token = getAccessToken(); + const res = await fetch("/api/auth/handoff", { + method: "POST", + headers: token ? { Authorization: `Bearer ${token}` } : undefined, + }); + if (!res.ok) throw new Error("handoff failed"); + const data = (await res.json()) as { ticket?: string }; + const url = new URL(businessDashboardUrl); + if (data.ticket) url.searchParams.set("handoff", data.ticket); + if (popup) { + popup.opener = null; + popup.location.replace(url.toString()); + } else { + window.open(url.toString(), "_blank", "noopener,noreferrer"); + } + } catch { + if (popup) popup.location.replace(businessDashboardUrl); + else window.open(businessDashboardUrl, "_blank", "noopener,noreferrer"); + } finally { + setHandoffBusy(false); + setOpen(false); + } + } + + if (!isAuthenticated || !user) { + return ( + <a href={customerPortalUrl} className="header-tool header-login" aria-label="ورود"> + <UserIcon /> + <span>ورود</span> + </a> + ); + } + + return ( + <div ref={menuRef} className="account-menu"> + <button + type="button" + className="header-tool account-menu__trigger" + onClick={() => setOpen((value) => !value)} + aria-expanded={open} + aria-haspopup="menu" + > + <UserIcon /> + <span>{userDisplayName(user)} عزیز</span> + <ChevronIcon open={open} /> + </button> + {open ? ( + <div role="menu" className="account-menu__dropdown"> + <a role="menuitem" href={`${customerPortalUrl}/profile`} onClick={() => setOpen(false)}> + پروفایل من + </a> + {isWebsiteAdmin ? ( + <a role="menuitem" href={businessDashboardUrl} onClick={openBusinessDashboard}> + {handoffBusy ? "در حال انتقال…" : "مدیریت وبسایت"} + </a> + ) : null} + <button + type="button" + role="menuitem" + onClick={() => { + setOpen(false); + logout(); + }} + > + خروج + </button> + </div> + ) : null} + </div> + ); +} diff --git a/src/components/BlogCarousel.tsx b/src/components/BlogCarousel.tsx new file mode 100644 index 0000000..4a1960e --- /dev/null +++ b/src/components/BlogCarousel.tsx @@ -0,0 +1,118 @@ +"use client"; + +import { type CSSProperties, useEffect, useState } from "react"; + +import SlotImage from "@/components/SlotImage"; +import SlotLink from "@/components/SlotLink"; +import type { SlotMediaItem } from "@/lib/static-images"; + +function perViewForWidth(width: number) { + if (width <= 760) return 1; + if (width <= 980) return 2; + return 3; +} + +export default function BlogCarousel({ posts }: { posts: SlotMediaItem[] }) { + const [index, setIndex] = useState(0); + const [perView, setPerView] = useState(3); + const [paused, setPaused] = useState(false); + + const maxIndex = Math.max(0, posts.length - perView); + + useEffect(() => { + function sync() { + setPerView(perViewForWidth(window.innerWidth)); + } + sync(); + window.addEventListener("resize", sync); + return () => window.removeEventListener("resize", sync); + }, []); + + useEffect(() => { + setIndex((value) => Math.min(value, maxIndex)); + }, [maxIndex]); + + useEffect(() => { + if (posts.length <= perView || paused) return undefined; + if (window.matchMedia("(prefers-reduced-motion: reduce)").matches) return undefined; + const timer = window.setInterval(() => { + setIndex((value) => (value >= maxIndex ? 0 : value + 1)); + }, 5200); + return () => window.clearInterval(timer); + }, [maxIndex, paused, perView, posts.length]); + + if (posts.length === 0) return null; + + function go(next: number) { + setIndex(Math.min(maxIndex, Math.max(0, next))); + } + + return ( + <div + className="blog-carousel" + style={{ "--blog-index": index, "--blog-per": perView } as CSSProperties} + onMouseEnter={() => setPaused(true)} + onMouseLeave={() => setPaused(false)} + > + <div className="blog-toolbar"> + <div className="blog-dots" role="tablist" aria-label="مقالات وبلاگ"> + {Array.from({ length: maxIndex + 1 }, (_, page) => ( + <button + key={page} + type="button" + role="tab" + aria-selected={page === index} + aria-label={`اسلاید ${page + 1}`} + className={page === index ? "blog-dot active" : "blog-dot"} + onClick={() => go(page)} + /> + ))} + </div> + <div className="blog-arrows"> + <button + type="button" + className="blog-arrow" + aria-label="قبلی" + onClick={() => setIndex(index <= 0 ? maxIndex : index - 1)} + > + ‹ + </button> + <button + type="button" + className="blog-arrow" + aria-label="بعدی" + onClick={() => setIndex(index >= maxIndex ? 0 : index + 1)} + > + › + </button> + </div> + </div> + + <div className="blog-viewport"> + <div className="blog-track"> + {posts.map((post) => ( + <article key={post.id} className="blog-card"> + <div className="blog-card-media"> + <SlotLink href={post.href} className="blog-card-hit"> + <SlotImage src={post.image} alt={post.title || "مقاله کابل کمان"} fill /> + </SlotLink> + </div> + <div className="blog-card-body"> + {post.kicker ? <p className="blog-date">{post.kicker}</p> : null} + <h3> + <SlotLink href={post.href}>{post.title}</SlotLink> + </h3> + {post.text ? <p className="blog-excerpt">{post.text}</p> : null} + {post.href ? ( + <SlotLink href={post.href} className="blog-more"> + ادامه مطلب + </SlotLink> + ) : null} + </div> + </article> + ))} + </div> + </div> + </div> + ); +} diff --git a/src/components/CallbackForm.tsx b/src/components/CallbackForm.tsx new file mode 100644 index 0000000..904b08d --- /dev/null +++ b/src/components/CallbackForm.tsx @@ -0,0 +1,69 @@ +"use client"; + +import { FormEvent, useState } from "react"; + +export default function CallbackForm() { + const [status, setStatus] = useState<"idle" | "sending" | "ok" | "error">("idle"); + const [message, setMessage] = useState(""); + + async function onSubmit(event: FormEvent<HTMLFormElement>) { + event.preventDefault(); + const form = event.currentTarget; + const data = new FormData(form); + const name = String(data.get("name") || "").trim(); + const cellNumber = String(data.get("cellNumber") || "").trim(); + setStatus("sending"); + setMessage(""); + + try { + const response = await fetch("/api/contact", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + name, + title: "درخواست تماس", + cellNumber, + text: `درخواست تماس از صفحه اصلی. شماره: ${cellNumber}`, + }), + }); + + if (!response.ok) { + setStatus("error"); + setMessage("ثبت شماره انجام نشد. لطفاً دوباره تلاش کنید."); + return; + } + + setStatus("ok"); + setMessage("شماره شما ثبت شد. به‌زودی تماس می‌گیریم."); + form.reset(); + } catch { + setStatus("error"); + setMessage("ارتباط با سرور برقرار نشد."); + } + } + + return ( + <form className="callback-form" onSubmit={onSubmit}> + <label className="sr-only" htmlFor="callback-name"> + نام + </label> + <input id="callback-name" name="name" type="text" required placeholder="نام" autoComplete="name" /> + <label className="sr-only" htmlFor="callback-phone"> + شماره تماس + </label> + <input + id="callback-phone" + name="cellNumber" + type="tel" + required + inputMode="tel" + placeholder="شماره تماس" + autoComplete="tel" + /> + <button type="submit" className="btn btn-gold" disabled={status === "sending"}> + {status === "sending" ? "در حال ارسال…" : "ثبت شماره"} + </button> + {message ? <p className={status === "ok" ? "ok" : "err"}>{message}</p> : null} + </form> + ); +} diff --git a/src/components/CatalogCard.tsx b/src/components/CatalogCard.tsx new file mode 100644 index 0000000..44cc915 --- /dev/null +++ b/src/components/CatalogCard.tsx @@ -0,0 +1,21 @@ +import Link from "next/link"; + +import SlotImage from "@/components/SlotImage"; +import { type CatalogProduct, productHref, productImage, productTitle } from "@/lib/products"; + +export default function CatalogCard({ product }: { product: CatalogProduct }) { + const title = productTitle(product); + return ( + <article className="catalog-card"> + <Link href={productHref(product)} className="catalog-card-media"> + <SlotImage src={productImage(product)} alt={title} fill /> + </Link> + <div className="catalog-card-body"> + <h3> + <Link href={productHref(product)}>{title}</Link> + </h3> + {product.title && product.nameFa ? <p className="en">{product.title}</p> : null} + </div> + </article> + ); +} diff --git a/src/components/CommentBox.tsx b/src/components/CommentBox.tsx new file mode 100644 index 0000000..4af2595 --- /dev/null +++ b/src/components/CommentBox.tsx @@ -0,0 +1,103 @@ +"use client"; + +import { FormEvent, useState } from "react"; + +import { customerPortalUrl } from "@/lib/config"; +import { formatFaDate } from "@/lib/format"; +import type { CommentItem } from "@/lib/comments"; +import { useSession, userDisplayName } from "@/lib/session"; + +export default function CommentBox({ + entityType, + entityId, + comments, +}: { + entityType: "product" | "blog"; + entityId: string; + comments: CommentItem[]; +}) { + const { user, isAuthenticated, ready } = useSession(); + const [status, setStatus] = useState<"idle" | "sending" | "ok" | "error">("idle"); + const [message, setMessage] = useState(""); + + async function onSubmit(event: FormEvent<HTMLFormElement>) { + event.preventDefault(); + if (!user) return; + const form = event.currentTarget; + const text = String(new FormData(form).get("text") || "").trim(); + if (!text) return; + + setStatus("sending"); + setMessage(""); + try { + const response = await fetch("/api/comments", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + entityType, + entityId, + authorName: userDisplayName(user), + authorEmail: user.email, + text, + }), + }); + if (!response.ok) { + setStatus("error"); + setMessage("ارسال نظر انجام نشد. لطفاً دوباره تلاش کنید."); + return; + } + setStatus("ok"); + setMessage("نظر شما ثبت شد و پس از تأیید نمایش داده می‌شود."); + form.reset(); + } catch { + setStatus("error"); + setMessage("ارتباط با سرور برقرار نشد."); + } + } + + return ( + <div className="comment-box"> + {comments.length === 0 ? ( + <p className="comment-empty">هنوز نظری ثبت نشده است.</p> + ) : ( + <div className="comment-list"> + {comments.map((comment, index) => ( + <article key={comment.id ?? index} className="comment-item"> + <strong>{comment.authorName || "کاربر"}</strong> + {comment.createdAt ? <time>{formatFaDate(comment.createdAt)}</time> : null} + <p>{comment.text}</p> + </article> + ))} + </div> + )} + + {ready && !isAuthenticated ? ( + <div className="comment-login"> + <p>برای ثبت نظر وارد حساب کاربری شوید.</p> + <a className="btn btn-gold" href={customerPortalUrl}> + ورود + </a> + </div> + ) : null} + + {ready && isAuthenticated ? ( + <form className="comment-form" onSubmit={onSubmit}> + <label className="sr-only" htmlFor={`comment-${entityType}-${entityId}`}> + نظر شما + </label> + <textarea + id={`comment-${entityType}-${entityId}`} + name="text" + rows={4} + required + placeholder="نظر خود را بنویسید…" + /> + <button type="submit" className="btn btn-gold" disabled={status === "sending"}> + {status === "sending" ? "در حال ارسال…" : "ثبت نظر"} + </button> + {message ? <p className={status === "ok" ? "ok" : "err"}>{message}</p> : null} + </form> + ) : null} + </div> + ); +} diff --git a/src/components/ContactBar.tsx b/src/components/ContactBar.tsx new file mode 100644 index 0000000..ff6048a --- /dev/null +++ b/src/components/ContactBar.tsx @@ -0,0 +1,112 @@ +import type { ReactNode } from "react"; + +import CallbackForm from "@/components/CallbackForm"; +import { site, socials } from "@/data/site"; + +function InstagramIcon() { + return ( + <svg width="16" height="16" viewBox="0 0 24 24" fill="none" aria-hidden> + <rect x="3.5" y="3.5" width="17" height="17" rx="5" stroke="currentColor" strokeWidth="1.7" /> + <circle cx="12" cy="12" r="4" stroke="currentColor" strokeWidth="1.7" /> + <circle cx="17.4" cy="6.6" r="1" fill="currentColor" /> + </svg> + ); +} + +function TelegramIcon() { + return ( + <svg width="16" height="16" viewBox="0 0 24 24" fill="none" aria-hidden> + <path + d="M20.5 4.5 3.8 11.2c-1.1.4-1.1 1.1-.2 1.4l4.3 1.3 1.7 5.2c.2.7.1.9.8.9.4 0 .6-.2.8-.4l2.4-2.3 4.5 3.3c.8.5 1.4.2 1.6-.8L21.7 5.7c.3-1.1-.4-1.6-1.2-1.2Z" + stroke="currentColor" + strokeWidth="1.4" + strokeLinejoin="round" + /> + </svg> + ); +} + +function LinkedinIcon() { + return ( + <svg width="16" height="16" viewBox="0 0 24 24" fill="none" aria-hidden> + <rect x="3.5" y="3.5" width="17" height="17" rx="2.5" stroke="currentColor" strokeWidth="1.7" /> + <path d="M8 10.5V16.5" stroke="currentColor" strokeWidth="1.7" strokeLinecap="round" /> + <circle cx="8" cy="8" r="0.9" fill="currentColor" /> + <path + d="M12 16.5v-3.6c0-1.2.8-2 1.9-2s1.9.8 1.9 2v3.6" + stroke="currentColor" + strokeWidth="1.7" + strokeLinecap="round" + /> + </svg> + ); +} + +function EmailIcon() { + return ( + <svg width="16" height="16" viewBox="0 0 24 24" fill="none" aria-hidden> + <rect x="3.5" y="5.5" width="17" height="13" rx="2" stroke="currentColor" strokeWidth="1.7" /> + <path d="M4.5 7.5 12 13l7.5-5.5" stroke="currentColor" strokeWidth="1.7" strokeLinejoin="round" /> + </svg> + ); +} + +function PhoneIcon() { + return ( + <svg width="18" height="18" viewBox="0 0 24 24" fill="none" aria-hidden> + <path + d="M7.2 3.8h2.6l1.2 3-1.6 1.1a12.5 12.5 0 0 0 5.7 5.7l1.1-1.6 3 1.2v2.6c0 .7-.6 1.4-1.3 1.5-2 .3-6.3-.2-10.1-4S5.4 6.9 5.7 4.9c.1-.7.8-1.1 1.5-1.1Z" + stroke="currentColor" + strokeWidth="1.6" + strokeLinejoin="round" + /> + </svg> + ); +} + +const icons: Record<string, () => ReactNode> = { + instagram: InstagramIcon, + telegram: TelegramIcon, + linkedin: LinkedinIcon, + email: EmailIcon, +}; + +export default function ContactBar() { + return ( + <section className="contact-bar" id="contact" aria-label="تماس با ما"> + <div className="container contact-bar-frame"> + <div className="contact-bar-inner"> + <div className="contact-bar-phone"> + <span className="contact-bar-kicker">تماس با ما</span> + <a href={`tel:${site.phone.replace(/-/g, "")}`} className="contact-bar-number"> + <span dir="ltr">{site.phone}</span> + <PhoneIcon /> + </a> + </div> + <div className="contact-socials"> + {socials.map((item) => { + const Icon = icons[item.id]; + return ( + <a + key={item.id} + href={item.href} + className="contact-social" + target={item.href.startsWith("mailto:") ? undefined : "_blank"} + rel="noopener noreferrer" + aria-label={item.label} + title={item.label} + > + {Icon ? <Icon /> : item.labelEn} + </a> + ); + })} + </div> + </div> + <div className="contact-bar-subscribe"> + <p className="contact-bar-subscribe-text">شماره تماس خود را بگذارید، ما با شما تماس میگیریم.</p> + <CallbackForm /> + </div> + </div> + </section> + ); +} diff --git a/src/components/ContactForm.tsx b/src/components/ContactForm.tsx new file mode 100644 index 0000000..135d02c --- /dev/null +++ b/src/components/ContactForm.tsx @@ -0,0 +1,76 @@ +"use client"; + +import { FormEvent, useState } from "react"; + +export default function ContactForm() { + const [status, setStatus] = useState<"idle" | "sending" | "ok" | "error">("idle"); + const [message, setMessage] = useState(""); + + async function onSubmit(event: FormEvent<HTMLFormElement>) { + event.preventDefault(); + const form = event.currentTarget; + const data = new FormData(form); + setStatus("sending"); + setMessage(""); + + try { + const response = await fetch("/api/contact", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + name: String(data.get("name") || ""), + title: String(data.get("title") || ""), + email: String(data.get("email") || ""), + cellNumber: String(data.get("cellNumber") || ""), + text: String(data.get("text") || ""), + }), + }); + + if (!response.ok) { + setStatus("error"); + setMessage("ارسال پیام انجام نشد. لطفاً دوباره تلاش کنید."); + return; + } + + setStatus("ok"); + setMessage("پیام شما ثبت شد. به‌زودی با شما تماس می‌گیریم."); + form.reset(); + } catch { + setStatus("error"); + setMessage("ارتباط با سرور برقرار نشد."); + } + } + + return ( + <form className="contact-form" onSubmit={onSubmit}> + <h2>پیام خود را ارسال کنید</h2> + <p className="en" style={{ margin: 0, color: "var(--gold)" }}> + Send a message + </p> + <label> + نام + <input name="name" type="text" required /> + </label> + <label> + موضوع + <input name="title" type="text" required /> + </label> + <label> + ایمیل + <input name="email" type="email" /> + </label> + <label> + شماره تماس + <input name="cellNumber" type="tel" /> + </label> + <label> + متن پیام + <textarea name="text" required /> + </label> + <button type="submit" className="btn btn-gold" disabled={status === "sending"}> + {status === "sending" ? "در حال ارسال…" : "ارسال پیام"} + </button> + {message ? <p className={status === "ok" ? "ok" : "err"}>{message}</p> : null} + </form> + ); +} diff --git a/src/components/Footer.tsx b/src/components/Footer.tsx new file mode 100644 index 0000000..8a966a1 --- /dev/null +++ b/src/components/Footer.tsx @@ -0,0 +1,76 @@ +import Image from "next/image"; +import Link from "next/link"; + +import { navLinks, productsNav, site } from "@/data/site"; + +export default function Footer() { + return ( + <footer className="site-footer"> + <div className="container footer-grid"> + <div> + <h3>{site.shortName}</h3> + <p>{site.description}</p> + </div> + <div> + <h3>دسترسی سریع</h3> + <ul> + <li> + <Link href="/">{navLinks[0].label}</Link> + </li> + <li> + <Link href={productsNav.href}>{productsNav.label}</Link> + </li> + {navLinks.slice(1).map((link) => ( + <li key={link.href}> + <Link href={link.href}>{link.label}</Link> + </li> + ))} + </ul> + </div> + <div> + <h3>تماس با ما</h3> + <ul> + <li>{site.address}</li> + <li> + <a href={`tel:${site.phone.replace(/-/g, "")}`}>{site.phoneFa}</a> + </li> + <li>{site.factory}</li> + </ul> + </div> + </div> + + <div className="footer-bottom"> + <div className="container"> + <div className="footer-bottom-bar"> + <div className="footer-bottom-site">{site.name}</div> + <a + href="https://meshkee.com" + target="_blank" + rel="noopener noreferrer" + dir="ltr" + className="meshkee-credit" + > + <span className="meshkee-credit__frame" aria-hidden> + <span className="meshkee-credit__line meshkee-credit__line--l" /> + <span className="meshkee-credit__line meshkee-credit__line--t" /> + <span className="meshkee-credit__line meshkee-credit__line--r" /> + <span className="meshkee-credit__line meshkee-credit__line--b" /> + </span> + <Image + src="/images/footer/MeshkeeLogo-White.png" + alt="Meshkee" + className="meshkee-credit__logo" + width={42} + height={42} + /> + <span className="meshkee-credit__text"> + <span className="meshkee-credit__label">Designed And Developed By</span> + <span className="meshkee-credit__name">Meshkee E-Commerce Team</span> + </span> + </a> + </div> + </div> + </div> + </footer> + ); +} diff --git a/src/components/Header.tsx b/src/components/Header.tsx new file mode 100644 index 0000000..4297594 --- /dev/null +++ b/src/components/Header.tsx @@ -0,0 +1,259 @@ +"use client"; + +import Link from "next/link"; +import { usePathname, useRouter } from "next/navigation"; +import { FormEvent, useEffect, useMemo, useRef, useState } from "react"; + +import AccountMenu from "@/components/AccountMenu"; +import { blogPosts, navLinks, products, productsNav, site } from "@/data/site"; + +type Locale = "fa" | "en"; + +function SearchIcon() { + return ( + <svg width="15" height="15" viewBox="0 0 24 24" fill="none" aria-hidden> + <circle cx="11" cy="11" r="6.25" stroke="currentColor" strokeWidth="1.75" /> + <path d="M16 16.5 20 20.5" stroke="currentColor" strokeWidth="1.75" strokeLinecap="round" /> + </svg> + ); +} + +function ChevronIcon() { + return ( + <svg className="nav-caret" width="10" height="10" viewBox="0 0 12 12" fill="none" aria-hidden> + <path d="M2.5 4.5 6 8l3.5-3.5" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" /> + </svg> + ); +} + +export default function Header() { + const pathname = usePathname(); + const router = useRouter(); + const [open, setOpen] = useState(false); + const [locale, setLocale] = useState<Locale>("fa"); + const [query, setQuery] = useState(""); + const [searchOpen, setSearchOpen] = useState(false); + const [productsOpen, setProductsOpen] = useState(false); + const [hash, setHash] = useState(""); + const searchRef = useRef<HTMLDivElement>(null); + const inputRef = useRef<HTMLInputElement>(null); + + useEffect(() => { + const saved = window.localStorage.getItem("kaman-locale"); + if (saved === "en" || saved === "fa") setLocale(saved); + }, []); + + useEffect(() => { + document.documentElement.lang = locale; + document.documentElement.dir = locale === "fa" ? "rtl" : "ltr"; + window.localStorage.setItem("kaman-locale", locale); + }, [locale]); + + useEffect(() => { + function onPointer(event: MouseEvent) { + if (!searchRef.current?.contains(event.target as Node)) setSearchOpen(false); + } + function onKey(event: KeyboardEvent) { + if (event.key === "Escape") setSearchOpen(false); + } + document.addEventListener("mousedown", onPointer); + document.addEventListener("keydown", onKey); + return () => { + document.removeEventListener("mousedown", onPointer); + document.removeEventListener("keydown", onKey); + }; + }, []); + + useEffect(() => { + if (searchOpen) inputRef.current?.focus(); + }, [searchOpen]); + + useEffect(() => { + function syncHash() { + setHash(window.location.hash); + } + syncHash(); + window.addEventListener("hashchange", syncHash); + return () => window.removeEventListener("hashchange", syncHash); + }, [pathname]); + + const results = useMemo(() => { + const q = query.trim().toLowerCase(); + if (q.length < 2) return []; + const pages = [ + ...navLinks.map((link) => ({ + href: link.href, + title: locale === "en" ? link.labelEn : link.label, + })), + { + href: "/products", + title: locale === "en" ? productsNav.labelEn : productsNav.label, + }, + { + href: "/blog", + title: locale === "en" ? "Blog" : "وبلاگ", + }, + ]; + const catalog = products.map((product) => ({ + href: `/products`, + title: locale === "en" ? product.titleEn : product.title, + })); + const articles = blogPosts.map((post) => ({ + href: `/blog`, + title: locale === "en" ? post.titleEn : post.title, + })); + return [...pages, ...catalog, ...articles].filter((item) => item.title.toLowerCase().includes(q)).slice(0, 8); + }, [locale, query]); + + function onSearch(event: FormEvent) { + event.preventDefault(); + const first = results[0]; + if (!first) return; + if (/^https?:\/\//i.test(first.href)) { + window.open(first.href, "_blank", "noopener,noreferrer"); + } else { + router.push(first.href); + } + setSearchOpen(false); + setQuery(""); + } + + return ( + <header className="site-header"> + <div className="container header-inner"> + <div className="header-start"> + <Link href="/" className="brand" onClick={() => setOpen(false)}> + {/* eslint-disable-next-line @next/next/no-img-element */} + <img src={site.logo} alt={site.shortName} width={32} height={32} /> + <span className="brand-text"> + <strong>{locale === "en" ? site.nameEnShort : site.shortName}</strong> + <span className="en">{site.nameEnShort}</span> + </span> + </Link> + + <button + type="button" + className="menu-toggle" + aria-expanded={open} + aria-label={locale === "en" ? "Menu" : "منو"} + onClick={() => setOpen((value) => !value)} + > + {locale === "en" ? "Menu" : "منو"} + </button> + + <nav className={open ? "nav open" : "nav"}> + {navLinks.slice(0, 1).map((link) => ( + <Link + key={link.href} + href={link.href} + className={pathname === "/" && !hash.startsWith("#product") && hash !== "#products" ? "active" : undefined} + onClick={() => setOpen(false)} + > + {locale === "en" ? link.labelEn : link.label} + </Link> + ))} + + <div + className={productsOpen ? "nav-dropdown open" : "nav-dropdown"} + onMouseEnter={() => setProductsOpen(true)} + onMouseLeave={() => setProductsOpen(false)} + > + <Link + href={productsNav.href} + className={pathname.startsWith("/products") || hash === "#products" || hash.startsWith("#product-") ? "active" : undefined} + onClick={() => setOpen(false)} + > + {locale === "en" ? productsNav.labelEn : productsNav.label} + <ChevronIcon /> + </Link> + <ul className="nav-sub"> + {products.map((product) => ( + <li key={product.id}> + <Link href={`/#product-${product.id}`} onClick={() => setOpen(false)}> + {locale === "en" ? product.titleEn : product.title} + </Link> + </li> + ))} + </ul> + </div> + + {navLinks.slice(1).map((link) => ( + <Link + key={link.href} + href={link.href} + className={pathname === link.href ? "active" : undefined} + onClick={() => setOpen(false)} + > + {locale === "en" ? link.labelEn : link.label} + </Link> + ))} + </nav> + </div> + + <div className="header-tools"> + <div className={searchOpen ? "header-search open" : "header-search"} ref={searchRef}> + <form onSubmit={onSearch}> + <label className="sr-only" htmlFor="site-search"> + {locale === "en" ? "Search" : "جستجو"} + </label> + <input + id="site-search" + ref={inputRef} + value={query} + tabIndex={searchOpen ? 0 : -1} + onChange={(event) => { + setQuery(event.target.value); + setSearchOpen(true); + }} + onFocus={() => setSearchOpen(true)} + placeholder={locale === "en" ? "Search" : "جستجو"} + /> + <button + type={searchOpen && query.trim() ? "submit" : "button"} + className="header-tool" + aria-expanded={searchOpen} + aria-label={locale === "en" ? "Search" : "جستجو"} + onClick={() => { + if (!searchOpen) setSearchOpen(true); + }} + > + <SearchIcon /> + </button> + </form> + {searchOpen && results.length > 0 ? ( + <ul className="header-search-results"> + {results.map((item) => ( + <li key={`${item.href}-${item.title}`}> + <Link + href={item.href} + onClick={() => { + setSearchOpen(false); + setQuery(""); + }} + > + {item.title} + </Link> + </li> + ))} + </ul> + ) : null} + </div> + + <label className="header-lang"> + <span className="sr-only">{locale === "en" ? "Language" : "زبان"}</span> + <select + value={locale} + onChange={(event) => setLocale(event.target.value as Locale)} + aria-label={locale === "en" ? "Language" : "انتخاب زبان"} + > + <option value="fa">FA</option> + <option value="en">EN</option> + </select> + </label> + + <AccountMenu /> + </div> + </div> + </header> + ); +} diff --git a/src/components/HeroSlider.tsx b/src/components/HeroSlider.tsx new file mode 100644 index 0000000..45035d2 --- /dev/null +++ b/src/components/HeroSlider.tsx @@ -0,0 +1,89 @@ +"use client"; + +import { useEffect, useState } from "react"; + +import SlotImage from "@/components/SlotImage"; +import SlotLink from "@/components/SlotLink"; +import { heroHighlights } from "@/data/site"; +import { slotRatioStyle, type SlotMediaItem } from "@/lib/static-images"; + +function RevealLine({ + text, + tone = "white", +}: { + text: string; + tone?: "gold" | "white"; +}) { + return ( + <span className={`hero-reveal hero-reveal--${tone}`}> + <span className="hero-reveal__text">{text}</span> + <span className="hero-reveal__mask" aria-hidden /> + </span> + ); +} + +export default function HeroSlider({ + slides, + aspectRatio, +}: { + slides: SlotMediaItem[]; + aspectRatio?: string; +}) { + const [index, setIndex] = useState(0); + const current = slides[index] ?? slides[0]; + const tabs = heroHighlights.slice(0, Math.max(slides.length, heroHighlights.length)); + + useEffect(() => { + if (slides.length < 2) return undefined; + const timer = window.setInterval(() => { + setIndex((value) => (value + 1) % slides.length); + }, 6000); + return () => window.clearInterval(timer); + }, [index, slides.length]); + + if (!current) return null; + + return ( + <section className="hero" style={slotRatioStyle(aspectRatio)}> + {slides.map((slide, slideIndex) => ( + <div key={slide.id} className={slideIndex === index ? "hero-slide active" : "hero-slide"}> + <SlotLink href={slide.href}> + <SlotImage src={slide.image} alt={slide.title || "اسلایدر صنایع کابل کمان"} fill priority={slideIndex === 0} /> + </SlotLink> + </div> + ))} + <div className="hero-overlay" /> + <div className="container hero-copy"> + {current.kicker ? ( + <p className="en"> + <RevealLine key={`kicker-${current.id}-${index}`} text={current.kicker} tone="gold" /> + </p> + ) : null} + <h2 className="hero-title"> + <RevealLine key={`title-${current.id}-${index}`} text={current.title || ""} tone="white" /> + </h2> + </div> + <div className="container"> + <div className="highlights" role="tablist" aria-label="اسلایدر"> + {tabs.map((item, tabIndex) => { + const active = tabIndex === index; + return ( + <button + key={item.n} + type="button" + role="tab" + aria-selected={active} + className={active ? "highlight active" : "highlight"} + onClick={() => setIndex(tabIndex % slides.length)} + > + <div className="n">{item.n}</div> + <h3>{item.title}</h3> + <p className="en">{item.titleEn}</p> + </button> + ); + })} + </div> + </div> + </section> + ); +} diff --git a/src/components/ProductGallery.tsx b/src/components/ProductGallery.tsx new file mode 100644 index 0000000..ec4859f --- /dev/null +++ b/src/components/ProductGallery.tsx @@ -0,0 +1,129 @@ +"use client"; + +import { useCallback, useEffect, useState, type MouseEvent } from "react"; + +type Props = { + images: string[]; + alt: string; +}; + +export default function ProductGallery({ images, alt }: Props) { + const [active, setActive] = useState(0); + const [lightbox, setLightbox] = useState(false); + const [origin, setOrigin] = useState("50% 50%"); + const list = images.length ? images : []; + + const go = useCallback( + (delta: number) => { + if (list.length === 0) return; + setActive((value) => (value + delta + list.length) % list.length); + }, + [list.length], + ); + + useEffect(() => { + if (!lightbox) return; + const onKey = (event: KeyboardEvent) => { + if (event.key === "Escape") setLightbox(false); + if (event.key === "ArrowLeft") go(1); + if (event.key === "ArrowRight") go(-1); + }; + document.body.style.overflow = "hidden"; + window.addEventListener("keydown", onKey); + return () => { + document.body.style.overflow = ""; + window.removeEventListener("keydown", onKey); + }; + }, [go, lightbox]); + + function updateOrigin(event: MouseEvent<HTMLButtonElement>) { + const bounds = event.currentTarget.getBoundingClientRect(); + const x = ((event.clientX - bounds.left) / bounds.width) * 100; + const y = ((event.clientY - bounds.top) / bounds.height) * 100; + setOrigin(`${x}% ${y}%`); + } + + if (list.length === 0) { + return <div className="product-gallery-empty">بدون تصویر</div>; + } + + return ( + <div className="product-gallery"> + <button + type="button" + className="product-gallery-main" + onClick={() => setLightbox(true)} + onMouseMove={updateOrigin} + onMouseLeave={() => setOrigin("50% 50%")} + aria-label="بزرگ‌نمایی تصویر" + > + {/* eslint-disable-next-line @next/next/no-img-element */} + <img src={list[active]} alt={alt} style={{ transformOrigin: origin }} /> + </button> + + {list.length > 1 ? ( + <div className="product-gallery-thumbs"> + {list.map((url, index) => ( + <button + key={`${url}-${index}`} + type="button" + className={index === active ? "active" : undefined} + onClick={() => setActive(index)} + aria-label={`تصویر ${index + 1}`} + > + {/* eslint-disable-next-line @next/next/no-img-element */} + <img src={url} alt={`${alt} — تصویر ${index + 1}`} /> + </button> + ))} + </div> + ) : null} + + {lightbox ? ( + <div + className="product-lightbox" + role="dialog" + aria-modal="true" + aria-label="گالری تصاویر" + onClick={() => setLightbox(false)} + > + <button type="button" className="product-lightbox-close" aria-label="بستن"> + × + </button> + {list.length > 1 ? ( + <> + <button + type="button" + className="product-lightbox-nav next" + aria-label="قبلی" + onClick={(event) => { + event.stopPropagation(); + go(-1); + }} + > + › + </button> + <button + type="button" + className="product-lightbox-nav prev" + aria-label="بعدی" + onClick={(event) => { + event.stopPropagation(); + go(1); + }} + > + ‹ + </button> + </> + ) : null} + {/* eslint-disable-next-line @next/next/no-img-element */} + <img src={list[active]} alt={alt} onClick={(event) => event.stopPropagation()} /> + {list.length > 1 ? ( + <p> + {active + 1} / {list.length} + </p> + ) : null} + </div> + ) : null} + </div> + ); +} diff --git a/src/components/ProductTabs.tsx b/src/components/ProductTabs.tsx new file mode 100644 index 0000000..67233cf --- /dev/null +++ b/src/components/ProductTabs.tsx @@ -0,0 +1,74 @@ +"use client"; + +import { useState } from "react"; + +import CommentBox from "@/components/CommentBox"; +import type { CommentItem } from "@/lib/comments"; +import type { TechnicalRow } from "@/lib/technical"; + +type TabId = "description" | "technical" | "comments"; + +export default function ProductTabs({ + descriptionHtml, + descriptionText, + technicalRows, + comments, + entityId, +}: { + descriptionHtml?: string; + descriptionText?: string; + technicalRows: TechnicalRow[]; + comments: CommentItem[]; + entityId: string; +}) { + const [tab, setTab] = useState<TabId>("description"); + + return ( + <section className="detail-tabs"> + <div className="detail-tabs-nav" role="tablist" aria-label="جزئیات محصول"> + <button type="button" className={tab === "description" ? "active" : undefined} onClick={() => setTab("description")}> + توضیحات + </button> + <button type="button" className={tab === "technical" ? "active" : undefined} onClick={() => setTab("technical")}> + مشخصات فنی + </button> + <button type="button" className={tab === "comments" ? "active" : undefined} onClick={() => setTab("comments")}> + نظرات + </button> + </div> + + <div className="detail-tabs-body"> + {tab === "description" ? ( + descriptionHtml ? ( + <div className="rich-text" dangerouslySetInnerHTML={{ __html: descriptionHtml }} /> + ) : descriptionText ? ( + <p>{descriptionText}</p> + ) : ( + <p className="comment-empty">توضیحاتی ثبت نشده است.</p> + ) + ) : null} + + {tab === "technical" ? ( + technicalRows.length > 0 ? ( + <table className="tech-table"> + <tbody> + {technicalRows.map((row) => ( + <tr key={row.id}> + <th>{row.label}</th> + <td>{row.value}</td> + </tr> + ))} + </tbody> + </table> + ) : ( + <p className="comment-empty">مشخصات فنی برای این محصول ثبت نشده است.</p> + ) + ) : null} + + {tab === "comments" ? ( + <CommentBox entityType="product" entityId={entityId} comments={comments} /> + ) : null} + </div> + </section> + ); +} diff --git a/src/components/Providers.tsx b/src/components/Providers.tsx new file mode 100644 index 0000000..4735e7c --- /dev/null +++ b/src/components/Providers.tsx @@ -0,0 +1,14 @@ +"use client"; + +import { SessionProvider } from "@/lib/session"; +import type { ReactNode } from "react"; + +export default function Providers({ + businessId, + children, +}: { + businessId: string; + children: ReactNode; +}) { + return <SessionProvider businessId={businessId}>{children}</SessionProvider>; +} diff --git a/src/components/Reveal.tsx b/src/components/Reveal.tsx new file mode 100644 index 0000000..3787211 --- /dev/null +++ b/src/components/Reveal.tsx @@ -0,0 +1,67 @@ +"use client"; + +import { type CSSProperties, type ReactNode, useEffect, useRef, useState } from "react"; + +type Props = { + children: ReactNode; + className?: string; + delay?: number; + stagger?: boolean; + variant?: "up" | "left" | "right"; + style?: CSSProperties; +}; + +export default function Reveal({ + children, + className = "", + delay = 0, + stagger = false, + variant = "up", + style, +}: Props) { + const ref = useRef<HTMLDivElement>(null); + const [shown, setShown] = useState(false); + + useEffect(() => { + const el = ref.current; + if (!el) return undefined; + + const reveal = () => setShown(true); + if (window.matchMedia("(prefers-reduced-motion: reduce)").matches) { + reveal(); + return undefined; + } + + const observer = new IntersectionObserver( + ([entry]) => { + if (entry.isIntersecting) { + reveal(); + observer.disconnect(); + } + }, + { threshold: 0.16, rootMargin: "0px 0px -10% 0px" }, + ); + observer.observe(el); + return () => observer.disconnect(); + }, []); + + const classes = [ + "reveal", + `reveal--${variant}`, + stagger ? "stagger" : "", + shown ? "is-in" : "", + className, + ] + .filter(Boolean) + .join(" "); + + return ( + <div + ref={ref} + className={classes} + style={{ "--reveal-delay": `${delay}ms`, ...style } as CSSProperties} + > + {children} + </div> + ); +} diff --git a/src/components/SectionTitle.tsx b/src/components/SectionTitle.tsx new file mode 100644 index 0000000..d33b690 --- /dev/null +++ b/src/components/SectionTitle.tsx @@ -0,0 +1,23 @@ +type Props = { + index?: string; + en: string; + title: string; + text?: string; +}; + +export default function SectionTitle({ index, en, title, text }: Props) { + return ( + <header className="section-head"> + <span className="section-head-bg" aria-hidden> + {en} + </span> + <div className="section-head-meta"> + {index ? <span className="section-head-index">{index}</span> : null} + <span className="section-head-rule" aria-hidden /> + <p className="section-head-en">{en}</p> + </div> + <h2>{title}</h2> + {text ? <p className="section-head-text">{text}</p> : null} + </header> + ); +} diff --git a/src/components/SlotImage.tsx b/src/components/SlotImage.tsx new file mode 100644 index 0000000..34ec152 --- /dev/null +++ b/src/components/SlotImage.tsx @@ -0,0 +1,54 @@ +import type { CSSProperties } from "react"; + +type Props = { + src: string; + alt: string; + width?: number; + height?: number; + fill?: boolean; + className?: string; + sizes?: string; + priority?: boolean; + style?: CSSProperties; + fallbackSrc?: string; +}; + +export default function SlotImage({ + src, + alt, + width, + height, + fill, + className, + priority, + style, + fallbackSrc, +}: Props) { + const resolved = src || fallbackSrc || ""; + if (!resolved) return null; + + if (fill) { + return ( + // eslint-disable-next-line @next/next/no-img-element + <img + src={resolved} + alt={alt} + className={className ? `${className} slot-fill` : "slot-fill"} + style={{ position: "absolute", inset: 0, width: "100%", height: "100%", objectFit: "cover", ...style }} + /> + ); + } + + return ( + // eslint-disable-next-line @next/next/no-img-element + <img + src={resolved} + alt={alt} + width={width} + height={height} + className={className} + style={style} + fetchPriority={priority ? "high" : undefined} + /> + ); +} diff --git a/src/components/SlotLink.tsx b/src/components/SlotLink.tsx new file mode 100644 index 0000000..e92f116 --- /dev/null +++ b/src/components/SlotLink.tsx @@ -0,0 +1,32 @@ +import Link from "next/link"; +import type { ReactNode } from "react"; + +function isExternal(href: string) { + return /^https?:\/\//i.test(href) || href.startsWith("//"); +} + +export default function SlotLink({ + href, + className, + children, +}: { + href?: string; + className?: string; + children: ReactNode; +}) { + if (!href) return <>{children}</>; + + if (isExternal(href)) { + return ( + <a href={href} className={className} target="_blank" rel="noopener noreferrer"> + {children} + </a> + ); + } + + return ( + <Link href={href} className={className}> + {children} + </Link> + ); +} diff --git a/src/data/site.ts b/src/data/site.ts new file mode 100644 index 0000000..134e414 --- /dev/null +++ b/src/data/site.ts @@ -0,0 +1,264 @@ +export const site = { + name: "صنایع سیم و کابل کمان", + shortName: "کابل کمان", + nameEn: "Kaman Cable & Wire Industries", + nameEnShort: "Kaman Cable", + tagline: "تولید سیم و کابل فشار ضعیف، مخابراتی، کنترل و ابزار دقیق", + description: + "شرکت صنایع کابل کمان از سال ۱۳۷۰ در شهر صنعتی زنجان به تولید انواع سیم و کابل فشار ضعیف برق و مخابراتی، کابل‌های کنترل و ابزار دقیق مطابق با استانداردهای ملی و بین‌المللی می‌پردازد.", + founded: "۱۳۷۰", + capacity: "۱۵۰۰۰ تن", + factoryArea: "۴۰۰۰۰ متر مربع", + factory: "شهر صنعتی شماره یک، استان زنجان", + address: + "تهران، خیابان کریم‌خان زند، خیابان سنایی، بالاتر از میدان سنایی، پلاک ۸۲، طبقه سوم، واحد ۶", + phone: "021-242624", + phoneFa: "۰۲۱-۲۴۲۶۲۴", + email: "info@kamancable.com", + appUrl: "https://kamancable.com/applanding/", + logo: "/images/logo/logo.png", +}; + +export const socials = [ + { + id: "instagram", + label: "اینستاگرام", + labelEn: "Instagram", + href: "https://www.instagram.com/kamancable/", + }, + { + id: "telegram", + label: "تلگرام", + labelEn: "Telegram", + href: "https://t.me/kamancable", + }, + { + id: "linkedin", + label: "لینکدین", + labelEn: "LinkedIn", + href: "https://www.linkedin.com/company/kaman-cable", + }, + { + id: "email", + label: "ایمیل", + labelEn: "Email", + href: "mailto:info@kamancable.com", + }, +]; + +export const navLinks = [ + { href: "/", label: "صفحه اصلی", labelEn: "Home" }, + { href: "/blog", label: "وبلاگ", labelEn: "Blog" }, + { href: "/about", label: "درباره ما", labelEn: "About" }, + { href: "/contact", label: "ارتباط با ما", labelEn: "Contact" }, +]; + +export const productsNav = { + href: "/products", + label: "محصولات", + labelEn: "Products", +}; + +export const slides = [ + { + id: "slide-1", + image: "/images/slider/bg1.jpg", + kicker: "KAMAN CABLE & WIRE INDUSTRIES", + title: "kaman cable & wire", + titleFa: "صنایع سیم و کابل کمان", + tone: "light" as const, + }, + { + id: "slide-2", + image: "/images/slider/bg2.jpg", + kicker: "ENDORSED BY THE MINISTRY OF ENERGY AND OIL COMPANY", + title: "دارای تاییدیه از وزارت نیرو و شرکت نفت", + titleFa: "دارای تاییدیه از وزارت نیرو و شرکت نفت", + tone: "dark" as const, + }, + { + id: "slide-3", + image: "/images/slider/bg3.jpg", + kicker: "HIGH QUALITY AND UP-TO-DATE PRODUCTS", + title: "پیشرو در ارائه محصولات با کیفیت و بروز", + titleFa: "پیشرو در ارائه محصولات با کیفیت و بروز", + tone: "dark" as const, + }, +]; + +export const heroHighlights = [ + { n: "۱", title: "تنوع محصولات", titleEn: "Variety of products" }, + { n: "۲", title: "خط تولید قدرتمند", titleEn: "Powerful Production Line" }, + { n: "۳", title: "تولید کننده نمونه", titleEn: "Top Producer" }, +]; + +export const resumeText = + "شرکت صنایع کابل کمان در سال ۱۳۷۰ در زمینی به وسعت ۴۰۰۰۰ متر مربع در شهر صنعتی شماره یک واقع در استان زنجان شروع به فعالیت نمود. این واحد تولیدی پس از گذراندن مراحل نصب ماشین آلات و تامین زیر ساخت‌های مناسب و با بهره‌مندی از متخصصین و کارشناسان با تجربه در این رشته، در ابتدای سال ۱۳۷۳ موفق به اخذ پروانه بهره‌برداری با ظرفیت ۱۵۰۰۰ تن و دریافت گواهینامه استاندارد ملی (ISIRI) گردید. از آنجایی که کیفیت و رضایتمندی مشتری سرلوحه کلیه فعالیت‌ها است، لذا این واحد تولیدی با تکیه بر اصل مشتری‌مداری و با تلاش متخصصین خود توانست در سال‌های نخست گواهینامه ISO 9001:2015 و CE را اخذ نموده و در راستای نیاز روزافزون بازارهای داخلی و کشورهای همسایه به صورت گسترده محصولات خود را با کیفیت مرغوب و سازگار با اهداف مشتری عرضه نماید."; + +export const goalsText = [ + "صداقت بهترین روش است.", + "شرکت صنایع کابل کمان به‌عنوان یکی از تولیدکنندگان سیم و کابل، رضایت مشتریان را از طریق ارتقاء کیفیت و رعایت کلیه قوانین و الزامات حاکم بر محصول خود، به‌عنوان برترین هدف برگزیده است. در این راستا مدیریت ارشد، اعتقاد و تعهد خود را به اجرا، حفظ و بهبود مستمر اثربخشی سیستم مدیریت کیفیت بر اساس استاندارد ISO 9001:2015 اعلام می‌دارد.", +]; + +export const qualityPolicy = [ + "افزایش تنوع و حجم محصولات تولیدی", + "افزایش سطح رضایتمندی مشتریان", + "توسعه استانداردهای ملی و بین‌المللی در سازمان", +]; + +export const aboutIntro = + "شرکت کابل کمان یک شرکت موفق در زمینه تولید کابل و برق است که سابقه زیادی در این زمینه دارد. شرکت کابل کمان در حال حاضر از بهترین ماشین‌آلات شرکت‌های سازنده معتبر اروپایی استفاده می‌کند و به تولید انواع سیم و کابل فشار ضعیف برق و مخابراتی، کابل‌های کنترل و ابزار دقیق مشغول است. محصولات شرکت کابل کمان مطابق با استانداردهای ملی و بین‌المللی است و محصولاتی با بهترین کیفیت را ارائه می‌دهد و به صورت گسترده نیاز بازارهای داخلی و کشورهای همسایه را با بالاترین کیفیت و سازگار با اهداف مشتری عرضه می‌کند."; + +export const laboratoryText = + "طبق تاریخچه آزمایشگاه شرکت صنایع کابل کمان در سال ۱۳۹۱ موفق به دریافت گواهینامه آزمایشگاه همکار از موسسه استاندارد استان زنجان شد و به‌عنوان همکار آن موسسه محترم اقدام به فعالیت نمود و هم‌اکنون دارای گواهینامه تایید صلاحیت آزمایشگاه از طرف مرکز ملی تایید صلاحیت ایران (ISIRI-ISO/IEC 17025) می‌باشد. علاوه بر گواهینامه‌های فوق، تاییدیه از شرکت توانیر، شرکت‌های توزیع نیروی برق استان‌های مختلف و شرکت‌های نفت را نیز دارا می‌باشد. آزمایشگاه شرکت با داشتن تجهیزات پیشرفته و متخصصین با تجربه و توانمند جهت انجام انواع تست، آمادگی خدمت‌رسانی مطابق با نیاز واحدهای مختلف صنعت برق و مشتریان گرامی را دارد."; + +export const machineryText = + "این شرکت هم‌اکنون مجهز به انواع ماشین‌آلات از شرکت‌های سازنده معتبر اروپایی، در زمینه تولید انواع سیم و کابل فشار ضعیف برق و مخابراتی، کابل‌های کنترل و ابزار دقیق مطابق با انواع استانداردهای ملی (ISIRI 607, 3569-1, 1926, …) و بین‌المللی (IEC, BS, VDE, …) قادر به ارائه محصولات با کیفیت است."; + +export const products = [ + { + id: "power", + title: "کابل قدرت", + titleEn: "Power Cable", + text: "سیم‌های برق وظیفه انتقال جریان الکتریسیته را دارند و این انتقال توسط بخش‌های داخلی تشکیل‌دهنده انجام می‌گیرد.", + image: "/images/samples/cables-coils.jpg", + icon: "/images/products/icon-1.png", + }, + { + id: "flat", + title: "کابل‌های تخت", + titleEn: "Flat Cables", + text: "این نوع کابل‌ها جهت استفاده در بالابرها و نیز در اتصالات متحرک مناسب می‌باشند.", + image: "/images/samples/wire-spools.avif", + icon: "/images/products/icon-2.png", + }, + { + id: "phone", + title: "کابل‌های مخابراتی", + titleEn: "Phone Cables", + text: "برای اینکه رطوبت حالت اتصال و اکسیدگی بین سیم‌ها ایجاد نشود درون کابل‌های مخابراتی را از نوعی ژله پلاستیکی چسبناک پر می‌کنند.", + image: "/images/samples/cable-closeup.jpg", + icon: "/images/products/icon-3.png", + }, + { + id: "network", + title: "کابل‌های شبکه", + titleEn: "Network Cable", + text: "برای برقراری ارتباط در هر شبکه نیازمند کابل شبکه هستیم. کابل LAN ارتباط بین دو یا چند کامپیوتر، سرور، روتر و … را فراهم می‌کند.", + image: "/images/samples/copper-coils.avif", + icon: "/images/products/icon-4.png", + }, + { + id: "instrument", + title: "ابزار دقیق و کنترل", + titleEn: "Instrumentation & Control", + text: "ابزار دقیق در حقیقت زیرساخت یک سیستم کنترل و اتوماسیون را تشکیل می‌دهند و شامل انواع سنسور، انواع کنترلر و … می‌باشند.", + image: "/images/samples/factory-floor.avif", + icon: "/images/products/icon-5.png", + }, + { + id: "coaxial", + title: "کواکسیال", + titleEn: "Coaxial", + text: "کابل کواکسیال نوع خیلی رایج از کابل انتقال داده‌های محافظت‌شده می‌باشد که از دو رسانا ساخته شده است.", + image: "/images/samples/production-line.avif", + icon: "/images/products/icon-6.png", + }, + { + id: "fire", + title: "اعلام حریق", + titleEn: "Fire Alarm", + text: "سیستم‌های اعلام حریق وظیفه اطلاع‌رسانی در هنگام آتش‌سوزی را بر عهده دارند و می‌توان آن‌ها را به دو گروه تقسیم کرد.", + image: "/images/slider/bg2.jpg", + icon: "/images/products/icon-7.png", + }, + { + id: "smart", + title: "خانه هوشمند", + titleEn: "Smart House", + text: "تفاوت خانه هوشمند با خانه معمولی این است که همه وسایل خانه هوشمند به یکدیگر متصل هستند و با دستگاه مرکزی کنترل می‌شوند.", + image: "/images/slider/bg3.jpg", + icon: "/images/products/icon-4.png", + }, +]; + +export const blogPosts = [ + { + id: "hv-clearance", + title: "حریم سیم برق فشار قوی", + titleEn: "High-voltage clearance", + date: "۱۲ مرداد ۱۴۰۴", + text: "حریم سیم برق فشار قوی فاصله‌ای ایمن برای جلوگیری از برق‌گرفتگی و آسیب به ساختمان‌ها است و با افزایش ولتاژ، این فاصله بیشتر می‌شود.", + image: "/images/samples/cables-coils.jpg", + href: "https://kamancable.com/high-voltage-power-wire-privacy/", + }, + { + id: "lv-mv", + title: "کابل فشار ضعیف و متوسط", + titleEn: "Low and medium voltage cable", + date: "۲۸ تیر ۱۴۰۴", + text: "کابل فشار ضعیف تا ۱ کیلوولت برای ساختمان و کابل فشار متوسط تا ۳۶ کیلوولت برای صنعت به کار می‌رود؛ انتخاب درست به ولتاژ، مقطع و محل نصب بستگی دارد.", + image: "/images/samples/production-line.avif", + href: "https://kamancable.com/low-and-medium-voltage-cable/", + }, + { + id: "cable-parts", + title: "اجزای تشکیل‌دهنده کابل", + titleEn: "Cable components", + date: "۱۵ تیر ۱۴۰۴", + text: "هادی، عایق، شیلد و روکش بیرونی ساختار کابل را می‌سازند و هر لایه نقش مشخصی در انتقال جریان و حفاظت مکانیکی دارد.", + image: "/images/samples/cable-closeup.jpg", + href: "https://kamancable.com/cable-components/", + }, + { + id: "two-three-core", + title: "تفاوت کابل دو رشته با سه رشته", + titleEn: "Two-core vs three-core cables", + date: "۲ خرداد ۱۴۰۴", + text: "کابل دو رشته فاز و نول را منتقل می‌کند و کابل سه رشته با افزودن ارت، ایمنی مدار را در ساختمان و صنعت بالاتر می‌برد.", + image: "/images/samples/wire-spools.avif", + href: "https://kamancable.com/the-difference-between-two-core-and-three-core-cables/", + }, + { + id: "wire-vs-cable", + title: "سیم و کابل چیست و چه تفاوتی دارند؟", + titleEn: "Wire vs cable", + date: "۱۸ اردیبهشت ۱۴۰۴", + text: "سیم معمولاً یک هادی عایق‌شده است؛ کابل چند رشته را در غلاف مشترک قرار می‌دهد تا مقاومت مکانیکی و تحمل ولتاژ بیشتری داشته باشد.", + image: "/images/samples/copper-coils.avif", + href: "https://kamancable.com/what-are-wires-and-cables/", + }, + { + id: "cross-section", + title: "سطح مقطع کابل چیست؟", + titleEn: "Cable cross-section", + date: "۴ اردیبهشت ۱۴۰۴", + text: "سطح مقطع، جریان مجاز و افت ولتاژ را تعیین می‌کند؛ انتخاب مقطع مناسب به بار، طول مسیر، نوع هادی و نحوه نصب وابسته است.", + image: "/images/samples/factory-floor.avif", + href: "https://kamancable.com/what-is-the-cross-section-of-the-cable/", + }, +]; + +export const clients = [ + { id: "c1", image: "/images/clients/client-1.png", title: "همکار ۱" }, + { id: "c2", image: "/images/clients/client-2.png", title: "همکار ۲" }, + { id: "c3", image: "/images/clients/client-3.png", title: "همکار ۳" }, + { id: "c4", image: "/images/clients/client-4.png", title: "همکار ۴" }, + { id: "c5", image: "/images/clients/client-5.png", title: "همکار ۵" }, +]; + +export const aboutGallery = [ + { id: "g1", image: "/images/about/factory.jpg", title: "کارخانه کابل کمان" }, + { id: "g2", image: "/images/samples/cables-coils.jpg", title: "کلاف‌های کابل" }, + { id: "g3", image: "/images/samples/cable-closeup.jpg", title: "جزئیات کابل" }, + { id: "g4", image: "/images/samples/copper-coils.avif", title: "کلاف مس" }, + { id: "g5", image: "/images/samples/factory-floor.avif", title: "خط تولید" }, + { id: "g6", image: "/images/samples/production-line.avif", title: "ماشین‌آلات تولید" }, +]; + +export const stats = [ + { value: "۱۳۷۰", label: "سال تاسیس", labelEn: "Founded" }, + { value: "۴۰٬۰۰۰", label: "متر مربع کارخانه", labelEn: "Factory Area" }, + { value: "۱۵٬۰۰۰", label: "تن ظرفیت سالانه", labelEn: "Annual Capacity" }, + { value: "ISO", label: "۹۰۰۱:۲۰۱۵ و ۱۷۰۲۵", labelEn: "Certified" }, +]; diff --git a/src/lib/blog.ts b/src/lib/blog.ts new file mode 100644 index 0000000..58dbb3d --- /dev/null +++ b/src/lib/blog.ts @@ -0,0 +1,83 @@ +import { blogPosts } from "@/data/site"; +import { formatFaDate } from "@/lib/format"; +import { meshkeeGet, meshkeeGetOrNull, tenantPath } from "@/lib/meshkee"; +import { detailPath, slugifyForUrl } from "@/lib/slug"; + +export type BlogPost = { + id: string; + title: string; + slug?: string | null; + abstract?: string | null; + mainTextHtml?: string | null; + titleImageUrl?: string | null; + publishedAt?: string | null; + createdAt?: string | null; + commentCount?: number; +}; + +export type PagedBlogs = { + items: BlogPost[]; + total: number; + page: number; + pageSize: number; +}; + +export async function fetchBlogs(params: { page?: number; pageSize?: number } = {}) { + const query = new URLSearchParams(); + query.set("page", String(params.page ?? 1)); + query.set("pageSize", String(params.pageSize ?? 12)); + const data = await meshkeeGet<PagedBlogs>(tenantPath(`/blogs?${query.toString()}`), { + items: [], + total: 0, + page: 1, + pageSize: params.pageSize ?? 12, + }); + return { + ...data, + items: (data.items ?? []).map((item) => ({ + ...item, + id: String(item.id), + })), + }; +} + +export async function fetchBlogById(id: string) { + const data = await meshkeeGetOrNull<{ blog?: BlogPost }>( + tenantPath(`/blogs/by-id/${encodeURIComponent(id)}`), + ); + const blog = data?.blog; + return blog ? { ...blog, id: String(blog.id) } : null; +} + +export function blogHref(post: Pick<BlogPost, "id" | "title">) { + return detailPath("/blog", post.id, post.title); +} + +export function blogTitleSlug(title: string) { + return slugifyForUrl(title); +} + +export function blogImage(post: BlogPost, fallback = "/images/samples/cables-coils.jpg") { + return post.titleImageUrl?.trim() || fallback; +} + +export function blogDate(post: BlogPost) { + return formatFaDate(post.publishedAt || post.createdAt); +} + +export function getLocalBlogPosts(): BlogPost[] { + return blogPosts.map((post) => ({ + id: post.id, + title: post.title, + slug: post.id, + abstract: post.text, + titleImageUrl: post.image, + publishedAt: null, + createdAt: null, + })); +} + +export function getLocalBlogPost(idOrSlug: string) { + const posts = getLocalBlogPosts(); + return posts.find((post) => post.id === idOrSlug || post.slug === idOrSlug) ?? null; +} diff --git a/src/lib/categories.ts b/src/lib/categories.ts new file mode 100644 index 0000000..d6fc010 --- /dev/null +++ b/src/lib/categories.ts @@ -0,0 +1,36 @@ +import { meshkeeGet, meshkeeGetOrNull, tenantPath } from "@/lib/meshkee"; +import { detailPath } from "@/lib/slug"; + +export type ProductCategory = { + id: string; + name?: string | null; + nameFa?: string | null; + slug?: string | null; + description?: string | null; + parentId?: string | null; + entityType?: string | null; +}; + +export function categoryTitle(category: Pick<ProductCategory, "nameFa" | "name">) { + return category.nameFa?.trim() || category.name?.trim() || "دسته‌بندی"; +} + +export function categoryHref(category: Pick<ProductCategory, "id" | "nameFa" | "name">) { + return detailPath("/products/category", category.id, categoryTitle(category)); +} + +export async function fetchCategories(entityType: "product" | "blog" = "product") { + const data = await meshkeeGet<{ items?: ProductCategory[] }>( + tenantPath(`/categories?entityType=${entityType}`), + { items: [] }, + ); + return (data.items ?? []).map((item) => ({ ...item, id: String(item.id) })); +} + +export async function fetchCategoryById(id: string, entityType: "product" | "blog" = "product") { + const data = await meshkeeGetOrNull<{ category?: ProductCategory }>( + tenantPath(`/categories/by-id/${encodeURIComponent(id)}?entityType=${entityType}`), + ); + const category = data?.category; + return category ? { ...category, id: String(category.id) } : null; +} diff --git a/src/lib/comments.ts b/src/lib/comments.ts new file mode 100644 index 0000000..4d4e3f5 --- /dev/null +++ b/src/lib/comments.ts @@ -0,0 +1,19 @@ +import { meshkeeGet, tenantPath } from "@/lib/meshkee"; + +export type CommentItem = { + id?: string; + authorName?: string | null; + text?: string | null; + createdAt?: string | null; +}; + +export async function fetchComments( + entityType: "product" | "blog", + entityId: string, +) { + const data = await meshkeeGet<{ items?: CommentItem[] }>( + tenantPath(`/comments?entityType=${entityType}&entityId=${encodeURIComponent(entityId)}`), + { items: [] }, + ); + return data.items ?? []; +} diff --git a/src/lib/config.ts b/src/lib/config.ts new file mode 100644 index 0000000..8314c24 --- /dev/null +++ b/src/lib/config.ts @@ -0,0 +1,20 @@ +export const websiteDomain = + process.env.NEXT_PUBLIC_WEBSITE_DOMAIN ?? + process.env.WEBSITE_DOMAIN ?? + process.env.NEXT_PUBLIC_MESHKEE_DOMAIN ?? + "kamancable.ir"; + +export const meshkeeApiBase = ( + process.env.NEXT_PUBLIC_MESHKEE_API_BASE ?? + process.env.MESHKEE_API_BASE ?? + process.env.NEXT_PUBLIC_MESHKEE_API_URL ?? + "https://api.meshkee.com/api/v1" +).replace(/\/$/, ""); + +export const customerPortalUrl = ( + process.env.NEXT_PUBLIC_CUSTOMER_PORTAL_URL ?? `https://customer.${websiteDomain}` +).replace(/\/$/, ""); + +export const businessDashboardUrl = ( + process.env.NEXT_PUBLIC_BUSINESS_ORIGIN ?? `https://business.${websiteDomain}` +).replace(/\/$/, ""); diff --git a/src/lib/format.ts b/src/lib/format.ts new file mode 100644 index 0000000..f0e26a5 --- /dev/null +++ b/src/lib/format.ts @@ -0,0 +1,29 @@ +export function formatFaDate(value?: string | null) { + if (!value) return ""; + const date = new Date(value); + if (Number.isNaN(date.getTime())) return ""; + return new Intl.DateTimeFormat("fa-IR", { + day: "numeric", + month: "long", + year: "numeric", + }).format(date); +} + +export function sanitizeHtml(html?: string | null) { + if (!html) return ""; + return html + .replace(/<script[\s\S]*?>[\s\S]*?<\/script>/gi, "") + .replace(/\son\w+="[^"]*"/gi, "") + .replace(/\son\w+='[^']*'/gi, "") + .replace(/javascript:/gi, ""); +} + +export function plainText(html?: string | null) { + if (!html) return ""; + return html + .replace(/<style[\s\S]*?>[\s\S]*?<\/style>/gi, " ") + .replace(/<[^>]+>/g, " ") + .replace(/ /g, " ") + .replace(/\s+/g, " ") + .trim(); +} diff --git a/src/lib/meshkee-server.ts b/src/lib/meshkee-server.ts new file mode 100644 index 0000000..97b9bce --- /dev/null +++ b/src/lib/meshkee-server.ts @@ -0,0 +1,42 @@ +import { meshkeeApiBase } from "@/lib/config"; + +export function getBearerToken(request: Request): string | null { + const auth = request.headers.get("authorization"); + if (auth?.startsWith("Bearer ")) return auth.slice(7); + return null; +} + +export async function meshkeeFetch( + path: string, + init?: RequestInit & { token?: string | null }, +): Promise<{ ok: boolean; status: number; data: unknown }> { + const headers = new Headers(init?.headers); + if (!headers.has("Content-Type") && init?.body) { + headers.set("Content-Type", "application/json"); + } + if (init?.token) { + headers.set("Authorization", `Bearer ${init.token}`); + } + + const res = await fetch(`${meshkeeApiBase}${path}`, { + ...init, + headers, + }); + + let data: unknown = null; + try { + data = await res.json(); + } catch { + data = null; + } + + return { ok: res.ok, status: res.status, data }; +} + +export function jsonResponse(data: unknown, status = 200) { + return Response.json(data, { status }); +} + +export function unauthorizedResponse() { + return jsonResponse({ error: "Unauthorized" }, 401); +} diff --git a/src/lib/meshkee.ts b/src/lib/meshkee.ts new file mode 100644 index 0000000..c6e5dac --- /dev/null +++ b/src/lib/meshkee.ts @@ -0,0 +1,45 @@ +import { meshkeeApiBase, websiteDomain } from "@/lib/config"; + +export async function meshkeeGet<T>(path: string, fallback: T, revalidate = 60): Promise<T> { + try { + const response = await fetch(`${meshkeeApiBase}${path}`, { + next: { revalidate }, + }); + if (!response.ok) return fallback; + return (await response.json()) as T; + } catch { + return fallback; + } +} + +export async function meshkeeGetOrNull<T>(path: string, revalidate = 60): Promise<T | null> { + try { + const response = await fetch(`${meshkeeApiBase}${path}`, { + next: { revalidate }, + }); + if (!response.ok) return null; + return (await response.json()) as T; + } catch { + return null; + } +} + +export function tenantPath(suffix: string) { + return `/tenants/${encodeURIComponent(websiteDomain)}${suffix}`; +} + +type Tenant = { + id: number | string; + nameFa?: string; + name?: string; + domain?: string; +}; + +export async function getTenant() { + return meshkeeGet<Tenant | null>(`/tenants/${encodeURIComponent(websiteDomain)}`, null); +} + +export async function getBusinessId() { + const tenant = await getTenant(); + return tenant?.id != null ? String(tenant.id) : ""; +} diff --git a/src/lib/products.ts b/src/lib/products.ts new file mode 100644 index 0000000..1d79ce3 --- /dev/null +++ b/src/lib/products.ts @@ -0,0 +1,126 @@ +import { products as localProducts } from "@/data/site"; +import { meshkeeGet, meshkeeGetOrNull, tenantPath } from "@/lib/meshkee"; +import { detailPath } from "@/lib/slug"; + +export type ProductImage = { + url?: string | null; + mediaId?: string | null; +}; + +export type CatalogProduct = { + id: string; + title?: string | null; + nameFa?: string | null; + summary?: string | null; + descriptionHtml?: string | null; + slug?: string | null; + categoryId?: string | null; + categoryName?: string | null; + categoryNameFa?: string | null; + thumbnailUrl?: string | null; + image?: string | null; + thumbnail?: string | null; + images?: ProductImage[] | string[] | null; + commentCount?: number; +}; + +export type ProductDetailPayload = { + product?: CatalogProduct; + relatedProducts?: CatalogProduct[]; +}; + +export type PagedProducts = { + items: CatalogProduct[]; + total: number; + page: number; + pageSize: number; +}; + +export type TechnicalFormField = { + id?: string | number; + label?: string | null; + key?: string | null; + type?: string | null; + sortOrder?: number | null; + options?: { id?: string | number; label?: string | null; value?: string | null }[]; +}; + +export type TechnicalInfoValue = { + fieldId?: string | number | null; + textValue?: string | null; + optionId?: string | null; + optionIds?: string[] | null; +}; + +export type ProductTechnicalInfo = { + form?: { fields?: TechnicalFormField[] } | null; + values?: TechnicalInfoValue[] | null; +}; + +export async function fetchProducts( + params: { page?: number; pageSize?: number; name?: string; categoryId?: string } = {}, +) { + const query = new URLSearchParams(); + query.set("page", String(params.page ?? 1)); + query.set("pageSize", String(params.pageSize ?? 12)); + if (params.name) query.set("name", params.name); + if (params.categoryId) query.set("categoryId", params.categoryId); + return meshkeeGet<PagedProducts>(tenantPath(`/products?${query.toString()}`), { + items: [], + total: 0, + page: 1, + pageSize: params.pageSize ?? 12, + }); +} + +export async function fetchProductById(id: string) { + return meshkeeGetOrNull<ProductDetailPayload>( + tenantPath(`/products/by-id/${encodeURIComponent(id)}`), + ); +} + +export async function fetchProductTechnicalInfo(id: string) { + return meshkeeGetOrNull<ProductTechnicalInfo>( + tenantPath(`/products/by-id/${encodeURIComponent(id)}/technical-info`), + ); +} + +export function productTitle(product: Pick<CatalogProduct, "nameFa" | "title">) { + return product.nameFa?.trim() || product.title?.trim() || "محصول"; +} + +export function productHref(product: Pick<CatalogProduct, "id" | "nameFa" | "title">) { + return detailPath("/products", product.id, productTitle(product)); +} + +export function productImage(product: CatalogProduct, fallback = "/images/samples/cables-coils.jpg") { + const gallery = productGallery(product); + return gallery[0] || fallback; +} + +export function productGallery(product: CatalogProduct) { + const extras = (product.images ?? []) + .map((item) => (typeof item === "string" ? item : item?.url)) + .filter((url): url is string => Boolean(url?.trim())); + return Array.from( + new Set( + [product.thumbnailUrl, product.image, product.thumbnail, ...extras].filter( + (url): url is string => Boolean(url?.trim()), + ), + ), + ); +} + +export function localProductText(product: CatalogProduct) { + const title = productTitle(product).toLowerCase(); + const english = (product.title || "").toLowerCase(); + return ( + localProducts.find( + (item) => + item.title === product.nameFa || + item.titleEn.toLowerCase() === english || + title.includes(item.title) || + english.includes(item.titleEn.toLowerCase()), + )?.text ?? "" + ); +} diff --git a/src/lib/seo.ts b/src/lib/seo.ts new file mode 100644 index 0000000..d10d322 --- /dev/null +++ b/src/lib/seo.ts @@ -0,0 +1,44 @@ +import type { Metadata } from "next"; + +import { site } from "@/data/site"; +import { websiteDomain } from "@/lib/config"; + +export const siteUrl = `https://${websiteDomain}`; + +export function pageMetadata({ + title, + description, + path = "/", + image, + absolute = false, +}: { + title: string; + description: string; + path?: string; + image?: string | null; + absolute?: boolean; +}): Metadata { + const url = `${siteUrl}${path.startsWith("/") ? path : `/${path}`}`; + const ogImage = image || `${siteUrl}/images/slider/bg1.jpg`; + + return { + title: absolute ? { absolute: title } : title, + description, + alternates: { canonical: url }, + openGraph: { + type: path === "/" ? "website" : "article", + locale: "fa_IR", + url, + siteName: site.shortName, + title, + description, + images: [{ url: ogImage, alt: title }], + }, + twitter: { + card: "summary_large_image", + title, + description, + images: [ogImage], + }, + }; +} diff --git a/src/lib/session.tsx b/src/lib/session.tsx new file mode 100644 index 0000000..bc953a3 --- /dev/null +++ b/src/lib/session.tsx @@ -0,0 +1,188 @@ +"use client"; + +import { + createContext, + useCallback, + useContext, + useEffect, + useMemo, + useState, + type ReactNode, +} from "react"; + +import { + clearSharedAuthCookies, + hydrateLocalTokensFromSharedCookies, + SHARED_ACCESS_TOKEN_KEY, + SHARED_REFRESH_TOKEN_KEY, + writeSharedAuthCookies, +} from "@/lib/shared-auth-cookie"; + +export type AuthBusinessMembership = { + id: string; + name: string; + slug: string; + isOwner: boolean; + teamRole: string | null; +}; + +export type AuthUser = { + id: string; + firstName?: string; + lastName?: string; + cellNumber?: string; + email?: string; + roles?: string[]; + isSuperAdmin?: boolean; + businesses?: AuthBusinessMembership[]; +}; + +type SessionContextValue = { + user: AuthUser | null; + isAuthenticated: boolean; + ready: boolean; + businessId: string; + isWebsiteAdmin: boolean; + logout: () => void; + getAccessToken: () => string | null; +}; + +function normalizeUser(raw: Partial<AuthUser> & { id?: string | number }): AuthUser | null { + if (raw.id == null) return null; + return { + id: String(raw.id), + firstName: raw.firstName ?? undefined, + lastName: raw.lastName ?? undefined, + cellNumber: raw.cellNumber ?? undefined, + email: raw.email ?? undefined, + roles: Array.isArray(raw.roles) ? raw.roles : undefined, + isSuperAdmin: Boolean(raw.isSuperAdmin), + businesses: Array.isArray(raw.businesses) + ? raw.businesses.map((business) => ({ + id: String(business.id), + name: business.name, + slug: business.slug, + isOwner: Boolean(business.isOwner), + teamRole: business.teamRole ?? null, + })) + : undefined, + }; +} + +export function isWebsiteAdminForBusiness(user: AuthUser | null | undefined, businessId: string): boolean { + if (!user || !businessId) return false; + if (user.isSuperAdmin || user.roles?.includes("super_admin")) return true; + return Boolean( + user.businesses?.some( + (business) => String(business.id) === String(businessId) && (business.isOwner || Boolean(business.teamRole)), + ), + ); +} + +const SessionContext = createContext<SessionContextValue | null>(null); + +function readToken(key: string): string | null { + try { + return localStorage.getItem(key); + } catch { + return null; + } +} + +function writeToken(key: string, value: string | null) { + try { + if (value) localStorage.setItem(key, value); + else localStorage.removeItem(key); + } catch { + // ignore + } +} + +export function SessionProvider({ + children, + businessId, +}: { + children: ReactNode; + businessId: string; +}) { + const [user, setUser] = useState<AuthUser | null>(null); + const [ready, setReady] = useState(false); + + const getAccessToken = useCallback(() => { + hydrateLocalTokensFromSharedCookies(SHARED_ACCESS_TOKEN_KEY, SHARED_REFRESH_TOKEN_KEY); + return ( + readToken(SHARED_ACCESS_TOKEN_KEY) || + readToken("accessToken") || + readToken("meshkeeToken") || + readToken("token") + ); + }, []); + + const clearTokens = useCallback(() => { + writeToken(SHARED_ACCESS_TOKEN_KEY, null); + writeToken(SHARED_REFRESH_TOKEN_KEY, null); + writeToken("accessToken", null); + writeToken("meshkeeToken", null); + writeToken("token", null); + clearSharedAuthCookies(); + setUser(null); + }, []); + + useEffect(() => { + const token = getAccessToken(); + if (!token) { + setReady(true); + return; + } + + const refresh = readToken(SHARED_REFRESH_TOKEN_KEY); + writeSharedAuthCookies(token, refresh ?? undefined); + + fetch("/api/auth/me", { headers: { Authorization: `Bearer ${token}` } }) + .then(async (res) => { + if (res.status === 401) { + clearTokens(); + return; + } + if (!res.ok) return; + const data = (await res.json()) as { user?: AuthUser } & AuthUser; + const next = normalizeUser(data.user ?? data); + if (next) setUser(next); + }) + .catch(() => undefined) + .finally(() => setReady(true)); + }, [clearTokens, getAccessToken]); + + const logout = useCallback(() => { + clearTokens(); + }, [clearTokens]); + + const isWebsiteAdmin = useMemo(() => isWebsiteAdminForBusiness(user, businessId), [user, businessId]); + + const value = useMemo( + () => ({ + user, + isAuthenticated: Boolean(user), + ready, + businessId, + isWebsiteAdmin, + logout, + getAccessToken, + }), + [user, ready, businessId, isWebsiteAdmin, logout, getAccessToken], + ); + + return <SessionContext.Provider value={value}>{children}</SessionContext.Provider>; +} + +export function useSession() { + const ctx = useContext(SessionContext); + if (!ctx) throw new Error("useSession must be used within SessionProvider"); + return ctx; +} + +export function userDisplayName(user: AuthUser | null | undefined) { + if (!user) return "کاربر"; + const full = [user.firstName, user.lastName].filter(Boolean).join(" ").trim(); + return full || user.cellNumber || user.email || "کاربر"; +} diff --git a/src/lib/shared-auth-cookie.ts b/src/lib/shared-auth-cookie.ts new file mode 100644 index 0000000..87843c4 --- /dev/null +++ b/src/lib/shared-auth-cookie.ts @@ -0,0 +1,123 @@ +/** Shared auth cookies so storefront + customer.{domain} share one login. */ + +export const SHARED_ACCESS_TOKEN_KEY = "meshkee_customer_access_token"; +export const SHARED_REFRESH_TOKEN_KEY = "meshkee_customer_refresh_token"; + +const CHUNK_SIZE = 2800; +const MAX_CHUNKS = 24; + +function cookieDomain(): string | undefined { + if (typeof window === "undefined") return undefined; + const host = window.location.hostname.toLowerCase(); + if (host === "localhost" || host === "127.0.0.1") return undefined; + const parts = host.split(".").filter(Boolean); + if (parts.length < 2) return undefined; + return `.${parts.slice(-2).join(".")}`; +} + +function readRawCookie(name: string): string | null { + if (typeof document === "undefined") return null; + const prefix = `${name}=`; + const match = document.cookie.split("; ").find((row) => row.startsWith(prefix)); + if (!match) return null; + return match.slice(prefix.length); +} + +function writeRawCookie(name: string, value: string | null) { + if (typeof document === "undefined") return; + const domain = cookieDomain(); + if (value == null) { + let cookie = `${name}=; path=/; max-age=0; SameSite=Lax`; + if (domain) cookie += `; domain=${domain}`; + document.cookie = cookie; + return; + } + const maxAge = 60 * 60 * 24 * 30; + let cookie = `${name}=${value}; path=/; max-age=${maxAge}; SameSite=Lax`; + if (domain) cookie += `; domain=${domain}`; + document.cookie = cookie; +} + +function clearChunkedCookie(name: string) { + writeRawCookie(name, null); + const n = Number(readRawCookie(`${name}_n`) || "0"); + writeRawCookie(`${name}_n`, null); + for (let i = 0; i < Math.max(n, MAX_CHUNKS); i++) { + writeRawCookie(`${name}_${i}`, null); + } +} + +function writeCookie(name: string, value: string | null) { + clearChunkedCookie(name); + if (value == null || value === "") return; + + const encoded = encodeURIComponent(value); + if (encoded.length <= 3500) { + writeRawCookie(name, encoded); + return; + } + + const chunks: string[] = []; + for (let i = 0; i < encoded.length; i += CHUNK_SIZE) { + chunks.push(encoded.slice(i, i + CHUNK_SIZE)); + } + writeRawCookie(`${name}_n`, String(chunks.length)); + chunks.forEach((chunk, i) => writeRawCookie(`${name}_${i}`, chunk)); +} + +function readCookie(name: string): string | null { + const n = Number(readRawCookie(`${name}_n`) || "0"); + if (n > 0) { + let encoded = ""; + for (let i = 0; i < n; i++) { + const part = readRawCookie(`${name}_${i}`); + if (part == null) return null; + encoded += part; + } + try { + return decodeURIComponent(encoded); + } catch { + return null; + } + } + + const raw = readRawCookie(name); + if (raw == null) return null; + try { + return decodeURIComponent(raw); + } catch { + return null; + } +} + +export function writeSharedAuthCookies(accessToken: string, refreshToken?: string) { + writeCookie(SHARED_ACCESS_TOKEN_KEY, accessToken); + if (refreshToken) writeCookie(SHARED_REFRESH_TOKEN_KEY, refreshToken); +} + +export function clearSharedAuthCookies() { + writeCookie(SHARED_ACCESS_TOKEN_KEY, null); + writeCookie(SHARED_REFRESH_TOKEN_KEY, null); +} + +export function readSharedAccessToken(): string | null { + return readCookie(SHARED_ACCESS_TOKEN_KEY); +} + +export function readSharedRefreshToken(): string | null { + return readCookie(SHARED_REFRESH_TOKEN_KEY); +} + +export function hydrateLocalTokensFromSharedCookies( + accessKey = SHARED_ACCESS_TOKEN_KEY, + refreshKey = SHARED_REFRESH_TOKEN_KEY, +): boolean { + if (typeof window === "undefined") return false; + if (localStorage.getItem(accessKey)) return false; + const access = readSharedAccessToken(); + if (!access) return false; + localStorage.setItem(accessKey, access); + const refresh = readSharedRefreshToken(); + if (refresh) localStorage.setItem(refreshKey, refresh); + return true; +} diff --git a/src/lib/sitemap-config.generated.json b/src/lib/sitemap-config.generated.json new file mode 100644 index 0000000..5548a6d --- /dev/null +++ b/src/lib/sitemap-config.generated.json @@ -0,0 +1,30 @@ +{ + "baseUrl": "https://kamancable.ir", + "staticPages": [ + { + "path": "/", + "changefreq": "daily", + "priority": 1 + }, + { + "path": "/about", + "changefreq": "monthly", + "priority": 0.6 + }, + { + "path": "/blog", + "changefreq": "weekly", + "priority": 0.7 + }, + { + "path": "/contact", + "changefreq": "monthly", + "priority": 0.5 + }, + { + "path": "/products", + "changefreq": "weekly", + "priority": 0.8 + } + ] +} diff --git a/src/lib/slug.ts b/src/lib/slug.ts new file mode 100644 index 0000000..a1af23c --- /dev/null +++ b/src/lib/slug.ts @@ -0,0 +1,16 @@ +/** URL segment from a Persian / Latin title. */ +export function slugifyForUrl(value: string, fallback = "item"): string { + const slug = value + .trim() + .normalize("NFC") + .replace(/\s+/g, "-") + .replace(/[^\u0600-\u06FF\u0750-\u077F\u08A0-\u08FF\uFB50-\uFDFF\uFE70-\uFEFFa-zA-Z0-9-]+/g, "") + .replace(/-+/g, "-") + .replace(/^-|-$/g, ""); + + return slug || fallback; +} + +export function detailPath(base: string, id: string | number, title: string) { + return `${base}/${encodeURIComponent(String(id))}/${encodeURIComponent(slugifyForUrl(title))}`; +} diff --git a/src/lib/static-images.ts b/src/lib/static-images.ts new file mode 100644 index 0000000..a2a6301 --- /dev/null +++ b/src/lib/static-images.ts @@ -0,0 +1,264 @@ +import { meshkeeApiBase, websiteDomain } from "@/lib/config"; + +export type SlotKind = "single" | "list"; + +export type SlotImage = { + id: string; + url: string; + titleFa?: string | null; + titleEn?: string | null; + subtext?: string | null; + linkUrl?: string | null; + width?: number | null; + height?: number | null; + sortOrder?: number | null; +}; + +export type StaticImageSlotCatalogItem = { + key: string; + label: string; + kind: SlotKind; + pageKey: string; + aspectRatio: string; + itemCount: number | null; + recommendedWidth: number; +}; + +export type StaticImageSlot = StaticImageSlotCatalogItem & { + images: SlotImage[]; +}; + +export type SlotMediaItem = { + id: string; + image: string; + href?: string; + title?: string; + kicker?: string; + text?: string; + fallbackSrc?: string; +}; + +/** + * Slot catalog published at GET /meshkee/static-image-slots. + * If a section’s CSS size or aspect ratio changes, update this catalog + * in the same change — Meshkee reads it as the source of truth. + * + * Homepage keys match the Meshkee dashboard placeholders: + * slider, about-us-image, categories, partners (all duplicatable except about-us-image). + */ +export const STATIC_IMAGE_SLOTS: StaticImageSlotCatalogItem[] = [ + { + key: "slider", + label: "Homepage slider", + kind: "list", + pageKey: "home", + aspectRatio: "1920:830", + itemCount: null, + recommendedWidth: 1440, + }, + { + key: "about-us-image", + label: "Homepage about photo", + kind: "single", + pageKey: "home", + aspectRatio: "440:609", + itemCount: 1, + recommendedWidth: 480, + }, + { + key: "categories", + label: "Homepage product categories", + kind: "list", + pageKey: "home", + aspectRatio: "3:4", + itemCount: null, + recommendedWidth: 480, + }, + { + key: "home-blog", + label: "Homepage blog carousel", + kind: "list", + pageKey: "home", + aspectRatio: "16:10", + itemCount: 6, + recommendedWidth: 720, + }, + { + key: "partners", + label: "Homepage partners", + kind: "list", + pageKey: "home", + aspectRatio: "3:2", + itemCount: null, + recommendedWidth: 280, + }, + { + key: "about-hero", + label: "About page hero", + kind: "single", + pageKey: "about", + aspectRatio: "1920:640", + itemCount: 1, + recommendedWidth: 1440, + }, + { + key: "about-gallery", + label: "About page gallery", + kind: "list", + pageKey: "about", + aspectRatio: "4:3", + itemCount: 6, + recommendedWidth: 720, + }, + { + key: "contact-hero", + label: "Contact page hero", + kind: "single", + pageKey: "contact", + aspectRatio: "1920:480", + itemCount: 1, + recommendedWidth: 1440, + }, + { + key: "products-hero", + label: "Products page hero", + kind: "single", + pageKey: "products", + aspectRatio: "1920:480", + itemCount: 1, + recommendedWidth: 1440, + }, + { + key: "blog-hero", + label: "Blog page hero", + kind: "single", + pageKey: "blog", + aspectRatio: "1920:480", + itemCount: 1, + recommendedWidth: 1440, + }, +]; + +/** Older dashboard keys still resolve if Meshkee has not been re-synced. */ +const SLOT_ALIASES: Record<string, string[]> = { + "about-us-image": ["home-about-image"], + categories: ["home-products"], + partners: ["home-clients"], +}; + +export function catalogSlot(key: string) { + return STATIC_IMAGE_SLOTS.find((slot) => slot.key === key); +} + +export function parseAspectRatio(ratio?: string | null) { + if (!ratio) return undefined; + const match = /^(\d+(?:\.\d+)?)\s*:\s*(\d+(?:\.\d+)?)$/.exec(ratio.trim()); + if (!match) return undefined; + return `${match[1]} / ${match[2]}`; +} + +/** CSS custom property consumed by `.about-photo`, `.product-card .thumb`, `.client`. */ +export function slotRatioStyle(ratio?: string | null): { "--slot-ratio": string } | undefined { + const parsed = parseAspectRatio(ratio); + return parsed ? { "--slot-ratio": parsed } : undefined; +} + +export function pickSlot(slots: StaticImageSlot[], key: string) { + const match = slots.find((slot) => slot.key === key); + if (match) return match; + const aliases = SLOT_ALIASES[key]; + if (!aliases) return undefined; + return slots.find((slot) => aliases.includes(slot.key)); +} + +function normalizeImages(images: SlotImage[] | undefined) { + return [...(images ?? [])] + .filter((image) => Boolean(image?.url)) + .sort((a, b) => (a.sortOrder ?? 0) - (b.sortOrder ?? 0)); +} + +export function imagesFromSlot(slot?: StaticImageSlot | null): SlotImage[] { + if (!slot) return []; + const images = normalizeImages(slot.images); + if (slot.kind === "single") return images.slice(0, 1); + return images; +} + +function toMedia(image: SlotImage, fallback?: SlotMediaItem): SlotMediaItem { + return { + id: String(image.id), + image: image.url, + href: image.linkUrl?.trim() || fallback?.href, + title: image.titleFa?.trim() || fallback?.title, + kicker: image.titleEn?.trim() || fallback?.kicker, + text: image.subtext?.trim() || fallback?.text, + fallbackSrc: fallback?.image || fallback?.fallbackSrc, + }; +} + +export function overlaySlotImages( + slot: StaticImageSlot | undefined, + fallbacks: SlotMediaItem[], + key: string, +): { items: SlotMediaItem[]; aspectRatio?: string } { + const catalog = catalogSlot(key); + const aspectRatio = slot?.aspectRatio || catalog?.aspectRatio; + const kind = slot?.kind ?? catalog?.kind ?? "list"; + const itemCount = slot?.itemCount ?? catalog?.itemCount ?? null; + const apiImages = imagesFromSlot(slot ? { ...slot, kind, images: slot.images ?? [] } : undefined); + + if (kind === "single") { + const image = apiImages[0]; + const fallback = fallbacks[0]; + return { + aspectRatio, + items: image ? [toMedia(image, fallback)] : fallbacks.slice(0, 1), + }; + } + + if (apiImages.length === 0) { + return { + aspectRatio, + items: itemCount ? fallbacks.slice(0, itemCount) : fallbacks, + }; + } + + if (itemCount) { + return { + aspectRatio, + items: Array.from({ length: itemCount }, (_, index) => { + const image = apiImages[index]; + const fallback = fallbacks[index] ?? fallbacks[fallbacks.length - 1]; + return image ? toMedia(image, fallback) : fallback; + }).filter(Boolean), + }; + } + + return { + aspectRatio, + items: apiImages.map((image, index) => toMedia(image, fallbacks[index])), + }; +} + +function readItems(payload: unknown): StaticImageSlot[] { + if (Array.isArray(payload)) return payload as StaticImageSlot[]; + if (payload && typeof payload === "object") { + const record = payload as { items?: StaticImageSlot[]; slots?: StaticImageSlot[] }; + return record.items ?? record.slots ?? []; + } + return []; +} + +export async function fetchStaticImageSlots(pageKey = "home"): Promise<StaticImageSlot[]> { + const url = `${meshkeeApiBase}/tenants/${encodeURIComponent(websiteDomain)}/website/static-images?pageKey=${encodeURIComponent(pageKey)}`; + + try { + const response = await fetch(url, { + next: { revalidate: 60 }, + }); + if (!response.ok) return []; + return readItems(await response.json()); + } catch { + return []; + } +} diff --git a/src/lib/technical.ts b/src/lib/technical.ts new file mode 100644 index 0000000..37eef72 --- /dev/null +++ b/src/lib/technical.ts @@ -0,0 +1,53 @@ +import type { ProductTechnicalInfo, TechnicalFormField, TechnicalInfoValue } from "@/lib/products"; + +export type TechnicalRow = { + id: string; + label: string; + value: string; +}; + +function pick(...values: Array<string | number | null | undefined>) { + for (const value of values) { + if (value == null) continue; + const text = String(value).trim(); + if (text) return text; + } + return ""; +} + +function optionLabel(field: TechnicalFormField | undefined, optionId: string) { + const option = field?.options?.find((item) => String(item.id) === optionId); + return pick(option?.label, option?.value, optionId); +} + +function valueText(value: TechnicalInfoValue, field?: TechnicalFormField) { + const optionIds = + value.optionIds?.filter(Boolean).map(String) ?? (value.optionId ? [String(value.optionId)] : []); + if (optionIds.length > 0) { + const labels = optionIds.map((id) => optionLabel(field, id)).filter(Boolean); + if (labels.length) return labels.join("، "); + } + return pick(value.textValue); +} + +export function rowsFromTechnicalInfo(info?: ProductTechnicalInfo | null): TechnicalRow[] { + const fields = info?.form?.fields ?? []; + const values = info?.values ?? []; + if (!fields.length || !values.length) return []; + + const fieldById = new Map(fields.map((field) => [String(field.id), field])); + + return values + .map((value, index) => { + const id = String(value.fieldId ?? `item-${index}`); + const field = fieldById.get(id); + const text = valueText(value, field); + if (!text) return null; + return { + id, + label: pick(field?.label, field?.key) || `فیلد ${index + 1}`, + value: text, + }; + }) + .filter((row): row is TechnicalRow => Boolean(row)); +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..b8cdcc7 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,23 @@ +{ + "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": "preserve", + "incremental": true, + "plugins": [{ "name": "next" }], + "paths": { + "@/*": ["./src/*"] + } + }, + "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], + "exclude": ["node_modules"] +}