@bearmenu/ui 0.8.8 → 0.8.10

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 (206) hide show
  1. package/dist/chunk-24RFI4RQ.js +167 -0
  2. package/dist/chunk-2XCZJWTV.js +121 -0
  3. package/dist/chunk-5RWTOT5Y.js +72 -0
  4. package/dist/chunk-62ES6N32.js +27 -0
  5. package/dist/chunk-AKQ57NXY.js +58 -0
  6. package/dist/chunk-CLLY6XB3.js +41 -0
  7. package/dist/chunk-DTXNDLSE.js +125 -0
  8. package/dist/chunk-F6FMZJDG.js +113 -0
  9. package/dist/{chunk-2WDJR6K7.js → chunk-G7IFL4Z4.js} +4 -1
  10. package/dist/chunk-MZL7CGCL.js +109 -0
  11. package/dist/chunk-Q6QH52LJ.js +38 -0
  12. package/dist/chunk-SY6RV476.js +48 -0
  13. package/dist/chunk-TH6W2PWG.js +213 -0
  14. package/dist/chunk-UDN7UHJW.js +60 -0
  15. package/dist/{chunk-TT3WFS6S.js → chunk-YOKHBAIH.js} +11 -11
  16. package/dist/components/alert.d.ts +2 -2
  17. package/dist/components/badge.d.ts +1 -1
  18. package/dist/components/concept-compare-rail.d.ts +50 -0
  19. package/dist/components/concept-compare-rail.js +5 -0
  20. package/dist/components/concept-compare.d.ts +110 -0
  21. package/dist/components/concept-compare.js +238 -0
  22. package/dist/components/concept-convergence.d.ts +41 -0
  23. package/dist/components/concept-convergence.js +53 -0
  24. package/dist/components/convergence.d.ts +25 -0
  25. package/dist/components/convergence.js +4 -0
  26. package/dist/components/date-input.d.ts +1 -1
  27. package/dist/components/event-card.js +1 -1
  28. package/dist/components/event-compact-row.d.ts +1 -1
  29. package/dist/components/event-compact-row.js +1 -1
  30. package/dist/components/event-detail-panel.d.ts +171 -25
  31. package/dist/components/event-detail-panel.js +472 -175
  32. package/dist/components/event-index-row.js +1 -1
  33. package/dist/components/event-list-row-wide.js +2 -2
  34. package/dist/components/event-list-row.js +2 -2
  35. package/dist/components/explainer-steps.d.ts +34 -0
  36. package/dist/components/explainer-steps.js +36 -0
  37. package/dist/components/glass-ground.d.ts +66 -0
  38. package/dist/components/glass-ground.js +3 -0
  39. package/dist/components/hub-surface.d.ts +27 -0
  40. package/dist/components/hub-surface.js +30 -0
  41. package/dist/components/hue-chip.d.ts +70 -0
  42. package/dist/components/hue-chip.js +3 -0
  43. package/dist/components/kitchen-dossier.d.ts +88 -0
  44. package/dist/components/kitchen-dossier.js +187 -0
  45. package/dist/components/menu-dead-end.d.ts +56 -0
  46. package/dist/components/menu-dead-end.js +49 -0
  47. package/dist/components/menu-family-block.d.ts +99 -0
  48. package/dist/components/menu-family-block.js +155 -0
  49. package/dist/components/menu-family-card.d.ts +65 -0
  50. package/dist/components/menu-family-card.js +79 -0
  51. package/dist/components/menu-field.d.ts +57 -0
  52. package/dist/components/menu-field.js +93 -0
  53. package/dist/components/menu-filter-chips.d.ts +86 -0
  54. package/dist/components/menu-filter-chips.js +122 -0
  55. package/dist/components/menu-group-rows.d.ts +53 -0
  56. package/dist/components/menu-group-rows.js +67 -0
  57. package/dist/components/menu-hub-hero.d.ts +65 -0
  58. package/dist/components/menu-hub-hero.js +103 -0
  59. package/dist/components/menu-index-header.d.ts +37 -0
  60. package/dist/components/menu-index-header.js +68 -0
  61. package/dist/components/menu-index-list.d.ts +48 -0
  62. package/dist/components/menu-index-list.js +96 -0
  63. package/dist/components/menu-intent-hero.d.ts +69 -0
  64. package/dist/components/menu-intent-hero.js +87 -0
  65. package/dist/components/menu-intent-tiles.d.ts +31 -0
  66. package/dist/components/menu-intent-tiles.js +50 -0
  67. package/dist/components/menu-item.d.ts +127 -0
  68. package/dist/components/menu-item.js +5 -0
  69. package/dist/components/menu-kitchen-list.d.ts +82 -0
  70. package/dist/components/menu-kitchen-list.js +5 -0
  71. package/dist/components/menu-mocks.d.ts +90 -0
  72. package/dist/components/menu-mocks.js +504 -0
  73. package/dist/components/menu-types.d.ts +421 -0
  74. package/dist/components/menu-types.js +1 -0
  75. package/dist/components/motion-icon-controls.js +2 -2
  76. package/dist/components/motion-icon-set.js +21 -9
  77. package/dist/components/motion-icon.d.ts +22 -1
  78. package/dist/components/motion-icon.js +1 -1
  79. package/dist/components/open-now-list.d.ts +40 -0
  80. package/dist/components/open-now-list.js +69 -0
  81. package/dist/components/photo-ground.d.ts +64 -0
  82. package/dist/components/photo-ground.js +3 -0
  83. package/dist/components/place-about-tab.js +2 -2
  84. package/dist/components/place-details-mobile.js +2 -2
  85. package/dist/components/place-types.d.ts +112 -1
  86. package/dist/components/qa-block.d.ts +36 -0
  87. package/dist/components/qa-block.js +24 -0
  88. package/dist/components/scroll-fade.js +3 -121
  89. package/dist/components/signature-chip.d.ts +37 -0
  90. package/dist/components/signature-chip.js +4 -0
  91. package/dist/components/snap-rail.d.ts +48 -5
  92. package/dist/components/snap-rail.js +3 -51
  93. package/dist/components/stat-banner.d.ts +47 -0
  94. package/dist/components/stat-banner.js +61 -0
  95. package/dist/components/story.d.ts +34 -0
  96. package/dist/components/story.js +3 -0
  97. package/dist/components/tag.d.ts +2 -2
  98. package/dist/components/text.d.ts +1 -1
  99. package/dist/components/top-comparison.d.ts +28 -0
  100. package/dist/components/top-comparison.js +76 -0
  101. package/dist/components/top-cross-link.d.ts +34 -0
  102. package/dist/components/top-cross-link.js +101 -0
  103. package/dist/components/top-ranking-entry.d.ts +53 -0
  104. package/dist/components/top-ranking-entry.js +217 -0
  105. package/dist/components/top-ranking-hero.d.ts +61 -0
  106. package/dist/components/top-ranking-hero.js +99 -0
  107. package/dist/components/top-ranking-legend.d.ts +69 -0
  108. package/dist/components/top-ranking-legend.js +124 -0
  109. package/dist/components/top-types.d.ts +109 -0
  110. package/dist/components/top-types.js +1 -0
  111. package/dist/components/venue-grade-dimensions.d.ts +38 -0
  112. package/dist/components/venue-grade-dimensions.js +3 -0
  113. package/dist/components/venue-quotes.d.ts +32 -0
  114. package/dist/components/venue-quotes.js +3 -0
  115. package/dist/components/venue-wall.d.ts +41 -0
  116. package/dist/components/venue-wall.js +118 -0
  117. package/dist/components/weekday-theme-banner.d.ts +70 -0
  118. package/dist/components/weekday-theme-banner.js +127 -0
  119. package/dist/components/weekend-panel.js +2 -2
  120. package/package.json +1 -1
  121. package/src/components/colour-dictionary.stories.tsx +227 -0
  122. package/src/components/concept-compare-rail.tsx +131 -0
  123. package/src/components/concept-compare.stories.tsx +172 -0
  124. package/src/components/concept-compare.tsx +443 -0
  125. package/src/components/concept-convergence.stories.tsx +53 -0
  126. package/src/components/concept-convergence.tsx +90 -0
  127. package/src/components/convergence.stories.tsx +54 -0
  128. package/src/components/convergence.tsx +177 -0
  129. package/src/components/event-compact-row.tsx +1 -1
  130. package/src/components/event-detail-panel.stories.tsx +205 -97
  131. package/src/components/event-detail-panel.test.tsx +181 -0
  132. package/src/components/event-detail-panel.tsx +703 -185
  133. package/src/components/explainer-steps.stories.tsx +46 -0
  134. package/src/components/explainer-steps.tsx +74 -0
  135. package/src/components/glass-ground.tsx +129 -0
  136. package/src/components/hub-surface.stories.tsx +68 -0
  137. package/src/components/hub-surface.tsx +50 -0
  138. package/src/components/hue-chip.stories.tsx +76 -0
  139. package/src/components/hue-chip.test.tsx +62 -0
  140. package/src/components/hue-chip.tsx +99 -0
  141. package/src/components/kitchen-dossier.stories.tsx +108 -0
  142. package/src/components/kitchen-dossier.tsx +322 -0
  143. package/src/components/menu-dead-end.stories.tsx +67 -0
  144. package/src/components/menu-dead-end.tsx +127 -0
  145. package/src/components/menu-family-block.stories.tsx +91 -0
  146. package/src/components/menu-family-block.tsx +314 -0
  147. package/src/components/menu-family-card.stories.tsx +120 -0
  148. package/src/components/menu-family-card.tsx +191 -0
  149. package/src/components/menu-field.stories.tsx +74 -0
  150. package/src/components/menu-field.tsx +199 -0
  151. package/src/components/menu-filter-chips.stories.tsx +107 -0
  152. package/src/components/menu-filter-chips.tsx +238 -0
  153. package/src/components/menu-group-rows.stories.tsx +54 -0
  154. package/src/components/menu-group-rows.tsx +133 -0
  155. package/src/components/menu-hub-hero.stories.tsx +92 -0
  156. package/src/components/menu-hub-hero.tsx +187 -0
  157. package/src/components/menu-index-header.stories.tsx +72 -0
  158. package/src/components/menu-index-header.tsx +125 -0
  159. package/src/components/menu-index-list.stories.tsx +50 -0
  160. package/src/components/menu-index-list.tsx +149 -0
  161. package/src/components/menu-intent-hero.stories.tsx +67 -0
  162. package/src/components/menu-intent-hero.tsx +189 -0
  163. package/src/components/menu-intent-tiles.stories.tsx +54 -0
  164. package/src/components/menu-intent-tiles.tsx +76 -0
  165. package/src/components/menu-item.tsx +416 -0
  166. package/src/components/menu-kitchen-list.stories.tsx +67 -0
  167. package/src/components/menu-kitchen-list.test.tsx +78 -0
  168. package/src/components/menu-kitchen-list.tsx +284 -0
  169. package/src/components/menu-mocks.ts +592 -0
  170. package/src/components/menu-types.ts +442 -0
  171. package/src/components/motion-icon-controls.tsx +11 -11
  172. package/src/components/motion-icon-set.tsx +36 -8
  173. package/src/components/motion-icon.test.tsx +23 -1
  174. package/src/components/motion-icon.tsx +20 -0
  175. package/src/components/open-now-list.stories.tsx +76 -0
  176. package/src/components/open-now-list.tsx +127 -0
  177. package/src/components/photo-ground.tsx +147 -0
  178. package/src/components/place-types.ts +112 -0
  179. package/src/components/qa-block.stories.tsx +43 -0
  180. package/src/components/qa-block.tsx +60 -0
  181. package/src/components/signature-chip.tsx +77 -0
  182. package/src/components/snap-rail.tsx +133 -9
  183. package/src/components/stat-banner.stories.tsx +100 -0
  184. package/src/components/stat-banner.tsx +117 -0
  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.stories.tsx +83 -0
  199. package/src/components/venue-grade-dimensions.tsx +91 -0
  200. package/src/components/venue-quotes.stories.tsx +59 -0
  201. package/src/components/venue-quotes.tsx +64 -0
  202. package/src/components/venue-wall.stories.tsx +78 -0
  203. package/src/components/venue-wall.tsx +185 -0
  204. package/src/components/weekday-theme-banner.stories.tsx +113 -0
  205. package/src/components/weekday-theme-banner.tsx +231 -0
  206. package/dist/{chunk-XWMRABZW.js → chunk-HSIYYA2E.js} +1 -1
