commit 4dec4eb92cda64f7cf2c2f390e7f9d76a8c4f963 Author: Alireza Hassani Date: Mon Aug 31 09:18:26 2026 +0330 Add the FarrokhWin Next.js site for Meshkee hosting. Co-authored-by: Cursor diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..4cf4abb --- /dev/null +++ b/.env.example @@ -0,0 +1,4 @@ +NEXT_PUBLIC_WEBSITE_DOMAIN=farrokhwin.com +NEXT_PUBLIC_MESHKEE_API_BASE=https://api.meshkee.com/api/v1 +NEXT_PUBLIC_CUSTOMER_PORTAL_URL=https://customer.farrokhwin.com +NEXT_PUBLIC_BUSINESS_ORIGIN=https://business.farrokhwin.com diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3eb9d54 --- /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..a07f0fc --- /dev/null +++ b/CONTEXT.md @@ -0,0 +1,151 @@ +# FarrokhWin — AI / Project Context + +## Source of truth +- Live reference: https://www.farrokhwin.com/ +- Backend / Meshkee Website API: https://api.meshkee.com/docs/website +- Domain: `farrokhwin.com` (`NEXT_PUBLIC_WEBSITE_DOMAIN`) +- Tenant business id: `28` (`GET /tenants/farrokhwin.com`) +- `enabledModules`: products, portfolio, blog, finance +- Customer portal (login / profile / cart): `https://customer.farrokhwin.com` +- Business dashboard: `https://business.farrokhwin.com` +- Language: `fa`, direction: `rtl` + +## Stack +- Next.js 15 (App Router) + React 19 + TypeScript +- Tailwind CSS v4 (utility import) + custom CSS in `src/app/globals.css` +- Fonts (downloaded from the live Meshkee CDN into `public/fonts/`): + - `morabba` — default UI / headings + - `iran-yekan` — numbers / accent figures + - `Montserrat` via `next/font/google` (English labels, class `.en`) + +## Color palette (from live site `:root`) +- Primary: `#19377d` +- Secondary / accent: `#34ace0` +- Header: `#23242D` +- Page background: `#1F2128` / `#23262D` +- Muted text: `#8F8F8F` +- White: `#FFFFFF` + +## Pages +| Route | Purpose | +|-------|---------| +| `/` | Homepage (hero, stats, intro, Aparat video, glass article, window types, why us, features, **latest blogs carousel**, CTA, **latest products carousel**, brands) | +| `/about` | درباره ما | +| `/contact` | تماس با ما (info + FAQ + Meshkee contact form) | +| `/products` | Meshkee product catalog (`GET /tenants/{domain}/products`) | +| `/products/category/{id}/{nameFaSlug}` | Product category (`GET /tenants/{domain}/categories/by-id/{id}`) | +| `/products/{id}/{nameFaSlug}` | Product details (gallery, zoom, tabs, comments, similar products) | +| `/blog` | Meshkee blog list (`GET /tenants/{domain}/blogs`) | +| `/blog/{id}/{titleSlug}` | Blog details (body HTML + comments) | +| `/portfolio` | Meshkee portfolio list (`GET /tenants/{domain}/portfolios`) | +| `/portfolios/{id}/{titleFaSlug}` | Portfolio details (`GET /tenants/{domain}/portfolios/by-id/{id}`) | +| `/videos` | Placeholder — videos from Meshkee later | + +Header: logo + phones + **AccountMenu** + hamburger overlay (image panel + RTL menu). Cart still in the hamburger. + +## Auth / account menu +- Login / user icon → `https://customer.farrokhwin.com` (`customerPortalUrl` in `src/lib/config.ts`) +- Shared session cookies: `src/lib/shared-auth-cookie.ts` + `src/lib/session.tsx` +- `GET /api/auth/me` proxies Meshkee `/auth/me` +- Logged in: show `{name} عزیز` with dropdown **پروفایل من**, **خروج**, and if website admin **مدیریت وبسایت** +- Admin handoff: `POST /api/auth/handoff` → `business.farrokhwin.com?handoff=…` +- Admin = super_admin or membership on business id `28` (`isOwner` or `teamRole`) +- Comments: form only when authenticated; otherwise a login box linking to the customer portal. `POST /api/comments` → Meshkee tenant comments + +## Meshkee API +Base: `{NEXT_PUBLIC_MESHKEE_API_BASE}/tenants/{domain}/...` +Helpers: `src/lib/meshkee.ts` → `meshkeeGet`, `meshkeeGetOrNull`, `tenantPath`, `getTenant`, `getBusinessId` + +| Resource | List | Detail | +|----------|------|--------| +| Products | `GET /tenants/{domain}/products` (`src/lib/products.ts`) | `GET /tenants/{domain}/products/by-id/{id}` + `/technical-info` | +| Blogs | `GET /tenants/{domain}/blogs` (`src/lib/blog.ts`) | `GET /tenants/{domain}/blogs/by-id/{id}` | +| Comments | `GET /tenants/{domain}/comments?entityType=&entityId=` | `POST` via `/api/comments` | +| Portfolio | `GET /tenants/{domain}/portfolios` | not wired yet | +| Contact | `POST /tenants/{domain}/contact-submissions` via `POST /api/contact` | Body: `{ title, name, text, email?, cellNumber? }` | + +Canonical URLs: +- Product: `/products/{id}/{nameFaSlug}` (`productHref` / `slugifyForUrl`) +- Blog: `/blog/{id}/{titleSlug}` (`blogHref`) + +Product details: +- Lightbox gallery + hover zoom (`src/components/ProductGallery.tsx`) +- Tabs: توضیحات / مشخصات فنی / نظرات (`src/components/ProductTabs.tsx`) +- Similar products from `relatedProducts`, else other catalog items + +Homepage carousels (`src/components/MediaCarousel.tsx`): latest blogs and latest products, **max 12** each. API items are primary; static slot images overlay by index when present. + +## Static image slots +API: `GET /tenants/{domain}/website/static-images?pageKey=home|about|contact` +Catalog: `GET /meshkee/static-image-slots` → `STATIC_IMAGE_SLOTS` in `src/lib/static-images.ts` + +| key | kind | itemCount | aspectRatio | recommendedWidth | section | +| --- | --- | --- | --- | --- | --- | +| `slider` | list | null | `1920:1080` | 1920 | Homepage hero banners | +| `home-video-poster` | single | 1 | `16:9` | 1280 | Homepage video | +| `home-windows` | list | 4 | `12:17` | 720 | Window type cards | +| `home-why-image` | single | 1 | `1:1` | 640 | Why Farrokh graphic | +| `home-blog` | list | 12 | `1:1` | 720 | Homepage latest-blog carousel (square overlay cards) | +| `home-products` | list | 12 | `4:3` | 640 | Homepage latest-products carousel (equal 4:3 boxes, contain) | +| `home-clients` | list | 4 | `3:2` | 320 | Brand logos | +| `about-hero` | single | 1 | `1920:480` | 1440 | About hero (currently CSS navy band) | +| `about-photo` | single | 1 | `4:3` | 800 | About photo | +| `contact-hero` | single | 1 | `1920:480` | 1440 | Contact hero band | +| `contact-faq-image` | single | 1 | `4:3` | 960 | Contact FAQ photo | + +Rules: +- Use `slot.images` (all if `kind=list`, first if `kind=single`) +- `url` → image src; `linkUrl` → wrap in link +- Empty / fail → local fallbacks from `src/data/site.ts` / `public/images/` +- Homepage blog/product carousels keep Meshkee API items (ids, titles, hrefs); slot images only replace `image` at the same index +- **If a slot’s on-page size or aspect ratio changes in CSS, update `STATIC_IMAGE_SLOTS` in the same change** + +## Content & assets +- Site copy, phones, addresses, FAQs: `src/data/site.ts` +- Logo: `public/images/logo/logo.png` (from live `kC9gmfo7ef.png`) +- Hero banners: `public/images/hero/slide-1.jpg` … `slide-3.jpg` +- Favicon: `/favicon.png` +- Meshkee footer credit (same as safeteb.com): `public/images/footer/MeshkeeLogo-White.png` +- Contact: `02538203597` · `09002032333` · `02533342698` +- Office: قم، بلوار امام رضا(ع)، مجتمع فردوس، واحد ۱۳۸ +- Factory: قم، شهرک صنعتی شکوهیه، فاز دو، خیابان فکوری، کوچه ۲، پلاک KH214 +- Social: Instagram / Telegram `@farrokhwin`, WhatsApp `989002032333` +- Homepage video: Aparat `w21jpe0` via official embed script (`src/components/AparatVideo.tsx`) + +## Important paths +- Layout / shell: `src/app/layout.tsx`, `src/components/Header.tsx`, `src/components/Footer.tsx`, `src/components/Providers.tsx` +- Account: `src/components/AccountMenu.tsx`, `src/lib/session.tsx` +- Home: `src/app/page.tsx`, `src/components/HeroSlider.tsx`, `src/components/MediaCarousel.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` +- About / contact: `src/app/about/page.tsx`, `src/app/contact/page.tsx` +- Config: `src/lib/config.ts` + +## Env +```bash +NEXT_PUBLIC_WEBSITE_DOMAIN=farrokhwin.com +NEXT_PUBLIC_MESHKEE_API_BASE=https://api.meshkee.com/api/v1 +NEXT_PUBLIC_CUSTOMER_PORTAL_URL=https://customer.farrokhwin.com +NEXT_PUBLIC_BUSINESS_ORIGIN=https://business.farrokhwin.com +``` + +## 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) +- Publish `GET /meshkee/sitemap-config.json` (generated at build from app routes) +- 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 +- Dynamic `/products/{id}/{slug}`, `/products/category/{id}/{slug}`, `/blog/{id}/{slug}`, and `/portfolios/{id}/{slug}` are not listed in the static sitemap config; Meshkee CMS syncs those URLs + +### On-page SEO rules +- Unique `` + meta description on every crawlable page (`src/lib/seo.ts` → `pageMetadata`) +- Exactly one `<h1>` per page; section headings use `<h2>` / `<h3>` +- Content images have meaningful `alt` +- Footer bottom Meshkee credit must stay in the safeteb.com style (animated frame + white logo + “Designed And Developed By Meshkee E-Commerce Team”) diff --git a/eslint.config.mjs b/eslint.config.mjs new file mode 100644 index 0000000..f40b270 --- /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..f40be10 --- /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..f6849f3 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,6263 @@ +{ + "name": "farrokhwin", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "farrokhwin", + "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..594c08a --- /dev/null +++ b/package.json @@ -0,0 +1,29 @@ +{ + "name": "farrokhwin", + "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..bc52b4a --- /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..e0c14cd Binary files /dev/null and b/public/favicon.png differ diff --git a/public/fonts/iran-yekan/iran-yekan-bold.ttf b/public/fonts/iran-yekan/iran-yekan-bold.ttf new file mode 100644 index 0000000..6d98764 Binary files /dev/null and b/public/fonts/iran-yekan/iran-yekan-bold.ttf differ diff --git a/public/fonts/iran-yekan/iran-yekan-light.ttf b/public/fonts/iran-yekan/iran-yekan-light.ttf new file mode 100644 index 0000000..b6114ee Binary files /dev/null and b/public/fonts/iran-yekan/iran-yekan-light.ttf differ diff --git a/public/fonts/iran-yekan/iran-yekan-medium.ttf b/public/fonts/iran-yekan/iran-yekan-medium.ttf new file mode 100644 index 0000000..cc9d9b4 Binary files /dev/null and b/public/fonts/iran-yekan/iran-yekan-medium.ttf differ diff --git a/public/fonts/iran-yekan/iran-yekan-regular.ttf b/public/fonts/iran-yekan/iran-yekan-regular.ttf new file mode 100644 index 0000000..c662408 Binary files /dev/null and b/public/fonts/iran-yekan/iran-yekan-regular.ttf differ diff --git a/public/fonts/montserrat/montserrat.css b/public/fonts/montserrat/montserrat.css new file mode 100644 index 0000000..2ba3503 --- /dev/null +++ b/public/fonts/montserrat/montserrat.css @@ -0,0 +1,59 @@ +@font-face { + font-family: "montserrat"; + src: url('https://cdn.meshkee.com/web-builder/fonts/montserrat/montserrat-regular.ttf'); +} + +@font-face { + font-family: "montserrat-medium"; + src: url('https://cdn.meshkee.com/web-builder/fonts/montserrat/montserrat-medium.ttf'); +} + +@font-face { + font-family: "montserrat-light"; + src: url('https://cdn.meshkee.com/web-builder/fonts/montserrat/montserrat-light.ttf'); +} + +@font-face { + font-family: "montserrat-extralight"; + src: url('https://cdn.meshkee.com/web-builder/fonts/montserrat/montserrat-extralight.ttf'); +} + +@font-face { + font-family: "montserrat-bold"; + src: url('https://cdn.meshkee.com/web-builder/fonts/montserrat/montserrat-bold.ttf'); +} + +@font-face { + font-family: "montserrat-semibold"; + src: url('https://cdn.meshkee.com/web-builder/fonts/montserrat/montserrat-semibold.ttf'); +} + +@font-face { + font-family: "montserrat-extrabold"; + src: url('https://cdn.meshkee.com/web-builder/fonts/montserrat/montserrat-extrabold.ttf'); +} + +@font-face { + font-family: "montserrat-thin"; + src: url('https://cdn.meshkee.com/web-builder/fonts/montserrat/montserrat-thin.ttf'); +} + +@font-face { + font-family: "montserrat-black"; + src: url('https://cdn.meshkee.com/web-builder/fonts/montserrat/montserrat-black.ttf'); +} + +@font-face { + font-family: "montserrat-alternates-regular"; + src: url('https://cdn.meshkee.com/web-builder/fonts/montserrat/MontserratAlternates-Regular.ttf'); +} + +@font-face { + font-family: "montserrat-alternates-light"; + src: url('https://cdn.meshkee.com/web-builder/fonts/montserrat/MontserratAlternates-Light.ttf'); +} + +@font-face { + font-family: "montserrat-alternates-extrabold"; + src: url('https://cdn.meshkee.com/web-builder/fonts/montserrat/MontserratAlternates-ExtraBold.ttf'); +} \ No newline at end of file diff --git a/public/fonts/morabba/Morabba-Black.ttf b/public/fonts/morabba/Morabba-Black.ttf new file mode 100644 index 0000000..da1b0cc Binary files /dev/null and b/public/fonts/morabba/Morabba-Black.ttf differ diff --git a/public/fonts/morabba/Morabba-Bold.ttf b/public/fonts/morabba/Morabba-Bold.ttf new file mode 100644 index 0000000..b362cca Binary files /dev/null and b/public/fonts/morabba/Morabba-Bold.ttf differ diff --git a/public/fonts/morabba/Morabba-Light.ttf b/public/fonts/morabba/Morabba-Light.ttf new file mode 100644 index 0000000..c74c9a0 Binary files /dev/null and b/public/fonts/morabba/Morabba-Light.ttf differ diff --git a/public/fonts/morabba/Morabba-Medium.ttf b/public/fonts/morabba/Morabba-Medium.ttf new file mode 100644 index 0000000..ae7b641 Binary files /dev/null and b/public/fonts/morabba/Morabba-Medium.ttf differ diff --git a/public/fonts/morabba/Morabba-Regular.ttf b/public/fonts/morabba/Morabba-Regular.ttf new file mode 100644 index 0000000..f5021df Binary files /dev/null and b/public/fonts/morabba/Morabba-Regular.ttf differ diff --git a/public/fonts/morabba/Morabba-SemiBold.ttf b/public/fonts/morabba/Morabba-SemiBold.ttf new file mode 100644 index 0000000..5e80baa Binary files /dev/null and b/public/fonts/morabba/Morabba-SemiBold.ttf differ diff --git a/public/images/about/hero.jpg b/public/images/about/hero.jpg new file mode 100644 index 0000000..bb993a5 Binary files /dev/null and b/public/images/about/hero.jpg differ diff --git a/public/images/about/icon.png b/public/images/about/icon.png new file mode 100644 index 0000000..16daa9d Binary files /dev/null and b/public/images/about/icon.png differ diff --git a/public/images/about/info.jpg b/public/images/about/info.jpg new file mode 100644 index 0000000..85c5862 Binary files /dev/null and b/public/images/about/info.jpg differ diff --git a/public/images/about/photo.jpg b/public/images/about/photo.jpg new file mode 100644 index 0000000..0281d17 Binary files /dev/null and b/public/images/about/photo.jpg differ diff --git a/public/images/blog/laminate.jpg b/public/images/blog/laminate.jpg new file mode 100644 index 0000000..14ab2e3 Binary files /dev/null and b/public/images/blog/laminate.jpg differ diff --git a/public/images/blog/placeholder.jpg b/public/images/blog/placeholder.jpg new file mode 100644 index 0000000..a30d78f Binary files /dev/null and b/public/images/blog/placeholder.jpg differ diff --git a/public/images/blog/sound.jpg b/public/images/blog/sound.jpg new file mode 100644 index 0000000..79cabea Binary files /dev/null and b/public/images/blog/sound.jpg differ diff --git a/public/images/blog/stained.jpg b/public/images/blog/stained.jpg new file mode 100644 index 0000000..cbd044f Binary files /dev/null and b/public/images/blog/stained.jpg differ diff --git a/public/images/brands/hoffman.jpg b/public/images/brands/hoffman.jpg new file mode 100644 index 0000000..93d233a Binary files /dev/null and b/public/images/brands/hoffman.jpg differ diff --git a/public/images/brands/kale.jpg b/public/images/brands/kale.jpg new file mode 100644 index 0000000..2b9e0a7 Binary files /dev/null and b/public/images/brands/kale.jpg differ diff --git a/public/images/brands/pluspen.jpg b/public/images/brands/pluspen.jpg new file mode 100644 index 0000000..a746acb Binary files /dev/null and b/public/images/brands/pluspen.jpg differ diff --git a/public/images/brands/wintech.jpg b/public/images/brands/wintech.jpg new file mode 100644 index 0000000..303fbae Binary files /dev/null and b/public/images/brands/wintech.jpg differ diff --git a/public/images/contact/address.png b/public/images/contact/address.png new file mode 100644 index 0000000..8085976 Binary files /dev/null and b/public/images/contact/address.png differ diff --git a/public/images/contact/hero.jpg b/public/images/contact/hero.jpg new file mode 100644 index 0000000..e8242d2 Binary files /dev/null and b/public/images/contact/hero.jpg differ diff --git a/public/images/contact/phone.png b/public/images/contact/phone.png new file mode 100644 index 0000000..50cc359 Binary files /dev/null and b/public/images/contact/phone.png differ diff --git a/public/images/contact/social.png b/public/images/contact/social.png new file mode 100644 index 0000000..d1c87c4 Binary files /dev/null and b/public/images/contact/social.png differ diff --git a/public/images/features/01.png b/public/images/features/01.png new file mode 100644 index 0000000..1834a6f Binary files /dev/null and b/public/images/features/01.png differ diff --git a/public/images/features/02.png b/public/images/features/02.png new file mode 100644 index 0000000..d3dd015 Binary files /dev/null and b/public/images/features/02.png differ diff --git a/public/images/features/03.png b/public/images/features/03.png new file mode 100644 index 0000000..e6a3c9f Binary files /dev/null and b/public/images/features/03.png differ diff --git a/public/images/features/04.png b/public/images/features/04.png new file mode 100644 index 0000000..7d48e01 Binary files /dev/null and b/public/images/features/04.png differ diff --git a/public/images/features/05.png b/public/images/features/05.png new file mode 100644 index 0000000..9fbf63f Binary files /dev/null and b/public/images/features/05.png differ diff --git a/public/images/features/06.png b/public/images/features/06.png new file mode 100644 index 0000000..f43790f Binary files /dev/null and b/public/images/features/06.png differ diff --git a/public/images/features/07.png b/public/images/features/07.png new file mode 100644 index 0000000..edec205 Binary files /dev/null and b/public/images/features/07.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/hero/slide-1.jpg b/public/images/hero/slide-1.jpg new file mode 100644 index 0000000..f9c9884 Binary files /dev/null and b/public/images/hero/slide-1.jpg differ diff --git a/public/images/hero/slide-2.jpg b/public/images/hero/slide-2.jpg new file mode 100644 index 0000000..1a43e3d Binary files /dev/null and b/public/images/hero/slide-2.jpg differ diff --git a/public/images/hero/slide-3.jpg b/public/images/hero/slide-3.jpg new file mode 100644 index 0000000..8dd7244 Binary files /dev/null and b/public/images/hero/slide-3.jpg differ diff --git a/public/images/hero/slider13-1.jpg b/public/images/hero/slider13-1.jpg new file mode 100644 index 0000000..be25f5d Binary files /dev/null and b/public/images/hero/slider13-1.jpg differ diff --git a/public/images/logo/logo.png b/public/images/logo/logo.png new file mode 100644 index 0000000..e0c14cd Binary files /dev/null and b/public/images/logo/logo.png differ diff --git a/public/images/menu/header-8.jpg b/public/images/menu/header-8.jpg new file mode 100644 index 0000000..67b4484 Binary files /dev/null and b/public/images/menu/header-8.jpg differ diff --git a/public/images/menu/panel.jpg b/public/images/menu/panel.jpg new file mode 100644 index 0000000..d0a50b3 Binary files /dev/null and b/public/images/menu/panel.jpg differ diff --git a/public/images/products/double-glaze.jpg b/public/images/products/double-glaze.jpg new file mode 100644 index 0000000..2e1425b Binary files /dev/null and b/public/images/products/double-glaze.jpg differ diff --git a/public/images/products/sliding.jpg b/public/images/products/sliding.jpg new file mode 100644 index 0000000..0afdd44 Binary files /dev/null and b/public/images/products/sliding.jpg differ diff --git a/public/images/products/upvc.jpg b/public/images/products/upvc.jpg new file mode 100644 index 0000000..a02c823 Binary files /dev/null and b/public/images/products/upvc.jpg differ diff --git a/public/images/windows/curved.jpg b/public/images/windows/curved.jpg new file mode 100644 index 0000000..3fd02c4 Binary files /dev/null and b/public/images/windows/curved.jpg differ diff --git a/public/images/windows/sliding.jpg b/public/images/windows/sliding.jpg new file mode 100644 index 0000000..cb9cf14 Binary files /dev/null and b/public/images/windows/sliding.jpg differ diff --git a/public/images/windows/tilt-turn.jpg b/public/images/windows/tilt-turn.jpg new file mode 100644 index 0000000..02a8fb5 Binary files /dev/null and b/public/images/windows/tilt-turn.jpg differ diff --git a/public/images/windows/upvc.jpg b/public/images/windows/upvc.jpg new file mode 100644 index 0000000..fa0cbbf Binary files /dev/null and b/public/images/windows/upvc.jpg differ diff --git a/scripts/generate-sitemap-config.mjs b/scripts/generate-sitemap-config.mjs new file mode 100644 index 0000000..3acfc50 --- /dev/null +++ b/scripts/generate-sitemap-config.mjs @@ -0,0 +1,100 @@ +/** + * 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 }, + "/portfolio": { changefreq: "weekly", priority: 0.7 }, + "/blog": { changefreq: "weekly", priority: 0.7 }, + "/videos": { changefreq: "weekly", priority: 0.6 }, +}; + +function websiteDomain() { + return ( + process.env.NEXT_PUBLIC_WEBSITE_DOMAIN || + process.env.WEBSITE_DOMAIN || + process.env.NEXT_PUBLIC_MESHKEE_DOMAIN || + "farrokhwin.com" + ); +} + +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..b6cbb84 --- /dev/null +++ b/src/app/about/page.tsx @@ -0,0 +1,119 @@ +import Link from "next/link"; + +import PageHero from "@/components/PageHero"; +import Reveal from "@/components/Reveal"; +import SlotImage from "@/components/SlotImage"; +import { aboutGoals, site } from "@/data/site"; +import { pageMetadata } from "@/lib/seo"; +import { fetchStaticImageSlots, overlaySlotImages, pickSlot } from "@/lib/static-images"; + +export const metadata = pageMetadata({ + title: "درباره ما", + description: site.tagline, + path: "/about", + image: "/images/about/hero.jpg", +}); + +function GoalIcon({ name }: { name: string }) { + if (name === "star") { + return ( + <svg width="28" height="28" viewBox="0 0 24 24" fill="none" aria-hidden> + <path + d="M12 3.5l2.4 4.9 5.4.8-3.9 3.8.9 5.4L12 15.8 7.2 18.4l.9-5.4L4.2 9.2l5.4-.8L12 3.5z" + stroke="currentColor" + strokeWidth="1.5" + strokeLinejoin="round" + /> + </svg> + ); + } + if (name === "diamond") { + return ( + <svg width="28" height="28" viewBox="0 0 24 24" fill="none" aria-hidden> + <path d="M4 9l8 11 8-11-3.5-5h-9L4 9z" stroke="currentColor" strokeWidth="1.5" strokeLinejoin="round" /> + <path d="M4 9h16M8.5 4L12 9l3.5-5" stroke="currentColor" strokeWidth="1.5" strokeLinejoin="round" /> + </svg> + ); + } + return ( + <svg width="28" height="28" viewBox="0 0 24 24" fill="none" aria-hidden> + <path + d="M4 11.5c0-2.8 2.1-5 5-5 1.4 0 2.6.6 3.5 1.6.9-1 2.1-1.6 3.5-1.6 2.9 0 5 2.2 5 5 0 5.2-8.5 9.5-8.5 9.5S4 16.7 4 11.5z" + stroke="currentColor" + strokeWidth="1.5" + strokeLinejoin="round" + /> + </svg> + ); +} + +export default async function AboutPage() { + const slots = await fetchStaticImageSlots("about"); + const photo = overlaySlotImages( + pickSlot(slots, "about-photo"), + [{ id: "about-photo", image: "/images/windows/upvc.jpg", title: site.slogan }], + "about-photo", + ); + const introText = site.footerAbout.split("\n")[0]; + + return ( + <> + <PageHero + title="درباره مـــــــا بیشتـــــــر بدانید..." + en="ABOUT US" + crumbs={[ + { href: "/", label: "خانه" }, + { href: "/about", label: "درباره ما" }, + ]} + /> + + <section className="section about-intro"> + <div className="container about-intro-grid"> + <Reveal variant="right"> + <div className="about-photo"> + <SlotImage + src={photo.items[0]?.image || "/images/windows/upvc.jpg"} + alt={photo.items[0]?.title || site.shortName} + fill + /> + </div> + </Reveal> + <Reveal variant="left" delay={120}> + <div> + <h2>{site.slogan}</h2> + <p>{introText}</p> + <Link className="text-link" href="/contact"> + تماس با ما + </Link> + </div> + </Reveal> + </div> + </section> + + <section className="section goals-section"> + <div className="container"> + <h2>هدف های شرکت ما</h2> + <p className="en">TARGET</p> + <div className="goals-layout"> + <div className="goals-graphic"> + <SlotImage src="/images/about/icon.png" alt="پنجره UPVC" fill /> + </div> + <div className="goals-list"> + {aboutGoals.map((goal) => ( + <article key={goal.title} className="goal-item"> + <span className="goal-icon"> + <GoalIcon name={goal.icon} /> + </span> + <div> + <h3>{goal.title}</h3> + <p>{goal.text}</p> + </div> + </article> + ))} + </div> + </div> + </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..a9fbfda --- /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..3a5c275 --- /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..59d8fdd --- /dev/null +++ b/src/app/api/comments/route.ts @@ -0,0 +1,37 @@ +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 auth = request.headers.get("authorization"); + const headers: Record<string, string> = { "Content-Type": "application/json" }; + if (auth) headers.Authorization = auth; + + const response = await fetch(`${meshkeeApiBase}/tenants/${encodeURIComponent(websiteDomain)}/comments`, { + method: "POST", + headers, + 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..cb0e1f2 --- /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..c0c2e30 --- /dev/null +++ b/src/app/blog/[id]/[slug]/page.tsx @@ -0,0 +1,107 @@ +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"> + <div className="container page-hero-inner"> + <div className="page-hero-en"> + <span>ARTICLE</span> + <i /> + </div> + <div className="page-hero-fa"> + <nav className="crumbs"> + <Link href="/">خانه</Link> + {" > "} + <Link href="/blog">مقالات</Link> + {" > "} + <span>{post.title}</span> + </nav> + <h1>{post.title}</h1> + {blogDate(post) ? <p className="muted">{blogDate(post)}</p> : null} + </div> + </div> + </section> + + <section className="section"> + <div className="container detail-article"> + {blogImage(post) ? ( + <div className="article-cover"> + <SlotImage src={blogImage(post)} alt={post.title} /> + </div> + ) : null} + {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="text-link"> + بازگشت به مقالات + </Link> + </div> + </section> + </> + ); +} diff --git a/src/app/blog/page.tsx b/src/app/blog/page.tsx new file mode 100644 index 0000000..5578ec8 --- /dev/null +++ b/src/app/blog/page.tsx @@ -0,0 +1,100 @@ +import type { Metadata } from "next"; +import Link from "next/link"; + +import BlogCoverCard from "@/components/BlogCoverCard"; +import PageHero from "@/components/PageHero"; +import { + blogCategory, + blogDateLong, + blogHref, + blogImage, + fetchBlogs, + getLocalBlogPosts, +} from "@/lib/blog"; +import { pageMetadata } from "@/lib/seo"; + +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: "آخرین مقالات پنجره فرخ درباره UPVC، شیشه دوجداره و صنعت درب و پنجره.", + path: page > 1 ? `/blog?page=${page}` : "/blog", + }); +} + +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 = await fetchBlogs({ page, pageSize: 12 }); + 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 ( + <> + <PageHero + title="مقالات" + en="ARTICLES" + crumbs={[ + { href: "/", label: "خانه" }, + { href: "/blog", label: "مقالات" }, + ]} + /> + + <section className="section"> + <div className="container"> + {posts.length === 0 ? ( + <p className="comment-empty">مقاله‌ای برای نمایش وجود ندارد.</p> + ) : ( + <div className="blog-list-grid"> + {posts.map((post) => ( + <BlogCoverCard + key={post.id} + href={blogHref(post)} + image={blogImage(post)} + title={post.title} + category={blogCategory(post)} + date={blogDateLong(post)} + heading="h2" + /> + ))} + </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..c13764d --- /dev/null +++ b/src/app/contact/page.tsx @@ -0,0 +1,99 @@ +import ContactForm from "@/components/ContactForm"; +import FaqList from "@/components/FaqList"; +import PageHero from "@/components/PageHero"; +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.officeFull} تلفن ${site.phones.sales}`, + path: "/contact", + image: "/images/contact/hero.jpg", +}); + +export default async function ContactPage() { + const slots = await fetchStaticImageSlots("contact"); + const faqImage = overlaySlotImages( + pickSlot(slots, "contact-faq-image"), + [{ id: "faq", image: "/images/contact/hero.jpg", title: "تماس با ما" }], + "contact-faq-image", + ); + + return ( + <> + <PageHero + title="بـــــــا مـــــــــا تمــــــــاس بگیریـــــــــــد..." + en="CONTACT US" + crumbs={[ + { href: "/", label: "خانه" }, + { href: "/contact", label: "تماس با ما" }, + ]} + /> + + <section className="contact-info"> + <div className="container contact-info-grid"> + <article> + <SlotImage src="/images/contact/social.png" alt="شبکه‌های اجتماعی" width={48} height={48} /> + <h2>آدرس شبکه های اجتماعی</h2> + <a href={site.social.eitaa} target="_blank" rel="noopener noreferrer"> + {site.social.handle} + </a> + </article> + <article> + <SlotImage src="/images/contact/phone.png" alt="تلفن تماس" width={48} height={48} /> + <h2>تلفن تماس</h2> + <a className="contact-phones" href={`tel:+98${site.phones.mobile.replace(/^0/, "")}`}> + {site.phones.sales} + <br /> + {site.phones.landline} + </a> + </article> + <article> + <SlotImage src="/images/contact/address.png" alt="آدرس" width={48} height={48} /> + <h2>آدرس ما</h2> + <a href="#map"> + {site.officeFull} + <br /> + {site.factoryFull} + </a> + </article> + </div> + </section> + + <section className="faq-section"> + <div className="faq-split"> + <div className="faq-photo"> + <SlotImage + src={faqImage.items[0]?.image || "/images/contact/hero.jpg"} + alt="سوالات متداول پنجره فرخ" + fill + /> + </div> + <div className="faq-copy"> + <p className="section-kicker">سوال های متداول شما</p> + <h2>برخی از سوالات متداول شما مشتریان عزیز</h2> + <FaqList /> + </div> + </div> + </section> + + <section className="section contact-form-section"> + <div className="container"> + <ContactForm /> + </div> + </section> + + <section className="contact-map" id="map"> + <iframe + title="دفتر پنجره فرخ روی نقشه" + src="https://www.google.com/maps/embed?pb=!1m18!1m12!1m3!1d3276.7487061444917!2d50.81011399999999!3d34.787101!2m3!1f0!2f0!3f0!3m2!1i1024!2i768!4f13.1!3m3!1m2!1s0x3f93adf4ab52d83d%3A0x3d84ac6e554cfc6a!2z2b7Zhtis2LHZhyDZgdix2K4vRmFycm9raCBVUFZD!5e0!3m2!1sen!2s!4v1692164739157!5m2!1sen!2s" + loading="lazy" + referrerPolicy="no-referrer-when-downgrade" + allowFullScreen + /> + </section> + </> + ); +} diff --git a/src/app/globals.css b/src/app/globals.css new file mode 100644 index 0000000..04f53e3 --- /dev/null +++ b/src/app/globals.css @@ -0,0 +1,2442 @@ +@import "tailwindcss"; + +@font-face { + font-family: "morabba"; + src: url("/fonts/morabba/Morabba-Light.ttf") format("truetype"); + font-weight: 300; + font-style: normal; + font-display: swap; +} + +@font-face { + font-family: "morabba"; + src: url("/fonts/morabba/Morabba-Regular.ttf") format("truetype"); + font-weight: 400; + font-style: normal; + font-display: swap; +} + +@font-face { + font-family: "morabba"; + src: url("/fonts/morabba/Morabba-Medium.ttf") format("truetype"); + font-weight: 500; + font-style: normal; + font-display: swap; +} + +@font-face { + font-family: "morabba"; + src: url("/fonts/morabba/Morabba-SemiBold.ttf") format("truetype"); + font-weight: 600; + font-style: normal; + font-display: swap; +} + +@font-face { + font-family: "morabba"; + src: url("/fonts/morabba/Morabba-Bold.ttf") format("truetype"); + font-weight: 700; + font-style: normal; + font-display: swap; +} + +@font-face { + font-family: "morabba"; + src: url("/fonts/morabba/Morabba-Black.ttf") format("truetype"); + font-weight: 900; + font-style: normal; + font-display: swap; +} + +@font-face { + font-family: "iran-yekan"; + src: url("/fonts/iran-yekan/iran-yekan-light.ttf") format("truetype"); + font-weight: 300; + font-style: normal; + font-display: swap; +} + +@font-face { + font-family: "iran-yekan"; + src: url("/fonts/iran-yekan/iran-yekan-regular.ttf") format("truetype"); + font-weight: 400; + font-style: normal; + font-display: swap; +} + +@font-face { + font-family: "iran-yekan"; + src: url("/fonts/iran-yekan/iran-yekan-medium.ttf") format("truetype"); + font-weight: 500; + font-style: normal; + font-display: swap; +} + +@font-face { + font-family: "iran-yekan"; + src: url("/fonts/iran-yekan/iran-yekan-bold.ttf") format("truetype"); + font-weight: 700; + font-style: normal; + font-display: swap; +} + +:root { + --primary: #19377d; + --accent: #34ace0; + --header: #23242d; + --bg: #1f2128; + --bg-2: #23262d; + --white: #ffffff; + --muted: #8f8f8f; + --line: rgba(255, 255, 255, 0.18); + --font-fa: "morabba", "iran-yekan", Tahoma, sans-serif; + --font-en: var(--font-montserrat), Montserrat, sans-serif; +} + +* { + box-sizing: border-box; +} + +html, +body { + margin: 0; + padding: 0; + background: var(--bg); + color: var(--white); + font-family: var(--font-fa); + direction: rtl; +} + +a { + color: inherit; + text-decoration: none; +} + +img { + max-width: 100%; + display: block; +} + +button, +input, +textarea { + font-family: inherit; +} + +.wrapper { + min-height: 100vh; +} + +.container { + width: min(1180px, calc(100% - 40px)); + margin: 0 auto; +} + +.en { + font-family: var(--font-en); + letter-spacing: 4px; + color: var(--muted); + font-size: 12px; + 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; +} + +.muted { + color: var(--muted); + line-height: 2; + text-align: justify; +} + +.section { + padding: 80px 0; +} + +.section-kicker { + color: var(--muted); + margin: 0 0 8px; +} + +h1, +h2, +h3 { + font-weight: 500; + line-height: 1.5; +} + +.btn, +.text-link { + display: inline-block; + color: var(--accent); + border-bottom: 1px solid var(--accent); + padding-bottom: 4px; +} + +.btn { + margin-top: 20px; + padding: 10px 22px; + border: 1px solid var(--accent); + color: #fff; +} + +.site-header { + position: relative; + z-index: 40; +} + +.header-bar { + background: var(--header); +} + +.header-inner { + display: flex; + align-items: center; + justify-content: space-between; + min-height: 100px; + direction: ltr; +} + +.brand img { + height: 48px; + width: auto; +} + +.header-actions { + display: flex; + align-items: center; + gap: 18px; +} + +.header-tool { + display: inline-flex; + align-items: center; + justify-content: center; + gap: 8px; + min-height: 32px; + padding: 0 12px; + color: #fff; + background: transparent; + border: 1px solid var(--line); + cursor: pointer; + white-space: nowrap; + font-size: 13px; +} + +.header-tool:hover, +.header-login:hover { + border-color: var(--accent); + color: var(--accent); +} + +.header-login { + text-decoration: none; +} + +.header-phone { + color: #fff; + font-size: 15px; + font-weight: 500; + direction: rtl; +} + +.push-button { + width: 42px; + height: 32px; + background: transparent; + border: 0; + cursor: pointer; + display: flex; + flex-direction: column; + justify-content: space-between; + padding: 6px 0; +} + +.push-button span { + display: block; + height: 2px; + background: #fff; + transition: 0.25s ease; +} + +.push-button.is-open span:nth-child(1) { + transform: translateY(8px) rotate(45deg); +} + +.push-button.is-open span:nth-child(2) { + opacity: 0; +} + +.push-button.is-open span:nth-child(3) { + transform: translateY(-8px) rotate(-45deg); +} + +.hamburger-menu { + position: fixed; + inset: 0; + background: #000; + z-index: 50; + opacity: 0; + visibility: hidden; + pointer-events: none; + transition: opacity 0.3s ease, visibility 0.3s ease; +} + +.hamburger-menu.is-open { + opacity: 1; + visibility: visible; + pointer-events: auto; +} + +.close-menu { + position: absolute; + top: 24px; + left: 24px; + background: transparent; + border: 0; + color: #fff; + font-size: 42px; + cursor: pointer; + z-index: 2; +} + +.hamburger-grid { + display: grid; + grid-template-columns: 1fr 1fr; + height: 100%; +} + +.hamburger-cover { + background-size: cover; + background-position: center; +} + +.hamburger-panel { + display: flex; + flex-direction: column; + justify-content: center; + padding: 60px; +} + +.menu-list { + list-style: none; + margin: 0; + padding: 0; +} + +.menu-list a { + display: block; + font-size: 32px; + padding: 8px 0; +} + +.menu-list a.is-active, +.menu-list a:hover { + color: var(--accent); +} + +.hamburger-meta { + display: flex; + align-items: center; + gap: 24px; + margin-top: 32px; + color: var(--muted); +} + +.hero { + position: relative; + min-height: calc(100vh - 100px); + background: #111; + overflow: hidden; +} + +.hero-slide { + position: absolute; + inset: 0; + opacity: 0; + transition: opacity 0.8s ease; +} + +.hero-slide.is-active { + opacity: 1; +} + +.hero-nav { + position: absolute; + left: 24px; + top: 50%; + transform: translateY(-50%); + display: flex; + flex-direction: column; + background: #fff; + z-index: 2; +} + +.hero-nav button { + width: 42px; + height: 46px; + border: 0; + background: #fff; + color: #111; + font-size: 22px; + cursor: pointer; +} + +.stats-bar { + background: #2a3144; + padding: 28px 0; +} + +.stats-inner { + display: grid; + grid-template-columns: repeat(3, 1fr); + text-align: center; +} + +.stats-inner div { + padding: 8px 16px; + border-left: 1px solid var(--line); +} + +.stats-inner div:last-child { + border: 0; +} + +.stats-inner strong { + display: block; + font-size: 28px; + margin-bottom: 6px; +} + +.stats-inner div:not(:last-child) strong { + font-family: "iran-yekan", sans-serif; + font-weight: 500; +} + +.intro-section, +.video-section, +.glass-section, +.windows-section, +.why-section, +.features-section, +.blog-section, +.brands-section { + background: var(--bg); +} + +.intro-section { + background: #23262d; + padding: 90px 0 70px; +} + +.intro-grid { + display: grid; + grid-template-columns: 0.9fr 0.9fr 1.4fr; + gap: 40px; + align-items: center; +} + +.years-box { + position: relative; + display: inline-flex; + align-items: center; + justify-content: center; + gap: 15px; + padding: 3.5rem; + min-height: 170px; +} + +.years-frame { + position: absolute; + top: 50%; + width: 67px; + height: 100%; + border: 10px solid rgba(255, 255, 255, 0.1); + transform: translateY(-50%); + pointer-events: none; +} + +.years-frame--left { + left: 0; + border-right: 0; +} + +.years-frame--right { + right: 0; + left: auto; + border-left: 0; +} + +.years-num { + font-family: "iran-yekan", sans-serif; + color: #34ace0; + font-size: 80px; + font-weight: 600; + line-height: 72px; + letter-spacing: 0; +} + +.years-label { + width: 90px; + color: #fff; + font-size: 21px; + line-height: 24px; + position: relative; + top: -4px; +} + +.intro-brand h1 { + font-size: 20px; + font-weight: 500; + line-height: 32px; + margin: 0; +} + +.from-year { + color: var(--muted); + margin: 0 0 8px; + text-align: center; +} + +.intro-brand { + text-align: center; +} + +.video-heading { + background: #fff; + padding: 56px 0 48px; +} + +.video-heading h2 { + margin: 0 0 28px; + color: #23242d; + font-size: 28px; + font-weight: 600; + letter-spacing: -0.5px; + text-align: center; +} + +.aparat-frame { + position: relative; + width: 100%; + padding-top: 57%; +} + +.aparat-frame > div, +.aparat-frame [id^="aparat"], +.aparat-frame .h_iframe-aparat_embed_frame { + position: absolute; + inset: 0; + width: 100%; + height: 100%; +} + +.aparat-frame .h_iframe-aparat_embed_frame > span { + display: none; +} + +.aparat-frame iframe { + position: absolute; + inset: 0; + width: 100%; + height: 100%; + border: 0; +} + +.windows-section { + background: #23262d; + padding-bottom: 40px; +} + +.windows-section .section-kicker, +.windows-section h2 { + text-align: center; +} + +.windows-section h2 { + color: #fff; + font-size: 28px; + margin: 0 0 40px; +} + +.windows-wrap { + width: 100%; + padding: 0 30px; +} + +.windows-grid { + display: grid; + grid-template-columns: repeat(4, 1fr); + gap: 30px; + direction: ltr; +} + +.window-card { + overflow: hidden; + direction: rtl; +} + +.window-card .window-photo { + position: relative; + overflow: hidden; +} + +.window-card .window-photo img { + width: 100%; + height: auto; + display: block; + object-fit: unset; + transition-duration: 2s; +} + +.window-card:hover .window-photo img { + transform: scale(1.4); + transition-duration: 4s; +} + +.window-dim { + position: absolute; + inset: 0; + background: rgba(0, 0, 0, 0.2); + z-index: 1; +} + +.window-content { + position: absolute; + inset: 0; + z-index: 2; + padding: 4rem; + display: flex; + flex-direction: column; + align-items: flex-start; +} + +.window-content h3 { + margin: 0; + width: 60%; + font-size: 19px; + font-weight: 500; + color: #fff; + position: relative; + z-index: 4; +} + +.window-arrow { + position: absolute; + left: 60px; + bottom: 60px; + width: 40px; + height: 40px; + border-radius: 50%; + background: #fff; + color: #23242d; + display: inline-flex; + align-items: center; + justify-content: center; + transition: opacity 0.5s ease, transform 0.5s ease; +} + +.window-hover { + position: absolute; + inset: 0; + z-index: 3; + display: flex; + flex-direction: column; + justify-content: flex-end; + background: linear-gradient( + to bottom, + rgba(255, 255, 255, 0) 0%, + rgba(0, 0, 0, 0.18) 18%, + rgba(0, 0, 0, 0.95) 100% + ); + opacity: 0; + bottom: -25px; + transition: opacity 0.5s ease, bottom 0.5s ease; +} + +.window-hover-inner { + padding: 4rem; + width: 100%; +} + +.window-hover p { + color: #939393; + font-size: 15px; + line-height: 26px; + text-align: justify; + margin: 0 0 10px; + display: -webkit-box; + -webkit-line-clamp: 3; + -webkit-box-orient: vertical; + overflow: hidden; + width: 85%; +} + +.window-details { + display: inline-flex; + align-items: center; + gap: 6px; + color: var(--accent); + font-size: 15px; + font-weight: 500; +} + +.window-details svg { + flex-shrink: 0; +} + +.window-card:hover .window-hover { + opacity: 1; + bottom: 0; +} + +.window-card:hover .window-arrow { + opacity: 0; + transform: scale(0); +} + +.why-section { + position: relative; + overflow: hidden; + background: #1f2128; + padding: 90px 0; +} + +.why-watermark { + position: absolute; + left: -50px; + top: 50%; + transform: translateY(-50%); + font-family: var(--font-en); + font-size: 141px; + font-weight: 400; + letter-spacing: -7px; + color: #fff; + opacity: 0.2; + line-height: 200px; + pointer-events: none; + user-select: none; + white-space: nowrap; +} + +.why-grid { + display: grid; + grid-template-columns: 2fr 5fr 4fr; + gap: 24px; + align-items: center; + position: relative; + z-index: 1; + direction: ltr; +} + +.why-grid > :first-child { + grid-column: 2; +} + +.why-grid > :last-child { + grid-column: 3; +} + +.why-copy { + direction: rtl; + text-align: right; +} + +.why-copy h2 { + font-size: 35px; + font-weight: 500; + margin: 0 0 18px; + text-align: justify; +} + +.why-copy p { + color: var(--muted); + line-height: 2; + text-align: justify; + font-size: 15px; + margin-bottom: 20px; +} + +.why-link { + color: #8f8f8f; + border-bottom-color: #8f8f8f; +} + +.why-photo img { + margin: 0 auto; + max-width: 458px; + width: 100%; + height: auto; +} + +.glass-section { + padding-top: 48px; +} + +.glass-box { + height: 200px; + overflow: hidden; + text-align: justify; +} + +.glass-box.is-open { + height: auto; + overflow: visible; +} + +.glass-lead { + margin: 0 0 1.5rem; + color: #fff; + font-size: 16px; +} + +.glass-ellipse.is-clamped { + overflow: hidden; + display: -webkit-box; + -webkit-line-clamp: 6; + -webkit-box-orient: vertical; +} + +.glass-box article { + margin-top: 22px; +} + +.glass-box article:first-child { + margin-top: 0; +} + +.glass-box h3 { + margin: 0 0 8px; + font-size: 16px; + font-weight: 700; +} + +.glass-box p, +.glass-box li { + color: var(--muted); + line-height: 2; +} + +.glass-box ul { + margin: 8px 0 0; + padding: 0; + list-style: none; +} + +.glass-more-wrap { + margin-top: 24px; + display: flex; + justify-content: flex-end; +} + +.see-more { + display: inline-flex; + align-items: center; + gap: 6px; + background: none; + border: 0; + padding: 0; + color: var(--muted); + font-size: 14px; + cursor: pointer; +} + +.see-more svg { + transition: transform 0.25s ease; +} + +.see-more svg.is-open { + transform: rotate(180deg); +} + +.windows-grid, +.blog-grid, +.products-grid, +.features-grid, +.brands-grid, +.goals-grid, +.about-stats, +.contact-info-grid { + display: grid; + gap: 28px; +} + +.blog-grid { + grid-template-columns: repeat(4, 1fr); +} + +.products-grid, +.goals-grid, +.features-grid, +.brands-grid, +.contact-info-grid, +.about-stats { + grid-template-columns: repeat(3, 1fr); +} + +.features-grid, +.brands-grid { + grid-template-columns: repeat(6, 1fr); +} + +.brands-grid { + grid-template-columns: repeat(4, 1fr); + align-items: center; +} + +.window-photo, +.faq-photo, +.about-photo { + position: relative; + overflow: hidden; + background: #111; +} + +.blog-card:not(.blog-card--cover) h3, +.product-card h3 { + margin: 16px 0 8px; + font-size: 18px; +} + +.product-card p { + color: var(--muted); +} + +.blog-photo { + position: relative; + overflow: hidden; + background: #111; + aspect-ratio: 16 / 10; +} + +.about-intro-grid, +.faq-grid { + display: grid; + grid-template-columns: 1.1fr 0.9fr; + gap: 48px; + align-items: center; +} + +.about-intro-grid { + direction: ltr; + align-items: stretch; +} + +.about-intro-grid > *:first-child, +.about-photo { + height: 100%; + min-height: 520px; +} + +.about-intro-grid > *:last-child { + direction: rtl; + text-align: right; + display: flex; + align-items: center; +} + +.about-photo { + position: relative; + overflow: hidden; + border-radius: 0; +} + +.why-photo img { + margin: 0 auto; +} + +.features-grid article { + text-align: center; +} + +.features-grid img { + margin: 0 auto 12px; + height: 56px; + width: auto; +} + +.cta-section { + background: var(--primary); + text-align: center; + padding: 48px 0; +} + +.cta-section a { + color: var(--accent); + border-bottom: 1px solid var(--accent); +} + +.page-hero { + position: relative; + isolation: isolate; + overflow: hidden; + background-color: #343840; + padding: 70px 0 50px; +} + +.page-hero::before { + content: ""; + position: absolute; + inset: 0; + z-index: 0; + pointer-events: none; + background-image: + radial-gradient(rgba(255, 255, 255, 0.14) 1px, transparent 1px), + repeating-linear-gradient( + 135deg, + rgba(255, 255, 255, 0.045) 0 1px, + transparent 1px 16px + ); + background-size: 22px 22px, auto; +} + +.page-hero h1 { + color: #fff; + font-weight: 500; +} + +.page-hero-inner { + position: relative; + z-index: 1; + display: flex; + justify-content: space-between; + align-items: flex-end; + gap: 24px; + direction: ltr; +} + +.page-hero-fa { + direction: rtl; + text-align: right; +} + +.page-hero-en { + display: flex; + align-items: center; + gap: 16px; + font-family: var(--font-en); + letter-spacing: 6px; + flex: 1; +} + +.page-hero-en i { + flex: 1; + height: 1px; + background: rgba(255, 255, 255, 0.45); +} + +.crumbs { + color: rgba(255, 255, 255, 0.75); + font-size: 13px; + margin-bottom: 10px; +} + +.page-hero .crumbs a { + color: inherit; +} + +.page-hero .muted { + color: rgba(255, 255, 255, 0.62); + text-align: right; +} + +.about-intro { + background: #15161b; + color: #fff; +} + +.about-intro h2 { + color: #fff; + font-size: 40px; + font-weight: 400; + margin: 0 0 22px; +} + +.about-intro p { + color: #b3b3b3; + line-height: 26px; + text-align: justify; +} + +.goals-section { + background-color: #121318; + background-image: linear-gradient(rgba(18, 19, 24, 0.88), rgba(18, 19, 24, 0.88)), url("/images/about/hero.jpg"); + background-size: cover; + background-position: center; + color: #fff; +} + +.goals-section h2 { + text-align: center; + font-size: 40px; + font-weight: 500; + margin: 0; +} + +.goals-section .en { + text-align: center; + color: rgba(255, 255, 255, 0.55); + margin: 12px 0 48px; +} + +.goals-layout { + display: grid; + grid-template-columns: 1.1fr 0.9fr; + gap: 48px; + align-items: stretch; +} + +.goals-list { + display: flex; + flex-direction: column; + justify-content: center; +} + +.goals-graphic { + position: relative; + min-height: 480px; + overflow: hidden; +} + +.goals-graphic img { + margin: 0; + max-width: none; +} + +.goal-item { + display: flex; + align-items: center; + gap: 28px; + margin-bottom: 42px; +} + +.goal-item:last-child { + margin-bottom: 0; +} + +.goal-icon { + flex-shrink: 0; + width: auto; + height: auto; + background: none; + border-radius: 0; + box-shadow: none; + color: #fff; + display: grid; + place-items: center; +} + +.goal-item h3 { + margin: 0 0 6px; + font-size: 25px; + font-weight: 500; + color: #fff; +} + +.goal-item p { + margin: 0; + color: #cfcfcf; + line-height: 26px; + text-align: justify; +} + + +.contact-info { + background: #23262d; + padding: 70px 0; +} + +.contact-info-grid { + text-align: center; + direction: ltr; +} + +.contact-info-grid article { + direction: rtl; +} + +.contact-info h2 { + font-size: 18px; + font-weight: 500; + margin: 0 0 8px; +} + +.contact-phones { + font-family: "iran-yekan", sans-serif; + font-size: 17px; + font-weight: 500; +} + +.faq-grid { + direction: ltr; +} + +.faq-grid > *:last-child { + direction: rtl; + text-align: right; +} + +.contact-info img { + margin: 0 auto 16px; + height: 48px; + width: auto; +} + +.contact-info a { + display: block; + border-bottom: 1px solid rgba(255, 255, 255, 0.25); + padding-bottom: 6px; + margin: 8px auto 0; + width: fit-content; + max-width: 90%; + line-height: 1.8; +} + +.faq-section { + padding: 0; + background: var(--bg); +} + +.faq-split { + display: grid; + grid-template-columns: 1fr 1fr; + min-height: 560px; +} + +.faq-photo { + position: relative; + min-height: 420px; + background: var(--accent); + overflow: hidden; +} + +.faq-photo img, +.faq-photo .slot-fill { + position: absolute; + inset: 0; + width: 100%; + height: 100%; + object-fit: cover; +} + +.faq-copy { + background: #23262d; + padding: 5rem 4.5rem; + direction: rtl; + text-align: right; +} + +.faq-copy .section-kicker { + color: #fff; + font-size: 20px; + font-weight: 400; + margin-bottom: 18px; +} + +.faq-copy h2 { + font-size: 35px; + font-weight: 500; + margin: 0 0 48px; +} + +.faq-item { + border-bottom: 1px solid rgba(255, 255, 255, 0.18); +} + +.faq-item button { + width: 100%; + background: transparent; + border: 0; + color: #fff; + display: flex; + justify-content: space-between; + align-items: center; + gap: 16px; + padding: 18px 0; + cursor: pointer; + text-align: right; + font-size: 16px; +} + +.faq-icon { + display: grid; + place-items: center; + flex-shrink: 0; +} + +.faq-item p { + color: var(--muted); + margin: 0 0 18px; + line-height: 2; +} + +.contact-form-section { + background: #1f2128; +} + +.contact-form { + max-width: 520px; + margin: 0 auto; +} + +.contact-form .form-kicker { + color: var(--muted); + letter-spacing: 3px; + text-transform: uppercase; +} + +.contact-form input, +.contact-form textarea { + width: 100%; + background: transparent; + border: 0; + border-bottom: 1px solid rgba(255, 255, 255, 0.25); + color: #fff; + padding: 14px 0; + margin-bottom: 18px; + outline: none; +} + +.contact-form .ok { + color: #7ddea4; +} + +.contact-form .err { + color: #ff8b8b; +} + +.contact-map { + height: 600px; + background: #111; +} + +.contact-map iframe { + width: 100%; + height: 100%; + border: 0; + filter: grayscale(1); +} + +.coming-soon { + background: var(--bg); +} + +.coming-soon p { + color: var(--muted); + font-size: 18px; +} + +.site-footer { + background: #15161b; + padding-top: 70px; +} + +.footer-grid { + display: grid; + grid-template-columns: 1.4fr 1fr 1fr 1fr; + gap: 32px; +} + +.footer-grid p, +.footer-grid li, +.footer-grid a { + color: #bebebe; + line-height: 2; +} + +.footer-grid ul { + list-style: none; + margin: 0; + padding: 0; +} + +.footer-social { + border-top: 1px solid rgba(255, 255, 255, 0.08); + margin-top: 40px; + padding: 24px 0; +} + +.footer-social-inner, +.footer-bottom-bar { + display: flex; + justify-content: space-between; + align-items: center; + gap: 16px; +} + +.social-links { + display: flex; + gap: 18px; + color: #bebebe; + font-size: 14px; +} + +.footer-bottom { + background: #101116; + padding: 18px 0; +} + +.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; + text-decoration: none; +} + +.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; +} + +@media (max-width: 980px) { + .header-phone, + .hamburger-cover { + display: none; + } + + .header-inner { + min-height: 70px; + } + + .hamburger-grid, + .intro-grid, + .why-grid, + .about-intro-grid, + .faq-grid, + .faq-split, + .goals-layout, + .footer-grid, + .windows-grid, + .blog-grid, + .products-grid, + .catalog-grid, + .blog-list-grid, + .product-detail-grid, + .goals-grid, + .features-grid, + .brands-grid, + .contact-info-grid, + .about-stats, + .stats-inner, + .page-hero-inner, + .footer-bottom-bar, + .footer-social-inner { + grid-template-columns: 1fr; + display: grid; + } + + .page-hero-inner, + .footer-bottom-bar, + .footer-social-inner { + text-align: center; + } + + .hero { + min-height: 52vh; + } + + .hamburger-panel { + padding: 80px 28px; + } + + .faq-copy { + padding: 3rem 1.5rem; + } + + .contact-map { + height: 300px; + } + + .goal-item { + gap: 16px; + } + + .windows-grid { + grid-template-columns: 1fr 1fr; + gap: 15px; + } + + .blog-list-grid { + grid-template-columns: 1fr 1fr; + } + + .windows-wrap { + padding: 0 15px; + } + + .window-content, + .window-hover-inner { + padding: 2rem; + } + + .window-arrow { + left: 30px; + bottom: 30px; + } + + .why-grid { + grid-template-columns: 1fr; + direction: rtl; + } + + .why-photo, + .why-copy, + .why-grid > :first-child, + .why-grid > :last-child { + grid-column: auto; + } + + .why-watermark { + display: none; + } + + .years-box { + min-height: 140px; + padding: 2.5rem; + } + + .header-login span, + .account-menu__trigger span { + display: none; + } + + .account-menu__dropdown { + inset-inline-end: 0; + } +} + +@media (max-width: 760px) { + .blog-list-grid { + grid-template-columns: 1fr; + } +} + +.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(--header); + border: 1px solid var(--line); +} + +.hamburger-meta .account-menu__dropdown { + inset-inline-start: 0; + inset-inline-end: auto; +} + +.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; + cursor: pointer; +} + +.account-menu__dropdown a:hover, +.account-menu__dropdown button:hover { + color: var(--accent); + background: var(--bg-2); +} + +.media-carousel { + --carousel-index: 0; + --carousel-per: 4; +} + +.media-toolbar { + display: flex; + align-items: center; + justify-content: space-between; + gap: 16px; + margin-bottom: 22px; +} + +.media-dots { + display: flex; + gap: 8px; +} + +.media-dot { + width: 8px; + height: 8px; + padding: 0; + border: 0; + border-radius: 999px; + background: rgba(255, 255, 255, 0.28); + cursor: pointer; +} + +.media-dot.active { + width: 22px; + background: var(--accent); +} + +.media-arrows { + display: flex; + gap: 8px; +} + +.media-arrow { + width: 36px; + height: 36px; + display: grid; + place-items: center; + color: #fff; + background: transparent; + border: 1px solid var(--line); + cursor: pointer; + font-size: 22px; + line-height: 1; +} + +.media-arrow:hover { + border-color: var(--accent); + color: var(--accent); +} + +.media-viewport { + overflow: hidden; +} + +.media-track { + display: flex; + gap: 28px; + width: 100%; + transition: transform 0.6s cubic-bezier(0.16, 1, 0.3, 1); + transform: translateX(calc(var(--carousel-index) * (100% + 28px) / var(--carousel-per))); +} + +html[dir="ltr"] .media-track { + transform: translateX(calc(var(--carousel-index) * -1 * (100% + 28px) / var(--carousel-per))); +} + +.media-track > article { + flex: 0 0 calc((100% - (var(--carousel-per) - 1) * 28px) / var(--carousel-per)); + min-width: 0; +} + +.media-carousel--blog .media-viewport { + padding-inline: 20px; +} + +.media-carousel--product .media-track { + align-items: stretch; +} + +.media-carousel--product .product-card { + border: 1px solid #e8e8e8; + background: #fff; +} + +.media-carousel--product .product-card a { + display: flex; + flex-direction: column; + height: 100%; +} + +.media-carousel--product .product-card h3 { + margin: 14px 14px 6px; +} + +.media-carousel--product .product-card p { + margin: 0 14px 14px; +} + +.carousel-full { + width: 100%; + padding-inline: 20px; +} + +.blog-section h2 { + margin: 0 0 36px; + color: var(--accent); + text-align: center; +} + +.blog-card--cover { + position: relative; + aspect-ratio: 1 / 1; + overflow: hidden; + background: #111; +} + +.blog-card--cover .blog-card-hit { + position: absolute; + inset: 0; + display: block; + color: #fff; +} + +.blog-card--cover .blog-card-hit img, +.blog-card--cover .slot-fill { + position: absolute; + inset: 0; + width: 100%; + height: 100%; + object-fit: cover; + transition: transform 0.7s cubic-bezier(0.16, 1, 0.3, 1); +} + +.blog-card--cover:hover .blog-card-hit img, +.blog-card--cover:hover .slot-fill { + transform: scale(1.06); +} + +.blog-card-cat { + position: absolute; + top: 16px; + right: 16px; + z-index: 2; + padding: 6px 12px; + background: rgba(15, 16, 20, 0.62); + color: #fff; + font-size: 12px; +} + +.blog-card-meta { + position: absolute; + inset-inline: 0; + bottom: 0; + z-index: 2; + padding: 56px 18px 18px; + background: linear-gradient(to top, rgba(0, 0, 0, 0.82) 0%, rgba(0, 0, 0, 0.4) 52%, transparent 100%); + text-align: right; +} + +.blog-card-meta time { + display: block; + margin-bottom: 8px; + color: rgba(255, 255, 255, 0.92); + font-size: 12px; +} + +.blog-card--cover h2, +.blog-card--cover h3 { + margin: 0; + color: #fff; + font-size: 16px; + font-weight: 700; + line-height: 1.55; +} + +.products-section { + background: #fff; + color: #23242d; +} + +.products-section .section-kicker { + color: #8f8f8f; +} + +.products-section h2 { + color: #23242d; +} + +.products-section .product-photo { + position: relative; + aspect-ratio: 4 / 3; + background: #fff; + overflow: hidden; +} + +.products-section .product-photo img { + position: absolute; + inset: 0; + width: 100%; + height: 100%; + object-fit: contain; +} + +.products-section .product-card h3 { + color: #23242d; +} + +.products-section .product-card p { + color: #8f8f8f; +} + +.products-section .media-dot { + background: rgba(35, 36, 45, 0.22); +} + +.products-section .media-arrow { + color: #23242d; + border-color: rgba(35, 36, 45, 0.18); +} + +.products-section .media-arrow:hover { + border-color: var(--accent); + color: var(--accent); +} + +.catalog-grid { + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: 28px; +} + +.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; +} + +.blog-list-grid { + display: grid; + grid-template-columns: repeat(4, 1fr); + gap: 28px; +} + +.catalog-card { + background: var(--bg-2); +} + +.catalog-card-media { + position: relative; + display: block; + aspect-ratio: 16 / 10; + overflow: hidden; + background: #111; +} + +.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 0 8px; +} + +.catalog-card-body h2, +.catalog-card-body h3 { + margin: 0; + font-size: 18px; +} + +.catalog-card-body .en { + margin: 6px 0 0; + color: var(--accent); + letter-spacing: 2px; +} + +.blog-excerpt { + display: -webkit-box; + margin: 8px 0 0; + overflow: hidden; + color: var(--muted); + font-size: 13px; + line-height: 1.9; + -webkit-box-orient: vertical; + -webkit-line-clamp: 3; +} + +.detail-crumbs { + display: flex; + flex-wrap: wrap; + gap: 8px; + margin-bottom: 28px; + color: var(--muted); + font-size: 13px; +} + +.detail-crumbs a:hover { + color: var(--accent); +} + +.detail-page { + padding: 48px 0 0; +} + +.detail-page > .container { + padding-bottom: 48px; +} + +.detail-page > .container:last-child { + padding-bottom: 88px; +} + +.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); +} + +.product-category { + margin: 8px 0 0; + color: var(--accent); + font-size: 13px; +} + +.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: #111; + 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: #111; + cursor: pointer; +} + +.product-gallery-thumbs button.active, +.product-gallery-thumbs button:hover { + border-color: var(--accent); +} + +.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, 20, 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(--bg-2); +} + +.detail-tabs > h2 { + margin: 0; + padding: 20px 24px 0; + font-size: 20px; +} + +.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; +} + +.detail-tabs-nav button.active { + color: var(--accent); + border-bottom-color: var(--accent); +} + +.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); + text-align: right; +} + +.tech-table th { + width: 36%; + color: var(--accent); +} + +.comment-box { + display: grid; + gap: 20px; + padding: 0 24px 24px; +} + +.detail-tabs-body .comment-box { + padding: 0; +} + +.comment-list { + display: grid; + gap: 16px; +} + +.comment-item { + padding-bottom: 16px; + border-bottom: 1px solid var(--line); +} + +.comment-item strong { + display: block; +} + +.comment-item time, +.comment-empty { + color: var(--muted); + font-size: 13px; +} + +.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-login .btn { + margin-top: 0; +} + +.comment-form { + display: grid; + gap: 12px; +} + +.comment-form textarea { + width: 100%; + min-height: 120px; + padding: 12px 14px; + color: #fff; + background: var(--bg); + border: 1px solid var(--line); + font: inherit; +} + +.comment-form textarea:focus { + border-color: var(--accent); + outline: none; +} + +.comment-form .btn { + margin-top: 0; + justify-self: start; +} + +.comment-form .ok { + color: #7dcea0; +} + +.comment-form .err { + color: #e57373; +} + +.related-section { + margin-top: 56px; + padding: 56px 0 72px; + background: #fff; + color: #23242d; +} + +.related-section h2 { + margin: 0; + font-size: clamp(20px, 2.4vw, 28px); + color: #23242d; +} + +.related-section > .container > .en { + margin: 6px 0 28px; + color: #8f8f8f; +} + +.detail-article { + max-width: 820px; +} + +.article-cover { + margin-bottom: 28px; + overflow: hidden; +} + +.article-cover img { + width: 100%; + height: auto; +} + +.detail-lead { + margin: 0 0 24px; + color: var(--muted); + font-size: 16px; + line-height: 2; +} + +.rich-text { + color: #b3b3b3; + line-height: 2; +} + +.rich-text p, +.rich-text li { + margin: 0 0 14px; + color: #b3b3b3; +} + +.rich-text img { + max-width: 100%; + height: auto; + margin: 16px 0; +} + +.rich-text h1, +.rich-text h2, +.rich-text h3, +.rich-text h4, +.rich-text h5, +.rich-text h6, +.rich-text h1 *, +.rich-text h2 *, +.rich-text h3 *, +.rich-text h4 *, +.rich-text h5 *, +.rich-text h6 * { + color: #fff; + font-weight: 500; +} + +.rich-text h1, +.rich-text h2, +.rich-text h3, +.rich-text h4, +.rich-text h5, +.rich-text h6 { + margin: 28px 0 12px; +} + +.rich-text a { + color: #d6d6d6; + border-bottom: 1px solid rgba(255, 255, 255, 0.28); +} + +.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(--accent); + color: var(--accent); +} diff --git a/src/app/layout.tsx b/src/app/layout.tsx new file mode 100644 index 0000000..cde27ae --- /dev/null +++ b/src/app/layout.tsx @@ -0,0 +1,57 @@ +import type { Metadata } from "next"; +import { Montserrat } 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 montserrat = Montserrat({ + subsets: ["latin"], + weight: ["300", "400", "500", "600", "700"], + variable: "--font-montserrat", + display: "swap", +}); + +export const metadata: Metadata = { + metadataBase: new URL(`https://${websiteDomain}`), + title: { + default: 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={montserrat.variable}> + <body> + <Providers businessId={businessId || "28"}> + <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..5fe9ea6 --- /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..62acd3d --- /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..5363046 --- /dev/null +++ b/src/app/page.tsx @@ -0,0 +1,310 @@ +import Link from "next/link"; + +import AparatVideo from "@/components/AparatVideo"; +import GlassReadMore from "@/components/GlassReadMore"; +import HeroSlider from "@/components/HeroSlider"; +import MediaCarousel from "@/components/MediaCarousel"; +import Reveal from "@/components/Reveal"; +import SlotImage from "@/components/SlotImage"; +import { blogCategory, blogDateLong, blogHref, blogImage, fetchBlogs } from "@/lib/blog"; +import { + blogPosts, + brands, + features, + homeProducts, + site, + slides, + whyText, + windowTypes, +} from "@/data/site"; +import { productHref, productImage, productTitle, fetchProducts } from "@/lib/products"; +import { toFaDigits } from "@/lib/format"; +import { pageMetadata } from "@/lib/seo"; +import { fetchStaticImageSlots, overlaySlotImages, pickSlot, type SlotMediaItem } from "@/lib/static-images"; + +function carouselItems(overlaid: SlotMediaItem[], fallbacks: SlotMediaItem[], max = 12) { + const seen = new Set<string>(); + return fallbacks.slice(0, max).map((item, index) => { + const slotItem = overlaid[index]; + if (!slotItem?.image) return item; + return { + ...item, + image: slotItem.image, + href: slotItem.href || item.href, + title: item.title || slotItem.title, + kicker: item.kicker || slotItem.kicker, + }; + }).filter((item) => { + if (!item.id || seen.has(item.id)) return false; + seen.add(item.id); + return true; + }); +} + +export const metadata = pageMetadata({ + title: site.name, + description: site.description, + path: "/", + image: "/images/hero/slide-1.jpg", + absolute: true, +}); + +export default async function HomePage() { + const [slots, apiBlogs, apiProducts] = await Promise.all([ + fetchStaticImageSlots("home"), + fetchBlogs({ pageSize: 12 }), + fetchProducts({ pageSize: 12 }), + ]); + const slider = overlaySlotImages( + pickSlot(slots, "slider"), + slides.map((slide) => ({ + id: slide.id, + image: slide.image, + title: slide.title, + })), + "slider", + ); + const windows = overlaySlotImages( + pickSlot(slots, "home-windows"), + windowTypes.map((item) => ({ + id: item.id, + image: item.image, + title: item.title, + text: item.text, + href: item.href, + })), + "home-windows", + ); + const whyImage = overlaySlotImages( + pickSlot(slots, "home-why-image"), + [{ id: "why", image: "/images/features/01.png", title: "چرا پنجره فرخ" }], + "home-why-image", + ); + const blogFallbacks: SlotMediaItem[] = apiBlogs.items.length + ? apiBlogs.items.map((post) => ({ + id: post.id, + image: blogImage(post), + title: post.title, + kicker: blogCategory(post) || undefined, + text: blogDateLong(post) || undefined, + href: blogHref(post), + })) + : blogPosts.map((post) => ({ + id: post.id, + image: post.image, + title: post.title, + kicker: post.category, + href: post.href, + })); + const blogItems = overlaySlotImages(pickSlot(slots, "home-blog"), blogFallbacks, "home-blog"); + + const productFallbacks: SlotMediaItem[] = apiProducts.items.length + ? apiProducts.items.map((product) => ({ + id: product.id, + image: productImage(product), + title: productTitle(product), + kicker: product.title && product.nameFa ? product.title : product.categoryNameFa || undefined, + href: productHref(product), + })) + : homeProducts.map((product) => ({ + id: product.id, + image: product.image, + title: product.title, + kicker: product.kicker, + href: product.href, + })); + const productItems = overlaySlotImages(pickSlot(slots, "home-products"), productFallbacks, "home-products"); + const clientItems = overlaySlotImages( + pickSlot(slots, "home-clients"), + brands.map((brand) => ({ + id: brand.id, + image: brand.image, + title: brand.title, + })), + "home-clients", + ); + + return ( + <> + <HeroSlider slides={slider.items} /> + + <section className="stats-bar"> + <div className="container stats-inner"> + <div> + <strong>{toFaDigits(site.projects)}</strong> + <span>پروژه انجام شده</span> + </div> + <div> + <strong>{toFaDigits(site.workYears)}</strong> + <span>تجربه کاری</span> + </div> + <div> + <strong>درب و پنجره پنجره فرخ</strong> + <span>{site.slogan}</span> + </div> + </div> + </section> + + <section className="section intro-section"> + <div className="container intro-grid"> + <Reveal> + <div className="years-box"> + <span className="years-frame years-frame--left" aria-hidden /> + <span className="years-frame years-frame--right" aria-hidden /> + <span className="years-num">{toFaDigits(site.experienceYears)}</span> + <span className="years-label">ســــال تجربه در صنعت</span> + </div> + </Reveal> + <Reveal delay={80}> + <div className="intro-brand"> + <p className="from-year">از سال {site.foundedYear}</p> + <h1> + شرکــــت درب و پنجــــــــــره + <br /> + پنجره فرخ + </h1> + </div> + </Reveal> + <Reveal delay={160}> + <div> + <p className="muted">{site.tagline}</p> + <Link className="text-link" href="/about"> + توضیحات بیشتر + </Link> + </div> + </Reveal> + </div> + </section> + + <section className="video-heading"> + <div className="container"> + <h2>فرخ وین تولید کننده انواع پنجره UPVC و شیشه دو جداره</h2> + <AparatVideo /> + </div> + </section> + + <section className="section glass-section"> + <div className="container"> + <GlassReadMore /> + </div> + </section> + + <section className="section windows-section"> + <div className="container"> + <p className="section-kicker">دسته بندی های ما</p> + <h2>انواع درب و پنجره دوجداره</h2> + </div> + <div className="windows-wrap"> + <div className="windows-grid"> + {windows.items.map((item) => ( + <article key={item.id} className="window-card"> + <Link href={item.href || "/products"}> + <div className="window-photo"> + <SlotImage src={item.image} alt={item.title || site.shortName} /> + <span className="window-dim" aria-hidden /> + <div className="window-content"> + <h3>{item.title}</h3> + <span className="window-arrow" aria-hidden> + <svg width="16" height="16" viewBox="0 0 24 24" fill="none"> + <path d="M15 6l-6 6 6 6" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" /> + </svg> + </span> + </div> + <div className="window-hover"> + <div className="window-hover-inner"> + <p>{item.text}</p> + <span className="window-details"> + نمایش جزئیات + <svg width="14" height="14" viewBox="0 0 24 24" fill="none" aria-hidden> + <path d="M15 6l-6 6 6 6" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" /> + </svg> + </span> + </div> + </div> + </div> + </Link> + </article> + ))} + </div> + </div> + </section> + + <section className="section why-section"> + <div className="why-watermark" aria-hidden> + Farrokh + </div> + <div className="container why-grid"> + <Reveal> + <div className="why-photo"> + <SlotImage + src={whyImage.items[0]?.image || "/images/features/01.png"} + alt={whyImage.items[0]?.title || "چرا پنجره فرخ"} + /> + </div> + </Reveal> + <Reveal delay={120} variant="left"> + <div className="why-copy"> + <h2>چرا پنجره فرخ ؟!</h2> + <p>{whyText}</p> + <Link className="text-link why-link" href="/products"> + محصولات ما + </Link> + </div> + </Reveal> + </div> + </section> + + <section className="section features-section"> + <div className="container"> + <p className="section-kicker">فرخ وین، تولید کننده شیشه دو جداره</p> + <div className="features-grid"> + {features.map((feature) => ( + <article key={feature.id}> + <SlotImage src={feature.image} alt={feature.title} width={72} height={72} /> + <h3>{feature.title}</h3> + </article> + ))} + </div> + </div> + </section> + + <section className="section blog-section"> + <div className="container"> + <h2>آخرین مقالات</h2> + </div> + <MediaCarousel items={carouselItems(blogItems.items, blogFallbacks)} variant="blog" ariaLabel="آخرین مقالات" /> + </section> + + <section className="cta-section"> + <div className="container"> + <h2> + آیا آماده همکاری با ما هستید؟{" "} + <Link href="/contact">شروع یک پروژه</Link> + </h2> + </div> + </section> + + <section className="section products-section"> + <div className="container"> + <p className="section-kicker">انتخاب مردم</p> + <h2>کارهای خاص</h2> + <MediaCarousel items={carouselItems(productItems.items, productFallbacks)} variant="product" ariaLabel="آخرین محصولات" /> + </div> + </section> + + <section className="section brands-section"> + <div className="container"> + <h2>برند های ما</h2> + <p className="en">OUR CUSTOMER</p> + <div className="brands-grid"> + {clientItems.items.map((brand) => ( + <Link key={brand.id} href="/products" className="brand-item" title={brand.title}> + <SlotImage src={brand.image} alt={brand.title || "برند"} /> + </Link> + ))} + </div> + </div> + </section> + </> + ); +} diff --git a/src/app/portfolio/page.tsx b/src/app/portfolio/page.tsx new file mode 100644 index 0000000..3606645 --- /dev/null +++ b/src/app/portfolio/page.tsx @@ -0,0 +1,98 @@ +import type { Metadata } from "next"; +import Link from "next/link"; + +import PageHero from "@/components/PageHero"; +import SlotImage from "@/components/SlotImage"; +import { + fetchPortfolios, + portfolioHref, + portfolioImage, + portfolioTitle, +} from "@/lib/portfolios"; +import { pageMetadata } from "@/lib/seo"; + +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: "نمونه کارهای اجرا شده پنجره فرخ در UPVC، شیشه دوجداره و سندبلاست.", + path: page > 1 ? `/portfolio?page=${page}` : "/portfolio", + }); +} + +export default async function PortfolioPage({ + searchParams, +}: { + searchParams: Promise<{ page?: string }>; +}) { + const { page: pageParam } = await searchParams; + const page = Math.max(1, Number(pageParam) || 1); + const result = await fetchPortfolios({ page, pageSize: 12 }); + const pageCount = Math.max(1, Math.ceil((result.total || result.items.length) / (result.pageSize || 12))); + + return ( + <> + <PageHero + title="نمونه کارها" + en="PORTFOLIO" + crumbs={[ + { href: "/", label: "خانه" }, + { href: "/portfolio", label: "نمونه کارها" }, + ]} + /> + + <section className="section"> + <div className="container"> + {result.items.length === 0 ? ( + <p className="comment-empty">نمونه کاری برای نمایش وجود ندارد.</p> + ) : ( + <div className="catalog-grid"> + {result.items.map((item) => { + const title = portfolioTitle(item); + return ( + <article key={item.id} className="catalog-card"> + <Link href={portfolioHref(item)} className="catalog-card-media"> + <SlotImage src={portfolioImage(item)} alt={title} fill /> + </Link> + <div className="catalog-card-body"> + <h2> + <Link href={portfolioHref(item)}>{title}</Link> + </h2> + </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 ? "/portfolio" : `/portfolio?page=${number}`} + className={number === page ? "active" : undefined} + > + {number} + </Link> + ); + })} + </nav> + ) : null} + </div> + </section> + </> + ); +} diff --git a/src/app/portfolios/[id]/[slug]/page.tsx b/src/app/portfolios/[id]/[slug]/page.tsx new file mode 100644 index 0000000..a5dccff --- /dev/null +++ b/src/app/portfolios/[id]/[slug]/page.tsx @@ -0,0 +1,91 @@ +import type { Metadata } from "next"; +import Link from "next/link"; +import { notFound, redirect } from "next/navigation"; + +import SlotImage from "@/components/SlotImage"; +import { plainText, sanitizeHtml } from "@/lib/format"; +import { + fetchPortfolioById, + portfolioHref, + portfolioImage, + portfolioTitle, +} from "@/lib/portfolios"; +import { pageMetadata } from "@/lib/seo"; +import { slugifyForUrl } from "@/lib/slug"; + +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 item = await fetchPortfolioById(id); + if (!item) { + return { + title: "نمونه کار یافت نشد", + description: "این نمونه کار در گالری پنجره فرخ موجود نیست.", + robots: { index: false }, + }; + } + const title = portfolioTitle(item); + return pageMetadata({ + title, + description: item.abstract?.trim() || plainText(item.mainTextHtml).slice(0, 160) || `${title} | نمونه کارهای پنجره فرخ`, + path: portfolioHref(item), + image: portfolioImage(item), + }); +} + +export default async function PortfolioDetailPage({ params }: { params: Promise<Params> }) { + const { id, slug } = await params; + const item = await fetchPortfolioById(id); + if (!item) notFound(); + + const title = portfolioTitle(item); + if (decodeURIComponent(slug) !== slugifyForUrl(title)) { + redirect(portfolioHref(item)); + } + + const html = sanitizeHtml(item.mainTextHtml); + + return ( + <> + <section className="page-hero"> + <div className="container page-hero-inner"> + <div className="page-hero-en"> + <span>PORTFOLIO</span> + <i /> + </div> + <div className="page-hero-fa"> + <nav className="crumbs"> + <Link href="/">خانه</Link> + {" > "} + <Link href="/portfolio">نمونه کارها</Link> + {" > "} + <span>{title}</span> + </nav> + <h1>{title}</h1> + </div> + </div> + </section> + + <section className="section"> + <div className="container detail-article"> + <div className="article-cover"> + <SlotImage src={portfolioImage(item)} alt={title} /> + </div> + {item.abstract ? <p className="detail-lead">{item.abstract}</p> : null} + {html ? <div className="rich-text" dangerouslySetInnerHTML={{ __html: html }} /> : null} + + <Link href="/portfolio" className="text-link"> + بازگشت به نمونه کارها + </Link> + </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..ccff5ba --- /dev/null +++ b/src/app/products/[id]/[slug]/page.tsx @@ -0,0 +1,173 @@ +import type { Metadata } from "next"; +import Link from "next/link"; +import { notFound, redirect } from "next/navigation"; + +import MediaCarousel from "@/components/MediaCarousel"; +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, + fetchProducts, + fetchProductTechnicalInfo, + productGallery, + productHref, + productImage, + productTitle, +} from "@/lib/products"; +import { pageMetadata } from "@/lib/seo"; +import { slugifyForUrl } from "@/lib/slug"; +import type { SlotMediaItem } from "@/lib/static-images"; +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() || ""; + let related = (data.relatedProducts ?? []).filter((item) => item.id !== product.id); + if (related.length < 8) { + const others = await fetchProducts({ pageSize: 12 }); + const extra = others.items.filter( + (item) => item.id !== product.id && !related.some((row) => row.id === item.id), + ); + related = [...related, ...extra]; + } + related = related.slice(0, 12); + const relatedItems: SlotMediaItem[] = related.map((item) => ({ + id: item.id, + image: productImage(item), + href: productHref(item), + title: productTitle(item), + kicker: item.title && item.nameFa ? item.title : item.categoryNameFa || undefined, + })); + + return ( + <section className="detail-page"> + <div className="container"> + <nav className="detail-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> + </> + ) : product.categoryNameFa || product.categoryName ? ( + <> + <span>/</span> + <span>{product.categoryNameFa || product.categoryName}</span> + </> + ) : 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} + <Link className="text-link" href="/contact"> + تماس با ما + </Link> + </div> + </div> + + <ProductTabs + descriptionHtml={descriptionHtml || undefined} + descriptionText={descriptionHtml ? undefined : descriptionText} + technicalRows={rowsFromTechnicalInfo(technicalInfo)} + comments={comments} + entityId={product.id} + /> + </div> + + {relatedItems.length > 0 ? ( + <section className="related-section products-section"> + <div className="container"> + <h2>محصولات مشابه</h2> + <p className="en">Similar Products</p> + <MediaCarousel + items={relatedItems} + variant="product" + ariaLabel="محصولات مشابه" + /> + </div> + </section> + ) : null} + </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..89b0af7 --- /dev/null +++ b/src/app/products/category/[id]/[slug]/page.tsx @@ -0,0 +1,133 @@ +import type { Metadata } from "next"; +import Link from "next/link"; +import { notFound, redirect } from "next/navigation"; + +import CatalogCard from "@/components/CatalogCard"; +import PageHero from "@/components/PageHero"; +import { + categoryHref, + categoryTitle, + fetchCategories, + fetchCategoryById, +} from "@/lib/categories"; +import { fetchProducts } from "@/lib/products"; +import { pageMetadata } from "@/lib/seo"; +import { slugifyForUrl } from "@/lib/slug"; + +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: page > 1 ? `${categoryHref(category)}?page=${page}` : categoryHref(category), + }); +} + +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 title = categoryTitle(category); + const canonicalSlug = slugifyForUrl(title); + if (decodeURIComponent(slug) !== canonicalSlug) { + redirect(categoryHref(category)); + } + + const [result, allCategories] = await Promise.all([ + fetchProducts({ page, pageSize: 12, categoryId: category.id }), + fetchCategories("product"), + ]); + const children = allCategories.filter((item) => item.parentId === category.id); + 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 ( + <> + <PageHero + title={title} + en={category.name && category.nameFa ? category.name : "PRODUCTS"} + crumbs={[ + { href: "/", label: "خانه" }, + { href: "/products", label: "محصولات" }, + ]} + /> + + <section className="section"> + <div className="container"> + {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..fbed964 --- /dev/null +++ b/src/app/products/page.tsx @@ -0,0 +1,98 @@ +import type { Metadata } from "next"; +import Link from "next/link"; + +import CatalogCard from "@/components/CatalogCard"; +import PageHero from "@/components/PageHero"; +import { categoryHref, categoryTitle, fetchCategories } from "@/lib/categories"; +import { fetchProducts } from "@/lib/products"; +import { pageMetadata } from "@/lib/seo"; + +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: "کاتالوگ پنجره UPVC، شیشه دوجداره و محصولات پنجره فرخ.", + path: page > 1 ? `/products?page=${page}` : "/products", + }); +} + +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] = await Promise.all([ + fetchProducts({ page, pageSize: 12 }), + fetchCategories("product"), + ]); + const pageCount = Math.max(1, Math.ceil((result.total || result.items.length) / (result.pageSize || 12))); + const topCategories = categories.filter((item) => !item.parentId); + + return ( + <> + <PageHero + title="محصولات" + en="PRODUCTS" + crumbs={[ + { href: "/", label: "خانه" }, + { href: "/products", label: "محصولات" }, + ]} + /> + + <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/app/videos/page.tsx b/src/app/videos/page.tsx new file mode 100644 index 0000000..e0d724e --- /dev/null +++ b/src/app/videos/page.tsx @@ -0,0 +1,18 @@ +import ComingSoon from "@/components/ComingSoon"; +import { pageMetadata } from "@/lib/seo"; + +export const metadata = pageMetadata({ + title: "ویدئو", + description: "ویدئوهای پنجره فرخ به‌زودی از پنل مشکی خوانده می‌شود.", + path: "/videos", +}); + +export default function VideosPage() { + return ( + <ComingSoon + title="ویدئو" + en="VIDEOS" + text="ویدئوها در مرحله بعد از بک‌اند مشکی نمایش داده می‌شوند." + /> + ); +} diff --git a/src/components/AccountMenu.tsx b/src/components/AccountMenu.tsx new file mode 100644 index 0000000..e48ed8e --- /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/AparatVideo.tsx b/src/components/AparatVideo.tsx new file mode 100644 index 0000000..de220eb --- /dev/null +++ b/src/components/AparatVideo.tsx @@ -0,0 +1,35 @@ +"use client"; + +import { useEffect, useRef } from "react"; + +const APARAT_HASH = "w21jpe0"; +const APARAT_BOX_ID = "aparat-farrokh-w21jpe0"; + +export default function AparatVideo() { + const hostRef = useRef<HTMLDivElement>(null); + + useEffect(() => { + const host = hostRef.current; + if (!host) return undefined; + + host.innerHTML = ""; + const target = document.createElement("div"); + target.id = APARAT_BOX_ID; + host.appendChild(target); + + const script = document.createElement("script"); + script.type = "text/JavaScript"; + script.src = `https://www.aparat.com/embed/${APARAT_HASH}?data[rnddiv]=${APARAT_BOX_ID}&data[responsive]=yes`; + target.appendChild(script); + + return () => { + host.innerHTML = ""; + }; + }, []); + + return ( + <div className="aparat-frame"> + <div ref={hostRef} /> + </div> + ); +} diff --git a/src/components/BlogCoverCard.tsx b/src/components/BlogCoverCard.tsx new file mode 100644 index 0000000..33805c5 --- /dev/null +++ b/src/components/BlogCoverCard.tsx @@ -0,0 +1,33 @@ +import SlotImage from "@/components/SlotImage"; +import SlotLink from "@/components/SlotLink"; + +export default function BlogCoverCard({ + href, + image, + title, + category, + date, + heading = "h3", +}: { + href?: string; + image: string; + title: string; + category?: string; + date?: string; + heading?: "h2" | "h3"; +}) { + const Heading = heading; + + return ( + <article className="blog-card blog-card--cover"> + <SlotLink href={href} className="blog-card-hit"> + <SlotImage src={image} alt={title} fill /> + {category ? <span className="blog-card-cat">{category}</span> : null} + <div className="blog-card-meta"> + {date ? <time>{date}</time> : null} + <Heading>{title}</Heading> + </div> + </SlotLink> + </article> + ); +} diff --git a/src/components/CatalogCard.tsx b/src/components/CatalogCard.tsx new file mode 100644 index 0000000..25034af --- /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/ComingSoon.tsx b/src/components/ComingSoon.tsx new file mode 100644 index 0000000..77bc38f --- /dev/null +++ b/src/components/ComingSoon.tsx @@ -0,0 +1,27 @@ +import PageHero from "@/components/PageHero"; + +type Props = { + title: string; + en: string; + text: string; +}; + +export default function ComingSoon({ title, en, text }: Props) { + return ( + <> + <PageHero + title={title} + en={en} + crumbs={[ + { href: "/", label: "خانه" }, + { href: "#", label: title }, + ]} + /> + <section className="section coming-soon"> + <div className="container"> + <p>{text}</p> + </div> + </section> + </> + ); +} diff --git a/src/components/CommentBox.tsx b/src/components/CommentBox.tsx new file mode 100644 index 0000000..5fe4c29 --- /dev/null +++ b/src/components/CommentBox.tsx @@ -0,0 +1,107 @@ +"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, getAccessToken } = 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 token = getAccessToken(); + const response = await fetch("/api/comments", { + method: "POST", + headers: { + "Content-Type": "application/json", + ...(token ? { Authorization: `Bearer ${token}` } : {}), + }, + 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" 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" disabled={status === "sending"}> + {status === "sending" ? "در حال ارسال…" : "ثبت نظر"} + </button> + {message ? <p className={status === "ok" ? "ok" : "err"}>{message}</p> : null} + </form> + ) : null} + </div> + ); +} diff --git a/src/components/ContactForm.tsx b/src/components/ContactForm.tsx new file mode 100644 index 0000000..29bc9e3 --- /dev/null +++ b/src/components/ContactForm.tsx @@ -0,0 +1,58 @@ +"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: "پیام از صفحه تماس", + 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}> + <p className="form-kicker">contact us</p> + <h2>ارسال پیام</h2> + <input name="name" type="text" required placeholder="* نام" /> + <input name="email" type="email" required placeholder="* ایمیل" /> + <input name="cellNumber" type="tel" required placeholder="شماره همراه *" /> + <textarea name="text" required placeholder="متن پیام" rows={5} /> + <button type="submit" className="btn" disabled={status === "sending"}> + {status === "sending" ? "در حال ارسال…" : "ارسال پیام"} + </button> + {message ? <p className={status === "ok" ? "ok" : "err"}>{message}</p> : null} + </form> + ); +} diff --git a/src/components/FaqList.tsx b/src/components/FaqList.tsx new file mode 100644 index 0000000..4421538 --- /dev/null +++ b/src/components/FaqList.tsx @@ -0,0 +1,36 @@ +"use client"; + +import { useState } from "react"; + +import { faqs } from "@/data/site"; + +export default function FaqList() { + const [open, setOpen] = useState(0); + + return ( + <div className="faq-list"> + {faqs.map((item, index) => { + const isOpen = open === index; + return ( + <article key={item.q} className={`faq-item${isOpen ? " is-open" : ""}`}> + <button type="button" onClick={() => setOpen(isOpen ? -1 : index)}> + <span>{item.q}</span> + <span className="faq-icon" aria-hidden> + {isOpen ? ( + <svg width="14" height="14" viewBox="0 0 24 24" fill="none"> + <path d="M6 15l6-6 6 6" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" /> + </svg> + ) : ( + <svg width="14" height="14" viewBox="0 0 24 24" fill="none"> + <path d="M6 9l6 6 6-6" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" /> + </svg> + )} + </span> + </button> + {isOpen ? <p>{item.a}</p> : null} + </article> + ); + })} + </div> + ); +} diff --git a/src/components/Footer.tsx b/src/components/Footer.tsx new file mode 100644 index 0000000..755e096 --- /dev/null +++ b/src/components/Footer.tsx @@ -0,0 +1,111 @@ +import Image from "next/image"; +import Link from "next/link"; + +import SlotImage from "@/components/SlotImage"; +import { footerArticles, footerLinks, navLinks, site } from "@/data/site"; + +export default function Footer() { + return ( + <footer className="site-footer"> + <div className="container footer-grid"> + <div> + <h2>مجموعه صنعتی پنجره فرخ</h2> + <p>{site.footerAbout}</p> + </div> + <div> + <h3>آدرس</h3> + <ul> + <li> + <a href={site.officeMap} target="_blank" rel="noopener noreferrer"> + دفتر مرکزی: {site.office} + </a> + </li> + {footerLinks.map((link) => ( + <li key={link.href + link.label}> + <Link href={link.href}>{link.label}</Link> + </li> + ))} + </ul> + </div> + <div> + <h3>کارخانه</h3> + <ul> + <li> + <a href={site.factoryMap} target="_blank" rel="noopener noreferrer"> + کارخانه: {site.factory} + </a> + </li> + <li>تولید کننده پنجره upvc</li> + </ul> + </div> + <div> + <h3>دسترسی آسان</h3> + <ul> + {footerArticles.map((link) => ( + <li key={link.label}> + <Link href={link.href}>{link.label}</Link> + </li> + ))} + {navLinks.slice(0, 2).map((link) => ( + <li key={`quick-${link.href}`}> + <Link href={link.href}>{link.label}</Link> + </li> + ))} + </ul> + </div> + </div> + + <div className="footer-social"> + <div className="container footer-social-inner"> + <Link href="/" className="footer-logo"> + <SlotImage src={site.logo} alt={site.shortName} width={140} height={46} /> + </Link> + <div className="social-links"> + <a href={site.social.telegram} target="_blank" rel="noopener noreferrer"> + تلگرام واحد فروش + </a> + <a href={site.social.instagram} target="_blank" rel="noopener noreferrer"> + اینستاگرام + </a> + <a href={site.social.whatsapp} target="_blank" rel="noopener noreferrer"> + واتساپ واحد فروش + </a> + </div> + </div> + </div> + + <div className="footer-bottom"> + <div className="container"> + <div className="footer-bottom-bar"> + <div className="footer-bottom-site">{site.shortName}</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/GlassReadMore.tsx b/src/components/GlassReadMore.tsx new file mode 100644 index 0000000..15c13e3 --- /dev/null +++ b/src/components/GlassReadMore.tsx @@ -0,0 +1,63 @@ +"use client"; + +import { useState } from "react"; + +import { glassTopics, glassTypes, qomText, upvcMakerText } from "@/data/site"; + +export default function GlassReadMore() { + const [open, setOpen] = useState(false); + + return ( + <div className="glass-readmore"> + <div className={`glass-box${open ? " is-open" : ""}`}> + <p className="glass-lead">پنجره فرخ تولید کننده شیشه دوجداره</p> + <div className={`glass-ellipse${open ? "" : " is-clamped"}`}> + {glassTopics.map((topic) => ( + <article key={topic.title}> + <h3>{topic.title}</h3> + <p>{topic.text}</p> + </article> + ))} + <article> + <h3>انواع شیشه دو جداره</h3> + <p>پنجره‌های دو جداره به انواع مختلفی تقسیم می‌شوند که عمدتاً از ترکیب مختلف شیشه‌ها با طراحی دو جداره ساخته می‌شوند:</p> + <ul> + {glassTypes.map((item) => ( + <li key={item}>{item}</li> + ))} + </ul> + </article> + <article> + <h3>تولید کننده شیشه دوجداره قم</h3> + <p>{qomText}</p> + </article> + <article> + <h3>فرخ وین، تولید کننده پنجره UPVC</h3> + <p>{upvcMakerText}</p> + </article> + </div> + </div> + <div className="glass-more-wrap"> + <button type="button" className="see-more" onClick={() => setOpen((value) => !value)}> + <span>{open ? "بستن" : "مشاهده ببشتر"}</span> + <svg + className={open ? "is-open" : ""} + width="14" + height="14" + viewBox="0 0 24 24" + fill="none" + aria-hidden + > + <path + d="M6 9l6 6 6-6" + stroke="currentColor" + strokeWidth="1.6" + strokeLinecap="round" + strokeLinejoin="round" + /> + </svg> + </button> + </div> + </div> + ); +} diff --git a/src/components/Header.tsx b/src/components/Header.tsx new file mode 100644 index 0000000..a4f5694 --- /dev/null +++ b/src/components/Header.tsx @@ -0,0 +1,78 @@ +"use client"; + +import Link from "next/link"; +import { usePathname } from "next/navigation"; +import { useEffect, useState } from "react"; + +import AccountMenu from "@/components/AccountMenu"; +import SlotImage from "@/components/SlotImage"; +import { navLinks, site } from "@/data/site"; + +export default function Header() { + const pathname = usePathname(); + const [open, setOpen] = useState(false); + + useEffect(() => { + setOpen(false); + }, [pathname]); + + useEffect(() => { + document.body.style.overflow = open ? "hidden" : ""; + return () => { + document.body.style.overflow = ""; + }; + }, [open]); + + return ( + <header className="site-header"> + <nav className="header-bar"> + <div className="container header-inner"> + <Link href="/" className="brand" title={site.shortName}> + <SlotImage src={site.logo} alt={site.shortName} width={169} height={55} priority /> + </Link> + <div className="header-actions"> + <a className="header-phone" href={`tel:${site.phones.sales}`}> + تماس بگیرید : {site.phones.sales} - {site.phones.mobile} + </a> + <AccountMenu /> + <button + type="button" + className={`push-button${open ? " is-open" : ""}`} + aria-label={open ? "بستن منو" : "باز کردن منو"} + aria-expanded={open} + onClick={() => setOpen((value) => !value)} + > + <span /> + <span /> + <span /> + </button> + </div> + </div> + </nav> + + <div className={`hamburger-menu${open ? " is-open" : ""}`} aria-hidden={!open}> + <button type="button" className="close-menu" aria-label="بستن منو" onClick={() => setOpen(false)}> + × + </button> + <div className="hamburger-grid"> + <div className="hamburger-cover" style={{ backgroundImage: "url(/images/menu/panel.jpg)" }} /> + <div className="hamburger-panel"> + <ul className="menu-list"> + {navLinks.map((link) => ( + <li key={link.href}> + <Link href={link.href} className={pathname === link.href ? "is-active" : ""}> + {link.label} + </Link> + </li> + ))} + </ul> + <div className="hamburger-meta"> + <a href={site.cartUrl}>سبد خرید</a> + <AccountMenu /> + </div> + </div> + </div> + </div> + </header> + ); +} diff --git a/src/components/HeroSlider.tsx b/src/components/HeroSlider.tsx new file mode 100644 index 0000000..92f67e0 --- /dev/null +++ b/src/components/HeroSlider.tsx @@ -0,0 +1,45 @@ +"use client"; + +import { useEffect, useState } from "react"; + +import SlotImage from "@/components/SlotImage"; +import type { SlotMediaItem } from "@/lib/static-images"; + +type Props = { + slides: SlotMediaItem[]; +}; + +export default function HeroSlider({ slides }: Props) { + const [index, setIndex] = useState(0); + const items = slides.filter((slide) => slide.image); + + useEffect(() => { + if (items.length < 2) return undefined; + const timer = window.setInterval(() => { + setIndex((current) => (current + 1) % items.length); + }, 3000); + return () => window.clearInterval(timer); + }, [items.length]); + + if (!items.length) return null; + + return ( + <section className="hero"> + {items.map((slide, slideIndex) => ( + <div key={slide.id} className={`hero-slide${slideIndex === index ? " is-active" : ""}`}> + <SlotImage src={slide.image} alt={slide.title || "فرخ وین"} fill priority={slideIndex === 0} /> + </div> + ))} + {items.length > 1 ? ( + <div className="hero-nav"> + <button type="button" aria-label="اسلاید بعدی" onClick={() => setIndex((current) => (current + 1) % items.length)}> + ‹ + </button> + <button type="button" aria-label="اسلاید قبلی" onClick={() => setIndex((current) => (current - 1 + items.length) % items.length)}> + › + </button> + </div> + ) : null} + </section> + ); +} diff --git a/src/components/MediaCarousel.tsx b/src/components/MediaCarousel.tsx new file mode 100644 index 0000000..1cfffd9 --- /dev/null +++ b/src/components/MediaCarousel.tsx @@ -0,0 +1,136 @@ +"use client"; + +import { type CSSProperties, useEffect, useState } from "react"; + +import BlogCoverCard from "@/components/BlogCoverCard"; +import SlotImage from "@/components/SlotImage"; +import SlotLink from "@/components/SlotLink"; +import type { SlotMediaItem } from "@/lib/static-images"; + +function perViewForWidth(width: number, max: number) { + if (width <= 760) return 1; + if (width <= 980) return Math.min(2, max); + return max; +} + +export default function MediaCarousel({ + items, + variant, + ariaLabel, + imageFit = "contain", + desktopPerView = 4, +}: { + items: SlotMediaItem[]; + variant: "blog" | "product"; + ariaLabel: string; + imageFit?: "contain" | "cover"; + desktopPerView?: number; +}) { + const [index, setIndex] = useState(0); + const [perView, setPerView] = useState(desktopPerView); + const [paused, setPaused] = useState(false); + + const maxIndex = Math.max(0, items.length - perView); + + useEffect(() => { + function sync() { + setPerView(perViewForWidth(window.innerWidth, desktopPerView)); + } + sync(); + window.addEventListener("resize", sync); + return () => window.removeEventListener("resize", sync); + }, [desktopPerView]); + + useEffect(() => { + setIndex((value) => Math.min(value, maxIndex)); + }, [maxIndex]); + + useEffect(() => { + if (items.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); + }, [items.length, maxIndex, paused, perView]); + + if (items.length === 0) return null; + + function go(next: number) { + setIndex(Math.min(maxIndex, Math.max(0, next))); + } + + return ( + <div + className={`media-carousel media-carousel--${variant}`} + style={{ "--carousel-index": index, "--carousel-per": perView } as CSSProperties} + onMouseEnter={() => setPaused(true)} + onMouseLeave={() => setPaused(false)} + > + {items.length > perView ? ( + <div className={variant === "blog" ? "container" : undefined}> + <div className="media-toolbar"> + <div className="media-dots" role="tablist" aria-label={ariaLabel}> + {Array.from({ length: maxIndex + 1 }, (_, page) => ( + <button + key={page} + type="button" + role="tab" + aria-selected={page === index} + aria-label={`اسلاید ${page + 1}`} + className={page === index ? "media-dot active" : "media-dot"} + onClick={() => go(page)} + /> + ))} + </div> + <div className="media-arrows"> + <button + type="button" + className="media-arrow" + aria-label="قبلی" + onClick={() => setIndex(index <= 0 ? maxIndex : index - 1)} + > + ‹ + </button> + <button + type="button" + className="media-arrow" + aria-label="بعدی" + onClick={() => setIndex(index >= maxIndex ? 0 : index + 1)} + > + › + </button> + </div> + </div> + </div> + ) : null} + + <div className="media-viewport"> + <div className="media-track"> + {items.map((item) => + variant === "blog" ? ( + <BlogCoverCard + key={item.id} + href={item.href} + image={item.image} + title={item.title || "مقاله"} + category={item.kicker} + date={item.text} + /> + ) : ( + <article key={item.id} className="product-card"> + <SlotLink href={item.href}> + <div className="product-photo"> + <SlotImage src={item.image} alt={item.title || "محصول"} fill={imageFit === "cover"} /> + </div> + <h3>{item.title}</h3> + {item.kicker ? <p>{item.kicker}</p> : null} + </SlotLink> + </article> + ), + )} + </div> + </div> + </div> + ); +} diff --git a/src/components/PageHero.tsx b/src/components/PageHero.tsx new file mode 100644 index 0000000..06c2442 --- /dev/null +++ b/src/components/PageHero.tsx @@ -0,0 +1,31 @@ +import Link from "next/link"; + +type Props = { + title: string; + en: string; + crumbs: { href: string; label: string }[]; +}; + +export default function PageHero({ title, en, crumbs }: Props) { + return ( + <section className="page-hero"> + <div className="container page-hero-inner"> + <div className="page-hero-en"> + <span>{en}</span> + <i /> + </div> + <div className="page-hero-fa"> + <nav className="crumbs"> + {crumbs.map((crumb, index) => ( + <span key={crumb.href}> + {index > 0 ? " > " : null} + <Link href={crumb.href}>{crumb.label}</Link> + </span> + ))} + </nav> + <h1>{title}</h1> + </div> + </div> + </section> + ); +} diff --git a/src/components/ProductGallery.tsx b/src/components/ProductGallery.tsx new file mode 100644 index 0000000..996584e --- /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..eb6cd64 --- /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..360b8ff --- /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..5063646 --- /dev/null +++ b/src/components/Reveal.tsx @@ -0,0 +1,65 @@ +"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"; +}; + +export default function Reveal({ + children, + className = "", + delay = 0, + stagger = false, + variant = "up", +}: 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` } as CSSProperties} + > + {children} + </div> + ); +} diff --git a/src/components/SlotImage.tsx b/src/components/SlotImage.tsx new file mode 100644 index 0000000..e8b193c --- /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..56af638 --- /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..047fc38 --- /dev/null +++ b/src/data/site.ts @@ -0,0 +1,246 @@ +export const site = { + name: "فرخ وین | تولید کننده انواع پنجره UPVC و شیشه دو جداره", + shortName: "پنجره فرخ", + brandEn: "FARROKH WIN", + slogan: "پنجره فرخ، همیشه در رخ", + description: + "پنجره فرخ تولید کننده شیشه دوجداره، انواع پنجره های UPVC، شیشه سندبلاست یکی از بزرگترین و مورد اعتماد ترین نام ها در این صنعت می باشد.", + tagline: + "فرخ وین تولید کننده شیشه دوجداره و یکی از بزرگترین و مورد اعتماد ترین نام ها در صنعت تولید درب و پنجره upvc می باشد. چه بخواهید در و پنجره‌های خود را بازسازی کنید یا اینکه ساختمان جدیدی داشته باشیدما می توانیم در و پنجره دوجداره upvc در اشکال و استایل های مختلف را به شما ارائه دهیم تا با استفاده از این محصولات، زیبایی، راحتی و مصرف پایین انرژی را برایتان به ارمغان بیاوریم.", + footerAbout: + "یکی از بزرگترین و مورد اعتماد ترین نام ها در صنعت تولید درب و پنجره دو جداره upvc می باشد. چه بخواهید در و پنجره‌های خود را بازسازی کنید یا اینکه ساختمان جدیدی داشته باشیدما می توانیم در و پنجره دوجداره upvc در اشکال و استایل های مختلف را به شما ارائه دهیم.\nفرخ وین تولید کننده پنجره UPVC در خدمت شما مشتریان عزیز است.", + logo: "/images/logo/logo.png", + foundedYear: "۱۳۹۰", + experienceYears: "16", + projects: "97+", + workYears: "12 سال", + phones: { + sales: "02538203597", + mobile: "09002032333", + landline: "02533342698", + }, + office: + "قم - بلوار امام رضا(ع) - مجتمع فردوس - واحد 138", + officeFull: + "آدرس دفتر مرکزی: قم. بلوار امام رضا(ع). مجتمع تجاری فردوس. طبقه همکف. پلاک ۱۳۸", + factory: + "قم-شهرک صنعتی شکوهیه-فاز دو-خیابان فکوری-کوچه 2-پلاک KH214", + factoryFull: + "آدرس کارخانه: قم، شهرک صنعتی شکوهیه، فاز۲، خیابان فکوری، کوچه۲، پلاک KH214", + officeMap: "https://goo.gl/maps/DZHEusDBxs7hzJGY6", + factoryMap: "https://maps.app.goo.gl/dEbhkvLPC6UPdbMo6", + social: { + instagram: "https://www.instagram.com/farrokhwin", + telegram: "https://t.me/farrokhwin", + whatsapp: "https://wa.me/989002032333", + eitaa: "https://eitaa.com/Farrokhwin", + handle: "@FARROKHWIN", + }, + loginUrl: "https://customer.farrokhwin.com", + cartUrl: "https://customer.farrokhwin.com/cart", + aparatEmbed: "https://www.aparat.com/video/video/embed/videohash/w21jpe0/vt/frame", +}; + +export const navLinks = [ + { href: "/", label: "صفحه اصلی" }, + { href: "/products", label: "محصولات" }, + { href: "/portfolio", label: "نمونه کارها" }, + { href: "/videos", label: "ویدئو" }, + { href: "/blog", label: "مقالات" }, + { href: "/about", label: "درباره ما" }, + { href: "/contact", label: "تماس با ما" }, +]; + +export const slides = [ + { id: "1", image: "/images/hero/slide-1.jpg", title: "FARROKH WIN" }, + { id: "2", image: "/images/hero/slide-2.jpg", title: "درب و پنجره UPVC" }, + { id: "3", image: "/images/hero/slide-3.jpg", title: "شیشه دوجداره" }, +]; + +export const windowTypes = [ + { + id: "sliding", + title: "پنجره دوجداره کشویی ریلی", + text: "پنجره دوجداره کشویی ریلی یکی از قدیمی ترین و پراستفاده ترین انواع پنجره دوجداره یو پی وی سی است و افراد در فضاهای کوچک، از این مدل بازشو احساس رضایت دارند.", + image: "/images/windows/sliding.jpg", + href: "/products", + }, + { + id: "tilt-turn", + title: "پنجره دوجداره دو حالته UPVC", + text: "پنجره دوجداره دو حالته UPVC برای تمامی مکان ها قابل استفاده است، چه برای محل کار چه برای محل زندگی زیبایی، کارایی، قیمت مناسب تنها بخشی از خصوصیات این پنجره هاست.", + image: "/images/windows/tilt-turn.jpg", + href: "/products", + }, + { + id: "curved", + title: "پنجره دوجداره خم UPVC", + text: "پنجره دوجداره خم یکی از عناصر مهم و انتخاب خوبی است برای هر ساختمانی که ساکنان آن به امنیت و آرامش خود می اندیشند، چون دارای پروفیل بادوام، ماندگار و همچنین عایق مناسب صوتی، گرما و سرما است.", + image: "/images/windows/curved.jpg", + href: "/products", + }, + { + id: "upvc", + title: "پنجره دوجداره UPVC", + text: "پنجره دوجداره UPVC آفتاب یکی از عناصر مهم و انتخاب خوبی است برای هر ساختمانی که ساکنان آن به امنیت و آرامش خود می اندیشند، پنجره دوجداره یو پی وی سی دارای پروفیل بادوام، ماندگار و همچنین عایق مناسب صوتی، گرما و سرما است.", + image: "/images/windows/upvc.jpg", + href: "/products", + }, +]; + +export const whyText = + "گروه صنعتی پنجره فرخ با برخورداری از خط تولید کامل درب و پنجره دوجداره،شیشه های دو و چند جداره صنعتی،شیشه های سندبلاست و دکوراتیو،خط رنگ پروفیل و انواع توری پلیسه،محصولات خود را با بهترین کیفیت روانه بازار می کند.همچنین کارشناسان مجموعه،آماده ارائه خدمات مشاوره ای به صورت رایگان هستند."; + +export const features = [ + { id: "1", title: "طراحی منحصر به فرد", image: "/images/features/02.png" }, + { id: "2", title: "خدمات پس از فروش", image: "/images/features/03.png" }, + { id: "3", title: "نماینده رسمی", image: "/images/features/04.png" }, + { id: "4", title: "نصب با کیفیت", image: "/images/features/05.png" }, + { id: "5", title: "تضمین کیفیت", image: "/images/features/06.png" }, + { id: "6", title: "خرید آنلاین", image: "/images/features/07.png" }, +]; + +export const glassTopics = [ + { + title: "شیشه دو جداره", + text: "شیشه دو جداره از دو یا چند لایه شیشه که موازی قرار گرفته‌اند و فاصله‌ای حدود چند میلی‌متری بین آنها وجود دارد، ساخته شده است. معمولاً فضای بین این دو لایه شیشه، توسط کارخانه تولید کننده با استفاده از گاز های بی اثر نظیر آرگون و موارد مشابه پر می‌شود. این فضا به عنوان یک عایق عمل می‌کند و از عبور صدا و تبادل حرارت بین داخل خانه و محیط خارجی جلوگیری می‌کند.", + }, + { + title: "پنجره دوجداره", + text: "پنجره‌های دوجداره به عنوان یکی از انتخاب‌های محبوب در صنعت ساختمانی، از ویژگی‌ها و مزایای فراوانی برخوردارند که می‌توانند به بهبود کیفیت زندگی در فضاهای داخلی کمک کنند. با استفاده از تکنولوژی‌های مدرن و استفاده از مواد با کیفیت، پنجره‌های دوجداره به طور موثری از نفوذ حرارت و صدا جلوگیری می‌کنند. طراحی‌های مناسب این پنجره‌ها، اجازه می‌دهد تا نور طبیعی به صورت بهینه وارد فضا شود و از ایجاد رطوبت جلوگیری کند.", + }, + { + title: "پروفیل دوجداره", + text: "پروفیل UPVC یک ماده پلیمری است که جزء اصلی آن پلی وینیل کلراید می‌باشد. این نوع پروفیل به دلیل ویژگی‌های فراوانی که دارد، به طور گسترده در ساخت درب و پنجره‌ها مورد استفاده قرار می‌گیرد. از مزایای این نوع پروفیل می‌توان به هزینه کم، دوام بالا، مقاومت در برابر زنگ‌زدگی و خوردگی، وزن سبک، هزینه نگهداری کم، و قابلیت تعمیر ساده اشاره نمود.", + }, + { + title: "شیشه سه جداره (سکوریت و غیر سکوریت)", + text: "ضریب U برای شیشه‌های سه جداره در حدود ۰٫۵ تا ۰٫۸ W/m2K است. این مقدار نشان می‌دهد که شیشه‌های سه جداره نسبت به شیشه‌های دو جداره، عملکرد بهتری به عنوان عایق حرارتی دارند. با اینکه هزینه ساخت شیشه سه جداره بیشتر از دو جداره است، اما این نوع شیشه‌ها در خانه‌های سبز، مدرن و امروزی به کار می‌روند.", + }, + { + title: "ضخامت شیشه دو جداره", + text: "در حالت استاندارد، از شیشه‌های دو جداره استفاده می‌شود که دارای جدار خارجی با ضخامت ۶ میلی متر و جدار داخلی با ضخامت ۴ میلی متر هستند. همچنین، فاصله بین دو شیشه نیز با استفاده از اسپیسر آلومینیومی به عرض ۱۲ میلی متر تعیین می‌شود. لایه‌های مختلف مورد استفاده در شیشه دو جداره دارای ضخامت‌های متفاوت باشند تا کارایی عایق صوتی بهبود یابد.", + }, +]; + +export const glassTypes = [ + "شیشه دو جداره ساده: این نوع از دو شیشه فلوت ساده ساخته شده است.", + "شیشه دو جداره رنگی: این نوع دارای یک لایه شیشه رنگی و یک لایه شیشه ساده است.", + "شیشه دو جداره رفلکس: این نوع از یک لایه شیشه رفلکس و یک لایه شیشه ساده تشکیل شده است.", + "شیشه دو جداره سکوریت: شیشه‌های سکوریت تحت فرآیندی با حرارت نزدیک به ۷۰۰ درجه سانتی گراد ساخته می‌شوند و برای مکان‌های پرخطر مورد استفاده قرار می‌گیرند.", + "شیشه دو جداره لمینت: این نوع شیشه از یک لایه شیشه لمینت در کنار یک لایه شیشه ساده یا دو لایه شیشه لمینت ساخته می‌شود و انتقال کمتر صدا و ایمنی بالا دارد.", + "شیشه دو جداره اسپایدر: شیشه‌های اسپایدر به صورت نقطه‌ای به هم متصل می‌شوند و علاوه بر شفافیت بالا، وزن مناسب و مقاومت بالایی نسبت به ضربه دارند.", +]; + +export const qomText = + "در شهر قم، تولید کننده‌هایی از شیشه‌های دوجداره وجود دارند که با بهره‌گیری از تکنولوژی‌های پیشرفته و استفاده از مواد با کیفیت، شیشه‌هایی با ویژگی‌های عایق حرارتی و صوتی بالا تولید می‌کنند. فرخ وین یکی از مطرح ترین تولیدکنندگان شیشه دوجداره در قم میباشد."; + +export const upvcMakerText = + "تولید کنندگان پنجره UPVC از جمله مهمترین عوامل در صنعت ساختمانی هستند که با استفاده از فناوری‌های روز و مواد باکیفیت، پنجره‌هایی با عمر طولانی، عایقیت بالا، و ظاهر زیبا تولید می‌کنند. فرخ وین یکی از تولیدکنندگان پنجره upvc مطرح در کشور می باشد که با توجه به نیازهای مختلف بازار، محصولات متنوعی از جمله پنجره‌های دوجداره، سه‌جداره، شیشه سندبلاست و... ارائه می‌دهد."; + +export const blogPosts = [ + { + id: "2667", + title: "مقایسه جامع رنگ مخصوص و لمینت پروفیل + مزایا و معایب", + category: "پنجره UPVC", + image: "/images/blog/laminate.jpg", + href: "/blog", + }, + { + id: "2666", + title: "شیشه استین گلاس (استند گلاس) چیست؟", + category: "شیشه سند بلاست", + image: "/images/blog/stained.jpg", + href: "/blog", + }, + { + id: "2665", + title: "بهترین پنجره عایق صدا برای آرامش در خانه", + category: "پنجره UPVC", + image: "/images/blog/sound.jpg", + href: "/blog", + }, + { + id: "2664", + title: "اسکای‌لایت (سقف شیشه‌ای) چیست؟", + category: "شیشه سند بلاست", + image: "/images/blog/placeholder.jpg", + href: "/blog", + }, +]; + +export const homeProducts = [ + { + id: "14129", + title: "پنجره upvc", + kicker: "upvc", + image: "/images/products/upvc.jpg", + href: "/products", + }, + { + id: "14130", + title: "پنجره دوجدار upvc", + kicker: "winupvc", + image: "/images/products/double-glaze.jpg", + href: "/products", + }, + { + id: "14131", + title: "دوجداره کشویی", + kicker: "TESTI", + image: "/images/products/sliding.jpg", + href: "/products", + }, +]; + +export const brands = [ + { id: "wintech", title: "وین تک", image: "/images/brands/wintech.jpg" }, + { id: "pluspen", title: "پلاس پن", image: "/images/brands/pluspen.jpg" }, + { id: "hoffman", title: "وایزر وین شرکت هافمن", image: "/images/brands/hoffman.jpg" }, + { id: "kale", title: "یراق آلات کاله", image: "/images/brands/kale.jpg" }, +]; + +export const aboutGoals = [ + { + icon: "heart", + title: "طراحی عالی", + text: "کارشناسان گروه صنعتی پنجره فرخ،با تجربه بیش از 1000 پروژه انجام شده،متناسب با خواسته مشتریان و در نظر گرفتن مسائل فنی،طرح های زیبا و متنوعی ارائه می کنند.", + }, + { + icon: "star", + title: "ایده های الهام بخش", + text: "این یک نوشته آزمایشی است که به طراحان و برنامه نویسان کمک میکند تا این عزیزان با بهره گیری از این نوشته تستی و آزمایشی بتوانند", + }, + { + icon: "diamond", + title: "کیفیت بالا", + text: "مجموعه صنعتی فرخ با بهره گیری از ماشین آلات روز دنیا و کادر مجرب،محصولات خود را با بهترین کیفیت ممکن روانه بازار می کند.", + }, +]; + +export const faqs = [ + { + q: "منظور شما از کالا و محصول نهایی چیست؟", + a: "منظور از محصول نهایی،درب و پنجره UPVC به همراه شیشه های مربوطه و یا توری های پلیسه است.", + }, + { + q: "چند نمونه از محصولات چیست؟", + a: "شرکت در زمینه درب و پنجره UPVC،شیشه های دو و سه جداره،شیشه های دکوراتیو و سندبلاست و انواع توری پلیسه محصولات خود را روانه بازار می کند.", + }, + { + q: "آیا می توانم آنچه را که خریداری کرده ام را تغییر دهم؟", + a: "سفارشات تا قبل از نهایی شدن و وارد خط تولید شدن،قابل تغییر و ویرایش هستند.", + }, +]; + +export const footerLinks = [ + { href: "/products", label: "محصولات" }, + { href: "/contact", label: "تماس با ما" }, + { href: "/about", label: "درباره ی ما" }, +]; + +export const footerArticles = [ + { href: "/blog", label: "مقالات" }, + { href: "/blog", label: "شیشه سند بلاست" }, + { href: "/blog", label: "خرید پنجره UPVC" }, +]; diff --git a/src/lib/blog.ts b/src/lib/blog.ts new file mode 100644 index 0000000..afa1568 --- /dev/null +++ b/src/lib/blog.ts @@ -0,0 +1,108 @@ +import { blogPosts } from "@/data/site"; +import { formatFaDate, formatFaDateLong } 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; + categoryId?: string | null; + categoryName?: string | null; + categoryNameFa?: string | null; + category?: 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/blog/placeholder.jpg") { + return post.titleImageUrl?.trim() || fallback; +} + +export function blogDate(post: BlogPost) { + return formatFaDate(post.publishedAt || post.createdAt); +} + +export function blogDateLong(post: BlogPost) { + return formatFaDateLong(post.publishedAt || post.createdAt); +} + +const CATEGORY_FA: Record<string, string> = { + "upvc window": "پنجره UPVC", + "sand blast": "شیشه سند بلاست", + "double glazed glass": "شیشه دوجداره", +}; + +export function blogCategory( + post: Pick<BlogPost, "title" | "categoryNameFa" | "categoryName" | "category">, +) { + const local = blogPosts.find((item) => item.title === post.title)?.category; + if (local) return local; + const raw = (post.categoryNameFa || post.categoryName || post.category || "").trim(); + if (!raw) return ""; + return CATEGORY_FA[raw.toLowerCase()] || raw; +} + +export function getLocalBlogPosts(): BlogPost[] { + return blogPosts.map((post) => ({ + id: post.id, + title: post.title, + slug: post.id, + abstract: post.category, + category: post.category, + 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..37544ca --- /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..a6c23b6 --- /dev/null +++ b/src/lib/comments.ts @@ -0,0 +1,16 @@ +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..210f63a --- /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 ?? + "farrokhwin.com"; + +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..8cf2919 --- /dev/null +++ b/src/lib/format.ts @@ -0,0 +1,56 @@ +const FA_DIGITS = "۰۱۲۳۴۵۶۷۸۹"; + +export function toFaDigits(value: string | number) { + return String(value).replace(/\d/g, (digit) => FA_DIGITS[Number(digit)]); +} + +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 formatFaDateLong(value?: string | null) { + if (!value) return ""; + const date = new Date(value); + if (Number.isNaN(date.getTime())) return ""; + const parts = new Intl.DateTimeFormat("fa-IR", { + weekday: "long", + day: "numeric", + month: "long", + year: "numeric", + }).formatToParts(date); + const pick = (type: Intl.DateTimeFormatPartTypes) => parts.find((part) => part.type === type)?.value || ""; + const weekday = pick("weekday"); + const day = pick("day"); + const month = pick("month"); + const year = pick("year"); + return [weekday, day, month, year].filter(Boolean).join(" "); +} + +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, "") + .replace(/color\s*:\s*[^;"']+;?/gi, "") + .replace(/\scolor="[^"]*"/gi, "") + .replace(/\scolor='[^']*'/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..ab47399 --- /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..5b1827a --- /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/portfolios.ts b/src/lib/portfolios.ts new file mode 100644 index 0000000..e584f68 --- /dev/null +++ b/src/lib/portfolios.ts @@ -0,0 +1,67 @@ +import { meshkeeGet, meshkeeGetOrNull, tenantPath } from "@/lib/meshkee"; +import { detailPath } from "@/lib/slug"; + +export type PortfolioItem = { + id: string; + title?: string | null; + titleFa?: string | null; + titleEn?: string | null; + slug?: string | null; + abstract?: string | null; + mainTextHtml?: string | null; + titleImageUrl?: string | null; + categoryId?: string | null; + categoryName?: string | null; + gallery?: { url?: string | null }[] | string[] | null; + publishedAt?: string | null; +}; + +export type PagedPortfolios = { + items: PortfolioItem[]; + total: number; + page: number; + pageSize: number; +}; + +function normalize(item: PortfolioItem): PortfolioItem { + return { ...item, id: String(item.id) }; +} + +export async function fetchPortfolios(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<PagedPortfolios>(tenantPath(`/portfolios?${query.toString()}`), { + items: [], + total: 0, + page: 1, + pageSize: params.pageSize ?? 12, + }); + return { + ...data, + items: (data.items ?? []).map(normalize), + }; +} + +export async function fetchPortfolioById(id: string) { + const data = await meshkeeGetOrNull<{ portfolio?: PortfolioItem }>( + tenantPath(`/portfolios/by-id/${encodeURIComponent(id)}`), + ); + const portfolio = data?.portfolio; + return portfolio ? normalize(portfolio) : null; +} + +export function portfolioTitle(item: Pick<PortfolioItem, "titleFa" | "title">) { + return item.titleFa?.trim() || item.title?.trim() || "نمونه کار"; +} + +export function portfolioHref(item: Pick<PortfolioItem, "id" | "titleFa" | "title">) { + return detailPath("/portfolios", item.id, portfolioTitle(item)); +} + +export function portfolioImage(item: PortfolioItem, fallback = "/images/blog/placeholder.jpg") { + const extras = (item.gallery ?? []) + .map((entry) => (typeof entry === "string" ? entry : entry?.url)) + .filter((url): url is string => Boolean(url?.trim())); + return item.titleImageUrl?.trim() || extras[0] || fallback; +} diff --git a/src/lib/products.ts b/src/lib/products.ts new file mode 100644 index 0000000..263439f --- /dev/null +++ b/src/lib/products.ts @@ -0,0 +1,124 @@ +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; +}; + +function normalizeProduct(product: CatalogProduct): CatalogProduct { + return { ...product, id: String(product.id) }; +} + +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); + const data = await meshkeeGet<PagedProducts>(tenantPath(`/products?${query.toString()}`), { + items: [], + total: 0, + page: 1, + pageSize: params.pageSize ?? 12, + }); + return { + ...data, + items: (data.items ?? []).map(normalizeProduct), + }; +} + +export async function fetchProductById(id: string) { + const data = await meshkeeGetOrNull<ProductDetailPayload>( + tenantPath(`/products/by-id/${encodeURIComponent(id)}`), + ); + if (!data?.product) return null; + return { + product: normalizeProduct(data.product), + relatedProducts: (data.relatedProducts ?? []).map(normalizeProduct), + }; +} + +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/products/upvc.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()), + ), + ), + ); +} diff --git a/src/lib/seo.ts b/src/lib/seo.ts new file mode 100644 index 0000000..2ed8ed8 --- /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/hero/slide-1.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..0f90820 --- /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..fe478e8 --- /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..ac4fa2f --- /dev/null +++ b/src/lib/sitemap-config.generated.json @@ -0,0 +1,40 @@ +{ + "baseUrl": "https://farrokhwin.com", + "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": "/portfolio", + "changefreq": "weekly", + "priority": 0.7 + }, + { + "path": "/products", + "changefreq": "weekly", + "priority": 0.8 + }, + { + "path": "/videos", + "changefreq": "weekly", + "priority": 0.6 + } + ] +} diff --git a/src/lib/slug.ts b/src/lib/slug.ts new file mode 100644 index 0000000..abee8f1 --- /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..658c546 --- /dev/null +++ b/src/lib/static-images.ts @@ -0,0 +1,253 @@ +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. + */ +export const STATIC_IMAGE_SLOTS: StaticImageSlotCatalogItem[] = [ + { + key: "slider", + label: "Homepage slider", + kind: "list", + pageKey: "home", + aspectRatio: "1920:1080", + itemCount: null, + recommendedWidth: 1920, + }, + { + key: "home-video-poster", + label: "Homepage video / intro image", + kind: "single", + pageKey: "home", + aspectRatio: "16:9", + itemCount: 1, + recommendedWidth: 1280, + }, + { + key: "home-windows", + label: "Homepage window types", + kind: "list", + pageKey: "home", + aspectRatio: "12:17", + itemCount: 4, + recommendedWidth: 720, + }, + { + key: "home-why-image", + label: "Homepage why Farrokh graphic", + kind: "single", + pageKey: "home", + aspectRatio: "1:1", + itemCount: 1, + recommendedWidth: 640, + }, + { + key: "home-blog", + label: "Homepage blog carousel", + kind: "list", + pageKey: "home", + aspectRatio: "1:1", + itemCount: 12, + recommendedWidth: 720, + }, + { + key: "home-products", + label: "Homepage products carousel", + kind: "list", + pageKey: "home", + aspectRatio: "4:3", + itemCount: 12, + recommendedWidth: 640, + }, + { + key: "home-clients", + label: "Homepage brand logos", + kind: "list", + pageKey: "home", + aspectRatio: "3:2", + itemCount: 4, + recommendedWidth: 320, + }, + { + key: "about-hero", + label: "About page hero", + kind: "single", + pageKey: "about", + aspectRatio: "1920:480", + itemCount: 1, + recommendedWidth: 1440, + }, + { + key: "about-photo", + label: "About page photo", + kind: "single", + pageKey: "about", + aspectRatio: "4:3", + itemCount: 1, + recommendedWidth: 800, + }, + { + key: "contact-hero", + label: "Contact page hero", + kind: "single", + pageKey: "contact", + aspectRatio: "1920:480", + itemCount: 1, + recommendedWidth: 1440, + }, + { + key: "contact-faq-image", + label: "Contact FAQ photo", + kind: "single", + pageKey: "contact", + aspectRatio: "4:3", + itemCount: 1, + recommendedWidth: 960, + }, +]; + +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]}`; +} + +export function pickSlot(slots: StaticImageSlot[], key: string) { + return slots.find((slot) => slot.key === 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..128ee06 --- /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..52f66e9 --- /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"] +}