@bearmenu/ui 0.8.7 → 0.8.9

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 (179) hide show
  1. package/dist/{chunk-637L7XQ3.js → chunk-3TNPSN4D.js} +28 -9
  2. package/dist/chunk-62ES6N32.js +27 -0
  3. package/dist/chunk-AHL45DD2.js +145 -0
  4. package/dist/chunk-AXV5O2IZ.js +58 -0
  5. package/dist/chunk-IYDH5IU6.js +66 -0
  6. package/dist/chunk-OPVKL4VS.js +18 -0
  7. package/dist/chunk-Q6QH52LJ.js +38 -0
  8. package/dist/chunk-TH6W2PWG.js +213 -0
  9. package/dist/chunk-THOVPZNN.js +37 -0
  10. package/dist/chunk-YS6BHKZO.js +92 -0
  11. package/dist/components/badge.d.ts +1 -1
  12. package/dist/components/button.d.ts +1 -1
  13. package/dist/components/category-deck.js +10 -1
  14. package/dist/components/command.d.ts +7 -7
  15. package/dist/components/concept-compare.d.ts +73 -0
  16. package/dist/components/concept-compare.js +162 -0
  17. package/dist/components/coverflow.d.ts +11 -3
  18. package/dist/components/coverflow.js +34 -51
  19. package/dist/components/date-input.d.ts +1 -1
  20. package/dist/components/drift-rail.d.ts +24 -10
  21. package/dist/components/drift-rail.js +1 -1
  22. package/dist/components/event-card.d.ts +8 -0
  23. package/dist/components/event-card.js +64 -34
  24. package/dist/components/event-compact-row.d.ts +33 -6
  25. package/dist/components/event-compact-row.js +120 -39
  26. package/dist/components/event-day-header.js +3 -1
  27. package/dist/components/event-day-separator.d.ts +7 -1
  28. package/dist/components/event-day-separator.js +3 -2
  29. package/dist/components/event-detail-panel.d.ts +171 -25
  30. package/dist/components/event-detail-panel.js +471 -174
  31. package/dist/components/event-hub-hero.d.ts +25 -4
  32. package/dist/components/event-hub-hero.js +89 -19
  33. package/dist/components/event-list-row-wide.js +1 -1
  34. package/dist/components/event-list-row.d.ts +19 -6
  35. package/dist/components/event-list-row.js +79 -35
  36. package/dist/components/hour-band.d.ts +38 -40
  37. package/dist/components/hour-band.js +55 -27
  38. package/dist/components/hour-group.js +1 -1
  39. package/dist/components/hub-surface.d.ts +27 -0
  40. package/dist/components/hub-surface.js +20 -0
  41. package/dist/components/menu-family-card.d.ts +59 -0
  42. package/dist/components/menu-family-card.js +121 -0
  43. package/dist/components/menu-field.d.ts +57 -0
  44. package/dist/components/menu-field.js +3 -0
  45. package/dist/components/menu-hub-hero.d.ts +65 -0
  46. package/dist/components/menu-hub-hero.js +103 -0
  47. package/dist/components/menu-intent-tiles.d.ts +31 -0
  48. package/dist/components/menu-intent-tiles.js +50 -0
  49. package/dist/components/menu-item.d.ts +127 -0
  50. package/dist/components/menu-item.js +5 -0
  51. package/dist/components/menu-mocks.d.ts +55 -0
  52. package/dist/components/menu-mocks.js +379 -0
  53. package/dist/components/menu-types.d.ts +245 -0
  54. package/dist/components/menu-types.js +1 -0
  55. package/dist/components/motion-icon-set.js +15 -3
  56. package/dist/components/open-now-list.d.ts +40 -0
  57. package/dist/components/open-now-list.js +69 -0
  58. package/dist/components/place-types.d.ts +112 -1
  59. package/dist/components/poster-tape.js +1 -1
  60. package/dist/components/poster-wall.d.ts +3 -3
  61. package/dist/components/poster-wall.js +73 -17
  62. package/dist/components/ranked-venue-list.d.ts +49 -0
  63. package/dist/components/ranked-venue-list.js +87 -0
  64. package/dist/components/section-header.d.ts +16 -1
  65. package/dist/components/section-header.js +26 -7
  66. package/dist/components/signature-chip.d.ts +37 -0
  67. package/dist/components/signature-chip.js +4 -0
  68. package/dist/components/signature-line-list.d.ts +45 -0
  69. package/dist/components/signature-line-list.js +99 -0
  70. package/dist/components/snap-rail.d.ts +54 -0
  71. package/dist/components/snap-rail.js +109 -0
  72. package/dist/components/spinner.d.ts +2 -2
  73. package/dist/components/stat-banner.d.ts +47 -0
  74. package/dist/components/stat-banner.js +61 -0
  75. package/dist/components/text.d.ts +3 -3
  76. package/dist/components/timeline-band.d.ts +20 -2
  77. package/dist/components/timeline-band.js +1 -1
  78. package/dist/components/timeline-playhead.d.ts +26 -0
  79. package/dist/components/timeline-playhead.js +25 -0
  80. package/dist/components/venue-busyness-strip.d.ts +35 -0
  81. package/dist/components/venue-busyness-strip.js +37 -0
  82. package/dist/components/venue-grade-dimensions.d.ts +36 -0
  83. package/dist/components/venue-grade-dimensions.js +3 -0
  84. package/dist/components/venue-quotes.d.ts +32 -0
  85. package/dist/components/venue-quotes.js +3 -0
  86. package/dist/components/venue-signature-block.d.ts +39 -0
  87. package/dist/components/venue-signature-block.js +6 -0
  88. package/dist/components/venue-spotlight.d.ts +74 -0
  89. package/dist/components/venue-spotlight.js +138 -0
  90. package/dist/components/venue-wall.d.ts +41 -0
  91. package/dist/components/venue-wall.js +118 -0
  92. package/dist/components/weekday-theme-banner.d.ts +70 -0
  93. package/dist/components/weekday-theme-banner.js +127 -0
  94. package/dist/components/weekend-panel.d.ts +5 -4
  95. package/dist/components/weekend-panel.js +85 -71
  96. package/dist/hooks/use-drag-swipe.d.ts +44 -0
  97. package/dist/hooks/use-drag-swipe.js +2 -0
  98. package/dist/hooks/use-media-query.d.ts +17 -0
  99. package/dist/hooks/use-media-query.js +2 -0
  100. package/package.json +1 -1
  101. package/src/components/category-deck.tsx +19 -8
  102. package/src/components/colour-dictionary.stories.tsx +227 -0
  103. package/src/components/concept-compare.stories.tsx +104 -0
  104. package/src/components/concept-compare.tsx +276 -0
  105. package/src/components/coverflow.test.tsx +28 -1
  106. package/src/components/coverflow.tsx +37 -43
  107. package/src/components/drift-rail.tsx +167 -42
  108. package/src/components/event-card.tsx +55 -10
  109. package/src/components/event-compact-row.stories.tsx +30 -0
  110. package/src/components/event-compact-row.test.tsx +49 -0
  111. package/src/components/event-compact-row.tsx +162 -38
  112. package/src/components/event-day-header.tsx +3 -1
  113. package/src/components/event-day-separator.tsx +8 -1
  114. package/src/components/event-detail-panel.stories.tsx +205 -97
  115. package/src/components/event-detail-panel.test.tsx +181 -0
  116. package/src/components/event-detail-panel.tsx +703 -185
  117. package/src/components/event-hub-hero.stories.tsx +30 -0
  118. package/src/components/event-hub-hero.tsx +102 -30
  119. package/src/components/event-list-row.stories.tsx +34 -0
  120. package/src/components/event-list-row.test.tsx +53 -0
  121. package/src/components/event-list-row.tsx +135 -87
  122. package/src/components/hour-band.stories.tsx +28 -5
  123. package/src/components/hour-band.tsx +90 -70
  124. package/src/components/hub-surface.stories.tsx +68 -0
  125. package/src/components/hub-surface.tsx +40 -0
  126. package/src/components/menu-family-card.stories.tsx +112 -0
  127. package/src/components/menu-family-card.tsx +211 -0
  128. package/src/components/menu-field.stories.tsx +74 -0
  129. package/src/components/menu-field.tsx +199 -0
  130. package/src/components/menu-hub-hero.stories.tsx +92 -0
  131. package/src/components/menu-hub-hero.tsx +187 -0
  132. package/src/components/menu-intent-tiles.stories.tsx +54 -0
  133. package/src/components/menu-intent-tiles.tsx +76 -0
  134. package/src/components/menu-item.tsx +416 -0
  135. package/src/components/menu-mocks.ts +417 -0
  136. package/src/components/menu-types.ts +259 -0
  137. package/src/components/motion-icon-set.tsx +30 -3
  138. package/src/components/open-now-list.stories.tsx +76 -0
  139. package/src/components/open-now-list.tsx +127 -0
  140. package/src/components/place-types.ts +112 -0
  141. package/src/components/poster-wall.test.tsx +28 -0
  142. package/src/components/poster-wall.tsx +96 -23
  143. package/src/components/ranked-venue-list.stories.tsx +80 -0
  144. package/src/components/ranked-venue-list.tsx +177 -0
  145. package/src/components/section-header.stories.tsx +24 -0
  146. package/src/components/section-header.test.tsx +28 -0
  147. package/src/components/section-header.tsx +45 -7
  148. package/src/components/signature-chip.tsx +77 -0
  149. package/src/components/signature-line-list.stories.tsx +85 -0
  150. package/src/components/signature-line-list.tsx +136 -0
  151. package/src/components/snap-rail.stories.tsx +79 -0
  152. package/src/components/snap-rail.test.tsx +18 -0
  153. package/src/components/snap-rail.tsx +148 -0
  154. package/src/components/stat-banner.stories.tsx +100 -0
  155. package/src/components/stat-banner.tsx +117 -0
  156. package/src/components/timeline-band.tsx +52 -12
  157. package/src/components/timeline-playhead.stories.tsx +30 -0
  158. package/src/components/timeline-playhead.test.tsx +11 -0
  159. package/src/components/timeline-playhead.tsx +46 -0
  160. package/src/components/venue-busyness-strip.stories.tsx +66 -0
  161. package/src/components/venue-busyness-strip.tsx +64 -0
  162. package/src/components/venue-grade-dimensions.stories.tsx +83 -0
  163. package/src/components/venue-grade-dimensions.tsx +91 -0
  164. package/src/components/venue-quotes.stories.tsx +59 -0
  165. package/src/components/venue-quotes.tsx +64 -0
  166. package/src/components/venue-signature-block.stories.tsx +68 -0
  167. package/src/components/venue-signature-block.tsx +71 -0
  168. package/src/components/venue-spotlight.stories.tsx +141 -0
  169. package/src/components/venue-spotlight.tsx +236 -0
  170. package/src/components/venue-wall.stories.tsx +78 -0
  171. package/src/components/venue-wall.tsx +185 -0
  172. package/src/components/weekday-theme-banner.stories.tsx +113 -0
  173. package/src/components/weekday-theme-banner.tsx +231 -0
  174. package/src/components/weekend-panel.test.tsx +43 -0
  175. package/src/components/weekend-panel.tsx +142 -108
  176. package/src/hooks/use-drag-swipe.test.tsx +66 -0
  177. package/src/hooks/use-drag-swipe.ts +105 -0
  178. package/src/hooks/use-media-query.ts +31 -0
  179. package/dist/chunk-GPH6L4DX.js +0 -55
