@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
@@ -0,0 +1,113 @@
1
+ import { cn } from './chunk-FEK5XGZW.js';
2
+ import * as React from 'react';
3
+ import { useInView } from 'framer-motion';
4
+ import { jsx } from 'react/jsx-runtime';
5
+
6
+ var StoryContext = React.createContext(null);
7
+ function useStory() {
8
+ const value = React.useContext(StoryContext);
9
+ if (!value) throw new Error("Story pieces must render inside StoryProvider");
10
+ return value;
11
+ }
12
+ var TICK_MS = 100;
13
+ function StoryProvider({
14
+ count,
15
+ durationMs = 5e3,
16
+ nextLabel,
17
+ className,
18
+ children
19
+ }) {
20
+ const ref = React.useRef(null);
21
+ const inView = useInView(ref, { amount: 0.6 });
22
+ const [active, setActive] = React.useState(0);
23
+ const [elapsed, setElapsed] = React.useState(0);
24
+ const [held, setHeld] = React.useState(false);
25
+ const playing = Boolean(inView && !held && count > 1);
26
+ const go = React.useCallback(
27
+ (index) => {
28
+ setActive((index % count + count) % count);
29
+ setElapsed(0);
30
+ },
31
+ [count]
32
+ );
33
+ const next = React.useCallback(() => go(active + 1), [go, active]);
34
+ React.useEffect(() => {
35
+ if (!playing) return;
36
+ const timer = window.setInterval(() => setElapsed((previous) => previous + TICK_MS), TICK_MS);
37
+ return () => window.clearInterval(timer);
38
+ }, [playing]);
39
+ React.useEffect(() => {
40
+ if (elapsed < durationMs) return;
41
+ setActive((current) => (current + 1) % count);
42
+ setElapsed(0);
43
+ }, [elapsed, durationMs, count]);
44
+ const value = React.useMemo(
45
+ () => ({ count, active, progress: Math.min(elapsed / durationMs, 1), playing, go, next }),
46
+ [count, active, elapsed, durationMs, playing, go, next]
47
+ );
48
+ const advance = (event) => {
49
+ if (count < 2) return;
50
+ if (event.target.closest("a, button")) return;
51
+ next();
52
+ };
53
+ return /* @__PURE__ */ jsx(StoryContext.Provider, { value, children: /* @__PURE__ */ jsx(
54
+ "div",
55
+ {
56
+ ref,
57
+ className: cn(count > 1 && "cursor-pointer", className),
58
+ title: count > 1 ? nextLabel : void 0,
59
+ onClick: advance,
60
+ onPointerDown: () => setHeld(true),
61
+ onPointerUp: () => setHeld(false),
62
+ onPointerLeave: () => setHeld(false),
63
+ onPointerCancel: () => setHeld(false),
64
+ children
65
+ }
66
+ ) });
67
+ }
68
+ function StorySegments({ labels, className }) {
69
+ const { count, active, progress, go } = useStory();
70
+ if (count < 2) return null;
71
+ return /* @__PURE__ */ jsx("div", { className: cn("flex gap-1", className), role: "tablist", children: Array.from({ length: count }, (_, index) => {
72
+ const fill = index < active ? 1 : index === active ? progress : 0;
73
+ return /* @__PURE__ */ jsx(
74
+ "button",
75
+ {
76
+ type: "button",
77
+ role: "tab",
78
+ "aria-selected": index === active,
79
+ "aria-label": labels[index],
80
+ onClick: () => go(index),
81
+ className: "group/segment flex h-6 flex-1 items-center focus-visible:outline-none",
82
+ children: /* @__PURE__ */ jsx("span", { className: "relative block h-[3px] w-full overflow-hidden rounded-full bg-white/25 group-focus-visible/segment:ring-2 group-focus-visible/segment:ring-white/70", children: /* @__PURE__ */ jsx(
83
+ "span",
84
+ {
85
+ className: "absolute inset-y-0 left-0 rounded-full bg-white",
86
+ style: { width: `${fill * 100}%` }
87
+ }
88
+ ) })
89
+ },
90
+ index
91
+ );
92
+ }) });
93
+ }
94
+ function StoryPanel({ parts, className }) {
95
+ const { active } = useStory();
96
+ return /* @__PURE__ */ jsx("div", { className: cn("relative", className), children: /* @__PURE__ */ jsx("div", { className: "grid [&>*]:col-start-1 [&>*]:row-start-1", children: parts.map((part, index) => {
97
+ const isActive = index === active;
98
+ return /* @__PURE__ */ jsx(
99
+ "div",
100
+ {
101
+ "aria-hidden": !isActive,
102
+ className: cn(
103
+ "transition-[opacity,transform] duration-200 ease-out motion-reduce:transition-none",
104
+ isActive ? "opacity-100" : "pointer-events-none invisible translate-y-1.5 opacity-0"
105
+ ),
106
+ children: part
107
+ },
108
+ index
109
+ );
110
+ }) }) });
111
+ }
112
+
113
+ export { StoryPanel, StoryProvider, StorySegments };
@@ -8,6 +8,9 @@ function beats(times, duration = 0.62) {
8
8
  return { duration, times, ease: "easeInOut" };
9
9
  }
