@mohasinac/appkit 4.56.0 → 4.58.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.
Files changed (172) hide show
  1. package/dist/.build-stamp.json +4 -4
  2. package/dist/_internal/server/features/beys/data.d.ts +44 -0
  3. package/dist/_internal/server/features/beys/data.js +107 -0
  4. package/dist/_internal/server/features/beys/og.d.ts +42 -0
  5. package/dist/_internal/server/features/beys/og.js +89 -0
  6. package/dist/_internal/server/features/checkout/actions.d.ts +31 -0
  7. package/dist/_internal/server/features/checkout/actions.js +117 -5
  8. package/dist/_internal/server/features/orders/actions.d.ts +30 -0
  9. package/dist/_internal/server/features/orders/actions.js +103 -0
  10. package/dist/_internal/server/features/orders/adapters.js +7 -0
  11. package/dist/_internal/server/features/parts/data.js +3 -2
  12. package/dist/_internal/shared/actions/action-registry.d.ts +1 -1
  13. package/dist/_internal/shared/actions/action-registry.js +139 -0
  14. package/dist/_internal/shared/shipping/quote.js +5 -6
  15. package/dist/client.d.ts +23 -0
  16. package/dist/client.js +3 -0
  17. package/dist/constants/api-endpoints.d.ts +12 -0
  18. package/dist/constants/api-endpoints.js +10 -0
  19. package/dist/constants/field-names.d.ts +46 -0
  20. package/dist/constants/field-names.js +46 -0
  21. package/dist/features/about/components/PublicProfileView.js +2 -1
  22. package/dist/features/account/components/UserOrdersView.js +25 -3
  23. package/dist/features/admin/components/AdminBeysView.d.ts +27 -0
  24. package/dist/features/admin/components/AdminBeysView.js +178 -0
  25. package/dist/features/admin/components/AdminPartsView.d.ts +27 -0
  26. package/dist/features/admin/components/AdminPartsView.js +160 -0
  27. package/dist/features/admin/components/OwnerQuickActions.d.ts +19 -2
  28. package/dist/features/admin/components/OwnerQuickActions.js +83 -8
  29. package/dist/features/admin/components/RecordOwnerQuickActions.d.ts +31 -0
  30. package/dist/features/admin/components/RecordOwnerQuickActions.js +128 -0
  31. package/dist/features/admin/components/ReferenceRecordEditor.d.ts +45 -0
  32. package/dist/features/admin/components/ReferenceRecordEditor.js +153 -0
  33. package/dist/features/admin/schemas/firestore.d.ts +1 -1
  34. package/dist/features/auctions/components/MarketplaceAuctionCard.js +15 -2
  35. package/dist/features/auth/permissions/constants.d.ts +1 -1
  36. package/dist/features/auth/permissions/constants.js +12 -1
  37. package/dist/features/beys/components/BeyDetailPageView.d.ts +50 -0
  38. package/dist/features/beys/components/BeyDetailPageView.js +98 -0
  39. package/dist/features/beys/schemas/firestore.d.ts +417 -0
  40. package/dist/features/beys/schemas/firestore.js +65 -0
  41. package/dist/features/blog/components/BlogFeaturedCard.js +15 -2
  42. package/dist/features/blog/components/BlogPostView.js +2 -1
  43. package/dist/features/cart/components/CartPriceBreakdown.d.ts +2 -0
  44. package/dist/features/cart/components/CartPriceBreakdown.js +1 -1
  45. package/dist/features/categories/components/BrandDetailPageView.js +4 -2
  46. package/dist/features/categories/components/BundleDetailView.js +2 -2
  47. package/dist/features/categories/components/CategoryDetailPageView.js +36 -3
  48. package/dist/features/categories/components/CategoryGrid.js +24 -2
  49. package/dist/features/categories/components/FranchiseDetailPageView.js +2 -1
  50. package/dist/features/events/components/EventCard.js +15 -2
  51. package/dist/features/faq/components/FAQPageContent.js +17 -4
  52. package/dist/features/faq/constants/help-guides.d.ts +36 -0
  53. package/dist/features/faq/constants/help-guides.js +64 -0
  54. package/dist/features/grouped/components/GroupedListingDetailView.js +2 -3
  55. package/dist/features/homepage/components/CustomCardsSection.js +67 -50
  56. package/dist/features/homepage/components/HeroCarousel.js +3 -1
  57. package/dist/features/homepage/components/ShopByCategorySection.js +23 -10
  58. package/dist/features/homepage/lib/section-renderer.js +2 -1
  59. package/dist/features/homepage/schemas/index.d.ts +10 -10
  60. package/dist/features/orders/components/EmiInstallmentsPanel.d.ts +19 -0
  61. package/dist/features/orders/components/EmiInstallmentsPanel.js +57 -0
  62. package/dist/features/orders/components/index.d.ts +1 -0
  63. package/dist/features/orders/components/index.js +1 -0
  64. package/dist/features/orders/schemas/index.d.ts +2 -2
  65. package/dist/features/orders/types/index.d.ts +15 -0
  66. package/dist/features/parts/components/PartDetailPageView.js +5 -3
  67. package/dist/features/parts/components/PartsSearchBox.js +79 -4
  68. package/dist/features/parts/schemas/admin-edit.d.ts +81 -0
  69. package/dist/features/parts/schemas/admin-edit.js +89 -0
  70. package/dist/features/parts/schemas/firestore.d.ts +2 -1
  71. package/dist/features/points/repository/points.repository.js +5 -5
  72. package/dist/features/points/schemas/firestore.d.ts +16 -0
  73. package/dist/features/points/schemas/firestore.js +17 -0
  74. package/dist/features/pre-orders/components/MarketplacePreorderCard.js +15 -2
  75. package/dist/features/products/components/MarketplaceBundleCard.js +15 -2
  76. package/dist/features/products/components/MarketplacePrizeDrawCard.js +15 -2
  77. package/dist/features/products/components/ProductGrid.js +18 -2
  78. package/dist/features/products/schemas/firestore.d.ts +18 -0
  79. package/dist/features/reviews/components/ReviewDetailPageView.js +3 -2
  80. package/dist/features/seller/components/CategoryInlineSelect.d.ts +10 -1
  81. package/dist/features/seller/components/CategoryInlineSelect.js +53 -5
  82. package/dist/features/seller/components/SellerProductShell.d.ts +39 -0
  83. package/dist/features/seller/components/SellerProductShell.js +31 -3
  84. package/dist/features/stores/components/StoreHeader.js +3 -1
  85. package/dist/index.d.ts +8 -0
  86. package/dist/index.js +13 -0
  87. package/dist/next/routing/route-map.d.ts +17 -1
  88. package/dist/next/routing/route-map.js +39 -0
  89. package/dist/schemas/registry.d.ts +8 -8
  90. package/dist/seed/_helpers/category-models/chunk0.d.ts +2 -0
  91. package/dist/seed/_helpers/category-models/chunk0.js +2648 -0
  92. package/dist/seed/_helpers/category-models/chunk1.d.ts +2 -0
  93. package/dist/seed/_helpers/category-models/chunk1.js +2318 -0
  94. package/dist/seed/_helpers/category-models/chunk2.d.ts +2 -0
  95. package/dist/seed/_helpers/category-models/chunk2.js +2560 -0
  96. package/dist/seed/_helpers/category-models/chunk3.d.ts +2 -0
  97. package/dist/seed/_helpers/category-models/chunk3.js +2196 -0
  98. package/dist/seed/_helpers/category-models/chunk4.d.ts +2 -0
  99. package/dist/seed/_helpers/category-models/chunk4.js +1732 -0
  100. package/dist/seed/_helpers/category-models.d.ts +9 -2
  101. package/dist/seed/_helpers/category-models.js +19 -1368
  102. package/dist/seed/_helpers/category-parts-glossary-map.js +38 -38
  103. package/dist/seed/_helpers/category-parts.js +39 -39
  104. package/dist/seed/bey-products-seed-data.d.ts +18 -0
  105. package/dist/seed/bey-products-seed-data.js +18011 -0
  106. package/dist/seed/beys/chunk0.d.ts +17 -0
  107. package/dist/seed/beys/chunk0.js +36733 -0
  108. package/dist/seed/beys/chunk1.d.ts +17 -0
  109. package/dist/seed/beys/chunk1.js +35728 -0
  110. package/dist/seed/beys/chunk2.d.ts +17 -0
  111. package/dist/seed/beys/chunk2.js +35857 -0
  112. package/dist/seed/beys/chunk3.d.ts +17 -0
  113. package/dist/seed/beys/chunk3.js +34476 -0
  114. package/dist/seed/beys-seed-data.d.ts +18 -0
  115. package/dist/seed/beys-seed-data.js +11 -0
  116. package/dist/seed/homepage-sections-seed-data.js +16 -0
  117. package/dist/seed/index.d.ts +2 -0
  118. package/dist/seed/index.js +3 -0
  119. package/dist/seed/media-assets-seed-data.js +4 -4
  120. package/dist/seed/products-catalogue/chunk0.d.ts +12 -0
  121. package/dist/seed/products-catalogue/chunk0.js +7262 -0
  122. package/dist/seed/products-catalogue/chunk1.d.ts +12 -0
  123. package/dist/seed/products-catalogue/chunk1.js +7290 -0
  124. package/dist/seed/products-catalogue/chunk10.d.ts +12 -0
  125. package/dist/seed/products-catalogue/chunk10.js +7290 -0
  126. package/dist/seed/products-catalogue/chunk11.d.ts +12 -0
  127. package/dist/seed/products-catalogue/chunk11.js +7005 -0
  128. package/dist/seed/products-catalogue/chunk12.d.ts +12 -0
  129. package/dist/seed/products-catalogue/chunk12.js +7002 -0
  130. package/dist/seed/products-catalogue/chunk13.d.ts +12 -0
  131. package/dist/seed/products-catalogue/chunk13.js +7002 -0
  132. package/dist/seed/products-catalogue/chunk14.d.ts +12 -0
  133. package/dist/seed/products-catalogue/chunk14.js +7002 -0
  134. package/dist/seed/products-catalogue/chunk15.d.ts +12 -0
  135. package/dist/seed/products-catalogue/chunk15.js +7002 -0
  136. package/dist/seed/products-catalogue/chunk16.d.ts +12 -0
  137. package/dist/seed/products-catalogue/chunk16.js +7002 -0
  138. package/dist/seed/products-catalogue/chunk17.d.ts +12 -0
  139. package/dist/seed/products-catalogue/chunk17.js +7002 -0
  140. package/dist/seed/products-catalogue/chunk18.d.ts +12 -0
  141. package/dist/seed/products-catalogue/chunk18.js +4398 -0
  142. package/dist/seed/products-catalogue/chunk2.d.ts +12 -0
  143. package/dist/seed/products-catalogue/chunk2.js +7268 -0
  144. package/dist/seed/products-catalogue/chunk3.d.ts +12 -0
  145. package/dist/seed/products-catalogue/chunk3.js +7264 -0
  146. package/dist/seed/products-catalogue/chunk4.d.ts +12 -0
  147. package/dist/seed/products-catalogue/chunk4.js +7268 -0
  148. package/dist/seed/products-catalogue/chunk5.d.ts +12 -0
  149. package/dist/seed/products-catalogue/chunk5.js +7258 -0
  150. package/dist/seed/products-catalogue/chunk6.d.ts +12 -0
  151. package/dist/seed/products-catalogue/chunk6.js +7268 -0
  152. package/dist/seed/products-catalogue/chunk7.d.ts +12 -0
  153. package/dist/seed/products-catalogue/chunk7.js +7311 -0
  154. package/dist/seed/products-catalogue/chunk8.d.ts +12 -0
  155. package/dist/seed/products-catalogue/chunk8.js +7304 -0
  156. package/dist/seed/products-catalogue/chunk9.d.ts +12 -0
  157. package/dist/seed/products-catalogue/chunk9.js +7309 -0
  158. package/dist/seed/products-catalogue-seed-data.d.ts +2 -22
  159. package/dist/seed/products-catalogue-seed-data.js +58 -86382
  160. package/dist/seed/seed-purpose.js +9 -0
  161. package/dist/server.d.ts +5 -1
  162. package/dist/server.js +19 -0
  163. package/dist/styles.css +468 -1
  164. package/dist/tailwind-utilities.css +1 -1
  165. package/dist/ui/components/PosterCard.d.ts +99 -0
  166. package/dist/ui/components/PosterCard.js +109 -0
  167. package/dist/ui/components/PosterCard.style.css +466 -0
  168. package/dist/ui/components/index.style.css +1 -0
  169. package/dist/ui/index.d.ts +2 -0
  170. package/dist/ui/index.js +2 -0
  171. package/package.json +1 -1
  172. package/scripts/seed-cli.mjs +108 -1