@@ -0,0 +1,127 @@
1
+ import * as React from 'react';
2
+ import * as react_jsx_runtime from 'react/jsx-runtime';
3
+ import { MenuItemPreview, MenuLinkComponent, MenuLinkProps } from './menu-types.js';
4
+ import './place-types.js';
5
+
6
+ /**
7
+ * MenuItem — THE menu row.
8
+ *
9
+ * A venue's page (its menu list, its signature picks, the "pairs well with"
10
+ * list inside the entity drawer) and the cross-venue surfaces (the city menu,
11
+ * the hub's "known for" list, the daily editorial buckets) all render this one
12
+ * component, so an item is visibly the same object wherever it is met.
13
+ *
14
+ * Hoisted out of the app, where it had grown a second and a third lookalike
15
+ * beside it. It carries the app version's design decisions verbatim; only the
16
+ * couplings changed — the locale, the translation keys and the category-colour
17
+ * table are injected, because the DS owns none of those.
18
+ *
19
+ * ## The left mark
20
+ *
21
+ * An 8px rounded tab in the section's CATEGORY colour, flat against the row's
22
+ * left edge and bulging right. It replaced an earlier monogram chip, and the
23
+ * trade was deliberate:
24
+ *
25
+ * + The text column widens. At 390px the monogram costs 50px (38 + gap) and
26
+ * the tab costs 16px, so the text column goes 308px → 342px: ~11% more room
27
+ * for names and descriptions, which means fewer wrapped names and more
28
+ * items per screen.
29
+ * + The hue answers "which section am I in" rather than "what is this made
30
+ * of". Every row in a category draws the same mark, so it survives the
31
+ * category header scrolling out of view — which a per-item ingredient hue
32
+ * cannot do, since it changes for reasons the reader cannot see.
33
+ * − COLOUR-BLINDNESS. The monogram gave a reader who cannot separate the hues
34
+ * a LETTER; a rule collapses to the same grey bar for everyone. Both marks
35
+ * are `aria-hidden` and neither carries meaning the text lacks, so this is
36
+ * not a WCAG 1.4.1 failure — but it is a real loss of scannability for ~8%
37
+ * of men, and it has not been bought back yet.
38
+ *
39
+ * `markGradient` is passed IN, never derived: inside a menu the colour depends
40
+ * on the neighbouring sections, which a single row cannot see.
41
+ *
42
+ * ## No chevron
43
+ *
44
+ * The row is already `role="button"`, so an arrow would only restate that, and
45
+ * the right edge belongs to the mention count, which the arrow would collide
46
+ * with.
47
+ */
48
+ /** @deprecated Alias of `MenuLinkProps`; kept so existing imports keep working. */
49
+ type MenuItemLinkProps = MenuLinkProps;
50
+ type MenuItemLabels = {
51
+ /** Currency unit beside the price, e.g. "RON". */
52
+ currency: string;
53
+ /** Fully-worded mention count — the accessible name for the glyph-and-numeral pair. */
54
+ mentions: (count: number) => string;
55
+ /** Chip text for a `signature`-strength item. */
56
+ signature: string;
57
+ /** Chip text for a `weak` (merely mentioned) item. */
58
+ mentioned: string;
59
+ };
60
+ type MenuItemProps = {
61
+ item: MenuItemPreview;
62
+ labels: MenuItemLabels;
63
+ /** Locale-aware money formatter. The DS owns no `Intl` locale. */
64
+ formatPrice: (amount: number) => string;
65
+ /** Locale-aware integer formatter for the mention count. Defaults to `String`. */
66
+ formatCount?: (value: number) => string;
67
+ /**
68
+ * Opens the item's own sheet. Ignored when `href` is set.
69
+ *
70
+ * Neither this nor `href` → an INERT row: same geometry, mark, type and meta
71
+ * line, but no `role="button"`, no tab stop and no hover or press states.
72
+ * That is the must-try mention with no menu row behind it — it has nothing to
73
+ * open, so it renders as content rather than as a control that goes nowhere.
74
+ */
75
+ onOpen?: () => void;
76
+ showSignatureBadge?: boolean;
77
+ signatureTier?: "strong" | "weak";
78
+ onSignatureEvidence?: () => void;
79
+ /** Opens the reviews behind the mention count. */
80
+ onMentions?: () => void;
81
+ /** The mark's colour, as a CSS `background-image` value. */
82
+ markGradient?: string;
83
+ /**
84
+ * The venue this item belongs to — an attribution line under the name.
85
+ *
86
+ * Set it ONLY on cross-venue surfaces, where ten near-identical burgers come
87
+ * from ten kitchens and the venue IS the tap decision. A venue's own menu
88
+ * page must never pass it: the page already said whose menu this is. Passed
89
+ * in rather than read off `item.place` so the redundant case cannot be the
90
+ * default.
91
+ */
92
+ venueName?: string;
93
+ /**
94
+ * Explicit subtitle, in italic, in place of the description. Used where the
95
+ * reviewer's reason IS the row's content rather than the payoff for tapping.
96
+ */
97
+ subtitle?: string;
98
+ /**
99
+ * Render the row as a LINK instead of a `role="button"` div — for surfaces
100
+ * where it navigates rather than opening a sheet, so it stays
101
+ * middle-clickable and shareable. Implemented as a stretched overlay rather
102
+ * than by wrapping the row: the mention count is a control in its own right
103
+ * and a button inside an anchor is invalid.
104
+ */
105
+ href?: string;
106
+ /** Accessible name for the link overlay. Defaults to the item's name. */
107
+ linkLabel?: string;
108
+ linkComponent?: MenuLinkComponent;
109
+ disabled?: boolean;
110
+ className?: string;
111
+ };
112
+ /**
113
+ * The mention count: a filled quote glyph and the numeral, pinned right.
114
+ *
115
+ * Deliberately NOT a badge — a chip would add a filled shape to a row that has
116
+ * none, and would read as a status label the venue was given rather than a
117
+ * count drawn from the same reviews.
118
+ */
119
+ declare function MentionSignal({ label, count, onMentions, formatCount, }: {
120
+ label: string;
121
+ count: number;
122
+ onMentions?: () => void;
123
+ formatCount?: (value: number) => string;
124
+ }): react_jsx_runtime.JSX.Element;
125
+ declare const MenuItem: React.NamedExoticComponent<MenuItemProps>;
126
+
127
+ export { MentionSignal, MenuItem, type MenuItemLabels, type MenuItemLinkProps, type MenuItemProps };
@@ -0,0 +1,5 @@
1
+ export { MentionSignal, MenuItem } from '../chunk-TH6W2PWG.js';
2
+ import '../chunk-Q6QH52LJ.js';
3
+ import '../chunk-UKBNQO6Q.js';
4
+ import '../chunk-FEK5XGZW.js';
5
+ import '../chunk-3GWWZKX7.js';
@@ -0,0 +1,55 @@
1
+ import { MenuConceptPreview, MenuFamilyPreview, MenuIntentTile, MenuItemPreview, MenuVenuePreview } from './menu-types.js';
2
+ import { PlacePreview } from './place-types.js';
3
+ import 'react';
4
+
5
+ /**
6
+ * Fixtures for the menu-hub surfaces — used by their stories, and shipped so
7
+ * that consumers rendering these components outside an app (post-generator's
8
+ * Remotion frames, admin previews) do not each rebuild a plausible view model.
9
+ *
10
+ * ## Every venue here is invented
11
+ *
12
+ * The names, ratings and quotes are fabricated. Real venues must never appear
13
+ * in marketing or promotional renders — a real kitchen's rating in a promo
14
+ * frame is a claim about that business, made without its involvement, and it
15
+ * goes stale the moment the data moves. Anything filmed from these fixtures is
16
+ * safe to publish; anything filmed from live data is not.
17
+ *
18
+ * The SHAPES are real, though: the gaps are the gaps the API actually has, so
19
+ * a layout that survives these fixtures survives production. `mentions` is
20
+ * populated everywhere because it is populated everywhere upstream; one venue
21
+ * has no `must_try`, one has no photograph, and one grade is missing the
22
+ * dimensions a coffee bar never gets.
23
+ */
24
+
25
+ declare const mockGradients: {
26
+ readonly pasta: "linear-gradient(150deg, oklch(0.555 0.105 80), oklch(0.455 0.10 70))";
27
+ readonly meat: "linear-gradient(150deg, oklch(0.49 0.13 28), oklch(0.41 0.12 22))";
28
+ readonly greens: "linear-gradient(150deg, oklch(0.505 0.115 148), oklch(0.415 0.11 142))";
29
+ readonly sweet: "linear-gradient(150deg, oklch(0.505 0.14 50), oklch(0.405 0.13 42))";
30
+ readonly cocktail: "linear-gradient(150deg, oklch(0.485 0.095 205), oklch(0.405 0.09 212))";
31
+ };
32
+ declare const mockPlaces: PlacePreview[];
33
+ declare const mockVenues: MenuVenuePreview[];
34
+ declare const mockItems: MenuItemPreview[];
35
+ declare const mockConcept: MenuConceptPreview;
36
+ declare const mockFamily: MenuFamilyPreview;
37
+ /** The drinks counterpart, and the reason `chip.count` is optional. */
38
+ declare const mockDrinkFamily: MenuFamilyPreview;
39
+ declare const mockIntentTiles: MenuIntentTile[];
40
+ /** Formatters the stories pass. Real callers inject locale-aware ones. */
41
+ declare const mockFormatters: {
42
+ price: (value: number) => string;
43
+ number: (value: number) => string;
44
+ score: (value: number) => string;
45
+ };
46
+ declare const mockHubLabels: {
47
+ mentions: (count: number) => string;
48
+ venues: (count: number) => string;
49
+ seeAll: string;
50
+ signature: string;
51
+ openNow: string;
52
+ until: string;
53
+ };
54
+
55
+ export { mockConcept, mockDrinkFamily, mockFamily, mockFormatters, mockGradients, mockHubLabels, mockIntentTiles, mockItems, mockPlaces, mockVenues };
@@ -0,0 +1,379 @@
1
+ import '../chunk-3GWWZKX7.js';
2
+
3
+ // src/components/menu-mocks.ts
4
+ var mockGradients = {
5
+ // The forest dictionary (docs/colour-dictionary.md, Foundations/Colour dictionary):
6
+ // wheat, ember, pine, maple, lake water. Post-generator films these, so they
7
+ // must be the same values the app paints.
8
+ pasta: "linear-gradient(150deg, oklch(0.555 0.105 80), oklch(0.455 0.10 70))",
9
+ meat: "linear-gradient(150deg, oklch(0.49 0.13 28), oklch(0.41 0.12 22))",
10
+ greens: "linear-gradient(150deg, oklch(0.505 0.115 148), oklch(0.415 0.11 142))",
11
+ sweet: "linear-gradient(150deg, oklch(0.505 0.14 50), oklch(0.405 0.13 42))",
12
+ cocktail: "linear-gradient(150deg, oklch(0.485 0.095 205), oklch(0.405 0.09 212))"
13
+ };
14
+ var mockPlaces = [
15
+ {
16
+ id: "p1",
17
+ slug: "the-copper-yard",
18
+ name: "The Copper Yard",
19
+ open_now: true,
20
+ rating: { rating: 4.6, count: 318 },
21
+ hours: [{ opens_at: 12, closes_at: 23 }],
22
+ price_dishes: { price_range_low: 19, price_range_high: 62, median_price: 28 },
23
+ cuisines: ["italian", "pizza"],
24
+ type: "pub",
25
+ busyness: [
26
+ { hour: 12, score: 22 },
27
+ { hour: 13, score: 26 },
28
+ { hour: 14, score: 30 },
29
+ { hour: 15, score: 38 },
30
+ { hour: 16, score: 52 },
31
+ { hour: 17, score: 74 },
32
+ { hour: 18, score: 92 },
33
+ { hour: 19, score: 100 },
34
+ { hour: 20, score: 86 },
35
+ { hour: 21, score: 64 }
36
+ ],
37
+ reviews_digest: {
38
+ review_count: 318,
39
+ grade: {
40
+ overall: 8.4,
41
+ show_number: true,
42
+ descriptor: "Foarte bun",
43
+ confidence: "high",
44
+ dimensions: {
45
+ food: 89,
46
+ service: 82,
47
+ value: 86,
48
+ ambiance: 78,
49
+ portion: 91,
50
+ consistency: 84
51
+ }
52
+ },
53
+ value: { value: "fair" },
54
+ tags: [{ value: "casual" }, { value: "cozy" }, { value: "family_friendly" }],
55
+ highlights: [
56
+ {
57
+ text: "The wings are the reason to sit at the bar \u2014 hot honey, and they keep going until the kitchen shuts.",
58
+ review_ids: ["r1", "r2", "r3"]
59
+ },
60
+ {
61
+ text: "Last kitchen open on this street. We came at half eleven and still ate properly.",
62
+ review_ids: ["r4", "r5"]
63
+ }
64
+ ],
65
+ must_try: [
66
+ {
67
+ name: "Chicken wings, 6 pc",
68
+ why: "Buttermilk brine and hot honey, fried to order until the kitchen shuts.",
69
+ strength: "signature",
70
+ slug: "chicken-wings",
71
+ mentions: 23,
72
+ sizes_and_prices: [{ size: 320, price: 32 }],
73
+ review_ids: ["r1", "r2", "r3"]
74
+ }
75
+ ]
76
+ }
77
+ },
78
+ {
79
+ id: "p2",
80
+ slug: "salt-and-stone",
81
+ name: "Salt & Stone",
82
+ open_now: true,
83
+ rating: { rating: 4.8, count: 204 },
84
+ hours: [{ opens_at: 11, closes_at: 22 }],
85
+ price_dishes: { price_range_low: 38, price_range_high: 84, median_price: 49 },
86
+ cuisines: ["italian"],
87
+ type: "restaurant",
88
+ reviews_digest: {
89
+ review_count: 204,
90
+ grade: {
91
+ overall: 8.9,
92
+ show_number: true,
93
+ dimensions: { food: 93, service: 88, value: 74, ambiance: 85 }
94
+ },
95
+ must_try: [
96
+ {
97
+ name: "Truffle Tagliatelle",
98
+ why: "Made in-house each morning with black truffle and parmesan.",
99
+ strength: "signature",
100
+ slug: "truffle-tagliatelle",
101
+ mentions: 17,
102
+ sizes_and_prices: [{ size: 320, price: 62 }],
103
+ review_ids: ["r6", "r7"]
104
+ }
105
+ ]
106
+ }
107
+ },
108
+ {
109
+ id: "p3",
110
+ slug: "the-quiet-hours",
111
+ name: "The Quiet Hours",
112
+ open_now: true,
113
+ rating: { rating: 4.5, count: 96 },
114
+ hours: [{ opens_at: 8, closes_at: 20 }],
115
+ price_dishes: { price_range_low: 8, price_range_high: 26, median_price: 14 },
116
+ type: "coffee_shop",
117
+ reviews_digest: {
118
+ review_count: 96,
119
+ // A coffee bar is graded on coffee_quality and never on food — the
120
+ // dimension row must survive the absence rather than draw a zero.
121
+ grade: {
122
+ overall: 8.6,
123
+ show_number: true,
124
+ dimensions: { coffee_quality: 91, service: 86, value: 80, ambiance: 88 }
125
+ },
126
+ must_try: [
127
+ {
128
+ name: "Flat white",
129
+ strength: "frequent",
130
+ slug: "flat-white",
131
+ mentions: 28,
132
+ sizes_and_prices: [{ size: 220, price: 14 }]
133
+ }
134
+ ]
135
+ }
136
+ },
137
+ {
138
+ id: "p4",
139
+ slug: "green-room-canteen",
140
+ name: "Green Room Canteen",
141
+ open_now: false,
142
+ rating: { rating: 4.7, count: 141 },
143
+ price_dishes: { price_range_low: 22, price_range_high: 51, median_price: 31 },
144
+ type: "restaurant"
145
+ // No digest at all: a place the census has not reached. Every venue surface
146
+ // has to render this without collapsing.
147
+ }
148
+ ];
149
+ var placeBySlug = (slug) => mockPlaces.find((place) => place.slug === slug);
150
+ var mockVenues = [
151
+ {
152
+ place: placeBySlug("the-copper-yard"),
153
+ href: "/places/the-copper-yard",
154
+ closes_at_label: "23:00",
155
+ signature: {
156
+ item: {
157
+ id: "s1",
158
+ slug: "chicken-wings",
159
+ name: "Chicken wings, 6 pc",
160
+ kind: "dish",
161
+ mentions: 23,
162
+ sizes_and_prices: [{ size: 320, price: 32 }]
163
+ },
164
+ why: "Buttermilk brine and hot honey, fried to order until the kitchen shuts.",
165
+ isSignature: true,
166
+ href: "/menu/dishes/chicken-wings"
167
+ }
168
+ },
169
+ {
170
+ place: placeBySlug("salt-and-stone"),
171
+ href: "/places/salt-and-stone",
172
+ closes_at_label: "22:00",
173
+ signature: {
174
+ item: {
175
+ id: "s2",
176
+ slug: "truffle-tagliatelle",
177
+ name: "Truffle Tagliatelle",
178
+ kind: "dish",
179
+ mentions: 17,
180
+ sizes_and_prices: [{ size: 320, price: 62 }]
181
+ },
182
+ why: "Made in-house each morning with black truffle and parmesan.",
183
+ isSignature: true,
184
+ href: "/menu/dishes/truffle-tagliatelle"
185
+ }
186
+ },
187
+ {
188
+ place: placeBySlug("the-quiet-hours"),
189
+ href: "/places/the-quiet-hours",
190
+ closes_at_label: "20:00",
191
+ signature: {
192
+ item: {
193
+ id: "s3",
194
+ slug: "flat-white",
195
+ name: "Flat white",
196
+ kind: "drink",
197
+ mentions: 28,
198
+ sizes_and_prices: [{ size: 220, price: 14 }]
199
+ },
200
+ href: "/menu/drinks/flat-white"
201
+ }
202
+ },
203
+ {
204
+ place: placeBySlug("green-room-canteen"),
205
+ href: "/places/green-room-canteen"
206
+ // No signature: the row must still read as a venue.
207
+ }
208
+ ];
209
+ var mockItems = [
210
+ {
211
+ id: "i1",
212
+ slug: "truffle-tagliatelle",
213
+ name: "Truffle Tagliatelle",
214
+ kind: "dish",
215
+ category_slug: "pasta",
216
+ mentions: 17,
217
+ sizes_and_prices: [{ size: 320, price: 62 }],
218
+ place: { slug: "salt-and-stone", name: "Salt & Stone" }
219
+ },
220
+ {
221
+ id: "i2",
222
+ slug: "chicken-wings",
223
+ name: "Chicken wings, 6 pc",
224
+ kind: "dish",
225
+ category_slug: "chicken-dish",
226
+ mentions: 23,
227
+ sizes_and_prices: [{ size: 320, price: 32 }],
228
+ place: { slug: "the-copper-yard", name: "The Copper Yard" }
229
+ },
230
+ {
231
+ id: "i3",
232
+ slug: "burrata-heirloom-tomato",
233
+ name: "Burrata & heirloom tomato",
234
+ kind: "dish",
235
+ category_slug: "salad",
236
+ mentions: 14,
237
+ sizes_and_prices: [{ size: 300, price: 49 }],
238
+ place: { slug: "green-room-canteen", name: "Green Room Canteen" }
239
+ },
240
+ {
241
+ id: "i4",
242
+ slug: "flat-white",
243
+ name: "Flat white",
244
+ kind: "drink",
245
+ category_slug: "non-alcoholic",
246
+ mentions: 28,
247
+ sizes_and_prices: [{ size: 220, price: 14 }],
248
+ place: { slug: "the-quiet-hours", name: "The Quiet Hours" }
249
+ },
250
+ {
251
+ id: "i5",
252
+ slug: "house-negroni",
253
+ name: "House negroni",
254
+ kind: "drink",
255
+ category_slug: "cocktail",
256
+ // Deliberately unmentioned: the badge must not render "0".
257
+ mentions: 0,
258
+ sizes_and_prices: [{ size: 150, price: 32 }],
259
+ place: { slug: "the-copper-yard", name: "The Copper Yard" }
260
+ }
261
+ ];
262
+ var mockConcept = {
263
+ slug: "margherita",
264
+ title: "Margherita",
265
+ kind: "dish",
266
+ venue_count: 87,
267
+ price_low: 10,
268
+ price_high: 120,
269
+ alias_count: 12,
270
+ mentions: 214,
271
+ rows: [
272
+ {
273
+ place_slug: "the-copper-yard",
274
+ place_name: "The Copper Yard",
275
+ alias: "Pizza Margherita",
276
+ price: 34,
277
+ mentions: 23,
278
+ rating: 4.6,
279
+ place_type: "pub",
280
+ featured: true
281
+ },
282
+ {
283
+ place_slug: "salt-and-stone",
284
+ place_name: "Salt & Stone",
285
+ alias: "Margherita D.O.P.",
286
+ price: 46,
287
+ mentions: 17,
288
+ rating: 4.8,
289
+ place_type: "restaurant"
290
+ },
291
+ {
292
+ place_slug: "green-room-canteen",
293
+ place_name: "Green Room Canteen",
294
+ alias: "Marinara Bianca",
295
+ price: 52,
296
+ mentions: 9,
297
+ rating: 4.7,
298
+ place_type: "canteen"
299
+ }
300
+ ]
301
+ };
302
+ var mockFamily = {
303
+ slug: "italian",
304
+ name: "Italian",
305
+ venue_count: 247,
306
+ chips: [
307
+ { slug: "pizza", label: "Pizza", count: 120 },
308
+ { slug: "pasta", label: "Pasta", count: 96 },
309
+ { slug: "salad", label: "Salad", count: 88 },
310
+ { slug: "chicken-dish", label: "Chicken", count: 77 }
311
+ ],
312
+ price_low: 32,
313
+ price_high: 84,
314
+ price_median: 49,
315
+ venue_photo_urls: [],
316
+ signature: {
317
+ item: {
318
+ id: "f1",
319
+ slug: "truffle-tagliatelle",
320
+ name: "Truffle Tagliatelle",
321
+ kind: "dish",
322
+ mentions: 17,
323
+ sizes_and_prices: [{ size: 320, price: 62 }]
324
+ },
325
+ why: "Salt & Stone",
326
+ href: "/menu/dishes/truffle-tagliatelle"
327
+ }
328
+ };
329
+ var mockDrinkFamily = {
330
+ slug: "cocktail",
331
+ name: "Cocktails",
332
+ venue_count: 21,
333
+ // No counts: there is no drink-group facet on places, so these ship as bare
334
+ // labels rather than with an invented number.
335
+ chips: [
336
+ { slug: "spritz", label: "Spritz" },
337
+ { slug: "negroni", label: "Negroni" },
338
+ { slug: "sour", label: "Sour" }
339
+ ],
340
+ price_low: 18,
341
+ price_high: 54,
342
+ price_median: 28,
343
+ venue_photo_urls: [],
344
+ signature: {
345
+ item: {
346
+ id: "f2",
347
+ slug: "house-negroni",
348
+ name: "House negroni",
349
+ kind: "drink",
350
+ mentions: 16,
351
+ sizes_and_prices: [{ size: 150, price: 32 }]
352
+ },
353
+ why: "The Copper Yard"
354
+ }
355
+ };
356
+ var intentGradient = (hue) => `linear-gradient(150deg, oklch(0.52 0.14 ${hue}), oklch(0.43 0.13 ${hue - 4}))`;
357
+ var mockIntentTiles = [
358
+ { slug: "italian", label: "Italian tonight", caption: "247 kitchens", href: "/top/italian?open=now", gradient: intentGradient(40) },
359
+ { slug: "burgeri", label: "Burgers", caption: "229 kitchens", href: "/top/burgeri", gradient: intentGradient(25) },
360
+ { slug: "restaurante", label: "Open right now", caption: "by the hour", href: "/top/restaurante?open=now", gradient: intentGradient(60) },
361
+ { slug: "brunch", label: "Brunch", caption: "314 kitchens", href: "/top/brunch", gradient: intentGradient(75) },
362
+ { slug: "vegan", label: "Vegan", caption: "108 kitchens", href: "/top/vegan", gradient: intentGradient(148) },
363
+ { slug: "centru", label: "In Centru", caption: "by neighbourhood", href: "/top/centru", gradient: intentGradient(300) }
364
+ ];
365
+ var mockFormatters = {
366
+ price: (value) => `${value} lei`,
367
+ number: (value) => value.toLocaleString("en-US"),
368
+ score: (value) => (value / 10).toFixed(1)
369
+ };
370
+ var mockHubLabels = {
371
+ mentions: (count) => `${count} mentions`,
372
+ venues: (count) => `${count} kitchens`,
373
+ seeAll: "See all",
374
+ signature: "SIGNATURE",
375
+ openNow: "Open now",
376
+ until: "until"
377
+ };
378
+
379
+ export { mockConcept, mockDrinkFamily, mockFamily, mockFormatters, mockGradients, mockHubLabels, mockIntentTiles, mockItems, mockPlaces, mockVenues };