@mohasinac/appkit 4.42.18 → 4.44.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/_internal/client/features/lottery/LotteryDetailView.js +22 -1
- package/dist/_internal/server/features/categories/adapters.d.ts +47 -0
- package/dist/_internal/server/features/categories/adapters.js +235 -0
- package/dist/_internal/server/features/checkout/actions.js +161 -22
- package/dist/_internal/server/features/checkout/expand-order-items.js +34 -0
- package/dist/_internal/server/features/orders/adapters.js +21 -0
- package/dist/_internal/server/features/products/list-public.d.ts +24 -0
- package/dist/_internal/server/features/products/list-public.js +10 -2
- package/dist/_internal/server/features/refunds/actions.js +1 -1
- package/dist/_internal/server/features/seo/sitemap.js +54 -15
- package/dist/_internal/server/features/stores/adapters.d.ts +1 -1
- package/dist/_internal/server/features/stores/adapters.js +6 -0
- package/dist/_internal/server/functions/scheduled.d.ts +4 -2
- package/dist/_internal/server/functions/scheduled.js +64 -6
- package/dist/_internal/server/jobs/core/adminAnalytics.d.ts +0 -1
- package/dist/_internal/server/jobs/core/adminAnalytics.js +9 -1
- package/dist/_internal/server/jobs/core/analyticsAlertEvaluate.d.ts +47 -0
- package/dist/_internal/server/jobs/core/analyticsAlertEvaluate.js +468 -0
- package/dist/_internal/server/jobs/core/autoPayoutEligibility.js +14 -1
- package/dist/_internal/server/jobs/core/countersReconcile.js +24 -8
- package/dist/_internal/server/jobs/core/onCategoryWrite.js +59 -0
- package/dist/_internal/server/jobs/core/onProductWrite.js +31 -0
- package/dist/_internal/server/jobs/core/onReviewWrite.js +8 -0
- package/dist/_internal/server/jobs/core/onStoreWrite.d.ts +1 -1
- package/dist/_internal/server/jobs/core/onStoreWrite.js +8 -2
- package/dist/_internal/server/jobs/core/priceIndexRollup.d.ts +47 -0
- package/dist/_internal/server/jobs/core/priceIndexRollup.js +234 -0
- package/dist/_internal/server/jobs/core/revalidateNotify.d.ts +28 -0
- package/dist/_internal/server/jobs/core/revalidateNotify.js +125 -0
- package/dist/_internal/server/jobs/core/{testerSandboxCleanup.d.ts → testDataCleanup.d.ts} +3 -3
- package/dist/_internal/server/jobs/core/{testerSandboxCleanup.js → testDataCleanup.js} +3 -3
- package/dist/_internal/server/jobs/core/weeklyPayoutEligibility.js +5 -1
- package/dist/_internal/server/jobs/handlers/analyticsAlertEvaluate.d.ts +2 -0
- package/dist/_internal/server/jobs/handlers/analyticsAlertEvaluate.js +2 -0
- package/dist/_internal/server/jobs/handlers/index.d.ts +3 -1
- package/dist/_internal/server/jobs/handlers/index.js +3 -1
- package/dist/_internal/server/jobs/handlers/messages.d.ts +5 -2
- package/dist/_internal/server/jobs/handlers/messages.js +5 -2
- package/dist/_internal/server/jobs/handlers/priceIndexRollup.d.ts +2 -0
- package/dist/_internal/server/jobs/handlers/priceIndexRollup.js +2 -0
- package/dist/_internal/server/jobs/handlers/testDataCleanup.d.ts +2 -0
- package/dist/_internal/server/jobs/handlers/testDataCleanup.js +2 -0
- package/dist/_internal/shared/actions/action-registry.js +34 -0
- package/dist/_internal/shared/cache/revalidate-targets.d.ts +49 -0
- package/dist/_internal/shared/cache/revalidate-targets.js +157 -0
- package/dist/_internal/shared/checkout/order-math.d.ts +113 -3
- package/dist/_internal/shared/checkout/order-math.js +194 -7
- package/dist/_internal/shared/features/categories/price-guidance.d.ts +110 -0
- package/dist/_internal/shared/features/categories/price-guidance.js +136 -0
- package/dist/_internal/shared/features/site-settings/gst-registration.d.ts +90 -0
- package/dist/_internal/shared/features/site-settings/gst-registration.js +126 -0
- package/dist/_internal/shared/fees/calculator.d.ts +105 -0
- package/dist/_internal/shared/fees/calculator.js +143 -0
- package/dist/_internal/shared/listing-types/art/config.d.ts +0 -6
- package/dist/_internal/shared/listing-types/classified/config.d.ts +0 -6
- package/dist/_internal/shared/listing-types/digital-code/config.d.ts +0 -6
- package/dist/_internal/shared/listing-types/live/config.d.ts +0 -6
- package/dist/_internal/shared/listing-types/standard/config.d.ts +0 -6
- package/dist/_internal/shared/listing-types/stickers/config.d.ts +0 -6
- package/dist/_internal/shared/templating/placeholders.d.ts +70 -0
- package/dist/_internal/shared/templating/placeholders.js +73 -0
- package/dist/client.d.ts +2 -0
- package/dist/client.js +1 -0
- package/dist/constants/api-endpoints.d.ts +42 -0
- package/dist/constants/api-endpoints.js +14 -0
- package/dist/features/admin/actions/admin-actions.d.ts +1 -1
- package/dist/features/admin/actions/admin-actions.js +1 -1
- package/dist/features/admin/components/AdminCategoryEditorView.js +269 -3
- package/dist/features/admin/components/AdminProductEditorView.js +8 -1
- package/dist/features/admin/components/AdminProductsView.js +78 -7
- package/dist/features/admin/components/AdminSiteSettingsView.js +20 -1
- package/dist/features/admin/components/AdminTaxCodeEditorView.d.ts +10 -0
- package/dist/features/admin/components/AdminTaxCodeEditorView.js +160 -0
- package/dist/features/admin/components/AdminTaxCodesView.d.ts +13 -0
- package/dist/features/admin/components/AdminTaxCodesView.js +86 -0
- package/dist/features/admin/components/AdminTesterChecklistView.js +1 -1
- package/dist/features/admin/components/analytics/AdminTopProductsTable.js +1 -1
- package/dist/features/auctions/components/AuctionBottomActions.js +10 -1
- package/dist/features/auctions/components/AuctionDetailPageView.js +2 -1
- package/dist/features/auctions/components/AuctionLiveStatusBadge.d.ts +20 -0
- package/dist/features/auctions/components/AuctionLiveStatusBadge.js +21 -0
- package/dist/features/auctions/components/PlaceBidFormClient.d.ts +1 -1
- package/dist/features/auctions/components/PlaceBidFormClient.js +20 -2
- package/dist/features/auctions/hooks/useIsAuctionEnded.d.ts +41 -0
- package/dist/features/auctions/hooks/useIsAuctionEnded.js +76 -0
- package/dist/features/auth/schemas/firestore.d.ts +0 -1
- package/dist/features/blog/api/route.js +16 -1
- package/dist/features/blog/schemas/firestore.d.ts +2 -2
- package/dist/features/catalogue/components/CatalogueItemEditorView.js +14 -3
- package/dist/features/categories/actions/category-actions.d.ts +2 -2
- package/dist/features/categories/actions/category-actions.js +22 -10
- package/dist/features/categories/api/[id]/description-template/route.d.ts +45 -0
- package/dist/features/categories/api/[id]/description-template/route.js +252 -0
- package/dist/features/categories/api/route.js +156 -11
- package/dist/features/categories/components/CategoriesIndexListing.d.ts +1 -3
- package/dist/features/categories/components/CategoriesIndexListing.js +1 -1
- package/dist/features/categories/components/CategoriesIndexPageView.d.ts +27 -1
- package/dist/features/categories/components/CategoriesIndexPageView.js +24 -33
- package/dist/features/categories/components/CategoryDetailPageView.js +43 -10
- package/dist/features/categories/components/CategoryTemplatePicker.d.ts +65 -0
- package/dist/features/categories/components/CategoryTemplatePicker.js +170 -0
- package/dist/features/categories/constants/listing-categories.d.ts +17 -0
- package/dist/features/categories/constants/listing-categories.js +53 -0
- package/dist/features/categories/hooks/useCategoryTree.js +28 -2
- package/dist/features/categories/repository/categories.repository.d.ts +73 -12
- package/dist/features/categories/repository/categories.repository.js +203 -42
- package/dist/features/categories/repository/price-index.repository.d.ts +115 -0
- package/dist/features/categories/repository/price-index.repository.js +85 -0
- package/dist/features/categories/schemas/category-content.d.ts +206 -0
- package/dist/features/categories/schemas/category-content.js +28 -0
- package/dist/features/categories/schemas/firestore.d.ts +74 -5
- package/dist/features/categories/schemas/firestore.js +74 -3
- package/dist/features/categories/schemas/index.d.ts +1047 -0
- package/dist/features/categories/schemas/index.js +96 -0
- package/dist/features/categories/server.d.ts +1 -0
- package/dist/features/categories/server.js +7 -0
- package/dist/features/categories/types/index.d.ts +9 -0
- package/dist/features/events/schemas/firestore.d.ts +2 -2
- package/dist/features/faq/schemas/firestore.d.ts +2 -1
- package/dist/features/faq/schemas/firestore.js +20 -8
- package/dist/features/faq/utils/getHomepageFaqsCached.d.ts +20 -0
- package/dist/features/faq/utils/getHomepageFaqsCached.js +22 -0
- package/dist/features/homepage/components/MarketplaceHomepageView.js +13 -3
- package/dist/features/homepage/lib/live-stats.js +18 -8
- package/dist/features/media/MediaImage.js +19 -2
- package/dist/features/orders/schemas/firestore.d.ts +70 -0
- package/dist/features/orders/types/index.d.ts +55 -0
- package/dist/features/products/actions/product-actions.d.ts +0 -9
- package/dist/features/products/actions/product-actions.js +25 -0
- package/dist/features/products/columns/index.d.ts +0 -1
- package/dist/features/products/columns/index.js +5 -1
- package/dist/features/products/components/ArtStickersListView.js +7 -1
- package/dist/features/products/components/PrizeDrawsSection.js +5 -0
- package/dist/features/products/components/ProductFilters.d.ts +0 -9
- package/dist/features/products/components/ProductForm.d.ts +27 -1
- package/dist/features/products/components/ProductForm.js +26 -12
- package/dist/features/products/components/ProductsIndexPageView.js +1 -1
- package/dist/features/products/constants/sieve.d.ts +0 -6
- package/dist/features/products/constants/sieve.js +35 -2
- package/dist/features/products/repository/products.repository.d.ts +115 -1
- package/dist/features/products/repository/products.repository.js +491 -39
- package/dist/features/products/schemas/firestore.d.ts +65 -5
- package/dist/features/products/schemas/firestore.js +9 -0
- package/dist/features/products/schemas/product-features.d.ts +140 -2
- package/dist/features/products/schemas/product-features.js +62 -1
- package/dist/features/promotions/repository/coupons.repository.js +6 -1
- package/dist/features/promotions/schemas/firestore.d.ts +44 -1
- package/dist/features/promotions/schemas/firestore.js +76 -2
- package/dist/features/reviews/repository/reviews.repository.d.ts +22 -0
- package/dist/features/reviews/repository/reviews.repository.js +35 -0
- package/dist/features/seller/actions/seller-actions.js +17 -2
- package/dist/features/seller/components/PrintCenterView.d.ts +3 -2
- package/dist/features/seller/components/PrintCenterView.js +3 -2
- package/dist/features/seller/components/SellerAnalyticsAlertsView.js +21 -11
- package/dist/features/seller/components/SellerOrdersView.js +6 -1
- package/dist/features/seller/components/SellerPayoutSettingsView.js +19 -3
- package/dist/features/seller/components/SellerProductShell.d.ts +50 -2
- package/dist/features/seller/components/SellerProductShell.js +101 -9
- package/dist/features/seller/components/SellerProductsFilterDrawer.d.ts +13 -1
- package/dist/features/seller/components/SellerProductsFilterDrawer.js +2 -2
- package/dist/features/seller/components/SellerProductsView.js +108 -3
- package/dist/features/seller/types/index.d.ts +17 -0
- package/dist/features/seller/utils/product-draft-mapping.d.ts +13 -0
- package/dist/features/seller/utils/product-draft-mapping.js +17 -0
- package/dist/features/store-extensions/constants/alert-metrics.d.ts +86 -0
- package/dist/features/store-extensions/constants/alert-metrics.js +142 -0
- package/dist/features/store-extensions/schemas/firestore.d.ts +19 -1
- package/dist/features/stores/schemas/firestore.d.ts +31 -2
- package/dist/features/tax-codes/repository/tax-codes.repository.d.ts +55 -0
- package/dist/features/tax-codes/repository/tax-codes.repository.js +131 -0
- package/dist/features/tax-codes/schemas/firestore.d.ts +125 -0
- package/dist/features/tax-codes/schemas/firestore.js +82 -0
- package/dist/features/tax-codes/schemas/tax-code-form.d.ts +63 -0
- package/dist/features/tax-codes/schemas/tax-code-form.js +152 -0
- package/dist/features/tax-codes/schemas/tax-codes.validators.d.ts +107 -0
- package/dist/features/tax-codes/schemas/tax-codes.validators.js +85 -0
- package/dist/features/tester/actions/index.d.ts +0 -1
- package/dist/features/tester/actions/index.js +0 -1
- package/dist/features/tester/components/AdminTesterFeedbackIssuesView.js +1 -1
- package/dist/features/tester/components/AdminTesterFeedbackListView.js +1 -1
- package/dist/features/tester/components/index.d.ts +0 -4
- package/dist/features/tester/components/index.js +0 -2
- package/dist/features/tester/repository/tester-checklist-item.repository.d.ts +1 -20
- package/dist/features/tester/repository/tester-checklist-item.repository.js +0 -52
- package/dist/features/tester/schemas/firestore.d.ts +0 -6
- package/dist/features/tester/seed-data/authored/admin__admin-detail-round-trips.js +3 -3
- package/dist/features/tester/seed-data/authored/admin__site-system.js +4 -5
- package/dist/features/tester/seed-data/authored/content-discovery__category-counts-and-rollup.js +2 -2
- package/dist/features/tester/seed-data/authored/index.js +0 -2
- package/dist/features/tester/seed-data/authored/page-wiring__reachability.js +0 -19
- package/dist/features/tester/seed-data/tester-checklist-seed-data.js +10 -29
- package/dist/index.d.ts +21 -7
- package/dist/index.js +53 -7
- package/dist/next/routing/route-map.d.ts +2 -4
- package/dist/next/routing/route-map.js +1 -2
- package/dist/repositories/index.d.ts +1 -0
- package/dist/repositories/index.js +1 -0
- package/dist/schemas/registry.d.ts +506 -0
- package/dist/seed/_helpers/brand-rows.d.ts +35 -0
- package/dist/seed/_helpers/brand-rows.js +576 -0
- package/dist/seed/_helpers/category-content.d.ts +39 -0
- package/dist/seed/_helpers/category-content.js +634 -0
- package/dist/seed/_helpers/category-forest.d.ts +2 -0
- package/dist/seed/_helpers/category-forest.js +585 -243
- package/dist/seed/_helpers/category-models.d.ts +5 -0
- package/dist/seed/_helpers/category-models.js +1364 -0
- package/dist/seed/_helpers/category-tree.js +17 -1
- package/dist/seed/_helpers/character-features.d.ts +10 -0
- package/dist/seed/_helpers/character-features.js +118 -0
- package/dist/seed/_helpers/media.d.ts +1 -1
- package/dist/seed/_helpers/media.js +44 -8
- package/dist/seed/_helpers/search-txt-wrappers.d.ts +10 -0
- package/dist/seed/_helpers/search-txt-wrappers.js +12 -1
- package/dist/seed/categories-seed-data.js +147 -169
- package/dist/seed/index.d.ts +1 -0
- package/dist/seed/index.js +1 -0
- package/dist/seed/manifest.d.ts +1 -1
- package/dist/seed/manifest.js +5 -0
- package/dist/seed/orders-seed-data.js +5 -3
- package/dist/seed/product-features-seed-data.d.ts +1 -8
- package/dist/seed/product-features-seed-data.js +358 -154
- package/dist/seed/products-art-seed-data.js +16 -4
- package/dist/seed/products-auctions-seed-data.js +6 -6
- package/dist/seed/products-classifieds-seed-data.js +17 -7
- package/dist/seed/products-digital-codes-seed-data.js +12 -12
- package/dist/seed/products-preorders-seed-data.js +8 -8
- package/dist/seed/products-prize-draws-seed-data.js +25 -8
- package/dist/seed/products-standard-seed-data.js +10 -60
- package/dist/seed/products-stickers-seed-data.js +6 -6
- package/dist/seed/site-settings-seed-data.js +24 -0
- package/dist/seed/store-extensions-seed-data.js +10 -4
- package/dist/seed/tax-codes-seed-data.d.ts +34 -0
- package/dist/seed/tax-codes-seed-data.js +100 -0
- package/dist/seed/test-data-ttl.d.ts +29 -0
- package/dist/seed/test-data-ttl.js +31 -0
- package/dist/server-entry.d.ts +1 -1
- package/dist/server-entry.js +6 -1
- package/dist/server.d.ts +4 -1
- package/dist/server.js +11 -2
- package/dist/styles.css +40 -0
- package/dist/ui/components/PaginatedSelect.d.ts +14 -0
- package/dist/ui/components/PaginatedSelect.js +0 -0
- package/dist/ui/components/PaginatedSelect.style.css +40 -0
- package/dist/utils/media-url.d.ts +26 -0
- package/dist/utils/media-url.js +144 -0
- package/dist/utils/physical-location.d.ts +23 -0
- package/dist/utils/physical-location.js +52 -0
- package/dist/utils/search-txt-builders.d.ts +20 -0
- package/dist/utils/search-txt-builders.js +36 -0
- package/package.json +1 -1
- package/scripts/seed-cli.mjs +4 -1
- package/dist/_internal/server/jobs/handlers/testerSandboxCleanup.d.ts +0 -2
- package/dist/_internal/server/jobs/handlers/testerSandboxCleanup.js +0 -2
- package/dist/features/tester/actions/leaderboard-actions.d.ts +0 -3
- package/dist/features/tester/actions/leaderboard-actions.js +0 -5
- package/dist/features/tester/components/BugHunterLeaderboardView.d.ts +0 -23
- package/dist/features/tester/components/BugHunterLeaderboardView.js +0 -18
- package/dist/features/tester/components/TesterHubView.d.ts +0 -5
- package/dist/features/tester/components/TesterHubView.js +0 -110
- package/dist/features/tester/seed-data/authored/public-pages__bug-hunters.d.ts +0 -2
- package/dist/features/tester/seed-data/authored/public-pages__bug-hunters.js +0 -78
- package/dist/features/tester/seed-data/tester-ttl.d.ts +0 -4
- package/dist/features/tester/seed-data/tester-ttl.js +0 -6
|
@@ -1,162 +1,366 @@
|
|
|
1
|
-
|
|
2
|
-
*
|
|
1
|
+
/*
|
|
2
|
+
* WHY: `productFeatures` held 10 platform badge flags while `ProductDocument.tags`
|
|
3
|
+
* held free text with no vocabulary at all — which is how `attack-type` and
|
|
4
|
+
* `attack type` coexist. B5 retires `tags` and makes this collection the ONE
|
|
5
|
+
* vocabulary, so it has to carry everything a facet needs: release type,
|
|
6
|
+
* authenticity, battle type, spin, card language, card format, grade, chase
|
|
7
|
+
* status, figure scale. Those are exactly the axes the crawl measured and
|
|
8
|
+
* that §4.4 refuses to put in the tree, because a product has one category
|
|
9
|
+
* chain and any number of features.
|
|
10
|
+
* WHAT: 171 platform-scope rows, grouped by `FeatureGroup`, built by a factory —
|
|
11
|
+
* 76 hand-authored from the crawl's measured vocabulary plus 95 GENERATED
|
|
12
|
+
* blader/character rows. id === slug, prefix `feature-`.
|
|
3
13
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
14
|
+
* EXPORTS:
|
|
15
|
+
* productFeaturesSeedData — ProductFeatureDocument[]
|
|
16
|
+
* PRODUCT_FEATURE_COUNT — asserted downstream so a silent drop is loud
|
|
6
17
|
*
|
|
7
|
-
*
|
|
18
|
+
* ## 🛑 Three things the factory exists to prevent
|
|
19
|
+
*
|
|
20
|
+
* 1. **`searchTxt` is built from the LABEL and the aliases, never the slug.**
|
|
21
|
+
* `buildProductSearchTxt` used to index `features` raw, so the id
|
|
22
|
+
* `feature-free-shipping` tokenised to free/shipping and searching the
|
|
23
|
+
* LABEL "Free Shipping" worked only by coincidence — while a label like
|
|
24
|
+
* "New in Box" whose slug is `feature-nib` was unfindable entirely.
|
|
25
|
+
*
|
|
26
|
+
* 2. **Aliases are BUDGETED.** They fold into a 600-token array that truncates
|
|
27
|
+
* SILENTLY, so each row caps at a handful. The misspelling corpus from
|
|
28
|
+
* bladekingbeys is deliberately NOT dumped in here wholesale — a
|
|
29
|
+
* counterfeiter's trademark-evasion spellings (`Pegasis`, `Jupitar`,
|
|
30
|
+
* `Fireblase`) are what real buyers mistype, so they belong on the MODEL
|
|
31
|
+
* leaf that owns the name, not on a generic feature.
|
|
32
|
+
*
|
|
33
|
+
* 3. **`group` is not `category`.** `category` groups by the SURFACE that shows
|
|
34
|
+
* the badge; `group` groups by what the feature MEANS, and drives one facet
|
|
35
|
+
* section each. "Sealed" on a TCG booster box and "sealed" on a bey are the
|
|
36
|
+
* same word about different things.
|
|
37
|
+
*
|
|
38
|
+
* @tag domain:products
|
|
39
|
+
* @tag layer:seed
|
|
40
|
+
* @tag pattern:shared-literal
|
|
41
|
+
* @tag access:server-only
|
|
42
|
+
* @tag consumers:seed/index.ts,seed/runner.ts
|
|
43
|
+
* @tag sideEffects:none
|
|
8
44
|
*/
|
|
45
|
+
import { buildSearchTxt } from "../utils/search-txt";
|
|
46
|
+
import { CHARACTER_SEEDS, CHARACTER_SEED_COUNT } from "./_helpers/character-features";
|
|
9
47
|
const NOW = new Date("2026-05-11T00:00:00.000Z");
|
|
10
48
|
const daysAgo = (n) => new Date(NOW.getTime() - n * 86400000);
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
id: "feature-verified-seller",
|
|
29
|
-
slug: "feature-verified-seller",
|
|
30
|
-
label: "Verified Seller",
|
|
31
|
-
description: "Sold by a LetItRip verified seller — identity and address confirmed.",
|
|
32
|
-
icon: "badge-check",
|
|
33
|
-
iconColor: "--appkit-color-primary",
|
|
34
|
-
category: "seller",
|
|
35
|
-
scope: "platform",
|
|
36
|
-
productTypes: ["all"],
|
|
37
|
-
isActive: true,
|
|
38
|
-
displayOrder: 20,
|
|
39
|
-
createdAt: daysAgo(30),
|
|
40
|
-
updatedAt: daysAgo(30),
|
|
41
|
-
},
|
|
42
|
-
{
|
|
43
|
-
id: "feature-accept-returns",
|
|
44
|
-
slug: "feature-accept-returns",
|
|
45
|
-
label: "Returns Accepted",
|
|
46
|
-
description: "Eligible for return within the platform return window.",
|
|
47
|
-
icon: "refresh-ccw",
|
|
48
|
-
iconColor: "--appkit-color-secondary",
|
|
49
|
-
category: "platform",
|
|
50
|
-
scope: "platform",
|
|
51
|
-
productTypes: ["all"],
|
|
52
|
-
isActive: true,
|
|
53
|
-
displayOrder: 30,
|
|
54
|
-
createdAt: daysAgo(30),
|
|
55
|
-
updatedAt: daysAgo(30),
|
|
56
|
-
},
|
|
57
|
-
{
|
|
58
|
-
id: "feature-condition-new",
|
|
59
|
-
slug: "feature-condition-new",
|
|
60
|
-
label: "Brand New",
|
|
61
|
-
description: "Factory-sealed or unopened, in original packaging.",
|
|
62
|
-
icon: "sparkles",
|
|
63
|
-
iconColor: "--appkit-color-primary",
|
|
64
|
-
category: "condition",
|
|
65
|
-
scope: "platform",
|
|
66
|
-
productTypes: ["all"],
|
|
67
|
-
isActive: true,
|
|
68
|
-
displayOrder: 40,
|
|
69
|
-
createdAt: daysAgo(30),
|
|
70
|
-
updatedAt: daysAgo(30),
|
|
71
|
-
},
|
|
72
|
-
{
|
|
73
|
-
id: "feature-condition-used",
|
|
74
|
-
slug: "feature-condition-used",
|
|
75
|
-
label: "Pre-owned",
|
|
76
|
-
description: "Used item — see listing description for condition details.",
|
|
77
|
-
icon: "package",
|
|
78
|
-
iconColor: "--appkit-color-secondary",
|
|
79
|
-
category: "condition",
|
|
80
|
-
scope: "platform",
|
|
81
|
-
productTypes: ["all"],
|
|
82
|
-
isActive: true,
|
|
83
|
-
displayOrder: 50,
|
|
84
|
-
createdAt: daysAgo(30),
|
|
85
|
-
updatedAt: daysAgo(30),
|
|
86
|
-
},
|
|
87
|
-
{
|
|
88
|
-
id: "feature-featured",
|
|
89
|
-
slug: "feature-featured",
|
|
90
|
-
label: "Featured",
|
|
91
|
-
description: "Hand-picked by the LetItRip editorial team.",
|
|
92
|
-
icon: "star",
|
|
93
|
-
iconColor: "--appkit-color-primary",
|
|
94
|
-
category: "platform",
|
|
95
|
-
scope: "platform",
|
|
96
|
-
productTypes: ["all"],
|
|
97
|
-
isActive: true,
|
|
98
|
-
displayOrder: 60,
|
|
99
|
-
createdAt: daysAgo(30),
|
|
100
|
-
updatedAt: daysAgo(30),
|
|
101
|
-
},
|
|
102
|
-
{
|
|
103
|
-
id: "feature-promoted",
|
|
104
|
-
slug: "feature-promoted",
|
|
105
|
-
label: "Promoted",
|
|
106
|
-
description: "Boosted placement — seller has promoted this listing.",
|
|
107
|
-
icon: "megaphone",
|
|
108
|
-
iconColor: "--appkit-color-secondary",
|
|
109
|
-
category: "platform",
|
|
110
|
-
scope: "platform",
|
|
111
|
-
productTypes: ["all"],
|
|
112
|
-
isActive: true,
|
|
113
|
-
displayOrder: 70,
|
|
114
|
-
createdAt: daysAgo(30),
|
|
115
|
-
updatedAt: daysAgo(30),
|
|
116
|
-
},
|
|
117
|
-
{
|
|
118
|
-
id: "feature-auction-winner-badge",
|
|
119
|
-
slug: "feature-auction-winner-badge",
|
|
120
|
-
label: "Winner Badge",
|
|
121
|
-
description: "Winners of this auction receive a special badge on their profile.",
|
|
122
|
-
icon: "trophy",
|
|
123
|
-
iconColor: "--appkit-color-primary",
|
|
124
|
-
category: "auction",
|
|
125
|
-
scope: "platform",
|
|
126
|
-
productTypes: ["auction"],
|
|
127
|
-
isActive: true,
|
|
128
|
-
displayOrder: 80,
|
|
129
|
-
createdAt: daysAgo(30),
|
|
130
|
-
updatedAt: daysAgo(30),
|
|
131
|
-
},
|
|
132
|
-
{
|
|
133
|
-
id: "feature-shipping-paid-by-seller",
|
|
134
|
-
slug: "feature-shipping-paid-by-seller",
|
|
135
|
-
label: "Seller Pays Shipping",
|
|
136
|
-
description: "Seller covers all shipping costs on this auction — winner pays only the bid.",
|
|
137
|
-
icon: "package-check",
|
|
138
|
-
iconColor: "--appkit-color-primary",
|
|
139
|
-
category: "shipping",
|
|
140
|
-
scope: "platform",
|
|
141
|
-
productTypes: ["auction"],
|
|
142
|
-
isActive: true,
|
|
143
|
-
displayOrder: 90,
|
|
144
|
-
createdAt: daysAgo(30),
|
|
145
|
-
updatedAt: daysAgo(30),
|
|
146
|
-
},
|
|
147
|
-
{
|
|
148
|
-
id: "feature-preorder-confirmed",
|
|
149
|
-
slug: "feature-preorder-confirmed",
|
|
150
|
-
label: "Pre-order Confirmed",
|
|
151
|
-
description: "Production and delivery dates are confirmed by the manufacturer.",
|
|
152
|
-
icon: "calendar-check",
|
|
153
|
-
iconColor: "--appkit-color-primary",
|
|
154
|
-
category: "preorder",
|
|
49
|
+
const STAMP = daysAgo(30);
|
|
50
|
+
const TONE = {
|
|
51
|
+
primary: "--appkit-color-primary",
|
|
52
|
+
secondary: "--appkit-color-secondary",
|
|
53
|
+
neutral: undefined,
|
|
54
|
+
};
|
|
55
|
+
function featureRow(input, order) {
|
|
56
|
+
const id = `feature-${input.slug}`;
|
|
57
|
+
return {
|
|
58
|
+
id,
|
|
59
|
+
slug: id,
|
|
60
|
+
label: input.label,
|
|
61
|
+
description: input.description,
|
|
62
|
+
icon: input.icon,
|
|
63
|
+
...(TONE[input.tone ?? "neutral"] ? { iconColor: TONE[input.tone ?? "neutral"] } : {}),
|
|
64
|
+
category: input.category,
|
|
65
|
+
group: input.group,
|
|
155
66
|
scope: "platform",
|
|
156
|
-
productTypes: ["
|
|
67
|
+
productTypes: input.types ?? ["all"],
|
|
68
|
+
...(input.aliases?.length ? { aliases: input.aliases } : {}),
|
|
69
|
+
/*
|
|
70
|
+
* 🛑 LABEL + aliases. Never the slug — see note 1. A row whose label and
|
|
71
|
+
* slug diverge ("New in Box" / `feature-nib`) is precisely the case a
|
|
72
|
+
* slug-derived index cannot find.
|
|
73
|
+
*/
|
|
74
|
+
searchTxt: buildSearchTxt([input.label, ...(input.aliases ?? [])]),
|
|
157
75
|
isActive: true,
|
|
158
|
-
displayOrder:
|
|
159
|
-
createdAt:
|
|
160
|
-
updatedAt:
|
|
161
|
-
}
|
|
76
|
+
displayOrder: order,
|
|
77
|
+
createdAt: STAMP,
|
|
78
|
+
updatedAt: STAMP,
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
82
|
+
// Platform badges — the ORIGINAL TEN, now grouped
|
|
83
|
+
//
|
|
84
|
+
// 🛑 Their ids are unchanged on purpose. `group` was optional purely so these
|
|
85
|
+
// compiled before B5; leaving them ungrouped would have put them in no facet
|
|
86
|
+
// section, which is indistinguishable from not existing.
|
|
87
|
+
//
|
|
88
|
+
// 🛑 `feature-condition-used` is relabelled "Pre-owned" with "Used" as an
|
|
89
|
+
// ALIAS rather than being joined by a second `feature-pre-owned` row. The two
|
|
90
|
+
// words describe one state — the category description templates treat them
|
|
91
|
+
// identically, by design — and a second row for a synonym is how the two
|
|
92
|
+
// vocabularies this merge exists to collapse started in the first place.
|
|
93
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
94
|
+
const PLATFORM = [
|
|
95
|
+
{ slug: "free-shipping", label: "Free Shipping", description: "Shipping is included in the listed price.", icon: "truck", group: "commercial", category: "shipping", tone: "primary", aliases: ["Free delivery"] },
|
|
96
|
+
{ slug: "verified-seller", label: "Verified Seller", description: "Sold by a LetItRip verified seller — identity and address confirmed.", icon: "badge-check", group: "commercial", category: "seller", tone: "primary" },
|
|
97
|
+
{ slug: "accept-returns", label: "Returns Accepted", description: "Eligible for return within the platform return window.", icon: "refresh-ccw", group: "commercial", category: "platform", tone: "secondary", aliases: ["Returnable"] },
|
|
98
|
+
{ slug: "condition-new", label: "Brand New", description: "Factory-sealed or unopened, in original packaging.", icon: "sparkles", group: "condition", category: "condition", tone: "primary", aliases: ["New", "Unused"] },
|
|
99
|
+
/*
|
|
100
|
+
* 🛑 "Preowned" unhyphenated is a real alias, not a typo. `buildSearchTxt`
|
|
101
|
+
* splits on punctuation, so the label "Pre-owned" indexes as `pre` + `owned`
|
|
102
|
+
* and a buyer who types "preowned" as one word matches NEITHER. "Pre owned"
|
|
103
|
+
* with a space would have been useless here — it tokenises identically to
|
|
104
|
+
* the label it was meant to supplement.
|
|
105
|
+
*/
|
|
106
|
+
{ slug: "condition-used", label: "Pre-owned", description: "Previously owned and sold as pictured. Condition is graded on the listing.", icon: "history", group: "condition", category: "condition", aliases: ["Used", "Second-hand", "Preowned"] },
|
|
107
|
+
{ slug: "featured", label: "Featured", description: "Highlighted by the platform across browse and homepage surfaces.", icon: "star", group: "commercial", category: "platform", tone: "primary" },
|
|
108
|
+
{ slug: "promoted", label: "Promoted", description: "A paid promotion placement by the seller.", icon: "megaphone", group: "commercial", category: "platform", tone: "secondary" },
|
|
109
|
+
{ slug: "auction-winner-badge", label: "Auction Win", description: "Acquired by winning an auction on LetItRip.", icon: "gavel", group: "commercial", category: "auction", types: ["auction"] },
|
|
110
|
+
{ slug: "shipping-paid-by-seller", label: "Seller Pays Shipping", description: "The seller covers shipping on this listing.", icon: "package", group: "commercial", category: "shipping", tone: "primary", aliases: ["Seller paid shipping"] },
|
|
111
|
+
{ slug: "preorder-confirmed", label: "Pre-order Confirmed", description: "Stock is allocated and the release date is confirmed by the seller.", icon: "calendar-check", group: "release", category: "preorder", types: ["preorder"] },
|
|
112
|
+
];
|
|
113
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
114
|
+
// condition — the packaging and wear vocabulary, from the crawl
|
|
115
|
+
//
|
|
116
|
+
// `without-box` is worth its own row for a measured reason: hobson's 57
|
|
117
|
+
// "Without Box" listings have a median of ₹599 against ₹1,699 for the same
|
|
118
|
+
// items boxed. A ~3× spread is a facet, not a prose detail.
|
|
119
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
120
|
+
const CONDITION = [
|
|
121
|
+
{ slug: "nib", label: "New in Box", description: "Unopened in its original box, with the box intact.", icon: "box", group: "condition", category: "condition", tone: "primary", aliases: ["NIB", "Boxed", "Sealed box", "MIB"] },
|
|
122
|
+
{ slug: "nip", label: "New in Packet", description: "New on its original card or in its original polybag, unopened.", icon: "package-2", group: "condition", category: "condition", tone: "primary", aliases: ["NIP", "Carded", "On card", "Blister"] },
|
|
123
|
+
{ slug: "sealed", label: "Sealed", description: "Factory wrap intact and never opened.", icon: "shield-check", group: "condition", category: "condition", tone: "primary", aliases: ["Factory sealed", "Shrink wrapped"] },
|
|
124
|
+
{ slug: "mint", label: "Mint", description: "No visible wear — indistinguishable from new on inspection.", icon: "gem", group: "condition", category: "condition", tone: "primary", aliases: ["Mint condition", "As new"] },
|
|
125
|
+
{ slug: "loose", label: "Loose", description: "Out of its packaging, complete and functional.", icon: "puzzle", group: "condition", category: "condition", aliases: ["Unboxed", "Open"] },
|
|
126
|
+
{ slug: "no-box", label: "Without Box", description: "The item only — no original box or card. Priced well below a boxed example.", icon: "box-select", group: "condition", category: "condition", aliases: ["No box", "Box missing", "Item only"] },
|
|
127
|
+
{ slug: "incomplete", label: "Incomplete", description: "One or more original parts are missing. What is included is listed and photographed.", icon: "circle-slash", group: "condition", category: "condition", aliases: ["Missing parts", "Partial"] },
|
|
128
|
+
{ slug: "damaged", label: "Damaged", description: "Has a chip, crack, stripped tooth or similar structural fault, disclosed and photographed.", icon: "alert-triangle", group: "condition", category: "condition", aliases: ["Broken", "Faulty", "Cracked"] },
|
|
129
|
+
];
|
|
130
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
131
|
+
// authenticity — mirrors `ProductDocument.authenticity`
|
|
132
|
+
//
|
|
133
|
+
// 🛑 There is NO "fake" row, and that is a product decision rather than an
|
|
134
|
+
// omission. A lead-bearing counterfeit is not a listing state we offer; it is
|
|
135
|
+
// a scam report. The duplication with the enum field is the one place it is
|
|
136
|
+
// justified: a feature can be left off and nothing notices, and a mislabelled
|
|
137
|
+
// reproduction is a refund while a mislabelled counterfeit is a child handling
|
|
138
|
+
// leaded metal.
|
|
139
|
+
//
|
|
140
|
+
// 🛑 "Midfake" is an ALIAS on `reproduction`, not a row. It is the word the
|
|
141
|
+
// community actually searches — worldhobbyshop ships a top-level nav category
|
|
142
|
+
// literally called "MidFake Beyblades" — but it names the same state.
|
|
143
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
144
|
+
const AUTHENTICITY = [
|
|
145
|
+
{ slug: "original", label: "Original", description: "Licensed manufacture — Takara, Takara Tomy, Hasbro or a regional licensee.", icon: "shield-check", group: "authenticity", category: "condition", tone: "primary", aliases: ["Genuine", "Authentic", "Licensed"] },
|
|
146
|
+
{ slug: "reproduction", label: "Reproduction", description: "An unlicensed copy of an original design, carrying no manufacturer's marque. Disclosed, never described as original.", icon: "copy", group: "authenticity", category: "condition", tone: "secondary", aliases: ["Midfake", "Repro", "1st copy", "Aftermarket"] },
|
|
147
|
+
{ slug: "unverified", label: "Authenticity Unverified", description: "The seller has not confirmed whether this is an original or a reproduction.", icon: "help-circle", group: "authenticity", category: "condition", aliases: ["Unconfirmed", "Unknown authenticity"] },
|
|
148
|
+
];
|
|
149
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
150
|
+
// release — measured as a REAL price multiplier, not a label
|
|
151
|
+
//
|
|
152
|
+
// beybladeshopindia, n=419: Limited Releases median ₹2,499 against Regular's
|
|
153
|
+
// ₹1,399 — 1.8×. Early Releases run 0.6×. That is why release type has to be a
|
|
154
|
+
// facet the price hint can account for.
|
|
155
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
156
|
+
const RELEASE = [
|
|
157
|
+
{ slug: "regular-release", label: "Regular Release", description: "A standard retail release, generally available.", icon: "circle", group: "release", category: "platform", aliases: ["Standard release"] },
|
|
158
|
+
{ slug: "limited-release", label: "Limited Release", description: "A limited production run. Measured at ~1.8× a regular release's median price.", icon: "timer", group: "release", category: "platform", tone: "secondary", aliases: ["Limited edition", "Limited press", "Ltd"] },
|
|
159
|
+
{ slug: "early-release", label: "Early Release", description: "Released ahead of general availability, often at an event.", icon: "fast-forward", group: "release", category: "platform", aliases: ["Pre-release", "Early"] },
|
|
160
|
+
{ slug: "random-booster", label: "Random Booster", description: "Sold in a sealed random assortment — the specific contents were not chosen.", icon: "dices", group: "release", category: "platform", aliases: ["RB", "Blind box", "Random"] },
|
|
161
|
+
{ slug: "rlc", label: "Red Line Club", description: "A members-only Hot Wheels release, produced in small numbers and sold directly by Mattel.", icon: "crown", group: "release", category: "platform", tone: "secondary", aliases: ["RLC", "Red Line"] },
|
|
162
|
+
{ slug: "remake", label: "Remake", description: "A modern reissue of an earlier design on current hardware.", icon: "rotate-ccw", group: "release", category: "platform", aliases: ["Reissue", "Reprint", "X-Over"] },
|
|
163
|
+
{ slug: "promo", label: "Promotional", description: "Given away or sold at an event, a campaign or a magazine tie-in rather than at retail.", icon: "gift", group: "release", category: "platform", aliases: ["Promotional", "Event exclusive", "Prize"] },
|
|
164
|
+
{ slug: "store-exclusive", label: "Store Exclusive", description: "Sold only through one retailer or region.", icon: "store", group: "release", category: "platform", aliases: ["Retailer exclusive", "Regional exclusive"] },
|
|
165
|
+
];
|
|
166
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
167
|
+
// type and spin — the Beyblade axes
|
|
168
|
+
//
|
|
169
|
+
// 🛑 FOUR type values and THREE spin values, and no more. The local corpus has
|
|
170
|
+
// 100% Type coverage in ~90 SPELLINGS and 97% Spin in 54 — a feature slug like
|
|
171
|
+
// `feature-attack-hammer-variant-heavy-slow-smash` is a vocabulary that has
|
|
172
|
+
// already failed. The parenthetical qualifier stays product prose; only the
|
|
173
|
+
// leading enum word becomes a feature.
|
|
174
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
175
|
+
const BATTLE_TYPE = [
|
|
176
|
+
{ slug: "type-attack", label: "Attack Type", description: "Built to strike hard and end a battle quickly, at the cost of stamina.", icon: "swords", group: "type", category: "platform", tone: "secondary", aliases: ["Attack", "Attack-type"] },
|
|
177
|
+
{ slug: "type-defense", label: "Defense Type", description: "Built to absorb hits and stay in the stadium.", icon: "shield", group: "type", category: "platform", aliases: ["Defense", "Defence", "Defense-type"] },
|
|
178
|
+
{ slug: "type-stamina", label: "Stamina Type", description: "Built to outlast — the last top spinning wins.", icon: "battery-full", group: "type", category: "platform", aliases: ["Stamina", "Endurance", "Stamina-type"] },
|
|
179
|
+
{ slug: "type-balance", label: "Balance Type", description: "A compromise build, competent at attack, defense and stamina without excelling.", icon: "scale", group: "type", category: "platform", aliases: ["Balance", "Balance-type"] },
|
|
180
|
+
];
|
|
181
|
+
const SPIN = [
|
|
182
|
+
{ slug: "right-spin", label: "Right Spin", description: "Spins clockwise — the standard direction for most releases.", icon: "rotate-cw", group: "spin", category: "platform", aliases: ["Right", "Clockwise", "Right-spin"] },
|
|
183
|
+
{ slug: "left-spin", label: "Left Spin", description: "Spins anticlockwise. Needs a left-capable or LR launcher, and disrupts a right-spin opponent on contact.", icon: "rotate-ccw", group: "spin", category: "platform", tone: "secondary", aliases: ["Left", "Anticlockwise", "Left-spin"] },
|
|
184
|
+
{ slug: "dual-spin", label: "Dual Spin", description: "Can be launched in either direction — the HMS standard, and rare elsewhere.", icon: "repeat", group: "spin", category: "platform", aliases: ["Dual", "Both directions", "Dual-spin"] },
|
|
185
|
+
];
|
|
186
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
187
|
+
// commercial, sourcing
|
|
188
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
189
|
+
const COMMERCIAL = [
|
|
190
|
+
{ slug: "clearance", label: "Clearance", description: "Discounted to clear. Often paired with a reproduction or damaged disclosure.", icon: "tag", group: "commercial", category: "platform", tone: "secondary", aliases: ["Sale", "Clearance sale"] },
|
|
191
|
+
{ slug: "on-sale", label: "On Sale", description: "Currently below the seller's usual price.", icon: "percent", group: "commercial", category: "platform", tone: "secondary", aliases: ["Discounted", "Reduced"] },
|
|
192
|
+
{ slug: "latest-release", label: "Latest Release", description: "From the most recent wave or set.", icon: "zap", group: "commercial", category: "platform", tone: "primary", aliases: ["New release", "Newest"] },
|
|
193
|
+
{ slug: "best-seller", label: "Best Seller", description: "Among the most-sold listings in its category here.", icon: "trending-up", group: "commercial", category: "platform", tone: "primary", aliases: ["Popular", "Top seller"] },
|
|
194
|
+
{ slug: "lot", label: "Lot", description: "Several items sold together as one listing.", icon: "layers", group: "commercial", category: "platform", aliases: ["Bundle lot", "Job lot", "Multi-pack"] },
|
|
195
|
+
];
|
|
196
|
+
const SOURCING = [
|
|
197
|
+
{ slug: "japan-import", label: "Japan Import", description: "A Japanese-market release, imported. Packaging and part names are the Takara Tomy originals.", icon: "plane", group: "sourcing", category: "shipping", tone: "secondary", aliases: ["JP import", "Japanese release", "TT release"] },
|
|
198
|
+
{ slug: "imported", label: "Imported", description: "Imported from outside India. Customs duty was paid on entry.", icon: "globe", group: "sourcing", category: "shipping", aliases: ["Import", "Overseas"] },
|
|
199
|
+
{ slug: "domestic", label: "Indian Release", description: "Sold through the Indian market — no import duty in the price.", icon: "home", group: "sourcing", category: "shipping", aliases: ["India release", "Local", "Funskool"] },
|
|
162
200
|
];
|
|
201
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
202
|
+
// Trading cards — language and format
|
|
203
|
+
//
|
|
204
|
+
// 🛑 Language is a FACET and not prose, because a Japanese print and an
|
|
205
|
+
// English print of one card are different products with different print runs,
|
|
206
|
+
// different pull rates and often very different prices. Measured at tcgindia:
|
|
207
|
+
// japanese 159, english 14, korean 12, chinese 2.
|
|
208
|
+
//
|
|
209
|
+
// 🛑 Format is a facet because the bands differ ~20× — a single is ₹350–500
|
|
210
|
+
// and a booster box ₹7,000–14,500 — and shoppers never cross-shop them.
|
|
211
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
212
|
+
const TCG_LANG = [
|
|
213
|
+
{ slug: "lang-japanese", label: "Japanese", description: "A Japanese-language print. Smaller print runs and generally better-regarded card stock.", icon: "languages", group: "tcg-lang", category: "platform", tone: "secondary", aliases: ["JP", "Japanese print"] },
|
|
214
|
+
{ slug: "lang-english", label: "English", description: "An English-language print — the largest market and the most liquid resale.", icon: "languages", group: "tcg-lang", category: "platform", aliases: ["EN", "English print"] },
|
|
215
|
+
{ slug: "lang-korean", label: "Korean", description: "A Korean-language print.", icon: "languages", group: "tcg-lang", category: "platform", aliases: ["KR"] },
|
|
216
|
+
{ slug: "lang-chinese", label: "Chinese", description: "A Simplified or Traditional Chinese print.", icon: "languages", group: "tcg-lang", category: "platform", aliases: ["CN", "Simplified Chinese"] },
|
|
217
|
+
];
|
|
218
|
+
const TCG_FORMAT = [
|
|
219
|
+
{ slug: "single-card", label: "Single Card", description: "One individual card, graded on condition and rarity.", icon: "credit-card", group: "tcg-format", category: "platform", aliases: ["Single", "Singles"] },
|
|
220
|
+
{ slug: "booster-pack", label: "Booster Pack", description: "One sealed pack. Contents are random by design.", icon: "package", group: "tcg-format", category: "platform", aliases: ["Pack", "Booster"] },
|
|
221
|
+
{ slug: "booster-box", label: "Booster Box", description: "A sealed box of packs — the best per-pack price of any sealed format.", icon: "boxes", group: "tcg-format", category: "platform", tone: "primary", aliases: ["Box", "Sealed box", "Display box"] },
|
|
222
|
+
{ slug: "etb", label: "Elite Trainer Box", description: "Fewer packs than a booster box, plus sleeves, dice and a deck box. Better value if you want the accessories.", icon: "briefcase", group: "tcg-format", category: "platform", aliases: ["ETB", "Elite Trainer"] },
|
|
223
|
+
{ slug: "blister", label: "Blister Pack", description: "A small carded pack, usually with a promo card.", icon: "package-2", group: "tcg-format", category: "platform", aliases: ["Blister", "Three-pack blister"] },
|
|
224
|
+
{ slug: "card-bundle", label: "Bundle", description: "A small sealed pack count at the lowest entry price of the sealed formats.", icon: "layers", group: "tcg-format", category: "platform", aliases: ["Pack bundle", "Booster bundle"] },
|
|
225
|
+
{ slug: "collection-box", label: "Collection Box", description: "A themed sealed box with promo cards and often a figure or a pin.", icon: "archive", group: "tcg-format", category: "platform", aliases: ["Collection", "Premium collection", "Box set"] },
|
|
226
|
+
{ slug: "premium-set", label: "Premium Set", description: "A high-tier sealed set, usually with an exclusive card treatment.", icon: "gem", group: "tcg-format", category: "platform", tone: "secondary", aliases: ["Premium", "UPC", "Ultra premium"] },
|
|
227
|
+
{ slug: "starter-deck", label: "Starter Deck", description: "A ready-to-play preconstructed deck.", icon: "list-ordered", group: "tcg-format", category: "platform", aliases: ["Deck", "Structure deck", "Preconstructed"] },
|
|
228
|
+
];
|
|
229
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
230
|
+
// grade — pairs with the `sublisting` + `itemCode` axis
|
|
231
|
+
//
|
|
232
|
+
// A graded slab is not a condition claim, it is a third party's certification,
|
|
233
|
+
// which is exactly why a PSA 10 of a ₹500 raw card can be worth twenty times
|
|
234
|
+
// that. hobbykart's 15 slabs run ₹4,600–11,000 against ₹350–500 raw.
|
|
235
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
236
|
+
const GRADE = [
|
|
237
|
+
{ slug: "grade-psa-10", label: "PSA 10", description: "Graded Gem Mint by Professional Sports Authenticator — their top grade.", icon: "award", group: "grade", category: "condition", tone: "primary", aliases: ["PSA10", "Gem Mint", "PSA Gem Mint 10"] },
|
|
238
|
+
{ slug: "grade-psa-9", label: "PSA 9", description: "Graded Mint by PSA — one step below Gem Mint, at a materially lower price.", icon: "award", group: "grade", category: "condition", aliases: ["PSA9", "PSA Mint 9"] },
|
|
239
|
+
{ slug: "grade-bgs", label: "BGS Graded", description: "Graded by Beckett Grading Services, with four subgrades on the label.", icon: "award", group: "grade", category: "condition", aliases: ["Beckett", "BGS", "Black label"] },
|
|
240
|
+
{ slug: "grade-cgc", label: "CGC Graded", description: "Graded by Certified Guaranty Company.", icon: "award", group: "grade", category: "condition", aliases: ["CGC"] },
|
|
241
|
+
{ slug: "grade-raw", label: "Raw", description: "Ungraded and unslabbed. Condition is the seller's own assessment.", icon: "file", group: "grade", category: "condition", aliases: ["Ungraded", "Unslabbed", "Not graded"] },
|
|
242
|
+
];
|
|
243
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
244
|
+
// chase — Hot Wheels variants
|
|
245
|
+
//
|
|
246
|
+
// 🛑 `indian-card` is here because it is a genuinely LOCAL signal no global
|
|
247
|
+
// taxonomy would surface: toycollectorsindia tags 40 products
|
|
248
|
+
// `Hotwheelsindiancard` / `hotwheelsindian` / `Hotwheels indian`, because the
|
|
249
|
+
// Indian-market blister card is printed differently from the US one and
|
|
250
|
+
// collectors here price the two differently.
|
|
251
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
252
|
+
const CHASE = [
|
|
253
|
+
{ slug: "treasure-hunt", label: "Treasure Hunt", description: "A chase casting within a mainline wave, marked with the flame-circle logo.", icon: "search", group: "chase", category: "platform", tone: "secondary", aliases: ["TH", "Treasure Hunt", "Regular TH"] },
|
|
254
|
+
{ slug: "super-treasure-hunt", label: "Super Treasure Hunt", description: "The premium chase: Spectraflame paint, Real Riders rubber tyres and the TH logo. Usually one per case.", icon: "flame", group: "chase", category: "platform", tone: "primary", aliases: ["STH", "Super TH", "Super Treasure"] },
|
|
255
|
+
{ slug: "real-riders", label: "Real Riders", description: "Rubber tyres rather than plastic — a Premium and Super Treasure Hunt marker.", icon: "circle-dot", group: "chase", category: "platform", aliases: ["Rubber tyres", "Rubber tires", "RR"] },
|
|
256
|
+
{ slug: "indian-card", label: "Indian Card", description: "On the Indian-market blister card, which is printed differently from the US release and priced differently here.", icon: "map-pin", group: "chase", category: "platform", tone: "secondary", aliases: ["India card", "Indian blister", "IND card"] },
|
|
257
|
+
];
|
|
258
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
259
|
+
// figure — scale, finish, packaging
|
|
260
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
261
|
+
const FIGURE = [
|
|
262
|
+
{ slug: "scale-1-12", label: "1/12 Scale", description: "Around 15 cm — the articulated-figure standard.", icon: "ruler", group: "figure", category: "platform", aliases: ["1:12", "6 inch", "15cm"] },
|
|
263
|
+
{ slug: "scale-1-7", label: "1/7 Scale", description: "Around 25 cm — the most common scale-statue size.", icon: "ruler", group: "figure", category: "platform", aliases: ["1:7", "25cm"] },
|
|
264
|
+
{ slug: "scale-1-6", label: "1/6 Scale", description: "Around 30 cm — large display statues and the premium tier.", icon: "ruler", group: "figure", category: "platform", tone: "secondary", aliases: ["1:6", "12 inch", "30cm"] },
|
|
265
|
+
{ slug: "prize-figure", label: "Prize Figure", description: "Produced for Japanese arcade prize machines rather than retail. Usually unarticulated, and genuinely cheaper.", icon: "joystick", group: "figure", category: "platform", aliases: ["Arcade prize", "Banpresto prize", "Crane figure"] },
|
|
266
|
+
{ slug: "articulated", label: "Articulated", description: "Poseable joints rather than a fixed sculpt.", icon: "move", group: "figure", category: "platform", aliases: ["Poseable", "Action figure", "Jointed"] },
|
|
267
|
+
{ slug: "statue", label: "Statue", description: "A fixed-pose sculpt with no articulation.", icon: "image", group: "figure", category: "platform", aliases: ["Fixed pose", "Scale statue"] },
|
|
268
|
+
{ slug: "pvc", label: "PVC", description: "Moulded PVC — the standard material for anime scale figures.", icon: "droplet", group: "figure", category: "platform", aliases: ["Polyvinyl", "PVC figure"] },
|
|
269
|
+
{ slug: "vintage-figure", label: "Vintage", description: "From a production run that has long ended — pre-2005 for most lines.", icon: "clock", group: "figure", category: "platform", tone: "secondary", aliases: ["Retro", "Old stock", "Classic"] },
|
|
270
|
+
];
|
|
271
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
272
|
+
// tournament — legality, as DATA
|
|
273
|
+
//
|
|
274
|
+
// The Beyblade Wiki's infoboxes carry Burst{Standard,GT,Limited,Classic}
|
|
275
|
+
// legality flags per release, so this is a fact to record rather than a claim
|
|
276
|
+
// to assert per listing.
|
|
277
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
278
|
+
const TOURNAMENT = [
|
|
279
|
+
{ slug: "wbo-legal", label: "WBO Legal", description: "Permitted in World Beyblade Organization standard formats — the rules most Indian events run.", icon: "check-circle", group: "tournament", category: "platform", tone: "primary", aliases: ["WBO", "Tournament legal", "Standard format"] },
|
|
280
|
+
{ slug: "limited-format", label: "Limited Format", description: "Restricted or banned in limited formats, usually for being too strong.", icon: "ban", group: "tournament", category: "platform", aliases: ["Limited", "Restricted", "Banned"] },
|
|
281
|
+
];
|
|
282
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
283
|
+
// character — the clickable owner linkage, GENERATED from the corpus
|
|
284
|
+
//
|
|
285
|
+
// 95 bladers across 183 of the 216 canonical beys, from a field that is 100%
|
|
286
|
+
// populated but written as prose: "Tyson Granger (Tyson's 9th and final bey)".
|
|
287
|
+
// The normalisation is in scripts/parse-bey-corpus.mjs and the rows in
|
|
288
|
+
// _helpers/character-features.ts.
|
|
289
|
+
//
|
|
290
|
+
// 🛑 Generated rather than hand-listed, and these 95 are the reason the
|
|
291
|
+
// hand-authored blocks above stop at 76: a name typed by hand is a name that
|
|
292
|
+
// can be typed two ways, and this group is where that would bite hardest —
|
|
293
|
+
// `Tyson Granger` and `Tyson Granger (Tyson's 4th bey` were BOTH produced by
|
|
294
|
+
// an earlier normaliser, and a facet offering both is worse than no facet.
|
|
295
|
+
//
|
|
296
|
+
// Each row is also what a model leaf's `productDefaults.defaultFeatures`
|
|
297
|
+
// points at, so a seller who files a Dragoon G gets the blader without
|
|
298
|
+
// choosing it. That is what turns an owner from prose into a facet.
|
|
299
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
300
|
+
const CHARACTERS = CHARACTER_SEEDS.map((c) => ({
|
|
301
|
+
slug: c.slug,
|
|
302
|
+
label: c.name,
|
|
303
|
+
description: c.beyCount > 1
|
|
304
|
+
? `${c.name}'s Beyblades — ${c.beyCount} models in the catalogue, across every generation they appear in.`
|
|
305
|
+
: `${c.name}'s Beyblade.`,
|
|
306
|
+
icon: "user",
|
|
307
|
+
group: "character",
|
|
308
|
+
category: "platform",
|
|
309
|
+
/*
|
|
310
|
+
* The FIRST NAME as an alias, because that is what people actually type —
|
|
311
|
+
* "tyson", not "tyson granger" — and `buildSearchTxt` indexes edge n-grams
|
|
312
|
+
* per WORD, so the full label already matches a "tyson" prefix. The alias
|
|
313
|
+
* earns its place on the handful of bladers known by a single name in the
|
|
314
|
+
* dub ("Tala", "Ozuma", "Lee"), where there is nothing to abbreviate and the
|
|
315
|
+
* alias is skipped.
|
|
316
|
+
*/
|
|
317
|
+
aliases: c.name.includes(" ") ? [c.name.split(" ")[0]] : undefined,
|
|
318
|
+
}));
|
|
319
|
+
const ALL_FEATURE_INPUTS = [
|
|
320
|
+
...PLATFORM,
|
|
321
|
+
...CONDITION,
|
|
322
|
+
...AUTHENTICITY,
|
|
323
|
+
...RELEASE,
|
|
324
|
+
...BATTLE_TYPE,
|
|
325
|
+
...SPIN,
|
|
326
|
+
...COMMERCIAL,
|
|
327
|
+
...SOURCING,
|
|
328
|
+
...TCG_LANG,
|
|
329
|
+
...TCG_FORMAT,
|
|
330
|
+
...GRADE,
|
|
331
|
+
...CHASE,
|
|
332
|
+
...FIGURE,
|
|
333
|
+
...TOURNAMENT,
|
|
334
|
+
...CHARACTERS,
|
|
335
|
+
];
|
|
336
|
+
/*
|
|
337
|
+
* 🛑 A duplicate slug is FATAL. The id derives from it, so two rows at one id
|
|
338
|
+
* write the first and SILENTLY overwrite it — and `appkit-seed status` cannot
|
|
339
|
+
* see it, because it counts ids and the id exists either way. With 68 rows
|
|
340
|
+
* authored across fourteen blocks, a collision is a realistic mistake rather
|
|
341
|
+
* than a theoretical one; `pre-owned` vs `condition-used` was exactly that
|
|
342
|
+
* near-miss, resolved as an alias.
|
|
343
|
+
*/
|
|
344
|
+
{
|
|
345
|
+
const seen = new Set();
|
|
346
|
+
for (const f of ALL_FEATURE_INPUTS) {
|
|
347
|
+
if (seen.has(f.slug))
|
|
348
|
+
throw new Error(`duplicate feature slug: feature-${f.slug}`);
|
|
349
|
+
seen.add(f.slug);
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
export const productFeaturesSeedData = ALL_FEATURE_INPUTS.map((f, i) => featureRow(f, (i + 1) * 10));
|
|
353
|
+
/** Asserted downstream so a silently dropped block is loud. */
|
|
354
|
+
/*
|
|
355
|
+
* 🛑 Asserted at MODULE LOAD, because the character block is GENERATED and a
|
|
356
|
+
* generated import that silently empties is the failure this cannot otherwise
|
|
357
|
+
* see: 95 facet rows would vanish, every model leaf's `defaultFeatures` would
|
|
358
|
+
* point at ids that do not exist, and the only symptom would be a "Blader &
|
|
359
|
+
* character" section that renders nothing — which reads as "no bladers in
|
|
360
|
+
* this category" rather than as a broken build.
|
|
361
|
+
*/
|
|
362
|
+
if (CHARACTERS.length !== CHARACTER_SEED_COUNT) {
|
|
363
|
+
throw new Error(`product features: ${CHARACTERS.length} character rows built, expected ` +
|
|
364
|
+
`${CHARACTER_SEED_COUNT}. Re-run node scripts/gen-category-models.mjs.`);
|
|
365
|
+
}
|
|
366
|
+
export const PRODUCT_FEATURE_COUNT = ALL_FEATURE_INPUTS.length;
|
|
@@ -27,8 +27,20 @@ const _rawProductsArtSeedData = [
|
|
|
27
27
|
slug: "art-dranzer-phoenix-poster",
|
|
28
28
|
title: "Dranzer Phoenix Rising — Fan Art Poster",
|
|
29
29
|
description: "A vibrant fan-art poster of Dranzer erupting in phoenix flame, hand-illustrated and giclée-printed on matte photo paper.",
|
|
30
|
-
|
|
31
|
-
|
|
30
|
+
/*
|
|
31
|
+
* 🛑 Re-filed in B5, and the old chain was wrong twice over: it pointed at
|
|
32
|
+
* `category-stadiums-standard` -> `category-gear-stadiums` ->
|
|
33
|
+
* `category-battle-gear`, all three of which were renamed when stadiums
|
|
34
|
+
* moved to per-generation nodes — AND a fan-art poster of Dranzer was
|
|
35
|
+
* never a stadium to begin with.
|
|
36
|
+
*
|
|
37
|
+
* The named-model leaf is where it belongs, and that is the whole point of
|
|
38
|
+
* having them: a Dranzer poster, a Dranzer sticker sheet and the Dranzer
|
|
39
|
+
* bey itself are siblings under one leaf, so "related items" is literally
|
|
40
|
+
* one `array-contains` rather than a heuristic.
|
|
41
|
+
*/
|
|
42
|
+
categorySlugs: ["category-dranzer-f", "category-original-spin-gear", "category-original-tops", "category-beyblade-original", "category-spinning-tops"],
|
|
43
|
+
categoryNames: ["Dranzer F", "Spin Gear System", "Original Tops", "Beyblade Original", "Spinning Tops"],
|
|
32
44
|
brandSlug: "brand-beyblade",
|
|
33
45
|
brand: "Beyblade",
|
|
34
46
|
price: 599,
|
|
@@ -111,8 +123,8 @@ const _rawProductsArtSeedData = [
|
|
|
111
123
|
slug: "art-beyblade-x-lineup-canvas",
|
|
112
124
|
title: "Beyblade X Full Lineup — Canvas Print",
|
|
113
125
|
description: "A gallery-style canvas print featuring the full current Beyblade X roster in battle formation. Stretched and ready to hang.",
|
|
114
|
-
categorySlugs: ["category-x-
|
|
115
|
-
categoryNames: ["
|
|
126
|
+
categorySlugs: ["category-x-basic", "category-x-tops", "category-beyblade-x", "category-spinning-tops"],
|
|
127
|
+
categoryNames: ["Basic Line", "Beyblade X Tops", "Beyblade X", "Spinning Tops"],
|
|
116
128
|
brandSlug: "brand-takara-tomy",
|
|
117
129
|
brand: "Takara-Tomy",
|
|
118
130
|
price: 2499,
|
|
@@ -138,8 +138,8 @@ const _rawProductsAuctionsSeedData = [
|
|
|
138
138
|
slug: "auction-beyblade-x-shark-edge",
|
|
139
139
|
title: "Beyblade X BX-04 Shark Edge (Buy It Now Available)",
|
|
140
140
|
description: "Shark Edge 3-60GF straight from a fresh case pull. Grab it at the Buy It Now price before the clock runs, or start the bidding.",
|
|
141
|
-
categorySlugs: ["category-x-
|
|
142
|
-
categoryNames: ["
|
|
141
|
+
categorySlugs: ["category-x-basic", "category-x-tops", "category-beyblade-x", "category-spinning-tops"],
|
|
142
|
+
categoryNames: ["Basic Line", "Beyblade X Tops", "Beyblade X", "Spinning Tops"],
|
|
143
143
|
brandSlug: "brand-takara-tomy",
|
|
144
144
|
brand: "Takara-Tomy",
|
|
145
145
|
startingBid: 799,
|
|
@@ -252,8 +252,8 @@ const _rawProductsAuctionsSeedData = [
|
|
|
252
252
|
slug: "auction-beyblade-metal-diablo-nemesis",
|
|
253
253
|
title: "Metal Fight Beyblade BB-122 Diablo Nemesis (Ended — Sold)",
|
|
254
254
|
description: "Diablo Nemesis XF, the final boss top of Metal Fight Beyblade Zero-G. Auction closed with a winning bidder.",
|
|
255
|
-
categorySlugs: ["category-metal-fury", "category-metal-tops", "category-beyblade-metal", "category-spinning-tops"],
|
|
256
|
-
categoryNames: ["Metal Fury", "Metal Fight Tops", "Beyblade Metal Fight", "Spinning Tops"],
|
|
255
|
+
categorySlugs: ["category-metal-fury-4d", "category-metal-tops", "category-beyblade-metal", "category-spinning-tops"],
|
|
256
|
+
categoryNames: ["Metal Fury (4D)", "Metal Fight Tops", "Beyblade Metal Fight", "Spinning Tops"],
|
|
257
257
|
brandSlug: "brand-takara-tomy",
|
|
258
258
|
brand: "Takara-Tomy",
|
|
259
259
|
startingBid: 3499,
|
|
@@ -367,8 +367,8 @@ const _rawProductsAuctionsSeedData = [
|
|
|
367
367
|
slug: "auction-beyblade-x-wizard-fafnir",
|
|
368
368
|
title: "Beyblade X BX-06 Wizard Fafnir (Long-Running Auction)",
|
|
369
369
|
description: "Wizard Fafnir 4-60F, one week left on the clock — a rare Beyblade X chase top with active bidding.",
|
|
370
|
-
categorySlugs: ["category-x-
|
|
371
|
-
categoryNames: ["
|
|
370
|
+
categorySlugs: ["category-x-basic", "category-x-tops", "category-beyblade-x", "category-spinning-tops"],
|
|
371
|
+
categoryNames: ["Basic Line", "Beyblade X Tops", "Beyblade X", "Spinning Tops"],
|
|
372
372
|
brandSlug: "brand-takara-tomy",
|
|
373
373
|
brand: "Takara-Tomy",
|
|
374
374
|
startingBid: 999,
|