@@ -0,0 +1,99 @@
1
+ import React from "react";
2
+ /**
3
+ * PosterCard — an image-led tile: artwork fills the card, a scrim carries the
4
+ * type, links sit as chips on the dark end.
5
+ *
6
+ * 🛑 The artwork is a `media` SLOT, not a `src` prop, and that is deliberate.
7
+ * This file lives in `ui/components/`, and reaching down into
8
+ * `features/media/MediaImage` from here would invert the layering and put a
9
+ * feature module on the import chain of every consumer of the UI barrel — the
10
+ * shape Root Cause #6/#18 is about. Taking a node instead lets the homepage
11
+ * pass `<MediaImage>` and the category tile pass `next/image`, each with the
12
+ * `sizes` hint that is right for its own grid.
13
+ *
14
+ * The caller wraps it in its own `<Link>`/`<a>` when the WHOLE card is one
15
+ * destination (the category tile), or leaves it inert and lets the chips be
16
+ * the links (the systems matrix). It is never both: a card that is a link and
17
+ * also contains links nests interactive content, which no browser resolves the
18
+ * way the markup implies.
19
+ */
20
+ export declare const POSTER_CLASS: {
21
+ readonly root: "appkit-poster";
22
+ readonly tall: "appkit-poster--tall";
23
+ readonly compact: "appkit-poster--compact";
24
+ readonly media: "appkit-poster__media";
25
+ readonly scrim: "appkit-poster__scrim";
26
+ readonly body: "appkit-poster__body";
27
+ readonly eyebrow: "appkit-poster__eyebrow";
28
+ readonly title: "appkit-poster__title";
29
+ readonly text: "appkit-poster__text";
30
+ readonly meta: "appkit-poster__meta";
31
+ readonly chips: "appkit-poster__chips";
32
+ readonly chip: "appkit-poster__chip";
33
+ readonly chipKey: "appkit-poster__chip--key";
34
+ readonly grid: "appkit-poster-grid";
35
+ readonly grid1: "appkit-poster-grid--1";
36
+ readonly grid2: "appkit-poster-grid--2";
37
+ readonly grid3: "appkit-poster-grid--3";
38
+ readonly grid4: "appkit-poster-grid--4";
39
+ };
40
+ export type PosterCardHeight = "compact" | "default" | "tall";
41
+ export interface PosterCardProps {
42
+ /** The artwork. Rendered into an absolutely-positioned fill box. */
43
+ media?: React.ReactNode;
44
+ /** Small uppercase label above the title — a date range, a count, an era. */
45
+ eyebrow?: React.ReactNode;
46
+ title?: React.ReactNode;
47
+ /** One short paragraph. Clamped to three lines by the stylesheet. */
48
+ text?: React.ReactNode;
49
+ /** A line below the body — an item count, a price band. */
50
+ meta?: React.ReactNode;
51
+ /** The chip row. Pass `<PosterChip>`s. */
52
+ chips?: React.ReactNode;
53
+ height?: PosterCardHeight;
54
+ className?: string;
55
+ }
56
+ export declare function PosterCard({ media, eyebrow, title, text, meta, chips, height, className, }: PosterCardProps): React.JSX.Element;
57
+ export interface PosterChipProps {
58
+ href?: string;
59
+ /** Solid-fill emphasis, for the one or two widest destinations on a card. */
60
+ emphasis?: "glass" | "solid";
61
+ target?: "_blank" | "_self";
62
+ children: React.ReactNode;
63
+ }
64
+ /**
65
+ * One chip. Renders an `<a>` when it has an `href` and a `<span>` otherwise,
66
+ * so a non-navigating chip is not announced as a link.
67
+ *
68
+ * 🛑 A plain `<a>`, not `next/link`, because `PosterCard` lives in the UI layer
69
+ * and must not pull a framework router into every consumer of the barrel — the
70
+ * same reason `media` is a slot. Consumers that want client-side navigation
71
+ * compose their own link around the chip classes via `POSTER_CLASS`.
72
+ */
73
+ export declare function PosterChip({ href, emphasis, target, children, }: PosterChipProps): React.JSX.Element;
74
+ export declare const PLAIN_CLASS: {
75
+ readonly root: "appkit-plain-card";
76
+ readonly eyebrow: "appkit-plain-card__eyebrow";
77
+ readonly title: "appkit-plain-card__title";
78
+ readonly text: "appkit-plain-card__text";
79
+ readonly chips: "appkit-plain-card__chips";
80
+ readonly chip: "appkit-chip";
81
+ readonly chipKey: "appkit-chip--key";
82
+ };
83
+ export interface PlainCardProps {
84
+ eyebrow?: React.ReactNode;
85
+ title?: React.ReactNode;
86
+ text?: React.ReactNode;
87
+ chips?: React.ReactNode;
88
+ /** Anything that has to sit below the chip row — an embedded form. */
89
+ footer?: React.ReactNode;
90
+ className?: string;
91
+ }
92
+ export declare function PlainCard({ eyebrow, title, text, chips, footer, className, }: PlainCardProps): React.JSX.Element;
93
+ export interface PosterGridProps {
94
+ columns?: 1 | 2 | 3 | 4;
95
+ children: React.ReactNode;
96
+ className?: string;
97
+ }
98
+ /** 1 → 2 → N. See the note on `.appkit-poster-grid` for why not `Grid cols="cards"`. */
99
+ export declare function PosterGrid({ columns, children, className }: PosterGridProps): React.JSX.Element;
@@ -0,0 +1,109 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Heading, Text } from "./Typography.js";
3
+ /**
4
+ * PosterCard — an image-led tile: artwork fills the card, a scrim carries the
5
+ * type, links sit as chips on the dark end.
6
+ *
7
+ * 🛑 The artwork is a `media` SLOT, not a `src` prop, and that is deliberate.
8
+ * This file lives in `ui/components/`, and reaching down into
9
+ * `features/media/MediaImage` from here would invert the layering and put a
10
+ * feature module on the import chain of every consumer of the UI barrel — the
11
+ * shape Root Cause #6/#18 is about. Taking a node instead lets the homepage
12
+ * pass `<MediaImage>` and the category tile pass `next/image`, each with the
13
+ * `sizes` hint that is right for its own grid.
14
+ *
15
+ * The caller wraps it in its own `<Link>`/`<a>` when the WHOLE card is one
16
+ * destination (the category tile), or leaves it inert and lets the chips be
17
+ * the links (the systems matrix). It is never both: a card that is a link and
18
+ * also contains links nests interactive content, which no browser resolves the
19
+ * way the markup implies.
20
+ */
21
+ /* ── Class names ──────────────────────────────────────────────────────────
22
+ *
23
+ * Exported because two consumers compose their own inner markup (the matrix
24
+ * renders a chip per taxonomy link, the tile renders a count), and a literal
25
+ * typed at the call site is a class `audit-dead-css-classes` would have to
26
+ * find by luck.
27
+ */
28
+ export const POSTER_CLASS = {
29
+ root: "appkit-poster",
30
+ tall: "appkit-poster--tall",
31
+ compact: "appkit-poster--compact",
32
+ media: "appkit-poster__media",
33
+ scrim: "appkit-poster__scrim",
34
+ body: "appkit-poster__body",
35
+ eyebrow: "appkit-poster__eyebrow",
36
+ title: "appkit-poster__title",
37
+ text: "appkit-poster__text",
38
+ meta: "appkit-poster__meta",
39
+ chips: "appkit-poster__chips",
40
+ chip: "appkit-poster__chip",
41
+ chipKey: "appkit-poster__chip--key",
42
+ grid: "appkit-poster-grid",
43
+ grid1: "appkit-poster-grid--1",
44
+ grid2: "appkit-poster-grid--2",
45
+ grid3: "appkit-poster-grid--3",
46
+ grid4: "appkit-poster-grid--4",
47
+ };
48
+ const HEIGHT_CLASS = {
49
+ compact: POSTER_CLASS.compact,
50
+ default: "",
51
+ tall: POSTER_CLASS.tall,
52
+ };
53
+ export function PosterCard({ media, eyebrow, title, text, meta, chips, height = "default", className = "", }) {
54
+ const classes = [POSTER_CLASS.root, HEIGHT_CLASS[height], className]
55
+ .filter(Boolean)
56
+ .join(" ");
57
+ return (_jsxs("div", { className: classes, children: [media ? _jsx("div", { className: POSTER_CLASS.media, children: media }) : null, _jsx("div", { className: POSTER_CLASS.scrim, "aria-hidden": "true" }), _jsxs("div", { className: POSTER_CLASS.body, children: [eyebrow ? _jsx("span", { className: POSTER_CLASS.eyebrow, children: eyebrow }) : null, title ? (_jsx(Heading, { level: 3, size: height === "compact" ? "lg" : "2xl", weight: "bold", color: "inherit", className: POSTER_CLASS.title, children: title })) : null, text ? (_jsx(Text, { size: "sm", color: "inherit", truncate: 3, className: POSTER_CLASS.text, children: text })) : null, meta ? _jsx("span", { className: POSTER_CLASS.meta, children: meta }) : null, chips ? _jsx("div", { className: POSTER_CLASS.chips, children: chips }) : null] })] }));
58
+ }
59
+ /**
60
+ * One chip. Renders an `<a>` when it has an `href` and a `<span>` otherwise,
61
+ * so a non-navigating chip is not announced as a link.
62
+ *
63
+ * 🛑 A plain `<a>`, not `next/link`, because `PosterCard` lives in the UI layer
64
+ * and must not pull a framework router into every consumer of the barrel — the
65
+ * same reason `media` is a slot. Consumers that want client-side navigation
66
+ * compose their own link around the chip classes via `POSTER_CLASS`.
67
+ */
68
+ export function PosterChip({ href, emphasis = "glass", target, children, }) {
69
+ const classes = [POSTER_CLASS.chip, emphasis === "solid" ? POSTER_CLASS.chipKey : ""]
70
+ .filter(Boolean)
71
+ .join(" ");
72
+ if (!href)
73
+ return _jsx("span", { className: classes, children: children });
74
+ return (_jsx("a", { href: href, className: classes, target: target ?? "_self", rel: target === "_blank" ? "noopener noreferrer" : undefined, children: children }));
75
+ }
76
+ /* ── The no-artwork sibling ───────────────────────────────────────────────
77
+ *
78
+ * Lives in this file, not at the call site, for one mechanical reason: raw
79
+ * `<div>`/`<span>`/`<h3>`/`<p>` are blocked outside the primitive source
80
+ * directories by `audit-html-wrappers`, and `features/homepage/` is not one.
81
+ * The two cards are also one decision — a `custom-cards` section can mix cards
82
+ * that have an image with cards that do not, and they have to read as one row.
83
+ */
84
+ export const PLAIN_CLASS = {
85
+ root: "appkit-plain-card",
86
+ eyebrow: "appkit-plain-card__eyebrow",
87
+ title: "appkit-plain-card__title",
88
+ text: "appkit-plain-card__text",
89
+ chips: "appkit-plain-card__chips",
90
+ chip: "appkit-chip",
91
+ chipKey: "appkit-chip--key",
92
+ };
93
+ export function PlainCard({ eyebrow, title, text, chips, footer, className = "", }) {
94
+ const classes = [PLAIN_CLASS.root, className].filter(Boolean).join(" ");
95
+ return (_jsxs("div", { className: classes, children: [eyebrow ? _jsx("span", { className: PLAIN_CLASS.eyebrow, children: eyebrow }) : null, title ? (_jsx(Heading, { level: 3, size: "xl", weight: "bold", color: "inherit", className: PLAIN_CLASS.title, children: title })) : null, text ? (_jsx(Text, { size: "sm", color: "inherit", className: PLAIN_CLASS.text, children: text })) : null, chips ? _jsx("div", { className: PLAIN_CLASS.chips, children: chips }) : null, footer] }));
96
+ }
97
+ const GRID_CLASS = {
98
+ 1: POSTER_CLASS.grid1,
99
+ 2: POSTER_CLASS.grid2,
100
+ 3: POSTER_CLASS.grid3,
101
+ 4: POSTER_CLASS.grid4,
102
+ };
103
+ /** 1 → 2 → N. See the note on `.appkit-poster-grid` for why not `Grid cols="cards"`. */
104
+ export function PosterGrid({ columns = 4, children, className = "" }) {
105
+ const classes = [POSTER_CLASS.grid, GRID_CLASS[columns], className]
106
+ .filter(Boolean)
107
+ .join(" ");
108
+ return _jsx("div", { className: classes, children: children });
109
+ }
@@ -0,0 +1,466 @@
1
+ /*
2
+ * PosterCard — an image-led tile. Artwork fills the whole card, a vertical
3
+ * scrim carries the type, and the links sit as chips on the dark end.
4
+ *
5
+ * 🛑 This family is THEME-INVARIANT on purpose, and that is not the usual
6
+ * mistake. Root Cause #67 is about pairing a theme-INVERTING token
7
+ * (`bg-{status}-surface`) with a fixed `text-white`; here the background is a
8
+ * photograph plus an opaque dark scrim in BOTH themes, so white ink is correct
9
+ * in both and a theme-relative token would be the bug. Same reasoning as
10
+ * `surface="media-dark"`.
11
+ *
12
+ * 🛑 Colours are `rgb(… / α)`, never hex — `audit-hex-tokens` walks
13
+ * `appkit/src` and a standalone hex literal is a category-D violation.
14
+ *
15
+ * 🛑 The scrim lives here rather than on a call site's className because
16
+ * `audit-html-wrappers/RAW_GRADIENT_UTILITY` blocks `bg-gradient-to-*` outside
17
+ * primitive sources, and because both consumers (the homepage systems matrix
18
+ * and the Shop-by-Category tiles) need the identical ramp — two hand-written
19
+ * copies is how they drift.
20
+ */
21
+
22
+ /* The near-black the whole family composes against. Declared once so the card
23
+ base, the scrim stops and the solid chip's ink cannot disagree. */
24
+ .appkit-poster {
25
+ --appkit-poster-ink: 9 9 11;
26
+
27
+ position: relative;
28
+ display: flex;
29
+ flex-direction: column;
30
+ justify-content: flex-end;
31
+ min-height: 20rem;
32
+ overflow: hidden;
33
+ isolation: isolate;
34
+ border-radius: var(--appkit-radius-2xl);
35
+ background-color: rgb(var(--appkit-poster-ink));
36
+ /* The card is itself a link on the category tile. */
37
+ text-decoration: none;
38
+ /* A hairline INSIDE the radius. A `border` would be clipped by
39
+ `overflow: hidden` on the image corners; an inset ring is not. */
40
+ box-shadow: inset 0 0 0 1px rgb(255 255 255 / 0.08);
41
+ transition:
42
+ transform 0.35s cubic-bezier(0.2, 0.7, 0.3, 1),
43
+ box-shadow 0.35s cubic-bezier(0.2, 0.7, 0.3, 1);
44
+ }
45
+
46
+ .appkit-poster--tall {
47
+ min-height: 26rem;
48
+ }
49
+
50
+ .appkit-poster--compact {
51
+ min-height: 14rem;
52
+ }
53
+
54
+ .appkit-poster--compact .appkit-poster__body {
55
+ gap: var(--appkit-space-1);
56
+ padding: var(--appkit-space-4);
57
+ }
58
+
59
+ /* Hover lift. Guarded so a touch device — where `:hover` sticks after a tap —
60
+ never leaves a card translated. */
61
+ @media (hover: hover) {
62
+ .appkit-poster:hover {
63
+ transform: translateY(-3px);
64
+ box-shadow:
65
+ inset 0 0 0 1px rgb(255 255 255 / 0.16),
66
+ 0 18px 40px -18px rgb(0 0 0 / 0.55);
67
+ }
68
+
69
+ .appkit-poster:hover .appkit-poster__media {
70
+ transform: scale(1.06);
71
+ }
72
+ }
73
+
74
+ /* The whole card is a link on the category tile, so the ring must be visible
75
+ against the artwork rather than against a surface token. */
76
+ .appkit-poster:focus-visible,
77
+ .appkit-poster:has(:focus-visible) {
78
+ outline: 2px solid rgb(255 255 255 / 0.9);
79
+ outline-offset: 2px;
80
+ }
81
+
82
+ @media (prefers-reduced-motion: reduce) {
83
+ .appkit-poster,
84
+ .appkit-poster__media {
85
+ transition: none;
86
+ }
87
+
88
+ .appkit-poster:hover {
89
+ transform: none;
90
+ }
91
+
92
+ .appkit-poster:hover .appkit-poster__media {
93
+ transform: none;
94
+ }
95
+ }
96
+
97
+ /* ── Artwork ──────────────────────────────────────────────────────────── */
98
+
99
+ /*
100
+ * The positioned parent `MediaImage` needs — it renders `fill`, so without an
101
+ * absolutely-positioned ancestor Next warns and the image collapses.
102
+ *
103
+ * `object-position: center 22%` rather than `center`: every one of the four
104
+ * generation posters is portrait key art with the cast in the upper half and a
105
+ * burnt-in wordmark at the bottom, so a centred crop in a landscape-ish box
106
+ * frames torsos and puts the wordmark straight under the card's own title.
107
+ */
108
+ .appkit-poster__media {
109
+ position: absolute;
110
+ inset: 0;
111
+ z-index: 0;
112
+ transform-origin: center 30%;
113
+ transition: transform 0.6s cubic-bezier(0.2, 0.7, 0.3, 1);
114
+ }
115
+
116
+ .appkit-poster__media img {
117
+ object-position: center 22%;
118
+ }
119
+
120
+ /* ── Scrim ────────────────────────────────────────────────────────────── */
121
+
122
+ /*
123
+ * Two ramps, and both are load-bearing:
124
+ *
125
+ * - the vertical one takes the bottom ~55% to near-opaque so body copy and a
126
+ * dozen chips are readable over busy, saturated artwork, while leaving the
127
+ * top third nearly clear so the art is still the thing you see;
128
+ * - the horizontal one is a shallow left wash, so the first word of every
129
+ * line has contrast even where the art happens to be pale.
130
+ *
131
+ * The stops are deliberately uneven. A linear fade reads as a grey veil over
132
+ * the whole image; an eased one looks like light falling off.
133
+ */
134
+ .appkit-poster__scrim {
135
+ position: absolute;
136
+ inset: 0;
137
+ z-index: 1;
138
+ pointer-events: none;
139
+ background:
140
+ linear-gradient(
141
+ to top,
142
+ rgb(var(--appkit-poster-ink) / 0.97) 0%,
143
+ rgb(var(--appkit-poster-ink) / 0.93) 22%,
144
+ rgb(var(--appkit-poster-ink) / 0.72) 42%,
145
+ rgb(var(--appkit-poster-ink) / 0.34) 64%,
146
+ rgb(var(--appkit-poster-ink) / 0.08) 84%,
147
+ rgb(var(--appkit-poster-ink) / 0) 100%
148
+ ),
149
+ linear-gradient(
150
+ to right,
151
+ rgb(var(--appkit-poster-ink) / 0.55) 0%,
152
+ rgb(var(--appkit-poster-ink) / 0.12) 48%,
153
+ rgb(var(--appkit-poster-ink) / 0) 100%
154
+ );
155
+ }
156
+
157
+ /* ── Content ──────────────────────────────────────────────────────────── */
158
+
159
+ .appkit-poster__body {
160
+ position: relative;
161
+ z-index: 2;
162
+ display: flex;
163
+ flex-direction: column;
164
+ gap: var(--appkit-space-3);
165
+ padding: var(--appkit-space-5);
166
+ }
167
+
168
+ .appkit-poster__eyebrow {
169
+ display: inline-flex;
170
+ align-items: center;
171
+ gap: var(--appkit-space-2);
172
+ color: rgb(255 255 255 / 0.62);
173
+ font-size: var(--appkit-text-xs);
174
+ font-weight: 600;
175
+ letter-spacing: 0.12em;
176
+ text-transform: uppercase;
177
+ font-variant-numeric: tabular-nums;
178
+ }
179
+
180
+ /* A short rule before the eyebrow — the one piece of ornament in the card, and
181
+ it earns its place by separating the date range from the artwork behind it. */
182
+ .appkit-poster__eyebrow::before {
183
+ content: "";
184
+ width: 1.25rem;
185
+ height: 1px;
186
+ flex: none;
187
+ background: rgb(255 255 255 / 0.45);
188
+ }
189
+
190
+ /*
191
+ * 🛑 NO `font-size` and NO `font-weight` here, and that is a decision.
192
+ *
193
+ * The title and body render through `<Heading>`/`<Text>`, which emit
194
+ * `.appkit-text--2xl` / `.appkit-font--bold` — plain CSS classes at the same
195
+ * (0,1,0) specificity as these. A tie is broken by ORDER IN THE BUNDLE, which
196
+ * is `index.style.css`'s import list: an invisible dependency that flips the
197
+ * moment someone reorders an unrelated line. So size and weight are passed as
198
+ * PROPS (including the compact tile's smaller title) and this rule owns only
199
+ * what a prop cannot express.
200
+ *
201
+ * Line clamping is `truncate={3}` on the `<Text>` for the same reason —
202
+ * one owner, not two.
203
+ */
204
+ .appkit-poster__title {
205
+ margin: 0;
206
+ color: rgb(255 255 255);
207
+ line-height: 1.15;
208
+ letter-spacing: -0.015em;
209
+ text-wrap: balance;
210
+ }
211
+
212
+ .appkit-poster__text {
213
+ margin: 0;
214
+ color: rgb(255 255 255 / 0.68);
215
+ line-height: 1.55;
216
+ }
217
+
218
+ .appkit-poster__meta {
219
+ color: rgb(255 255 255 / 0.55);
220
+ font-size: var(--appkit-text-xs);
221
+ font-weight: 500;
222
+ font-variant-numeric: tabular-nums;
223
+ }
224
+
225
+ /* ── Chips ────────────────────────────────────────────────────────────── */
226
+
227
+ /*
228
+ * A hairline rule above the chip row. The body copy and the links are two
229
+ * different kinds of thing — prose versus navigation — and on a card with
230
+ * seven or eight chips that boundary is what stops the block reading as a
231
+ * paragraph that ran out of punctuation.
232
+ */
233
+ .appkit-poster__chips {
234
+ display: flex;
235
+ flex-wrap: wrap;
236
+ gap: var(--appkit-space-2);
237
+ margin-top: var(--appkit-space-1);
238
+ padding-top: var(--appkit-space-3);
239
+ border-top: 1px solid rgb(255 255 255 / 0.12);
240
+ }
241
+
242
+ .appkit-poster__chip {
243
+ display: inline-flex;
244
+ align-items: center;
245
+ flex: none;
246
+ padding: 0.3125rem 0.6875rem;
247
+ border: 1px solid rgb(255 255 255 / 0.16);
248
+ border-radius: var(--appkit-radius-full);
249
+ background: rgb(255 255 255 / 0.09);
250
+ /* Progressive: unsupported browsers get the flat 9% wash, which is already
251
+ legible against the scrim. Nothing depends on the blur. */
252
+ backdrop-filter: blur(8px);
253
+ color: rgb(255 255 255 / 0.88);
254
+ font-size: var(--appkit-text-xs);
255
+ font-weight: 500;
256
+ line-height: 1.25;
257
+ white-space: nowrap;
258
+ text-decoration: none;
259
+ transition:
260
+ background-color 0.2s ease,
261
+ border-color 0.2s ease,
262
+ color 0.2s ease;
263
+ }
264
+
265
+ .appkit-poster__chip:hover {
266
+ background: rgb(255 255 255 / 0.2);
267
+ border-color: rgb(255 255 255 / 0.38);
268
+ color: rgb(255 255 255);
269
+ }
270
+
271
+ /*
272
+ * The destination chips — "Parts", "Launchers", "Stadiums". These are the
273
+ * widest pages behind the card, so they invert to a solid fill and sort to the
274
+ * end of the row. One emphasis level, never two: a card where three chips all
275
+ * shout has no hierarchy at all.
276
+ */
277
+ .appkit-poster__chip--key {
278
+ background: rgb(255 255 255 / 0.95);
279
+ border-color: transparent;
280
+ color: rgb(var(--appkit-poster-ink));
281
+ font-weight: 600;
282
+ }
283
+
284
+ .appkit-poster__chip--key:hover {
285
+ background: rgb(255 255 255);
286
+ border-color: transparent;
287
+ color: rgb(var(--appkit-poster-ink));
288
+ }
289
+
290
+ /* ── On-surface chips ─────────────────────────────────────────────────────
291
+ *
292
+ * The light-theme counterpart of `.appkit-poster__chip`, for the same control
293
+ * on a card that has NO artwork — a `custom-cards` section an admin authored
294
+ * without images still renders a row of taxonomy links, and those must not
295
+ * fall back to the four generic button variants that made the row read as a
296
+ * pile of mismatched buttons.
297
+ *
298
+ * Theme-RELATIVE, unlike everything above: there is no scrim here, so the ink
299
+ * has to invert with the theme. `bg-{…}-surface`-style tokens throughout, no
300
+ * fixed white — Root Cause #67.
301
+ */
302
+
303
+ .appkit-chip {
304
+ display: inline-flex;
305
+ align-items: center;
306
+ flex: none;
307
+ padding: 0.3125rem 0.6875rem;
308
+ border: 1px solid var(--appkit-color-border);
309
+ border-radius: var(--appkit-radius-full);
310
+ background: var(--appkit-color-surface-elevated);
311
+ color: var(--appkit-color-text-muted);
312
+ font-size: var(--appkit-text-xs);
313
+ font-weight: 500;
314
+ line-height: 1.25;
315
+ white-space: nowrap;
316
+ text-decoration: none;
317
+ transition:
318
+ background-color 0.2s ease,
319
+ border-color 0.2s ease,
320
+ color 0.2s ease;
321
+ }
322
+
323
+ .appkit-chip:hover {
324
+ border-color: var(--appkit-color-primary);
325
+ background: var(--appkit-color-primary-surface);
326
+ color: var(--appkit-color-primary);
327
+ }
328
+
329
+ .appkit-chip--key {
330
+ border-color: transparent;
331
+ background: var(--appkit-color-primary);
332
+ color: var(--appkit-color-text-on-primary);
333
+ font-weight: 600;
334
+ }
335
+
336
+ .appkit-chip--key:hover {
337
+ border-color: transparent;
338
+ background: var(--appkit-color-primary-600);
339
+ color: var(--appkit-color-text-on-primary);
340
+ }
341
+
342
+ /* ── Plain card ───────────────────────────────────────────────────────────
343
+ *
344
+ * The no-artwork sibling of `.appkit-poster`. Same radius, same hairline, same
345
+ * hover — so a mixed section (some cards with images, some without) still
346
+ * reads as one row rather than two designs.
347
+ */
348
+
349
+ .appkit-plain-card {
350
+ position: relative;
351
+ display: flex;
352
+ flex-direction: column;
353
+ gap: var(--appkit-space-3);
354
+ padding: var(--appkit-space-5);
355
+ min-height: 14rem;
356
+ border-radius: var(--appkit-radius-2xl);
357
+ background: var(--appkit-color-surface);
358
+ box-shadow: inset 0 0 0 1px var(--appkit-color-border);
359
+ transition:
360
+ transform 0.35s cubic-bezier(0.2, 0.7, 0.3, 1),
361
+ box-shadow 0.35s cubic-bezier(0.2, 0.7, 0.3, 1);
362
+ }
363
+
364
+ @media (hover: hover) {
365
+ .appkit-plain-card:hover {
366
+ transform: translateY(-3px);
367
+ box-shadow:
368
+ inset 0 0 0 1px var(--appkit-color-primary),
369
+ 0 18px 40px -18px rgb(0 0 0 / 0.18);
370
+ }
371
+ }
372
+
373
+ @media (prefers-reduced-motion: reduce) {
374
+ .appkit-plain-card {
375
+ transition: none;
376
+ }
377
+
378
+ .appkit-plain-card:hover {
379
+ transform: none;
380
+ }
381
+ }
382
+
383
+ .appkit-plain-card__eyebrow {
384
+ display: inline-flex;
385
+ align-items: center;
386
+ gap: var(--appkit-space-2);
387
+ color: var(--appkit-color-text-faint);
388
+ font-size: var(--appkit-text-xs);
389
+ font-weight: 600;
390
+ letter-spacing: 0.12em;
391
+ text-transform: uppercase;
392
+ font-variant-numeric: tabular-nums;
393
+ }
394
+
395
+ .appkit-plain-card__eyebrow::before {
396
+ content: "";
397
+ width: 1.25rem;
398
+ height: 1px;
399
+ flex: none;
400
+ background: var(--appkit-color-border);
401
+ }
402
+
403
+ /* Size and weight come from the `<Heading>`/`<Text>` props — see the note on
404
+ `.appkit-poster__title`. */
405
+ .appkit-plain-card__title {
406
+ margin: 0;
407
+ color: var(--appkit-color-text);
408
+ line-height: 1.2;
409
+ letter-spacing: -0.015em;
410
+ text-wrap: balance;
411
+ }
412
+
413
+ .appkit-plain-card__text {
414
+ margin: 0;
415
+ color: var(--appkit-color-text-muted);
416
+ line-height: 1.55;
417
+ }
418
+
419
+ .appkit-plain-card__chips {
420
+ display: flex;
421
+ flex-wrap: wrap;
422
+ gap: var(--appkit-space-2);
423
+ margin-top: auto;
424
+ padding-top: var(--appkit-space-3);
425
+ border-top: 1px solid var(--appkit-color-border-subtle);
426
+ }
427
+
428
+ /* ── Grid ─────────────────────────────────────────────────────────────── */
429
+
430
+ /*
431
+ * Posters are tall, so they do NOT use `<Grid cols="cards">` — that ladder's
432
+ * `auto-fill` tier is tuned for ~240px product tiles and would open a fifth
433
+ * track on a wide screen, breaking a set of exactly four generations into
434
+ * "four and a gap". A fixed 1 → 2 → 4 ladder is the honest shape for a fixed
435
+ * set.
436
+ */
437
+ .appkit-poster-grid {
438
+ display: grid;
439
+ gap: var(--appkit-space-4);
440
+ grid-template-columns: minmax(0, 1fr);
441
+ }
442
+
443
+ /* Explicit rather than inherited from the base. A modifier that generates no
444
+ rule of its own is what `audit-dead-css-classes` exists to catch, and
445
+ "it happens to equal the default today" is not a reason to omit it. */
446
+ .appkit-poster-grid--1 {
447
+ grid-template-columns: minmax(0, 1fr);
448
+ }
449
+
450
+ @media (min-width: 40rem) {
451
+ .appkit-poster-grid--2,
452
+ .appkit-poster-grid--3,
453
+ .appkit-poster-grid--4 {
454
+ grid-template-columns: repeat(2, minmax(0, 1fr));
455
+ }
456
+ }
457
+
458
+ @media (min-width: 64rem) {
459
+ .appkit-poster-grid--4 {
460
+ grid-template-columns: repeat(4, minmax(0, 1fr));
461
+ }
462
+
463
+ .appkit-poster-grid--3 {
464
+ grid-template-columns: repeat(3, minmax(0, 1fr));
465
+ }
466
+ }
@@ -77,6 +77,7 @@
77
77
  @import "./Menu.style.css";
78
78
  @import "./OverflowRow.style.css";
79
79
  @import "./MediaTabs.style.css";
80
+ @import "./PosterCard.style.css";
80
81
  @import "./Semantic.style.css";
81
82
  @import "./UnsavedChangesModal.style.css";
82
83
  @import "../DataTable.style.css";
@@ -109,6 +109,8 @@ export { ImageMosaic, MOSAIC_MIN } from "./components/ImageMosaic";
109
109
  export type { ImageMosaicProps, ImageMosaicSize } from "./components/ImageMosaic";
110
110
  export { SplitTile } from "./components/SplitTile";
111
111
  export type { SplitTileProps, SplitTilePane } from "./components/SplitTile";
112
+ export { PosterCard, PosterChip, PosterGrid, PlainCard, POSTER_CLASS, PLAIN_CLASS, } from "./components/PosterCard";
113
+ export type { PosterCardProps, PosterCardHeight, PosterChipProps, PosterGridProps, PlainCardProps, } from "./components/PosterCard";
112
114
  export type { ImageLightboxProps, LightboxImage, } from "./components/ImageLightbox";
113
115
  export { ImageLightbox } from "./components/ImageLightbox";
114
116
  export type { TagInputProps } from "./components/TagInput";