10
10
  var returnTransition = { type: "spring", stiffness: 320, damping: 26 };
11
+ function pivot(x, y) {
12
+ return { originX: `${x}px`, originY: `${y}px`, transformBox: "view-box" };
13
+ }
11
14
  function poses(rest, active, play, transition) {
12
15
  return {
13
16
  rest: __spreadProps(__spreadValues({}, rest), { transition: returnTransition }),
@@ -73,4 +76,4 @@ function MotionIcon({
73
76
  );
74
77
  }
75
78
 
76
- export { MotionIcon, beats, poses, returnTransition, useIconGesture };
79
+ export { MotionIcon, beats, pivot, poses, returnTransition, useIconGesture };
@@ -0,0 +1,109 @@
1
+ import { stagger, ease, duration, springSnappy } from './chunk-LEAYBICI.js';
2
+ import { cn } from './chunk-FEK5XGZW.js';
3
+ import { __spreadValues, __spreadProps } from './chunk-3GWWZKX7.js';
4
+ import { motion, useReducedMotion } from 'framer-motion';
5
+ import { jsx, jsxs } from 'react/jsx-runtime';
6
+
7
+ var LINE_LEAD = 0.15;
8
+ var TARGET_DELAY = 0.38;
9
+ var sourcesContainer = {
10
+ hidden: {},
11
+ show: { transition: { delayChildren: 0.06, staggerChildren: stagger.tight } }
12
+ };
13
+ var sourceItem = {
14
+ hidden: { opacity: 0, x: -8 },
15
+ show: { opacity: 1, x: 0, transition: { duration: duration.quick, ease: ease.enter } }
16
+ };
17
+ var targetItem = {
18
+ hidden: { opacity: 0, scale: 0.92 },
19
+ show: { opacity: 1, scale: 1, transition: __spreadValues({ delay: TARGET_DELAY }, springSnappy) }
20
+ };
21
+ var LINES_WIDTH = 40;
22
+ var LINES_HEIGHT = 100;
23
+ function ConvergeLines({ count, animate = true, className }) {
24
+ const mid = LINES_HEIGHT / 2;
25
+ return /* @__PURE__ */ jsx(
26
+ "svg",
27
+ {
28
+ "aria-hidden": true,
29
+ viewBox: `0 0 ${LINES_WIDTH} ${LINES_HEIGHT}`,
30
+ preserveAspectRatio: "none",
31
+ className: cn("h-full w-10 text-border", className),
32
+ children: Array.from({ length: count }, (_, index) => {
33
+ const y = (index + 0.5) / count * LINES_HEIGHT;
34
+ const d = `M0 ${y} C ${LINES_WIDTH / 2} ${y}, ${LINES_WIDTH / 2} ${mid}, ${LINES_WIDTH} ${mid}`;
35
+ const stroke = {
36
+ d,
37
+ fill: "none",
38
+ stroke: "currentColor",
39
+ strokeWidth: 1.5,
40
+ strokeLinecap: "round",
41
+ vectorEffect: "non-scaling-stroke"
42
+ };
43
+ return animate ? /* @__PURE__ */ jsx(
44
+ motion.path,
45
+ __spreadProps(__spreadValues({}, stroke), {
46
+ initial: { pathLength: 0, opacity: 0 },
47
+ animate: { pathLength: 1, opacity: 1 },
48
+ transition: {
49
+ delay: LINE_LEAD + index * stagger.tight,
50
+ duration: duration.standard,
51
+ ease: ease.standard
52
+ }
53
+ }),
54
+ index
55
+ ) : /* @__PURE__ */ jsx("path", __spreadValues({}, stroke), index);
56
+ })
57
+ }
58
+ );
59
+ }
60
+ function Convergence({
61
+ sources,
62
+ target,
63
+ animate = true,
64
+ linesClassName,
65
+ className
66
+ }) {
67
+ const reduce = useReducedMotion();
68
+ const run = animate && !reduce;
69
+ const fadeOnly = animate && reduce;
70
+ return /* @__PURE__ */ jsxs(
71
+ motion.div,
72
+ {
73
+ className: cn("grid grid-cols-[minmax(0,1fr)_auto_auto] items-center", className),
74
+ initial: fadeOnly ? { opacity: 0 } : false,
75
+ animate: fadeOnly ? { opacity: 1 } : void 0,
76
+ transition: { duration: duration.quick, ease: ease.standard },
77
+ children: [
78
+ /* @__PURE__ */ jsx(
79
+ motion.div,
80
+ {
81
+ className: cn(
82
+ "flex min-w-0 flex-col items-start justify-center gap-2 py-1",
83
+ /* The fan: the middle sources sit a little further right than the
84
+ ends, so five chips read as one hand of cards, not as a menu. */
85
+ "[&>*:nth-child(2)]:translate-x-1.5 [&>*:nth-child(3)]:translate-x-3 [&>*:nth-child(4)]:translate-x-1.5"
86
+ ),
87
+ variants: run ? sourcesContainer : void 0,
88
+ initial: run ? "hidden" : false,
89
+ animate: run ? "show" : void 0,
90
+ children: sources.map((node, index) => /* @__PURE__ */ jsx(motion.div, { className: "flex max-w-full", variants: run ? sourceItem : void 0, children: node }, index))
91
+ }
92
+ ),
93
+ /* @__PURE__ */ jsx("div", { className: "self-stretch", children: /* @__PURE__ */ jsx(ConvergeLines, { count: sources.length, animate: run, className: linesClassName }) }),
94
+ /* @__PURE__ */ jsx(
95
+ motion.div,
96
+ {
97
+ className: "flex items-center",
98
+ variants: run ? targetItem : void 0,
99
+ initial: run ? "hidden" : false,
100
+ animate: run ? "show" : void 0,
101
+ children: target
102
+ }
103
+ )
104
+ ]
105
+ }
106
+ );
107
+ }
108
+
109
+ export { ConvergeLines, Convergence };
@@ -0,0 +1,38 @@
1
+ import { Badge } from './chunk-UKBNQO6Q.js';
2
+ import { cn } from './chunk-FEK5XGZW.js';
3
+ import { jsx } from 'react/jsx-runtime';
4
+
5
+ function SignatureChip({
6
+ label,
7
+ onEvidence,
8
+ surface = "canvas",
9
+ className
10
+ }) {
11
+ const shared = "h-4 shrink-0 px-1.5 text-[10px] font-medium uppercase tracking-wider";
12
+ const onImage = surface === "image";
13
+ if (!onEvidence) {
14
+ return /* @__PURE__ */ jsx(Badge, { variant: onImage ? "overlay" : "secondary", className: cn(shared, className), children: label });
15
+ }
16
+ return /* @__PURE__ */ jsx(
17
+ "button",
18
+ {
19
+ type: "button",
20
+ onClick: (event) => {
21
+ event.stopPropagation();
22
+ event.preventDefault();
23
+ onEvidence();
24
+ },
25
+ className: cn(
26
+ shared,
27
+ "inline-flex items-center rounded-full border border-dashed",
28
+ onImage ? "border-background/70 bg-foreground/70 text-background backdrop-blur-sm hover:bg-foreground/85" : "border-accent/40 text-accent hover:bg-accent/10",
29
+ "focus-visible:outline-none focus-visible:ring-2",
30
+ onImage ? "focus-visible:ring-background/70" : "focus-visible:ring-accent/40",
31
+ className
32
+ ),
33
+ children: label
34
+ }
35
+ );
36
+ }
37
+
38
+ export { SignatureChip };
@@ -0,0 +1,48 @@
1
+ import { cn } from './chunk-FEK5XGZW.js';
2
+ import { __spreadProps, __spreadValues } from './chunk-3GWWZKX7.js';
3
+ import { jsxs, jsx } from 'react/jsx-runtime';
4
+
5
+ var STOPS = [0, 0.25, 0.5, 0.75, 1];
6
+ var EASE = [0, 0.06, 0.25, 0.56, 1];
7
+ var FROST_SOFT = "[mask-image:linear-gradient(var(--glass-dir),transparent_var(--glass-s0),rgba(0,0,0,0.25)_var(--glass-s1),black_var(--glass-s2))] lg:[mask-image:linear-gradient(var(--glass-lg-dir),transparent_var(--glass-lg-s0),rgba(0,0,0,0.25)_var(--glass-lg-s1),black_var(--glass-lg-s2))]";
8
+ var FROST_HEAVY = "[mask-image:linear-gradient(var(--glass-dir),transparent_var(--glass-s1),rgba(0,0,0,0.25)_var(--glass-s2),rgba(0,0,0,0.6)_var(--glass-s3),black_var(--glass-s4))] lg:[mask-image:linear-gradient(var(--glass-lg-dir),transparent_var(--glass-lg-s1),rgba(0,0,0,0.25)_var(--glass-lg-s2),rgba(0,0,0,0.6)_var(--glass-lg-s3),black_var(--glass-lg-s4))]";
9
+ var INK = "bg-[linear-gradient(var(--glass-dir),oklch(0.16_0.012_55_/_0)_var(--glass-s0),oklch(0.16_0.012_55_/_var(--glass-a1))_var(--glass-s1),oklch(0.16_0.012_55_/_var(--glass-a2))_var(--glass-s2),oklch(0.16_0.012_55_/_var(--glass-a3))_var(--glass-s3),oklch(0.16_0.012_55_/_var(--glass-a4))_var(--glass-s4),oklch(0.16_0.012_55_/_var(--glass-a-peak))_100%)] lg:bg-[linear-gradient(var(--glass-lg-dir),oklch(0.16_0.012_55_/_0)_var(--glass-lg-s0),oklch(0.16_0.012_55_/_var(--glass-a1))_var(--glass-lg-s1),oklch(0.16_0.012_55_/_var(--glass-a2))_var(--glass-lg-s2),oklch(0.16_0.012_55_/_var(--glass-a3))_var(--glass-lg-s3),oklch(0.16_0.012_55_/_var(--glass-a4))_var(--glass-lg-s4),oklch(0.16_0.012_55_/_var(--glass-a-peak))_100%)]";
10
+ var BLUR = {
11
+ sm: ["backdrop-blur-[2px]", "backdrop-blur-sm"],
12
+ md: ["backdrop-blur-sm", "backdrop-blur-md"],
13
+ lg: ["backdrop-blur-md", "backdrop-blur-lg"],
14
+ xl: ["backdrop-blur-lg", "backdrop-blur-xl"]
15
+ };
16
+ function measure(value) {
17
+ const match = value.trim().match(/^(-?\d*\.?\d+)\s*([a-z%]*)$/i);
18
+ if (!match) throw new Error(`GlassGround: cannot read stop "${value}"`);
19
+ return [Number(match[1]), match[2] || "px"];
20
+ }
21
+ function positions(clear, full) {
22
+ const [from, unit] = measure(clear);
23
+ const [to] = measure(full);
24
+ return STOPS.map((fraction) => `${from + (to - from) * fraction}${unit}`);
25
+ }
26
+ function GlassGround({
27
+ stops,
28
+ lgStops = stops,
29
+ peak = 0.96,
30
+ blur = "xl",
31
+ className
32
+ }) {
33
+ const base = positions(stops.clear, stops.full);
34
+ const lg = positions(lgStops.clear, lgStops.full);
35
+ const style = __spreadProps(__spreadValues(__spreadValues(__spreadValues({
36
+ "--glass-dir": stops.direction,
37
+ "--glass-lg-dir": lgStops.direction
38
+ }, Object.fromEntries(base.map((position, index) => [`--glass-s${index}`, position]))), Object.fromEntries(lg.map((position, index) => [`--glass-lg-s${index}`, position]))), Object.fromEntries(EASE.slice(1).map((share, index) => [`--glass-a${index + 1}`, String(peak * 0.8 * share)]))), {
39
+ "--glass-a-peak": String(peak)
40
+ });
41
+ return /* @__PURE__ */ jsxs("span", { "aria-hidden": true, className: cn("pointer-events-none absolute inset-0 block", className), style, children: [
42
+ /* @__PURE__ */ jsx("span", { className: cn("absolute inset-0", BLUR[blur][0], FROST_SOFT) }),
43
+ /* @__PURE__ */ jsx("span", { className: cn("absolute inset-0", BLUR[blur][1], FROST_HEAVY) }),
44
+ /* @__PURE__ */ jsx("span", { className: cn("absolute inset-0", INK) })
45
+ ] });
46
+ }
47
+
48
+ export { GlassGround };
@@ -0,0 +1,213 @@
1
+ import { SignatureChip } from './chunk-Q6QH52LJ.js';
2
+ import { cn } from './chunk-FEK5XGZW.js';
3
+ import { __objRest, __spreadValues } from './chunk-3GWWZKX7.js';
4
+ import { memo } from 'react';
5
+ import { MessageSquareQuote } from 'lucide-react';
6
+ import { jsxs, jsx } from 'react/jsx-runtime';
7
+
8
+ function DefaultLink(_a) {
9
+ var _b = _a, { href } = _b, props = __objRest(_b, ["href"]);
10
+ return /* @__PURE__ */ jsx("a", __spreadValues({ href }, props));
11
+ }
12
+ function MentionSignal({
13
+ label,
14
+ count,
15
+ onMentions,
16
+ formatCount = String
17
+ }) {
18
+ const content = /* @__PURE__ */ jsxs("span", { className: "inline-flex items-center gap-0.5", children: [
19
+ /* @__PURE__ */ jsx(MessageSquareQuote, { className: "h-3.5 w-3.5 opacity-60", "aria-hidden": true }),
20
+ /* @__PURE__ */ jsx("span", { className: "tabular-nums", children: formatCount(count) })
21
+ ] });
22
+ const shared = cn(
23
+ // `relative z-[2]` keeps this above the row's stretched link overlay.
24
+ "relative z-[2] inline-flex shrink-0 items-center text-accent",
25
+ // Same size and weight as the price at the other end of the meta row — keep
26
+ // these two in step if the price's type changes.
27
+ "text-[15px] font-bold",
28
+ // `min-h-6` holds the tap target at the 24px WCAG 2.5.8 floor; `px-1.5
29
+ // -mx-1.5` buys the horizontal half without moving the pair on screen.
30
+ "min-h-6 px-1.5 -mx-1.5"
31
+ );
32
+ if (!onMentions) {
33
+ return /* @__PURE__ */ jsx("span", { className: shared, "aria-label": label, children: content });
34
+ }
35
+ return /* @__PURE__ */ jsx(
36
+ "button",
37
+ {
38
+ type: "button",
39
+ "data-slot": "mentions",
40
+ "aria-label": label,
41
+ onClick: (event) => {
42
+ event.stopPropagation();
43
+ event.preventDefault();
44
+ onMentions();
45
+ },
46
+ className: cn(
47
+ shared,
48
+ "rounded-sm hover:underline focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
49
+ ),
50
+ children: content
51
+ }
52
+ );
53
+ }
54
+ var MenuItem = memo(function MenuItem2({
55
+ item,
56
+ labels,
57
+ formatPrice,
58
+ formatCount = String,
59
+ onOpen,
60
+ showSignatureBadge = false,
61
+ signatureTier = "strong",
62
+ onSignatureEvidence,
63
+ onMentions,
64
+ markGradient,
65
+ venueName,
66
+ subtitle,
67
+ href,
68
+ linkLabel,
69
+ linkComponent: LinkComponent = DefaultLink,
70
+ disabled = false,
71
+ className
72
+ }) {
73
+ var _a, _b, _c, _d, _e;
74
+ const firstEntry = (_a = item.sizes_and_prices) == null ? void 0 : _a[0];
75
+ const quantity = firstEntry == null ? void 0 : firstEntry.size;
76
+ const hasQuantity = quantity != null && quantity > 0;
77
+ const allPrices = (_c = (_b = item.sizes_and_prices) == null ? void 0 : _b.map((entry) => entry.price).filter((price) => typeof price === "number" && price > 0)) != null ? _c : [];
78
+ const minPrice = allPrices.length > 0 ? Math.min(...allPrices) : null;
79
+ const maxPrice = allPrices.length > 0 ? Math.max(...allPrices) : null;
80
+ const isPriceRange = minPrice != null && maxPrice != null && minPrice !== maxPrice;
81
+ const mentionCount = (_e = (_d = item.mentions) != null ? _d : item.review_count) != null ? _e : 0;
82
+ const isInert = !href && !onOpen;
83
+ const isActionable = !disabled && !isInert;
84
+ const handleRowClick = () => {
85
+ if (disabled) return;
86
+ onOpen == null ? void 0 : onOpen();
87
+ };
88
+ const handleKeyDown = (event) => {
89
+ if (disabled) return;
90
+ if (event.key === "Enter" || event.key === " ") {
91
+ event.preventDefault();
92
+ onOpen == null ? void 0 : onOpen();
93
+ }
94
+ };
95
+ return /* @__PURE__ */ jsxs(
96
+ "div",
97
+ {
98
+ role: href || isInert ? void 0 : "button",
99
+ tabIndex: href || isInert ? void 0 : disabled ? -1 : 0,
100
+ "aria-disabled": disabled || void 0,
101
+ onClick: href || isInert ? void 0 : handleRowClick,
102
+ onKeyDown: href || isInert ? void 0 : handleKeyDown,
103
+ className: cn(
104
+ "group relative flex min-h-[72px] items-start gap-3 px-4 py-3",
105
+ // Radius only where the row is a grid cell. Full-bleed rows run edge to
106
+ // edge, where a corner radius would round against the viewport.
107
+ "md:rounded-xl",
108
+ // 180ms is the EXIT; `hover:duration-*` overrides it to 120ms on the
109
+ // way in. A hover that leaves as fast as it arrives reads as a flicker
110
+ // when the pointer crosses a list.
111
+ !isInert && [
112
+ "transition-[background-color,box-shadow,transform] duration-[180ms] ease-out motion-reduce:transition-none",
113
+ "focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-accent"
114
+ ],
115
+ isActionable && [
116
+ "cursor-pointer hover:duration-[120ms]",
117
+ // Opaque, never alpha: `hover:bg-muted/40` composited to a lightness
118
+ // delta of 0.004 on the light canvas — a hover state that measured as
119
+ // nothing.
120
+ "hover:bg-card dark:hover:bg-surface-2",
121
+ // Grid cells only. On a full-bleed row the shadow runs off both
122
+ // viewport edges and reads as a smear; that row widens its tab instead.
123
+ "md:hover:shadow-[var(--shadow-row-hover)]",
124
+ // Hover lifts, press sinks — the two must not read as one gesture.
125
+ "active:scale-[0.985] active:duration-[90ms] active:ease-in active:shadow-none",
126
+ "active:bg-muted",
127
+ "motion-reduce:active:scale-100",
128
+ // The mention count is a button inside a button. While the pointer is
129
+ // on it the row stops claiming the hover, so only one target is lit.
130
+ "has-[[data-slot=mentions]:hover]:bg-transparent has-[[data-slot=mentions]:hover]:shadow-none"
131
+ ],
132
+ disabled && "cursor-not-allowed opacity-50",
133
+ className
134
+ ),
135
+ children: [
136
+ href && /* @__PURE__ */ jsx(
137
+ LinkComponent,
138
+ {
139
+ href,
140
+ "aria-label": linkLabel != null ? linkLabel : item.name,
141
+ className: "absolute inset-0 z-[1] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-accent md:rounded-xl"
142
+ }
143
+ ),
144
+ /* @__PURE__ */ jsx(
145
+ "div",
146
+ {
147
+ "aria-hidden": true,
148
+ className: cn(
149
+ "-ml-4 w-2 shrink-0 self-stretch rounded-r-full bg-muted",
150
+ // The full-bleed row's hover signal, since it cannot take a shadow.
151
+ // `-mr-1` absorbs the extra 4px so the text column does not shift as
152
+ // the mark grows. Grid cells get the shadow instead and hold the mark
153
+ // still — the tab encodes which section you are in, so the less it
154
+ // moves, the better it does that job.
155
+ !isInert && [
156
+ "transition-[width] duration-[120ms] ease-out motion-reduce:transition-none",
157
+ "group-hover:-mr-1 group-hover:w-3 md:group-hover:mr-0 md:group-hover:w-2"
158
+ ]
159
+ ),
160
+ style: markGradient ? { backgroundImage: markGradient } : void 0
161
+ }
162
+ ),
163
+ /* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
164
+ /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-1.5", children: [
165
+ /* @__PURE__ */ jsx("h3", { className: "text-[15px] font-semibold leading-snug text-foreground", children: item.name }),
166
+ showSignatureBadge && /* @__PURE__ */ jsx(
167
+ SignatureChip,
168
+ {
169
+ label: signatureTier === "weak" ? labels.mentioned : labels.signature,
170
+ onEvidence: onSignatureEvidence,
171
+ className: "mt-0.5"
172
+ }
173
+ )
174
+ ] }),
175
+ venueName && /* @__PURE__ */ jsx("p", { className: "mt-0.5 truncate text-[12px] font-medium text-foreground/70", children: venueName }),
176
+ subtitle ? /* @__PURE__ */ jsx("p", { className: "mt-0.5 line-clamp-2 text-xs italic leading-snug text-muted-foreground", children: subtitle }) : item.description && /* @__PURE__ */ jsx(
177
+ "p",
178
+ {
179
+ className: cn(
180
+ "mt-0.5 text-xs text-muted-foreground",
181
+ venueName ? "line-clamp-1" : "line-clamp-2"
182
+ ),
183
+ children: item.description
184
+ }
185
+ ),
186
+ (minPrice != null || hasQuantity || mentionCount > 0) && /* @__PURE__ */ jsxs("div", { className: "mt-1.5 flex items-baseline justify-between gap-x-2 gap-y-1", children: [
187
+ /* @__PURE__ */ jsxs("div", { className: "flex min-w-0 items-baseline gap-x-2", children: [
188
+ minPrice != null && /* @__PURE__ */ jsxs("span", { className: "flex items-baseline gap-1", children: [
189
+ /* @__PURE__ */ jsx("span", { className: "text-[15px] font-bold tabular-nums text-foreground", children: isPriceRange ? `${formatPrice(minPrice)}\u2013${formatPrice(maxPrice)}` : formatPrice(minPrice) }),
190
+ /* @__PURE__ */ jsx("span", { className: "text-[11px] text-muted-foreground", children: labels.currency })
191
+ ] }),
192
+ hasQuantity && /* @__PURE__ */ jsxs("span", { className: "text-[12px] tabular-nums text-muted-foreground", children: [
193
+ quantity,
194
+ item.kind === "dish" ? "g" : "ml"
195
+ ] })
196
+ ] }),
197
+ mentionCount > 0 && /* @__PURE__ */ jsx(
198
+ MentionSignal,
199
+ {
200
+ label: labels.mentions(mentionCount),
201
+ count: mentionCount,
202
+ onMentions,
203
+ formatCount
204
+ }
205
+ )
206
+ ] })
207
+ ] })
208
+ ]
209
+ }
210
+ );
211
+ });
212
+
213
+ export { MentionSignal, MenuItem };
@@ -0,0 +1,60 @@
1
+ import { SnapRail, SnapRailDots } from './chunk-DTXNDLSE.js';
2
+ import { PhotoGround } from './chunk-5RWTOT5Y.js';
3
+ import { cn } from './chunk-FEK5XGZW.js';
4
+ import * as React from 'react';
5
+ import { jsxs, Fragment, jsx } from 'react/jsx-runtime';
6
+
7
+ var FOOT_GLASS = cn(
8
+ "before:pointer-events-none before:absolute before:inset-0 before:-z-10 before:content-[''] before:backdrop-blur-lg",
9
+ "before:bg-[oklch(0.16_0.012_55_/_0.9)]",
10
+ "lg:before:[mask-image:linear-gradient(to_top,black_60%,transparent_100%)] lg:before:bg-[linear-gradient(to_top,oklch(0.16_0.012_55_/_0.88)_0%,oklch(0.16_0.012_55_/_0.84)_60%,oklch(0.16_0.012_55_/_0.4)_85%,oklch(0.16_0.012_55_/_0)_100%)]"
11
+ );
12
+ function ConceptCompareRail({
13
+ backdrops,
14
+ railClassName,
15
+ foot,
16
+ children
17
+ }) {
18
+ const [active, setActive] = React.useState(0);
19
+ const count = React.Children.count(children);
20
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
21
+ /* @__PURE__ */ jsx(
22
+ PhotoGround,
23
+ {
24
+ className: "-z-10 lg:hidden",
25
+ mode: "stack",
26
+ active,
27
+ photos: backdrops.map((backdrop) => backdrop.src),
28
+ initials: backdrops.map((backdrop) => backdrop.initial),
29
+ overlay: true,
30
+ loading: "eager"
31
+ }
32
+ ),
33
+ /* @__PURE__ */ jsx(
34
+ SnapRail,
35
+ {
36
+ itemWidth: "full",
37
+ gap: "sm",
38
+ onActiveChange: setActive,
39
+ className: cn("pb-0 overscroll-x-contain", railClassName),
40
+ children
41
+ }
42
+ ),
43
+ foot && /* @__PURE__ */ jsxs(
44
+ "div",
45
+ {
46
+ className: cn(
47
+ "relative isolate flex flex-col gap-3 px-4 pb-4 pt-3",
48
+ "lg:absolute lg:inset-x-0 lg:bottom-0 lg:z-10 lg:flex-row lg:items-center lg:gap-5 lg:px-5 lg:pb-4 lg:pt-6",
49
+ FOOT_GLASS
50
+ ),
51
+ children: [
52
+ /* @__PURE__ */ jsx(SnapRailDots, { count, active, className: "lg:hidden" }),
53
+ foot
54
+ ]
55
+ }
56
+ )
57
+ ] });
58
+ }
59
+
60
+ export { ConceptCompareRail };