@@ -1,6 +1,6 @@
1
- import { Separator } from '../chunk-WSRWFA6K.js';
2
1
  import { TagGroup } from '../chunk-6XMYEZVR.js';
3
2
  import { Tag } from '../chunk-ZDAHKI3A.js';
3
+ import { Separator } from '../chunk-WSRWFA6K.js';
4
4
  import { EventPosterFallback } from '../chunk-FDITZ2VM.js';
5
5
  import { cn } from '../chunk-FEK5XGZW.js';
6
6
  import { __objRest, __spreadValues } from '../chunk-3GWWZKX7.js';
@@ -1,6 +1,6 @@
1
- import { Separator } from '../chunk-WSRWFA6K.js';
2
- import { Tag } from '../chunk-ZDAHKI3A.js';
3
1
  import { resolveEventEntryFee } from '../chunk-DNSXMHYX.js';
2
+ import { Tag } from '../chunk-ZDAHKI3A.js';
3
+ import { Separator } from '../chunk-WSRWFA6K.js';
4
4
  import { EventPosterFallback } from '../chunk-FDITZ2VM.js';
5
5
  import { Skeleton } from '../chunk-QXHMPJ35.js';
6
6
  import { buttonVariants } from '../chunk-H3BV4VAG.js';
@@ -1,7 +1,7 @@
1
- import { Separator } from '../chunk-WSRWFA6K.js';
1
+ import { resolveEventEntryFee } from '../chunk-DNSXMHYX.js';
2
2
  import { TagGroup } from '../chunk-6XMYEZVR.js';
