@bearmenu/ui 0.8.9 → 0.8.11

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 (225) hide show
  1. package/dist/chunk-24RFI4RQ.js +167 -0
  2. package/dist/{chunk-UKBNQO6Q.js → chunk-2CXU5WZO.js} +1 -1
  3. package/dist/chunk-2XCZJWTV.js +121 -0
  4. package/dist/{chunk-62ES6N32.js → chunk-43RC7MHY.js} +1 -1
  5. package/dist/chunk-5RWTOT5Y.js +72 -0
  6. package/dist/{chunk-Q6QH52LJ.js → chunk-6TEEY5YA.js} +2 -2
  7. package/dist/{chunk-AXV5O2IZ.js → chunk-AKQ57NXY.js} +3 -3
  8. package/dist/chunk-CLLY6XB3.js +41 -0
  9. package/dist/chunk-DTXNDLSE.js +125 -0
  10. package/dist/chunk-F6FMZJDG.js +113 -0
  11. package/dist/{chunk-2WDJR6K7.js → chunk-G7IFL4Z4.js} +4 -1
  12. package/dist/chunk-H3ZZDX5L.js +61 -0
  13. package/dist/{chunk-H3BV4VAG.js → chunk-JS3EINZD.js} +2 -2
  14. package/dist/chunk-MZL7CGCL.js +109 -0
  15. package/dist/{chunk-TH6W2PWG.js → chunk-UDTG3RSE.js} +1 -1
  16. package/dist/chunk-WCTHZKU7.js +37 -0
  17. package/dist/{chunk-TT3WFS6S.js → chunk-YOKHBAIH.js} +11 -11
  18. package/dist/components/alert-dialog.js +1 -1
  19. package/dist/components/alert.d.ts +2 -2
  20. package/dist/components/avatar.d.ts +3 -3
  21. package/dist/components/badge.d.ts +1 -1
  22. package/dist/components/badge.js +1 -1
  23. package/dist/components/button.d.ts +2 -2
  24. package/dist/components/button.js +1 -1
  25. package/dist/components/calendar.js +1 -1
  26. package/dist/components/card.d.ts +4 -4
  27. package/dist/components/carousel.js +1 -1
  28. package/dist/components/combobox.js +1 -1
  29. package/dist/components/command.d.ts +2 -2
  30. package/dist/components/concept-compare-rail.d.ts +50 -0
  31. package/dist/components/concept-compare-rail.js +5 -0
  32. package/dist/components/concept-compare.d.ts +75 -38
  33. package/dist/components/concept-compare.js +209 -133
  34. package/dist/components/concept-convergence.d.ts +41 -0
  35. package/dist/components/concept-convergence.js +53 -0
  36. package/dist/components/convergence.d.ts +25 -0
  37. package/dist/components/convergence.js +4 -0
  38. package/dist/components/dialog.d.ts +2 -2
  39. package/dist/components/empty-state.js +1 -1
  40. package/dist/components/event-card.js +1 -1
  41. package/dist/components/event-compact-row.js +1 -1
  42. package/dist/components/event-detail-panel.js +2 -2
  43. package/dist/components/event-index-row.js +1 -1
  44. package/dist/components/event-list-row-wide.js +2 -2
  45. package/dist/components/event-list-row.js +2 -2
  46. package/dist/components/explainer-steps.d.ts +34 -0
  47. package/dist/components/explainer-steps.js +36 -0
  48. package/dist/components/filter-category-row.js +1 -1
  49. package/dist/components/glass-ground.d.ts +61 -0
  50. package/dist/components/glass-ground.js +3 -0
  51. package/dist/components/haptic-button.js +1 -1
  52. package/dist/components/hub-surface.js +11 -1
  53. package/dist/components/hue-chip.d.ts +70 -0
  54. package/dist/components/hue-chip.js +3 -0
  55. package/dist/components/input-group.d.ts +2 -2
  56. package/dist/components/input.d.ts +1 -1
  57. package/dist/components/item.d.ts +4 -4
  58. package/dist/components/kbd.d.ts +2 -2
  59. package/dist/components/kitchen-dossier.d.ts +88 -0
  60. package/dist/components/kitchen-dossier.js +186 -0
  61. package/dist/components/link-button.js +1 -1
  62. package/dist/components/menu-dead-end.d.ts +56 -0
  63. package/dist/components/menu-dead-end.js +49 -0
  64. package/dist/components/menu-family-block.d.ts +98 -0
  65. package/dist/components/menu-family-block.js +153 -0
  66. package/dist/components/menu-family-card.d.ts +33 -27
  67. package/dist/components/menu-family-card.js +41 -85
  68. package/dist/components/menu-field.js +92 -2
  69. package/dist/components/menu-filter-chips.d.ts +86 -0
  70. package/dist/components/menu-filter-chips.js +122 -0
  71. package/dist/components/menu-group-rows.d.ts +53 -0
  72. package/dist/components/menu-group-rows.js +67 -0
  73. package/dist/components/menu-hub-hero.js +10 -5
  74. package/dist/components/menu-index-header.d.ts +37 -0
  75. package/dist/components/menu-index-header.js +68 -0
  76. package/dist/components/menu-index-list.d.ts +48 -0
  77. package/dist/components/menu-index-list.js +96 -0
  78. package/dist/components/menu-intent-hero.d.ts +69 -0
  79. package/dist/components/menu-intent-hero.js +87 -0
  80. package/dist/components/menu-item.js +3 -3
  81. package/dist/components/menu-kitchen-list.d.ts +82 -0
  82. package/dist/components/menu-kitchen-list.js +5 -0
  83. package/dist/components/menu-mocks.d.ts +37 -2
  84. package/dist/components/menu-mocks.js +152 -27
  85. package/dist/components/menu-types.d.ts +182 -6
  86. package/dist/components/motion-icon-controls.js +2 -2
  87. package/dist/components/motion-icon-set.js +6 -6
  88. package/dist/components/motion-icon.d.ts +22 -1
  89. package/dist/components/motion-icon.js +1 -1
  90. package/dist/components/multi-select-combobox.js +2 -2
  91. package/dist/components/native-select.d.ts +2 -2
  92. package/dist/components/pagination.js +1 -1
  93. package/dist/components/photo-ground.d.ts +64 -0
  94. package/dist/components/photo-ground.js +3 -0
  95. package/dist/components/place-about-tab.js +2 -2
  96. package/dist/components/place-details-mobile.js +3 -3
  97. package/dist/components/place-types.d.ts +2 -2
  98. package/dist/components/qa-block.d.ts +36 -0
  99. package/dist/components/qa-block.js +24 -0
  100. package/dist/components/scroll-fade.js +3 -121
  101. package/dist/components/searchable-select.js +1 -1
  102. package/dist/components/select.d.ts +1 -1
  103. package/dist/components/signature-chip.d.ts +2 -2
  104. package/dist/components/signature-chip.js +2 -2
  105. package/dist/components/sliding-panels.js +1 -1
  106. package/dist/components/snap-rail.d.ts +26 -3
  107. package/dist/components/snap-rail.js +3 -109
  108. package/dist/components/spinner.d.ts +4 -4
  109. package/dist/components/story.d.ts +34 -0
  110. package/dist/components/story.js +3 -0
  111. package/dist/components/tag.d.ts +4 -4
  112. package/dist/components/text.d.ts +5 -5
  113. package/dist/components/toggle.d.ts +1 -1
  114. package/dist/components/top-comparison.d.ts +28 -0
  115. package/dist/components/top-comparison.js +76 -0
  116. package/dist/components/top-cross-link.d.ts +34 -0
  117. package/dist/components/top-cross-link.js +101 -0
  118. package/dist/components/top-ranking-entry.d.ts +53 -0
  119. package/dist/components/top-ranking-entry.js +217 -0
  120. package/dist/components/top-ranking-hero.d.ts +61 -0
  121. package/dist/components/top-ranking-hero.js +99 -0
  122. package/dist/components/top-ranking-legend.d.ts +69 -0
  123. package/dist/components/top-ranking-legend.js +124 -0
  124. package/dist/components/top-types.d.ts +109 -0
  125. package/dist/components/top-types.js +1 -0
  126. package/dist/components/venue-grade-dimensions.d.ts +3 -1
  127. package/dist/components/venue-grade-dimensions.js +1 -1
  128. package/dist/components/venue-quotes.d.ts +1 -1
  129. package/dist/components/venue-quotes.js +1 -1
  130. package/dist/components/venue-wall.js +5 -3
  131. package/dist/components/weekday-theme-banner.js +1 -1
  132. package/dist/components/weekend-panel.js +2 -2
  133. package/package.json +1 -1
  134. package/src/components/badge.tsx +1 -1
  135. package/src/components/button.tsx +2 -2
  136. package/src/components/concept-compare-rail.tsx +132 -0
  137. package/src/components/concept-compare.stories.tsx +122 -54
  138. package/src/components/concept-compare.tsx +368 -196
  139. package/src/components/concept-convergence.stories.tsx +53 -0
  140. package/src/components/concept-convergence.tsx +90 -0
  141. package/src/components/convergence.stories.tsx +54 -0
  142. package/src/components/convergence.tsx +177 -0
  143. package/src/components/explainer-steps.stories.tsx +46 -0
  144. package/src/components/explainer-steps.tsx +74 -0
  145. package/src/components/glass-ground.tsx +104 -0
  146. package/src/components/hub-surface.tsx +11 -1
  147. package/src/components/hue-chip.stories.tsx +76 -0
  148. package/src/components/hue-chip.test.tsx +62 -0
  149. package/src/components/hue-chip.tsx +99 -0
  150. package/src/components/kitchen-dossier.stories.tsx +108 -0
  151. package/src/components/kitchen-dossier.tsx +321 -0
  152. package/src/components/menu-dead-end.stories.tsx +67 -0
  153. package/src/components/menu-dead-end.tsx +127 -0
  154. package/src/components/menu-family-block.stories.tsx +91 -0
  155. package/src/components/menu-family-block.tsx +303 -0
  156. package/src/components/menu-family-card.stories.tsx +43 -35
  157. package/src/components/menu-family-card.tsx +115 -142
  158. package/src/components/menu-filter-chips.stories.tsx +107 -0
  159. package/src/components/menu-filter-chips.tsx +238 -0
  160. package/src/components/menu-group-rows.stories.tsx +54 -0
  161. package/src/components/menu-group-rows.tsx +133 -0
  162. package/src/components/menu-hub-hero.test.tsx +39 -0
  163. package/src/components/menu-hub-hero.tsx +17 -5
  164. package/src/components/menu-index-header.stories.tsx +72 -0
  165. package/src/components/menu-index-header.tsx +125 -0
  166. package/src/components/menu-index-list.stories.tsx +50 -0
  167. package/src/components/menu-index-list.tsx +149 -0
  168. package/src/components/menu-intent-hero.stories.tsx +67 -0
  169. package/src/components/menu-intent-hero.tsx +189 -0
  170. package/src/components/menu-kitchen-list.stories.tsx +67 -0
  171. package/src/components/menu-kitchen-list.test.tsx +78 -0
  172. package/src/components/menu-kitchen-list.tsx +284 -0
  173. package/src/components/menu-mocks.ts +198 -23
  174. package/src/components/menu-types.ts +189 -6
  175. package/src/components/motion-icon-controls.tsx +11 -11
  176. package/src/components/motion-icon-set.tsx +6 -5
  177. package/src/components/motion-icon.test.tsx +23 -1
  178. package/src/components/motion-icon.tsx +20 -0
  179. package/src/components/photo-ground.tsx +147 -0
  180. package/src/components/place-types.ts +2 -2
  181. package/src/components/qa-block.stories.tsx +43 -0
  182. package/src/components/qa-block.tsx +60 -0
  183. package/src/components/signature-chip.tsx +3 -3
  184. package/src/components/snap-rail.tsx +58 -15
  185. package/src/components/story.tsx +225 -0
  186. package/src/components/top-comparison.stories.tsx +78 -0
  187. package/src/components/top-comparison.tsx +151 -0
  188. package/src/components/top-cross-link.stories.tsx +84 -0
  189. package/src/components/top-cross-link.tsx +171 -0
  190. package/src/components/top-ranking-entry.stories.tsx +171 -0
  191. package/src/components/top-ranking-entry.test.tsx +105 -0
  192. package/src/components/top-ranking-entry.tsx +425 -0
  193. package/src/components/top-ranking-hero.stories.tsx +76 -0
  194. package/src/components/top-ranking-hero.tsx +218 -0
  195. package/src/components/top-ranking-legend.stories.tsx +78 -0
  196. package/src/components/top-ranking-legend.tsx +239 -0
  197. package/src/components/top-types.ts +110 -0
  198. package/src/components/venue-grade-dimensions.tsx +2 -2
  199. package/src/components/venue-quotes.stories.tsx +1 -1
  200. package/src/components/venue-quotes.tsx +4 -2
  201. package/src/components/venue-wall.tsx +9 -4
  202. package/src/components/weekday-theme-banner.tsx +1 -1
  203. package/dist/chunk-THOVPZNN.js +0 -37
  204. package/dist/chunk-YS6BHKZO.js +0 -92
  205. package/dist/components/ranked-venue-list.d.ts +0 -49
  206. package/dist/components/ranked-venue-list.js +0 -87
  207. package/dist/components/signature-line-list.d.ts +0 -45
  208. package/dist/components/signature-line-list.js +0 -99
  209. package/dist/components/venue-busyness-strip.d.ts +0 -35
  210. package/dist/components/venue-busyness-strip.js +0 -37
  211. package/dist/components/venue-signature-block.d.ts +0 -39
  212. package/dist/components/venue-signature-block.js +0 -6
  213. package/dist/components/venue-spotlight.d.ts +0 -74
  214. package/dist/components/venue-spotlight.js +0 -138
  215. package/src/components/ranked-venue-list.stories.tsx +0 -80
  216. package/src/components/ranked-venue-list.tsx +0 -177
  217. package/src/components/signature-line-list.stories.tsx +0 -85
  218. package/src/components/signature-line-list.tsx +0 -136
  219. package/src/components/venue-busyness-strip.stories.tsx +0 -66
  220. package/src/components/venue-busyness-strip.tsx +0 -64
  221. package/src/components/venue-signature-block.stories.tsx +0 -68
  222. package/src/components/venue-signature-block.tsx +0 -71
  223. package/src/components/venue-spotlight.stories.tsx +0 -141
  224. package/src/components/venue-spotlight.tsx +0 -236
  225. package/dist/{chunk-XWMRABZW.js → chunk-HSIYYA2E.js} +1 -1