3
3
  import { Tag } from '../chunk-ZDAHKI3A.js';
4
- import { resolveEventEntryFee } from '../chunk-DNSXMHYX.js';
4
+ import { Separator } from '../chunk-WSRWFA6K.js';
5
5
  import { EventPosterFallback } from '../chunk-FDITZ2VM.js';
6
6
  import { Skeleton } from '../chunk-QXHMPJ35.js';
7
7
  import { cn } from '../chunk-FEK5XGZW.js';
@@ -0,0 +1,34 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+ import { ReactNode } from 'react';
3
+
4
+ /**
5
+ * ExplainerSteps — how something works, as numbered rules a reader can scan.
6
+ *
7
+ * Each step is a numeral in a ring, one line of prose, and the live figure
8
+ * that proves the line, right-aligned with its unit. A thread runs from one
9
+ * numeral to the next so the three read as a sequence, and the last numeral
10
+ * is filled: the rules end where the reader is asked to do something.
11
+ *
12
+ * A step without a figure shows none — an invented number in the column is
13
+ * worse than an empty one. But a list where no step has a figure should be
14
+ * prose, not steps.
15
+ *
16
+ * Server-safe. Data in, not children: a server component composes these from
17
+ * translated strings and numbers, and an array is easier to number than a
18
+ * child list is to clone.
19
+ */
20
+ type ExplainerStepItem = {
21
+ /** One line. Two at most on a phone; a third is a paragraph and belongs elsewhere. */
22
+ text: ReactNode;
23
+ /** The live figure, pre-formatted. */
24
+ figure?: string;
25
+ /** A unit or caption beside the figure, e.g. "names". */
26
+ unit?: string;
27
+ };
28
+ type ExplainerStepsProps = {
29
+ steps: ExplainerStepItem[];
30
+ className?: string;
31
+ };
32
+ declare function ExplainerSteps({ steps, className }: ExplainerStepsProps): react_jsx_runtime.JSX.Element;
33
+
34
+ export { type ExplainerStepItem, ExplainerSteps, type ExplainerStepsProps };
@@ -0,0 +1,36 @@
1
+ import { cn } from '../chunk-FEK5XGZW.js';
2
+ import '../chunk-3GWWZKX7.js';
3
+ import { jsx, jsxs } from 'react/jsx-runtime';
4
+
5
+ function ExplainerSteps({ steps, className }) {
6
+ return /* @__PURE__ */ jsx("ol", { className: cn("flex flex-col", className), children: steps.map((step, index) => {
7
+ const isLast = index === steps.length - 1;
8
+ return /* @__PURE__ */ jsxs("li", { className: "relative flex gap-3.5 pb-5 last:pb-0", children: [
9
+ !isLast && /* @__PURE__ */ jsx(
10
+ "span",
11
+ {
12
+ "aria-hidden": true,
13
+ className: "absolute bottom-0 left-[11px] top-7 w-px bg-border"
14
+ }
15
+ ),
16
+ /* @__PURE__ */ jsx(
17
+ "span",
18
+ {
19
+ "aria-hidden": true,
20
+ className: cn(
21
+ "relative flex h-6 w-6 shrink-0 items-center justify-center rounded-full text-[11px] font-bold tabular-nums",
22
+ isLast ? "bg-primary text-primary-foreground" : "border border-border bg-background text-muted-foreground"
23
+ ),
24
+ children: index + 1
25
+ }
26
+ ),
27
+ /* @__PURE__ */ jsx("p", { className: "min-w-0 flex-1 pt-0.5 text-[14px] leading-[1.45] text-foreground", children: step.text }),
28
+ step.figure && /* @__PURE__ */ jsxs("span", { className: "shrink-0 pt-0.5 text-right text-[15px] font-bold leading-[1.45] tabular-nums text-foreground", children: [
29
+ step.figure,
30
+ step.unit && /* @__PURE__ */ jsx("span", { className: "ml-1 text-[11px] font-normal text-muted-foreground", children: step.unit })
31
+ ] })
32
+ ] }, index);
33
+ }) });
34
+ }
35
+
36
+ export { ExplainerSteps };
@@ -0,0 +1,66 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+
3
+ /**
4
+ * GlassGround — a frosted, inked caption rising out of one edge of a
5
+ * photograph.
6
+ *
7
+ * Layers over a `PhotoGround`: two masked `backdrop-blur`s (the frost) and
8
+ * an ink gradient (the contrast), running in one direction — absent where the
9
+ * photograph is the subject, solid where the type is. `MenuFamilyBlock` drew
10
+ * this first; this is the same idea with the stops as data, so a card can say
11
+ * "clear for 128px, then glass" without a sixth copy of the gradient strings.
12
+ *
13
+ * ## Why two blurs, and why the ramps are eased
14
+ *
15
+ * One blur under a linear mask has an edge: the eye reads the line where
16
+ * sharp becomes soft, however gently the mask fades it in. Two blurs — a
17
+ * light one that starts early and a heavier one that starts late — make the
18
+ * softening itself a gradient (the "progressive blur" of a phone's lock
19
+ * screen), and every ramp here is an ease-in through five stops rather than
20
+ * a straight line, because a straight ramp has a visible crease at each end
21
+ * where its slope changes.
22
+ *
23
+ * ## Why the stops are custom properties
24
+ *
25
+ * Tailwind emits only the classes it can read verbatim, so the gradients
26
+ * cannot be assembled from props into a class. The classes below are literal
27
+ * and read `--glass-*` variables; the component writes those inline, having
28
+ * interpolated the quarter stops in JS (`clear` and `full` must share a
29
+ * unit). Same mechanism as `HueChip`'s hue. The ink's colour is the
30
+ * package's one scrim literal, `oklch(0.16 0.012 55)`, never `var(--scrim)`.
31
+ *
32
+ * ## The ramp ends where the type begins
33
+ *
34
+ * `clear` is where the frost starts, `full` where it and the ink are solid.
35
+ * Set `full` to the exact seam the content starts at — a ramp that runs past
36
+ * the seam puts the first row of type on half-frost.
37
+ */
38
+ type GlassStops = {
39
+ /** Which way the ink runs — toward the type. */
40
+ direction: "to bottom" | "to right";
41
+ /** Where the frost begins, e.g. `"0px"` or `"24%"`. */
42
+ clear: string;
43
+ /** Where frost and ink are solid — the seam the content starts at. Same unit as `clear`. */
44
+ full: string;
45
+ };
46
+ type GlassGroundProps = {
47
+ stops: GlassStops;
48
+ /** Stops from `lg`. Omit to keep the same ramp at every width. */
49
+ lgStops?: GlassStops;
50
+ /**
51
+ * How dark the ink gets at the far edge, 0–1. `0.96` is a caption the
52
+ * photograph is only a texture under; `0.4` is a photograph the caption is
53
+ * drawn on — the type is lifted, the room stays a room.
54
+ */
55
+ peak?: number;
56
+ /**
57
+ * How much the frost softens the photograph where it is solid. `xl` turns
58
+ * a room into colour; `sm` keeps it a room and only takes the edge off the
59
+ * detail behind the type.
60
+ */
61
+ blur?: "sm" | "md" | "lg" | "xl";
62
+ className?: string;
63
+ };
64
+ declare function GlassGround({ stops, lgStops, peak, blur, className, }: GlassGroundProps): react_jsx_runtime.JSX.Element;
65
+
66
+ export { GlassGround, type GlassGroundProps, type GlassStops };
@@ -0,0 +1,3 @@
1
+ export { GlassGround } from '../chunk-SY6RV476.js';
2
+ import '../chunk-FEK5XGZW.js';
3
+ import '../chunk-3GWWZKX7.js';
@@ -0,0 +1,27 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+
3
+ /**
4
+ * HubSurface — the surface a hub section sits on BELOW `lg`.
5
+ *
6
+ * On a phone a hub is an app screen, not a page: each section is a white card
7
+ * on the tinted ground, 18px radius, one card shadow and no border, with 14px
8
+ * of inset. Hairlines live inside a card, never across the page. From `lg` up
9
+ * the card dissolves — no fill, no radius, no inset — and the sections sit flat
10
+ * the way the desktop design draws them. One element, two looks; the section
11
+ * markup inside is identical at every width.
12
+ *
13
+ * `flush` drops the inset so a section whose content bleeds to the card edge —
14
+ * a snap rail, a full-width photograph — can pad its own header and let the
15
+ * rest run.
16
+ *
17
+ * Generalised out of the events hub's `EventsHubCard`, which had the same job
18
+ * and lived in the app. Nothing here is menu- or event-shaped.
19
+ */
20
+ type HubSurfaceProps = {
21
+ flush?: boolean;
22
+ className?: string;
23
+ children: React.ReactNode;
24
+ };
25
+ declare function HubSurface({ flush, className, children }: HubSurfaceProps): react_jsx_runtime.JSX.Element;
26
+
27
+ export { HubSurface, type HubSurfaceProps };
@@ -0,0 +1,30 @@
1
+ import { cn } from '../chunk-FEK5XGZW.js';
2
+ import '../chunk-3GWWZKX7.js';
3
+ import { jsx } from 'react/jsx-runtime';
4
+
5
+ function HubSurface({ flush, className, children }) {
6
+ return /* @__PURE__ */ jsx(
7
+ "section",
8
+ {
9
+ className: cn(
10
+ "rounded-[18px] border border-border bg-card shadow-none",
11
+ flush ? "overflow-hidden" : "px-3.5 py-4",
12
+ /*
13
+ * `lg:border-0` is load-bearing and was missing.
14
+ *
15
+ * Above `lg` this surface stops being a card — the background, the
16
+ * radius, the padding and the shadow are all dropped so the section
17
+ * sits on the page. The border was not, so every section on every
18
+ * desktop page kept a 1px rectangle around content that no longer had
19
+ * a card behind it. A caller that genuinely wants an edge at desktop
20
+ * adds `lg:border` in `className`, which lands after this in `cn`.
21
+ */
22
+ "lg:overflow-visible lg:rounded-none lg:border-0 lg:bg-transparent lg:p-0 lg:shadow-none",
23
+ className
24
+ ),
25
+ children
26
+ }
27
+ );
28
+ }
29
+
30
+ export { HubSurface };
@@ -0,0 +1,70 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+
3
+ /**
4
+ * HueChip — a pill that wears a category's colour as a pale wash.
5
+ *
6
+ * Menu surfaces label things with a family's hue: a cuisine's dish groups, a
7
+ * going-out family's tags, a price band. Every one of them was an inline
8
+ * `style` with a hand-written `color-mix` next to a hand-written pill class
9
+ * list, which is how the two below drifted apart and how the mix space bug
10
+ * survived as long as it did — it had to be found and fixed in each copy.
11
+ *
12
+ * ## The mix space is the whole reason this is a component
13
+ *
14
+ * **`in oklab`, and never `in oklch`.** They describe the same colours, but
15
+ * oklch is POLAR, so `color-mix(in oklch, …)` interpolates the hue ANGLE — and
16
+ * the paper being mixed into is achromatic (`--card` is `oklch(1 0 0)`, hue 0).
17
+ * Mixing 20% of a family colour into that lands at a fifth of the family's hue:
18
+ * honey 82° came out at 16°, which is pink, so a Breakfast & Brunch card wore
19
+ * pink chips under an amber mosaic. Cool families were worse — a bar's 205°
20
+ * blue mixes to 41°, orange; a club's 300° violet to 60°, mustard.
21
+ *
22
+ * oklab is rectangular: mixing toward an achromatic base scales chroma along
23
+ * the SAME hue instead of rotating it, which is what "a pale version of this
24
+ * colour" has to mean.
25
+ *
26
+ * ## Why an inline style and not a Tailwind class
27
+ *
28
+ * The hue is DATA — one of ~40 cuisine values resolved by the app at request
29
+ * time — not one of a fixed set of variants. Tailwind can only emit classes it
30
+ * can see at build time, so a class-based version would mean either a
31
+ * safelisted colour per cuisine (a palette that must be edited to add a
32
+ * cuisine) or an arbitrary-value class built from a runtime string, which
33
+ * Tailwind cannot compile. A CSS custom property set inline is the honest
34
+ * mechanism; this component is the one place that writes it.
35
+ */
36
+ /** Mix strengths, so callers say what a chip IS rather than picking a number. */
37
+ declare const STRENGTH: {
38
+ /** A labelled facet: readable at 11.5px, still clearly the family's colour. */
39
+ readonly chip: 20;
40
+ /** A quieter wash, for a chip that sits beside a stronger one. */
41
+ readonly subtle: 12;
42
+ };
43
+ type HueChipTone = keyof typeof STRENGTH;
44
+ /**
45
+ * The tint itself, exported for the surfaces that need the colour rather than
46
+ * the pill — a section ground, a hover state. Same rule: `in oklab`.
47
+ */
48
+ declare function hueTint(hue: string, tone?: HueChipTone, base?: string): string;
49
+ type HueChipProps = {
50
+ /**
51
+ * A finished CSS colour — the DS owns no category table, so the app resolves
52
+ * it. Without one the chip falls back to `--muted`, which is the right
53
+ * neutral for a facet that has no family behind it.
54
+ */
55
+ hue?: string;
56
+ tone?: HueChipTone;
57
+ /** The paper the wash is mixed into. Must be the surface the chip sits on. */
58
+ base?: string;
59
+ /** Drawn before the label, e.g. a wallet on a price band. */
60
+ icon?: React.ReactNode;
61
+ /** `li` inside a list, which is where most of these live. */
62
+ as?: "span" | "li" | "div";
63
+ children: React.ReactNode;
64
+ className?: string;
65
+ /** For a chip whose visible text is a bare figure, e.g. "22–86 lei". */
66
+ "aria-label"?: string;
67
+ };
68
+ declare function HueChip({ hue, tone, base, icon, as: Tag, children, className, "aria-label": ariaLabel, }: HueChipProps): react_jsx_runtime.JSX.Element;
69
+
70
+ export { HueChip, type HueChipProps, type HueChipTone, hueTint };
@@ -0,0 +1,3 @@
1
+ export { HueChip, hueTint } from '../chunk-CLLY6XB3.js';
2
+ import '../chunk-FEK5XGZW.js';
3
+ import '../chunk-3GWWZKX7.js';
@@ -0,0 +1,88 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+ import { ReactNode } from 'react';
3
+ import { MenuVenueSignature, MenuLinkComponent, MenuLinkProps } from './menu-types.js';
4
+ import { PlacePreview, CitedText, PlaceGradeDimensions } from './place-types.js';
5
+ import { VenueGradeDimensionKey } from './venue-grade-dimensions.js';
6
+
7
+ /**
8
+ * KitchenDossier — one kitchen, told as a story on its own photograph.
9
+ *
10
+ * The hub used to say this three times: a spotlight with everything the
11
+ * digest knows, a ranked row with a grade, a card with the signature dish —
12
+ * and the same kitchen led all three. This is the one card: the venue's
13
+ * photograph clear at the top, a frosted caption rising from the foot with
14
+ * the identity (rating, name, type · area · reviews), and under it a PANEL
15
+ * that says the three or four things the reviews established one at a time —
16
+ * the dish it is known for, what people say, the grade — with segments at the
17
+ * top edge showing which is up. The story runs on its own while the card is
18
+ * in view; a tap advances.
19
+ *
20
+ * ## A photograph with a caption, not a dark card
21
+ *
22
+ * `ConceptCompare` is a dark card with a photograph showing through two glass
23
+ * bands. This is the reverse — the room is the subject and the facts are its
24
+ * caption — which is `GlassGround` at its lightest and smallest: anchored to
25
+ * the CAPTION, not the card, so it covers the identity, the panel and the
26
+ * action plus a short lead above them and nothing else; a `sm` blur that
27
+ * only takes the edge off the detail; ink that never passes 0.45; a text
28
+ * shadow under the identity doing the rest. Everything above the caption is
29
+ * the photograph, untouched. The panel is its own glass, so its rule reads
30
+ * as a panel and not a hole. The same layout at every width; the desktop
31
+ * card is wider, not turned on its side.
32
+ *
33
+ * ## Two targets, and everything else is "next"
34
+ *
35
+ * The name and the action open the venue; the dish name in the first part
36
+ * opens the item. A tap anywhere else on the card advances the story.
37
+ */
38
+ /** @deprecated Alias of `MenuLinkProps`; kept for symmetry with the other menu surfaces. */
39
+ type KitchenDossierLinkProps = MenuLinkProps;
40
+ type KitchenDossierLabels = {
41
+ /** Kickers over the three parts. */
42
+ knownFor: string;
43
+ whatPeopleSay: string;
44
+ theGrade: string;
45
+ /** Currency beside the price, e.g. "lei". */
46
+ currency: string;
47
+ /** Beside the mention count, e.g. "4 name it". */
48
+ mentions: (count: number) => string;
49
+ /** "427 reviews", composed by the caller. */
50
+ reviewCount?: string;
51
+ /** "Open until 22:00", composed by the caller. Omit when closed or unknown. */
52
+ openUntil?: string;
53
+ /** Accessible name per segment, e.g. "Part 2 of 3". */
54
+ part: (index: number, count: number) => string;
55
+ /** Accessible name of the tap-to-advance. */
56
+ next: string;
57
+ /** Dimension key → translated label. */
58
+ dimensions: Partial<Record<VenueGradeDimensionKey, string>>;
59
+ };
60
+ type KitchenDossierProps = {
61
+ place: PlacePreview;
62
+ /** The venue's destination. */
63
+ href: string;
64
+ /** "Restaurant · Centru", composed by the caller. */
65
+ meta?: string;
66
+ /** Part one. */
67
+ signature?: MenuVenueSignature;
68
+ /** Part two — the digest's highlights; two are shown. */
69
+ quotes?: CitedText[];
70
+ /** Part three — the grade, with the overall already formatted. */
71
+ grade?: {
72
+ overall?: string;
73
+ dimensions?: PlaceGradeDimensions;
74
+ };
75
+ labels: KitchenDossierLabels;
76
+ formatPrice: (amount: number) => string;
77
+ /** 0–100 → display numeral. */
78
+ formatScore: (value: number) => string;
79
+ /** How long each part shows. */
80
+ partDurationMs?: number;
81
+ /** The foot's action — pass a rendered link or button. */
82
+ action?: ReactNode;
83
+ linkComponent?: MenuLinkComponent;
84
+ className?: string;
85
+ };
86
+ declare function KitchenDossier({ place, href, meta, signature, quotes, grade, labels, formatPrice, formatScore, partDurationMs, action, linkComponent: LinkComponent, className, }: KitchenDossierProps): react_jsx_runtime.JSX.Element;
87
+
88
+ export { KitchenDossier, type KitchenDossierLabels, type KitchenDossierLinkProps, type KitchenDossierProps };
@@ -0,0 +1,187 @@
1
+ import { DIMENSION_ORDER, VenueGradeDimensions } from '../chunk-AKQ57NXY.js';
2
+ import { VenueQuotes } from '../chunk-62ES6N32.js';
3
+ import { StoryProvider, StorySegments, StoryPanel } from '../chunk-F6FMZJDG.js';
4
+ import { GlassGround } from '../chunk-SY6RV476.js';
5
+ import { Separator } from '../chunk-WSRWFA6K.js';
6
+ import { PhotoGround, PHOTO_GROUND } from '../chunk-5RWTOT5Y.js';
7
+ import { cn } from '../chunk-FEK5XGZW.js';
8
+ import { __objRest, __spreadValues } from '../chunk-3GWWZKX7.js';
9
+ import { MessageSquareQuote, Star } from 'lucide-react';
10
+ import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
11
+
12
+ function DefaultLink(_a) {
13
+ var _b = _a, { href } = _b, props = __objRest(_b, ["href"]);
14
+ return /* @__PURE__ */ jsx("a", __spreadValues({ href }, props));
15
+ }
16
+ var GLASS_LEAD = "128px";
17
+ var DIMENSION_LIMIT = 3;
18
+ var QUOTE_LIMIT = 2;
19
+ var initialOf = (name) => {
20
+ var _a, _b;
21
+ return ((_b = (_a = name.match(new RegExp("\\p{L}", "u"))) == null ? void 0 : _a[0]) != null ? _b : "").toLocaleUpperCase();
22
+ };
23
+ function KitchenDossier({
24
+ place,
25
+ href,
26
+ meta,
27
+ signature,
28
+ quotes,
29
+ grade,
30
+ labels,
31
+ formatPrice,
32
+ formatScore,
33
+ partDurationMs,
34
+ action,
35
+ linkComponent: LinkComponent = DefaultLink,
36
+ className
37
+ }) {
38
+ var _a, _b, _c, _d;
39
+ const rating = (_a = place.rating) == null ? void 0 : _a.rating;
40
+ const parts = [];
41
+ if (signature) {
42
+ const item = signature.item;
43
+ const firstEntry = (_b = item.sizes_and_prices) == null ? void 0 : _b[0];
44
+ const mentions = (_d = (_c = item.mentions) != null ? _c : item.review_count) != null ? _d : 0;
45
+ parts.push(
46
+ /* @__PURE__ */ jsxs(Part, { kicker: labels.knownFor, children: [
47
+ signature.href ? /* @__PURE__ */ jsx(
48
+ LinkComponent,
49
+ {
50
+ href: signature.href,
51
+ className: "w-max max-w-full text-[16px] font-bold leading-tight tracking-[-0.01em] text-white hover:underline focus-visible:outline-none focus-visible:underline",
52
+ children: item.name
53
+ }
54
+ ) : /* @__PURE__ */ jsx("span", { className: "text-[16px] font-bold leading-tight tracking-[-0.01em] text-white", children: item.name }),
55
+ signature.why && /* @__PURE__ */ jsx("p", { className: "mt-1 line-clamp-2 font-serif text-[13px] italic leading-snug text-white/75", children: signature.why }),
56
+ (firstEntry || mentions > 0) && /* @__PURE__ */ jsxs(Fragment, { children: [
57
+ /* @__PURE__ */ jsx(Separator, { className: "my-2.5 via-white/20" }),
58
+ /* @__PURE__ */ jsxs("div", { className: "flex items-end justify-between gap-3", children: [
59
+ /* @__PURE__ */ jsx("span", { className: "flex items-baseline gap-1.5", children: firstEntry && /* @__PURE__ */ jsxs(Fragment, { children: [
60
+ /* @__PURE__ */ jsxs("span", { className: "text-[18px] font-extrabold leading-none tabular-nums text-white", children: [
61
+ formatPrice(firstEntry.price),
62
+ /* @__PURE__ */ jsx("span", { className: "ml-1 text-[12px] font-bold", children: labels.currency })
63
+ ] }),
64
+ firstEntry.size > 0 && /* @__PURE__ */ jsxs("span", { className: "text-[11.5px] tabular-nums text-white/60", children: [
65
+ firstEntry.size,
66
+ item.kind === "dish" ? "g" : "ml"
67
+ ] })
68
+ ] }) }),
69
+ mentions > 0 && /* @__PURE__ */ jsxs("span", { className: "inline-flex items-center gap-1 text-[12px] font-semibold tabular-nums text-white/85", children: [
70
+ /* @__PURE__ */ jsx(MessageSquareQuote, { className: "h-3.5 w-3.5", "aria-hidden": true }),
71
+ labels.mentions(mentions)
72
+ ] })
73
+ ] })
74
+ ] })
75
+ ] }, "signature")
76
+ );
77
+ }
78
+ if (quotes && quotes.length > 0) {
79
+ parts.push(
80
+ /* @__PURE__ */ jsx(Part, { kicker: labels.whatPeopleSay, children: /* @__PURE__ */ jsx(VenueQuotes, { quotes, limit: QUOTE_LIMIT, className: "lg:grid-cols-1" }) }, "quotes")
81
+ );
82
+ }
83
+ const dimensions = grade == null ? void 0 : grade.dimensions;
84
+ const strongest = dimensions ? DIMENSION_ORDER.filter((key) => typeof dimensions[key] === "number").sort((a, b) => {
85
+ var _a2, _b2;
86
+ return ((_a2 = dimensions[b]) != null ? _a2 : 0) - ((_b2 = dimensions[a]) != null ? _b2 : 0);
87
+ }).slice(0, DIMENSION_LIMIT) : [];
88
+ if (grade && (grade.overall || strongest.length > 0)) {
89
+ parts.push(
90
+ /* @__PURE__ */ jsx(Part, { kicker: labels.theGrade, children: /* @__PURE__ */ jsxs("div", { className: "flex items-end gap-4", children: [
91
+ grade.overall && /* @__PURE__ */ jsx("span", { className: "text-[34px] font-extrabold leading-none tracking-[-0.03em] tabular-nums text-[oklch(0.82_0.15_85)]", children: grade.overall }),
92
+ dimensions && strongest.length > 0 && /* @__PURE__ */ jsx(
93
+ VenueGradeDimensions,
94
+ {
95
+ className: "min-w-0 flex-1",
96
+ dimensions,
97
+ labels: labels.dimensions,
98
+ order: strongest,
99
+ formatScore
100
+ }
101
+ )
102
+ ] }) }, "grade")
103
+ );
104
+ }
105
+ return /* @__PURE__ */ jsxs(
106
+ "article",
107
+ {
108
+ className: cn(
109
+ /* `flex flex-col` so the story fills the card's height: in a rail
110
+ the cards are stretched to the tallest, and a story that stopped
111
+ at its own min-height left a band of bare photograph under the
112
+ action on every shorter card. */
113
+ "dark relative isolate flex flex-col overflow-hidden rounded-[18px] border border-border text-foreground shadow-[var(--shadow-card)]",
114
+ PHOTO_GROUND,
115
+ className
116
+ ),
117
+ children: [
118
+ /* @__PURE__ */ jsx(PhotoGround, { className: "-z-20", photos: [place.thumbnail_url], initials: initialOf(place.name), loading: "eager" }),
119
+ /* @__PURE__ */ jsxs(
120
+ StoryProvider,
121
+ {
122
+ count: parts.length,
123
+ durationMs: partDurationMs,
124
+ nextLabel: labels.next,
125
+ className: "relative flex min-h-[560px] flex-1 flex-col p-4 lg:min-h-[440px] lg:p-5",
126
+ children: [
127
+ /* @__PURE__ */ jsx(
128
+ StorySegments,
129
+ {
130
+ labels: parts.map((_, index) => labels.part(index, parts.length)),
131
+ className: "-mt-1"
132
+ }
133
+ ),
134
+ /* @__PURE__ */ jsx("div", { className: "relative min-h-[184px] flex-1", children: labels.openUntil && /* @__PURE__ */ jsxs("span", { className: "absolute left-0 top-3 inline-flex h-[30px] items-center gap-[7px] rounded-full bg-[oklch(0.16_0.012_55_/_0.72)] px-3 text-[11.5px] font-bold text-white backdrop-blur-[2px]", children: [
135
+ /* @__PURE__ */ jsx("i", { className: "h-[7px] w-[7px] rounded-full bg-success", "aria-hidden": true }),
136
+ labels.openUntil
137
+ ] }) }),
138
+ /* @__PURE__ */ jsxs("div", { className: "relative flex flex-col gap-3.5", children: [
139
+ /* @__PURE__ */ jsx(
140
+ "div",
141
+ {
142
+ "aria-hidden": true,
143
+ className: "pointer-events-none absolute -inset-x-4 -bottom-4 -top-32 -z-10 lg:-inset-x-5 lg:-bottom-5",
144
+ children: /* @__PURE__ */ jsx(
145
+ GlassGround,
146
+ {
147
+ stops: { direction: "to bottom", clear: "0px", full: GLASS_LEAD },
148
+ peak: 0.4,
149
+ blur: "sm"
150
+ }
151
+ )
152
+ }
153
+ ),
154
+ /* @__PURE__ */ jsxs("div", { className: "[text-shadow:0_1px_12px_oklch(0_0_0/0.55)]", children: [
155
+ /* @__PURE__ */ jsxs("div", { className: "flex items-baseline justify-between gap-4", children: [
156
+ /* @__PURE__ */ jsx("h3", { className: "min-w-0 font-serif text-[26px] font-semibold leading-tight tracking-[-0.02em] text-white lg:text-[32px]", children: /* @__PURE__ */ jsx(LinkComponent, { href, className: "focus-visible:outline-none focus-visible:underline", children: place.name }) }),
157
+ rating !== void 0 && /* @__PURE__ */ jsxs("span", { className: "inline-flex shrink-0 items-center gap-1 text-[18px] font-extrabold leading-none tabular-nums text-white lg:text-[22px]", children: [
158
+ rating.toFixed(1),
159
+ /* @__PURE__ */ jsx(Star, { className: "h-3.5 w-3.5 lg:h-4 lg:w-4", "aria-hidden": true })
160
+ ] })
161
+ ] }),
162
+ (meta || labels.reviewCount) && /* @__PURE__ */ jsx("p", { className: "mt-1 truncate text-[12.5px] text-white/70", children: [meta, labels.reviewCount].filter(Boolean).join(" \xB7 ") })
163
+ ] }),
164
+ parts.length > 0 && /* @__PURE__ */ jsx(
165
+ StoryPanel,
166
+ {
167
+ parts,
168
+ className: "rounded-[14px] border border-white/10 bg-[oklch(0.16_0.012_55_/_0.35)] p-4 backdrop-blur-md"
169
+ }
170
+ ),
171
+ action && /* @__PURE__ */ jsx("div", { className: "flex [&>*]:w-full lg:[&>*]:w-auto", children: action })
172
+ ] })
173
+ ]
174
+ }
175
+ )
176
+ ]
177
+ }
178
+ );
179
+ }
180
+ function Part({ kicker, children }) {
181
+ return /* @__PURE__ */ jsxs("div", { className: "flex flex-col", children: [
182
+ /* @__PURE__ */ jsx("span", { className: "mb-1.5 block text-[9.5px] font-bold uppercase tracking-[0.12em] text-white/60", children: kicker }),
183
+ children
184
+ ] });
185
+ }
186
+
187
+ export { KitchenDossier };
@@ -0,0 +1,56 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+ import { MenuLinkComponent, MenuLoosenOption } from './menu-types.js';
3
+ import 'react';
4
+ import './place-types.js';
5
+
6
+ /**
7
+ * MenuDeadEnd — what dropped out, and the one press that brings it back.
8
+ *
9
+ * A filter combination that returns nothing is the intent page's worst state,
10
+ * and an empty-state illustration is the worst answer to it: the reader knows
11
+ * the list is empty, what they do not know is WHICH of their four filters did
12
+ * it. So this says what dropped out, why, and what widening one specific filter
13
+ * would restore — with the count, because "+5 kitchens, including the
14
+ * highest-rated Italian room in the city" is an answer and "try removing a
15
+ * filter" is a shrug.
16
+ *
17
+ * It is also drawn on a NON-empty list, whenever a filter has cost the reader
18
+ * something notable. The board's case is 34 kitchens still standing and Casa
19
+ * Boema — the one they came for — missing.
20
+ *
21
+ * Interactive: the consumer marks its own module `"use client"`.
22
+ */
23
+ type MenuDeadEndProps = {
24
+ /**
25
+ * The explanation, composed by the caller.
26
+ *
27
+ * A node rather than a string because it emphasises real names ("**Casa
28
+ * Boema** and six others dropped out"), and assembling that from fragments
29
+ * here would produce translated word-salad in one of the two locales.
30
+ */
31
+ children: React.ReactNode;
32
+ /** The widening this offers, e.g. "Raise to 60 lei · 16 kitchens". */
33
+ actionLabel?: string;
34
+ /** Where the widening leads. Preferred over `onAction` — see `MenuLoosenOption.href`. */
35
+ actionHref?: string;
36
+ onAction?: () => void;
37
+ linkComponent?: MenuLinkComponent;
38
+ className?: string;
39
+ };
40
+ declare function MenuDeadEnd({ children, actionLabel, actionHref, onAction, linkComponent: LinkComponent, className, }: MenuDeadEndProps): react_jsx_runtime.JSX.Element;
41
+ /**
42
+ * MenuLoosenList — one row per active filter, each priced in kitchens.
43
+ *
44
+ * The companion to the panel above: the panel names the single best widening,
45
+ * this lists every one of them so the reader can choose which constraint they
46
+ * care least about. Every row states what it would return, because a filter the
47
+ * reader cannot price is a filter they will not drop.
48
+ */
49
+ type MenuLoosenListProps = {
50
+ options: MenuLoosenOption[];
51
+ linkComponent?: MenuLinkComponent;
52
+ className?: string;
53
+ };
54
+ declare function MenuLoosenList({ options, linkComponent: LinkComponent, className, }: MenuLoosenListProps): react_jsx_runtime.JSX.Element;
55
+
56
+ export { MenuDeadEnd, type MenuDeadEndProps, MenuLoosenList, type MenuLoosenListProps };