@@ -0,0 +1,34 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+ import { MenuLinkComponent } from './menu-types.js';
3
+ import 'react';
4
+ import './place-types.js';
5
+
6
+ type TopCrossLinkProps = {
7
+ /**
8
+ * `row` (default) — the way OUT of the ranking, full width under the list.
9
+ * `panel` — the way IN, stacked, for a narrow column beside a browse list.
10
+ */
11
+ variant?: "row" | "panel";
12
+ /** e.g. "RANKED". `panel` only; drawn over the title with `icon` inline. */
13
+ kicker?: string;
14
+ title: string;
15
+ description: string;
16
+ /**
17
+ * The link's text. Prefer the destination's own headline over a generic
18
+ * "see more": the reader lands on a page whose `<h1>` is the string they
19
+ * tapped, and it is the anchor text the destination actually wants.
20
+ */
21
+ cta: string;
22
+ href: string;
23
+ /** Finished CSS gradient for the ground. `panel` only. */
24
+ gradient?: string;
25
+ /** Names the DESTINATION. Defaults to `LayoutList` on `row`, `Trophy` on `panel`. */
26
+ icon?: React.ReactNode;
27
+ /** The title's element. `b` by default on `row`, `h2` on `panel`. */
28
+ as?: "h2" | "h3" | "b";
29
+ linkComponent?: MenuLinkComponent;
30
+ className?: string;
31
+ };
32
+ declare function TopCrossLink({ variant, kicker, title, description, cta, href, gradient, icon, as, linkComponent: LinkComponent, className, }: TopCrossLinkProps): react_jsx_runtime.JSX.Element;
33
+
34
+ export { TopCrossLink, type TopCrossLinkProps };
@@ -0,0 +1,101 @@
1
+ import { cn } from '../chunk-FEK5XGZW.js';
2
+ import { __objRest, __spreadValues } from '../chunk-3GWWZKX7.js';
3
+ import { ArrowRight, Trophy, LayoutList } from 'lucide-react';
4
+ import { jsxs, jsx } from 'react/jsx-runtime';
5
+
6
+ function DefaultLink(_a) {
7
+ var _b = _a, { href } = _b, props = __objRest(_b, ["href"]);
8
+ return /* @__PURE__ */ jsx("a", __spreadValues({ href }, props));
9
+ }
10
+ var CTA = cn(
11
+ "rounded-xl bg-accent font-bold text-accent-foreground",
12
+ "transition-opacity duration-150 hover:opacity-90",
13
+ "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
14
+ "motion-reduce:transition-none"
15
+ );
16
+ function TopCrossLink({
17
+ variant = "row",
18
+ kicker,
19
+ title,
20
+ description,
21
+ cta,
22
+ href,
23
+ gradient,
24
+ icon,
25
+ as,
26
+ linkComponent: LinkComponent = DefaultLink,
27
+ className
28
+ }) {
29
+ const isPanel = variant === "panel";
30
+ const Heading = as != null ? as : isPanel ? "h2" : "b";
31
+ const glyph = icon != null ? icon : isPanel ? /* @__PURE__ */ jsx(Trophy, {}) : /* @__PURE__ */ jsx(LayoutList, {});
32
+ if (!isPanel) {
33
+ return /* @__PURE__ */ jsxs(
34
+ "div",
35
+ {
36
+ className: cn(
37
+ "flex flex-col gap-3 rounded-[18px] border border-border bg-card p-4",
38
+ "lg:flex-row lg:items-center lg:gap-5 lg:p-5",
39
+ className
40
+ ),
41
+ children: [
42
+ /* @__PURE__ */ jsx("span", { "aria-hidden": true, className: "shrink-0 text-muted-foreground [&>svg]:h-5 [&>svg]:w-5", children: glyph }),
43
+ /* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
44
+ /* @__PURE__ */ jsx(Heading, { className: "block text-[15px] font-bold tracking-[-0.01em]", children: title }),
45
+ /* @__PURE__ */ jsx("p", { className: "mt-1 text-[13px] leading-relaxed text-muted-foreground", children: description })
46
+ ] }),
47
+ /* @__PURE__ */ jsxs(
48
+ LinkComponent,
49
+ {
50
+ href,
51
+ className: cn(
52
+ CTA,
53
+ "inline-flex min-h-11 shrink-0 items-center justify-center gap-2 px-5 py-2.5 text-sm"
54
+ ),
55
+ children: [
56
+ cta,
57
+ /* @__PURE__ */ jsx(ArrowRight, { className: "h-4 w-4 shrink-0", "aria-hidden": true })
58
+ ]
59
+ }
60
+ )
61
+ ]
62
+ }
63
+ );
64
+ }
65
+ return /* @__PURE__ */ jsxs(
66
+ "div",
67
+ {
68
+ className: cn(
69
+ "relative overflow-hidden rounded-[18px] border border-border bg-card shadow-[var(--shadow-card)]",
70
+ className
71
+ ),
72
+ children: [
73
+ gradient && /* @__PURE__ */ jsx("span", { "aria-hidden": true, className: "absolute inset-0", style: { backgroundImage: gradient } }),
74
+ /* @__PURE__ */ jsxs("div", { className: "relative z-[1] p-4 lg:p-5", children: [
75
+ kicker && /* @__PURE__ */ jsxs("span", { className: "flex items-center gap-1.5 text-[10.5px] font-extrabold uppercase tracking-[0.13em] text-muted-foreground", children: [
76
+ /* @__PURE__ */ jsx("span", { "aria-hidden": true, className: "[&>svg]:h-3 [&>svg]:w-3 [&>svg]:shrink-0", children: glyph }),
77
+ kicker
78
+ ] }),
79
+ /* @__PURE__ */ jsx(Heading, { className: "mt-2 block font-serif text-[20px] font-bold leading-[1.12] tracking-[-0.02em] text-foreground lg:text-[21px]", children: title }),
80
+ /* @__PURE__ */ jsx("p", { className: "mt-1.5 text-[13px] leading-relaxed text-muted-foreground", children: description }),
81
+ /* @__PURE__ */ jsxs(
82
+ LinkComponent,
83
+ {
84
+ href,
85
+ className: cn(
86
+ CTA,
87
+ "mt-4 flex min-h-11 w-full items-center justify-between gap-3 px-4 py-3 text-left text-[13.5px] leading-snug"
88
+ ),
89
+ children: [
90
+ /* @__PURE__ */ jsx("span", { children: cta }),
91
+ /* @__PURE__ */ jsx(ArrowRight, { className: "h-4 w-4 shrink-0", "aria-hidden": true })
92
+ ]
93
+ }
94
+ )
95
+ ] })
96
+ ]
97
+ }
98
+ );
99
+ }
100
+
101
+ export { TopCrossLink };
@@ -0,0 +1,53 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+ import { MenuItemLabels } from './menu-item.js';
3
+ import { MenuItemPreview, MenuLinkComponent } from './menu-types.js';
4
+ import { TopRankEntry } from './top-types.js';
5
+ import 'react';
6
+ import './place-types.js';
7
+
8
+ type TopRankingEntryLabels = {
9
+ /** Heading over the menu rows when the venue's own group is unknown. */
10
+ signature: string;
11
+ /** Accessible name for the dish link. */
12
+ openDish: (name: string) => string;
13
+ /** Passed through to the production `<MenuItem>`. */
14
+ item: MenuItemLabels;
15
+ /**
16
+ * Opens an item's drawer. The DS owns no routing, so the caller composes it.
17
+ *
18
+ * Takes the ITEM, not its slug: a menu row is a dish or a drink and the two
19
+ * resolve through different endpoints, so the caller has to see `kind` to
20
+ * build the right link. `undefined` leaves the row inert, which is the
21
+ * text-only must-try mention with no live menu row behind it.
22
+ */
23
+ itemHref: (item: MenuItemPreview) => string | undefined;
24
+ };
25
+ type TopRankingEntryProps = {
26
+ entry: TopRankEntry;
27
+ labels: TopRankingEntryLabels;
28
+ formatPrice: (amount: number) => string;
29
+ formatCount?: (value: number) => string;
30
+ /** The category's hue, for the menu rows' colour mark. */
31
+ markGradient?: string;
32
+ linkComponent?: MenuLinkComponent;
33
+ className?: string;
34
+ };
35
+ declare function TopRankingEntry({ entry, labels, formatPrice, formatCount, markGradient, linkComponent: LinkComponent, className, }: TopRankingEntryProps): react_jsx_runtime.JSX.Element;
36
+ /**
37
+ * The entry's own loading state, at the entry's footprint.
38
+ *
39
+ * The cover keeps its 3:2 on the phone and its 4:5 at 300px wide on desktop, so
40
+ * the article does not jump when the photographs land — the one reflow a long,
41
+ * scrolled page cannot afford. (This said "4:3 … 300×225" for a while after the
42
+ * cover changed underneath it, which is the failure mode a skeleton in the same
43
+ * file as its component is supposed to prevent.)
44
+ */
45
+ declare function TopRankingEntrySkeleton({ className }: {
46
+ className?: string;
47
+ }): react_jsx_runtime.JSX.Element;
48
+ /** A run of entries — the ranking's loading state. */
49
+ declare function TopRankingListSkeleton({ rows }: {
50
+ rows?: number;
51
+ }): react_jsx_runtime.JSX.Element;
52
+
53
+ export { TopRankingEntry, type TopRankingEntryLabels, type TopRankingEntryProps, TopRankingEntrySkeleton, TopRankingListSkeleton };
@@ -0,0 +1,217 @@
1
+ import { MenuItem } from '../chunk-UDTG3RSE.js';
2
+ import '../chunk-6TEEY5YA.js';
3
+ import { Separator } from '../chunk-WSRWFA6K.js';
4
+ import { Skeleton } from '../chunk-QXHMPJ35.js';
5
+ import '../chunk-2CXU5WZO.js';
6
+ import { cn } from '../chunk-FEK5XGZW.js';
7
+ import { __objRest, __spreadValues } from '../chunk-3GWWZKX7.js';
8
+ import { MapPin, Star, MessageSquareQuote } from 'lucide-react';
9
+ import { jsxs, jsx } from 'react/jsx-runtime';
10
+
11
+ function DefaultLink(_a) {
12
+ var _b = _a, { href } = _b, props = __objRest(_b, ["href"]);
13
+ return /* @__PURE__ */ jsx("a", __spreadValues({ href }, props));
14
+ }
15
+ var ENTRY_COVER_SCRIM = "bg-[linear-gradient(180deg,oklch(0.16_0.012_55_/_0)_0px,oklch(0.16_0.012_55_/_0.06)_16px,oklch(0.16_0.012_55_/_0.3)_32px,oklch(0.16_0.012_55_/_0.72)_48px,oklch(0.16_0.012_55_/_0.95)_100%)]";
16
+ function TopRankingEntry({
17
+ entry,
18
+ labels,
19
+ formatPrice,
20
+ formatCount,
21
+ markGradient,
22
+ linkComponent: LinkComponent = DefaultLink,
23
+ className
24
+ }) {
25
+ var _a, _b, _c;
26
+ const initial = ((_b = (_a = entry.name.match(new RegExp("\\p{L}", "u"))) == null ? void 0 : _a[0]) != null ? _b : "").toLocaleUpperCase();
27
+ return /* @__PURE__ */ jsxs(
28
+ "article",
29
+ {
30
+ id: entry.slug,
31
+ className: cn("grid grid-cols-1 gap-0 py-6 lg:flex lg:flex-row lg:gap-7", className),
32
+ children: [
33
+ /* @__PURE__ */ jsx(
34
+ LinkComponent,
35
+ {
36
+ href: entry.href,
37
+ className: "col-start-1 row-start-1 block shrink-0 overflow-hidden rounded-xl transition-opacity focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring active:opacity-90 motion-reduce:transition-none lg:sticky lg:top-4 lg:w-[300px] lg:self-start",
38
+ children: /* @__PURE__ */ jsx("span", { className: "relative block aspect-[3/2] w-full bg-[oklch(0.21_0.012_55)] lg:aspect-[4/5] lg:bg-[repeating-linear-gradient(135deg,oklch(0.82_0.014_55)_0_10px,oklch(0.87_0.01_58)_10px_20px)]", children: entry.coverUrl ? (
39
+ // eslint-disable-next-line @next/next/no-img-element
40
+ /* @__PURE__ */ jsx(
41
+ "img",
42
+ {
43
+ src: entry.coverUrl,
44
+ alt: "",
45
+ loading: "lazy",
46
+ decoding: "async",
47
+ className: "h-full w-full object-cover"
48
+ }
49
+ )
50
+ ) : (
51
+ /* Never a grey box: the warm ground with the venue's initial, the
52
+ rule `MenuFamilyBlock` states. Phone only — desktop keeps its
53
+ stripes. The stripes are a LIGHT surface in both themes, so at
54
+ this size they would be a pale rectangle under white type.
55
+ The ground stays on the parent span, so a 404 on `coverUrl`
56
+ degrades to the same surface for free. */
57
+ /* @__PURE__ */ jsx(
58
+ "span",
59
+ {
60
+ "aria-hidden": true,
61
+ className: "absolute inset-0 flex items-center justify-center lg:hidden",
62
+ children: /* @__PURE__ */ jsx("span", { className: "font-serif text-[64px] font-semibold leading-none text-white/25", children: initial })
63
+ }
64
+ )
65
+ ) })
66
+ }
67
+ ),
68
+ /* @__PURE__ */ jsxs("div", { className: "contents lg:block lg:min-w-0 lg:flex-1", children: [
69
+ /* @__PURE__ */ jsxs(
70
+ "div",
71
+ {
72
+ className: cn(
73
+ /* `relative z-[1]`: without it this block's background is a
74
+ non-positioned box's background, which paints in an earlier phase
75
+ than the cover's replaced `<img>` — so the scrim went UNDER the
76
+ photograph and the white type landed on bare image. Positioning
77
+ it lifts the whole block, background included, above the image.
78
+ Harmless at `lg`, where the two no longer overlap. */
79
+ "pointer-events-none relative z-[1] col-start-1 row-start-1 flex flex-wrap items-end gap-x-3 gap-y-0 self-end",
80
+ "rounded-b-xl px-3.5 pb-3.5 pt-12 [text-shadow:0_1px_2px_oklch(0.16_0.012_55_/_0.45)]",
81
+ ENTRY_COVER_SCRIM,
82
+ "lg:pointer-events-auto lg:flex-nowrap lg:items-start lg:gap-3 lg:rounded-none lg:bg-none lg:p-0 lg:[text-shadow:none]"
83
+ ),
84
+ children: [
85
+ /* @__PURE__ */ jsx(
86
+ "span",
87
+ {
88
+ "aria-hidden": true,
89
+ className: "basis-full text-[11px] font-bold leading-none tabular-nums text-white/70 lg:basis-auto lg:font-serif lg:text-[28px] lg:font-semibold lg:text-muted-foreground",
90
+ children: entry.rank
91
+ }
92
+ ),
93
+ /* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
94
+ /* @__PURE__ */ jsx("h3", { className: "line-clamp-2 text-lg font-bold leading-tight tracking-[-0.015em] text-white lg:line-clamp-none lg:text-foreground", children: /* @__PURE__ */ jsx(
95
+ LinkComponent,
96
+ {
97
+ href: entry.href,
98
+ className: "pointer-events-auto focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-white lg:focus-visible:ring-ring lg:hover:text-accent",
99
+ children: entry.name
100
+ }
101
+ ) }),
102
+ entry.district && /* @__PURE__ */ jsxs("span", { className: "mt-0.5 flex items-center gap-1 text-[12.5px] text-white/80 lg:text-muted-foreground", children: [
103
+ /* @__PURE__ */ jsx(MapPin, { className: "h-3 w-3 shrink-0", "aria-hidden": true }),
104
+ entry.district
105
+ ] })
106
+ ] }),
107
+ entry.gradeLabel ? /* @__PURE__ */ jsxs("span", { className: "inline-flex shrink-0 items-center gap-1.5 rounded-full bg-white/95 px-2.5 py-1 backdrop-blur-[2px] lg:gap-2 lg:rounded-lg lg:bg-accent-subtle lg:px-2.5 lg:py-1.5 lg:backdrop-blur-none", children: [
108
+ /* @__PURE__ */ jsx("b", { className: "text-[15px] font-extrabold tabular-nums text-[oklch(0.21_0.012_55)] lg:text-accent", children: entry.gradeLabel }),
109
+ entry.tierLabel && /* @__PURE__ */ jsx("span", { className: "text-[11.5px] text-[oklch(0.21_0.012_55)]/70 lg:text-muted-foreground", children: entry.tierLabel })
110
+ ] }) : entry.rating !== void 0 && /* The star fallback inverts the same way as the grade — a white
111
+ pill, deliberately quieter than a grade, because a third-party
112
+ rating is weaker evidence and should look it. Filled, since an
113
+ outlined star at this size disappears over photography. */
114
+ /* @__PURE__ */ jsxs("span", { className: "inline-flex shrink-0 items-center gap-1 rounded-full bg-white/95 px-2.5 py-1 text-[13px] font-semibold tabular-nums text-[oklch(0.21_0.012_55)] backdrop-blur-[2px] lg:bg-transparent lg:px-0 lg:py-0 lg:text-foreground lg:backdrop-blur-none", children: [
115
+ /* @__PURE__ */ jsx(
116
+ Star,
117
+ {
118
+ className: "h-3.5 w-3.5 shrink-0 fill-[oklch(0.21_0.012_55)] lg:fill-none",
119
+ "aria-hidden": true
120
+ }
121
+ ),
122
+ entry.rating
123
+ ] })
124
+ ]
125
+ }
126
+ ),
127
+ entry.editorial && /* The article's row gap is 0 on the phone now that the identity block
128
+ lives inside the photograph, so the paragraph owns the space under
129
+ it and gives it back at `lg`. */
130
+ /* @__PURE__ */ jsx("p", { className: "mt-4 font-serif text-[17px] italic leading-[1.65] text-muted-foreground [text-wrap:pretty] lg:mt-3", children: entry.editorial }),
131
+ entry.menuItems && entry.menuItems.length > 0 && /* @__PURE__ */ jsxs("div", { className: "mt-4", children: [
132
+ /* @__PURE__ */ jsx("span", { className: "block pb-1.5 text-[15px] font-bold tracking-[-0.01em]", children: (_c = entry.menuGroupLabel) != null ? _c : labels.signature }),
133
+ /* @__PURE__ */ jsx(Separator, {}),
134
+ entry.menuItems.map((item) => /* @__PURE__ */ jsx(
135
+ MenuItem,
136
+ {
137
+ item,
138
+ labels: labels.item,
139
+ formatPrice,
140
+ formatCount,
141
+ markGradient,
142
+ href: labels.itemHref(item),
143
+ linkLabel: labels.openDish(item.name),
144
+ linkComponent: LinkComponent
145
+ },
146
+ item.id
147
+ ))
148
+ ] }),
149
+ entry.evidence && /* @__PURE__ */ jsxs("figure", { className: "mt-4 border-l-[3px] border-border pl-4", children: [
150
+ entry.evidence.quote && /* @__PURE__ */ jsx("blockquote", { className: "text-[13.5px] leading-relaxed text-muted-foreground [text-wrap:pretty]", children: `\u201C${entry.evidence.quote}\u201D` }),
151
+ /* @__PURE__ */ jsxs(
152
+ "figcaption",
153
+ {
154
+ className: cn(
155
+ "flex items-center gap-1.5 text-[11.5px] text-muted-foreground",
156
+ entry.evidence.quote && "mt-1.5"
157
+ ),
158
+ children: [
159
+ /* @__PURE__ */ jsx(MessageSquareQuote, { className: "h-3 w-3 shrink-0 opacity-60", "aria-hidden": true }),
160
+ entry.evidence.count && /* @__PURE__ */ jsx("b", { className: "font-bold tabular-nums text-foreground", children: entry.evidence.count }),
161
+ entry.evidence.sourceLabel
162
+ ]
163
+ }
164
+ ),
165
+ entry.evidence.extraQuotes && entry.evidence.extraQuotes.length > 0 && /* @__PURE__ */ jsxs("details", { className: "mt-2", children: [
166
+ /* @__PURE__ */ jsx("summary", { className: "cursor-pointer text-[11.5px] font-semibold text-accent hover:underline", children: entry.evidence.extraLabel }),
167
+ /* @__PURE__ */ jsx("ul", { className: "mt-2 flex flex-col gap-2", children: entry.evidence.extraQuotes.map((quote) => /* @__PURE__ */ jsx(
168
+ "li",
169
+ {
170
+ className: "text-[13px] leading-relaxed text-muted-foreground [text-wrap:pretty]",
171
+ children: `\u201C${quote}\u201D`
172
+ },
173
+ quote
174
+ )) })
175
+ ] })
176
+ ] })
177
+ ] })
178
+ ]
179
+ }
180
+ );
181
+ }
182
+ function TopRankingEntrySkeleton({ className }) {
183
+ return /* @__PURE__ */ jsxs(
184
+ "div",
185
+ {
186
+ className: cn("flex flex-col gap-0 py-6 lg:flex-row lg:gap-7", className),
187
+ "aria-hidden": true,
188
+ children: [
189
+ /* @__PURE__ */ jsx(Skeleton, { className: "aspect-[3/2] w-full shrink-0 rounded-xl lg:aspect-[4/5] lg:w-[300px]" }),
190
+ /* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
191
+ /* @__PURE__ */ jsxs("div", { className: "hidden items-start gap-3 lg:flex", children: [
192
+ /* @__PURE__ */ jsx(Skeleton, { className: "h-7 w-6" }),
193
+ /* @__PURE__ */ jsxs("div", { className: "flex min-w-0 flex-1 flex-col gap-1.5", children: [
194
+ /* @__PURE__ */ jsx(Skeleton, { className: "h-5 w-[220px]" }),
195
+ /* @__PURE__ */ jsx(Skeleton, { className: "h-3 w-[100px]" })
196
+ ] }),
197
+ /* @__PURE__ */ jsx(Skeleton, { className: "h-8 w-[110px] rounded-lg" })
198
+ ] }),
199
+ /* @__PURE__ */ jsxs("div", { className: "mt-4 flex flex-col gap-2 lg:mt-3", children: [
200
+ /* @__PURE__ */ jsx(Skeleton, { className: "h-4 w-full" }),
201
+ /* @__PURE__ */ jsx(Skeleton, { className: "h-4 w-[92%]" }),
202
+ /* @__PURE__ */ jsx(Skeleton, { className: "h-4 w-[64%]" })
203
+ ] }),
204
+ /* @__PURE__ */ jsx(Skeleton, { className: "mt-4 h-[62px] w-full rounded-lg" })
205
+ ] })
206
+ ]
207
+ }
208
+ );
209
+ }
210
+ function TopRankingListSkeleton({ rows = 6 }) {
211
+ return /* @__PURE__ */ jsx("div", { "aria-hidden": true, children: Array.from({ length: rows }, (_, index) => /* @__PURE__ */ jsxs("div", { children: [
212
+ index > 0 && /* @__PURE__ */ jsx(Separator, {}),
213
+ /* @__PURE__ */ jsx(TopRankingEntrySkeleton, {})
214
+ ] }, index)) });
215
+ }
216
+
217
+ export { TopRankingEntry, TopRankingEntrySkeleton, TopRankingListSkeleton };
@@ -0,0 +1,61 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+ import { MenuLinkComponent } from './menu-types.js';
3
+ import { TopExit } from './top-types.js';
4
+ import 'react';
5
+ import './place-types.js';
6
+
7
+ type TopRankingHeroProps = {
8
+ /** e.g. "RANKED · CUISINE". */
9
+ kicker?: string;
10
+ title: string;
11
+ /** One paragraph: the size of the pool, and what the order means. */
12
+ lede?: string;
13
+ /** e.g. "Updated 6 September 2026". */
14
+ updatedLabel?: string;
15
+ /** e.g. "247 kitchens considered · 4,318 reviews read". */
16
+ metaLabel?: string;
17
+ exits?: TopExit[];
18
+ /** Finished CSS gradient for the ground. */
19
+ gradient?: string;
20
+ /** Watermark behind the text — the category's icon, resolved by the caller. */
21
+ icon?: React.ReactNode;
22
+ linkComponent?: MenuLinkComponent;
23
+ className?: string;
24
+ };
25
+ declare function TopRankingHero({ kicker, title, lede, updatedLabel, metaLabel, exits, gradient, icon, linkComponent: LinkComponent, className, }: TopRankingHeroProps): react_jsx_runtime.JSX.Element;
26
+ /**
27
+ * TopMethodNote — how the ranking was computed, in one block.
28
+ *
29
+ * Under the hero rather than at the foot: a reader deciding whether to trust a
30
+ * list decides it before reading the list, not after.
31
+ */
32
+ /**
33
+ * The note's surface, exported so a caller can put the same look on a TRIGGER.
34
+ *
35
+ * On `/top` the banner opens the grade explainer, and that explainer owns its
36
+ * own `<button>` (Radix clones it, so it cannot be handed a ready-made
37
+ * element). The caller therefore needs the surface without the element — hence
38
+ * a class string rather than a second component. `cn` at the call site lets the
39
+ * button's own `inline-flex` lose to the `flex` here.
40
+ */
41
+ declare const topMethodNoteSurface = "flex w-full items-start gap-3 rounded-[14px] bg-muted p-4 text-left";
42
+ type TopMethodNoteProps = {
43
+ /** The heading fragment, e.g. "How this is ranked." Rendered bold, inline. */
44
+ heading: string;
45
+ children: React.ReactNode;
46
+ /** Rendered after the prose — a chevron where the note is a disclosure trigger. */
47
+ affordance?: React.ReactNode;
48
+ className?: string;
49
+ };
50
+ /**
51
+ * The note's CONTENT, with no element of its own.
52
+ *
53
+ * Every tag here is phrasing content — spans, not paragraphs — because this
54
+ * renders inside a `<button>` when the note is a trigger, and a `<p>` inside a
55
+ * button is invalid. Keeping one definition of the content means the static
56
+ * note and the interactive one cannot drift apart.
57
+ */
58
+ declare function TopMethodNoteBody({ heading, children, affordance, }: Omit<TopMethodNoteProps, "className">): react_jsx_runtime.JSX.Element;
59
+ declare function TopMethodNote({ heading, children, affordance, className }: TopMethodNoteProps): react_jsx_runtime.JSX.Element;
60
+
61
+ export { TopMethodNote, TopMethodNoteBody, type TopMethodNoteProps, TopRankingHero, type TopRankingHeroProps, topMethodNoteSurface };
@@ -0,0 +1,99 @@
1
+ import { cn } from '../chunk-FEK5XGZW.js';
2
+ import { __objRest, __spreadValues } from '../chunk-3GWWZKX7.js';
3
+ import { Trophy, CalendarCheck, LayoutList, ArrowRight, Info } from 'lucide-react';
4
+ import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
5
+
6
+ function DefaultLink(_a) {
7
+ var _b = _a, { href } = _b, props = __objRest(_b, ["href"]);
8
+ return /* @__PURE__ */ jsx("a", __spreadValues({ href }, props));
9
+ }
10
+ function TopRankingHero({
11
+ kicker,
12
+ title,
13
+ lede,
14
+ updatedLabel,
15
+ metaLabel,
16
+ exits,
17
+ gradient,
18
+ icon,
19
+ linkComponent: LinkComponent = DefaultLink,
20
+ className
21
+ }) {
22
+ return /* @__PURE__ */ jsxs(
23
+ "header",
24
+ {
25
+ className: cn(
26
+ "relative overflow-hidden rounded-[18px] px-5 py-6 lg:px-8 lg:py-8",
27
+ !gradient && "bg-muted",
28
+ className
29
+ ),
30
+ children: [
31
+ gradient && /* @__PURE__ */ jsx("span", { "aria-hidden": true, className: "absolute inset-0", style: { backgroundImage: gradient } }),
32
+ icon && /* @__PURE__ */ jsx(
33
+ "span",
34
+ {
35
+ "aria-hidden": true,
36
+ className: "pointer-events-none absolute -right-6 -top-6 opacity-[0.14] [&>svg]:h-[150px] [&>svg]:w-[150px] lg:[&>svg]:h-[190px] lg:[&>svg]:w-[190px]",
37
+ children: icon
38
+ }
39
+ ),
40
+ /* @__PURE__ */ jsxs("div", { className: "relative z-[1]", children: [
41
+ kicker && /* @__PURE__ */ jsxs("span", { className: "inline-flex items-center gap-1.5 rounded-full bg-background/70 px-3 py-1 text-[11px] font-extrabold uppercase tracking-[0.09em] backdrop-blur-[2px]", children: [
42
+ /* @__PURE__ */ jsx(Trophy, { className: "h-3 w-3 shrink-0", "aria-hidden": true }),
43
+ kicker
44
+ ] }),
45
+ /* @__PURE__ */ jsx("h1", { className: "mt-3 max-w-[22ch] font-serif text-[30px] font-bold leading-[1.08] tracking-[-0.025em] lg:text-[44px]", children: title }),
46
+ lede && /* @__PURE__ */ jsx("p", { className: "mt-3 max-w-[62ch] text-[14.5px] leading-relaxed opacity-85", children: lede }),
47
+ (updatedLabel || metaLabel) && /* @__PURE__ */ jsxs("div", { className: "mt-4 flex flex-wrap items-center gap-x-3 gap-y-1.5 text-[12.5px]", children: [
48
+ updatedLabel && /* @__PURE__ */ jsxs("span", { className: "inline-flex items-center gap-1.5 font-semibold", children: [
49
+ /* @__PURE__ */ jsx(CalendarCheck, { className: "h-3.5 w-3.5 shrink-0", "aria-hidden": true }),
50
+ updatedLabel
51
+ ] }),
52
+ updatedLabel && metaLabel && /* @__PURE__ */ jsx("span", { "aria-hidden": true, className: "h-1 w-1 rounded-full bg-current opacity-40" }),
53
+ metaLabel && /* @__PURE__ */ jsx("span", { className: "opacity-75", children: metaLabel })
54
+ ] }),
55
+ exits && exits.length > 0 && /* @__PURE__ */ jsx("div", { className: "mt-5 hidden flex-col gap-2 sm:flex-row lg:flex", children: exits.map((exit, index) => {
56
+ var _a;
57
+ return /* @__PURE__ */ jsxs(
58
+ LinkComponent,
59
+ {
60
+ href: exit.href,
61
+ className: cn(
62
+ "inline-flex h-11 items-center justify-center gap-2 rounded-xl px-4 text-sm font-bold transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 motion-reduce:transition-none",
63
+ index === 0 ? "bg-background text-foreground hover:bg-background/90" : "border border-current/25 hover:bg-background/25"
64
+ ),
65
+ children: [
66
+ (_a = exit.icon) != null ? _a : /* @__PURE__ */ jsx(LayoutList, { className: "h-4 w-4 shrink-0", "aria-hidden": true }),
67
+ exit.label,
68
+ /* @__PURE__ */ jsx(ArrowRight, { className: "h-3.5 w-3.5 shrink-0", "aria-hidden": true })
69
+ ]
70
+ },
71
+ exit.href
72
+ );
73
+ }) })
74
+ ] })
75
+ ]
76
+ }
77
+ );
78
+ }
79
+ var topMethodNoteSurface = "flex w-full items-start gap-3 rounded-[14px] bg-muted p-4 text-left";
80
+ function TopMethodNoteBody({
81
+ heading,
82
+ children,
83
+ affordance
84
+ }) {
85
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
86
+ /* @__PURE__ */ jsx(Info, { className: "mt-0.5 h-4 w-4 shrink-0 text-muted-foreground", "aria-hidden": true }),
87
+ /* @__PURE__ */ jsxs("span", { className: "block text-[13px] leading-relaxed text-muted-foreground", children: [
88
+ /* @__PURE__ */ jsx("b", { className: "font-bold text-foreground", children: heading }),
89
+ " ",
90
+ children
91
+ ] }),
92
+ affordance
93
+ ] });
94
+ }
95
+ function TopMethodNote({ heading, children, affordance, className }) {
96
+ return /* @__PURE__ */ jsx("aside", { className: cn(topMethodNoteSurface, className), children: /* @__PURE__ */ jsx(TopMethodNoteBody, { heading, affordance, children }) });
97
+ }
98
+
99
+ export { TopMethodNote, TopMethodNoteBody, TopRankingHero, topMethodNoteSurface };
@@ -0,0 +1,69 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+
3
+ /**
4
+ * TopRankingLegend — the ranking as a place you are inside of, not a table.
5
+ *
6
+ * ## What it stopped being
7
+ *
8
+ * It was a two-column table: rank, name, grade, with a rule under every row.
9
+ * Three things were wrong with that, and they compounded.
10
+ *
11
+ * The **grade column** was the loudest thing in it and the least useful thing
12
+ * for navigating — twenty-five near-identical numbers in accent colour,
13
+ * duplicating a grade shown at full size two inches to the right, and eating
14
+ * the width that was truncating the names. The one field you navigate by was
15
+ * the one being cut. It is gone.
16
+ *
17
+ * The **dividers** ran full-bleed to the column's right edge, so every rule was
18
+ * a small arrow pointing at the article and nothing ever visually ended the
19
+ * aside. Space separates now; contrast is spent on state instead.
20
+ *
21
+ * And there was **no active row at all** — twenty-five identical entries in a
22
+ * component whose entire job is orientation. That is what the observer below
23
+ * is for.
24
+ *
25
+ * ## The rank numeral is the rail
26
+ *
27
+ * The reference this is drawn from uses a status ring down the left edge. A
28
+ * ring is wrong here: it promises completion state we do not track and would
29
+ * never fill. The rank numeral is already present, already tabular, and is
30
+ * itself the thing being ranked — a better rail, for free.
31
+ *
32
+ * ## Grouped, because twenty-five is not a list you can hold
33
+ *
34
+ * By grade band, which is the one honest grouping available: the tiers are
35
+ * already printed on the entries, so the aside is not inventing a taxonomy. It
36
+ * also says something true — that the top few are a different class.
37
+ *
38
+ * The column will still exceed the viewport at twenty-five, and that is
39
+ * accepted rather than solved: an internally-scrolling sidebar is a second page,
40
+ * and grouping plus a live active row makes the overflow navigable.
41
+ *
42
+ * ## The phone keeps its chips
43
+ *
44
+ * Below `lg` this is a horizontal scroller of the flat list, unchanged. None of
45
+ * the above applies to a chip row — there is nothing to group, no column to
46
+ * inset, and the active state is a press away rather than a scroll away.
47
+ */
48
+ type TopRankingLegendEntry = {
49
+ rank: number;
50
+ slug: string;
51
+ name: string;
52
+ };
53
+ type TopRankingLegendGroup = {
54
+ key: string;
55
+ /** Grade band, e.g. "Exceptional". Letterspaced caps — structure, not content. */
56
+ label: string;
57
+ entries: TopRankingLegendEntry[];
58
+ };
59
+ type TopRankingLegendProps = {
60
+ groups: TopRankingLegendGroup[];
61
+ /** e.g. "The ranking". */
62
+ heading: string;
63
+ /** The size and freshness of the thing, e.g. "25 places · updated September 2026". */
64
+ meta?: string;
65
+ className?: string;
66
+ };
67
+ declare function TopRankingLegend({ groups, heading, meta, className }: TopRankingLegendProps): react_jsx_runtime.JSX.Element;
68
+
69
+ export { TopRankingLegend, type TopRankingLegendEntry, type TopRankingLegendGroup, type TopRankingLegendProps };