@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,109 @@
1
+ import { cn } from '../chunk-FEK5XGZW.js';
2
+ import { __objRest, __spreadProps, __spreadValues } from '../chunk-3GWWZKX7.js';
3
+ import * as React from 'react';
4
+ import { cva } from 'class-variance-authority';
5
+ import { jsx, jsxs } from 'react/jsx-runtime';
6
+
7
+ var snapRailVariants = cva(
8
+ cn(
9
+ "flex snap-x snap-mandatory overflow-x-auto pb-0.5 scrollbar-hide",
10
+ "[&>*]:shrink-0 [&>*]:snap-start"
11
+ ),
12
+ {
13
+ variants: {
14
+ itemWidth: {
15
+ sm: "[&>*]:w-[46%]",
16
+ md: "[&>*]:w-[60%]",
17
+ lg: "[&>*]:w-[80%]",
18
+ /** One card per view — pair with `dots` so the reader knows there is more. */
19
+ full: "[&>*]:w-full"
20
+ },
21
+ gap: {
22
+ sm: "gap-2",
23
+ default: "gap-3"
24
+ },
25
+ bleed: {
26
+ none: "",
27
+ card: "-mx-3.5 px-3.5 scroll-px-3.5",
28
+ page: "-mx-4 px-4 scroll-px-4"
29
+ }
30
+ },
31
+ defaultVariants: {
32
+ itemWidth: "sm",
33
+ gap: "default",
34
+ bleed: "none"
35
+ }
36
+ }
37
+ );
38
+ var SnapRail = React.forwardRef(
39
+ (_a, ref) => {
40
+ var _b = _a, { className, itemWidth, gap, bleed, dots, dotsClassName, children } = _b, props = __objRest(_b, ["className", "itemWidth", "gap", "bleed", "dots", "dotsClassName", "children"]);
41
+ const railRef = React.useRef(null);
42
+ const [active, setActive] = React.useState(0);
43
+ const count = React.Children.count(children);
44
+ const setRefs = React.useCallback(
45
+ (node) => {
46
+ railRef.current = node;
47
+ if (typeof ref === "function") ref(node);
48
+ else if (ref) ref.current = node;
49
+ },
50
+ [ref]
51
+ );
52
+ React.useEffect(() => {
53
+ if (!dots) return;
54
+ const rail2 = railRef.current;
55
+ if (!rail2) return;
56
+ let frame = 0;
57
+ const sync = () => {
58
+ frame = 0;
59
+ const origin = rail2.scrollLeft + parseFloat(getComputedStyle(rail2).scrollPaddingLeft || "0");
60
+ let best = 0;
61
+ let bestDistance = Number.POSITIVE_INFINITY;
62
+ Array.from(rail2.children).forEach((child, index) => {
63
+ const distance = Math.abs(child.offsetLeft - origin);
64
+ if (distance < bestDistance) {
65
+ bestDistance = distance;
66
+ best = index;
67
+ }
68
+ });
69
+ setActive(best);
70
+ };
71
+ const onScroll = () => {
72
+ if (!frame) frame = requestAnimationFrame(sync);
73
+ };
74
+ sync();
75
+ rail2.addEventListener("scroll", onScroll, { passive: true });
76
+ return () => {
77
+ if (frame) cancelAnimationFrame(frame);
78
+ rail2.removeEventListener("scroll", onScroll);
79
+ };
80
+ }, [dots, count]);
81
+ const rail = /* @__PURE__ */ jsx(
82
+ "div",
83
+ __spreadProps(__spreadValues({
84
+ ref: setRefs,
85
+ "data-slot": "snap-rail",
86
+ className: cn(snapRailVariants({ itemWidth, gap, bleed }), className)
87
+ }, props), {
88
+ children
89
+ })
90
+ );
91
+ if (!dots) return rail;
92
+ return /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-3", children: [
93
+ rail,
94
+ /* @__PURE__ */ jsx("div", { "aria-hidden": true, className: cn("flex justify-center gap-1.5", dotsClassName), children: Array.from({ length: count }, (_, index) => /* @__PURE__ */ jsx(
95
+ "span",
96
+ {
97
+ className: cn(
98
+ "h-1.5 rounded-full transition-[width,background-color] duration-200 motion-reduce:transition-none",
99
+ index === active ? "w-4 bg-foreground" : "w-1.5 bg-foreground/25"
100
+ )
101
+ },
102
+ index
103
+ )) })
104
+ ] });
105
+ }
106
+ );
107
+ SnapRail.displayName = "SnapRail";
108
+
109
+ export { SnapRail };
@@ -4,11 +4,11 @@ import { VariantProps } from 'class-variance-authority';
4
4
 
5
5
  declare const spinnerVariants: (props?: ({
6
6
  size?: "default" | "xs" | "sm" | "lg" | "xl" | null | undefined;
7
- tone?: "inherit" | "default" | "destructive" | "primary" | null | undefined;
7
+ tone?: "default" | "destructive" | "inherit" | "primary" | null | undefined;
8
8
  } & class_variance_authority_types.ClassProp) | undefined) => string;
9
9
  declare const Spinner: React.ForwardRefExoticComponent<React.HTMLAttributes<HTMLSpanElement> & VariantProps<(props?: ({
10
10
  size?: "default" | "xs" | "sm" | "lg" | "xl" | null | undefined;
11
- tone?: "inherit" | "default" | "destructive" | "primary" | null | undefined;
11
+ tone?: "default" | "destructive" | "inherit" | "primary" | null | undefined;
12
12
  } & class_variance_authority_types.ClassProp) | undefined) => string> & {
13
13
  /** Visually-hidden label for screen readers. Defaults to "Loading". */
14
14
  label?: string;
@@ -0,0 +1,47 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+ import * as class_variance_authority_types from 'class-variance-authority/types';
3
+ import { VariantProps } from 'class-variance-authority';
4
+
5
+ /**
6
+ * StatBanner — one claim about the catalogue, carried by a single number.
7
+ *
8
+ * These sit between the modules that list things and give the page somewhere to
9
+ * state what it knows rather than only showing it. Two side by side is the
10
+ * usual arrangement, which is a caller's grid rather than a `StatBannerPair`
11
+ * component — pairing is layout, and a consumer wanting one, or three, or a
12
+ * different order should not have to fight a wrapper for it.
13
+ *
14
+ * `scale` draws the distribution the figure sits in — low, the figure, high.
15
+ * Pass it only when all three are real numbers from the same measurement; a
16
+ * scale invented around a figure is worse than no scale, because it implies a
17
+ * spread nobody counted.
18
+ *
19
+ * Server-safe.
20
+ */
21
+ declare const bannerVariants: (props?: ({
22
+ tone?: "dark" | "warm" | null | undefined;
23
+ } & class_variance_authority_types.ClassProp) | undefined) => string;
24
+ type StatBannerProps = VariantProps<typeof bannerVariants> & {
25
+ /** Small caps kicker, e.g. "THE REVIEW CENSUS". */
26
+ kicker?: string;
27
+ /**
28
+ * The headline claim. On the dark tone this is the whole point; on the warm
29
+ * one it usually sits under `figure`.
30
+ */
31
+ headline?: string;
32
+ body?: string;
33
+ /** The number itself, pre-formatted, e.g. "100". */
34
+ figure?: string;
35
+ /** Unit set small beside the figure, e.g. "%" or "lei". */
36
+ figureUnit?: string;
37
+ /** Oversized watermark numeral behind the content. */
38
+ watermark?: string;
39
+ /** Low, middle and high of the distribution. All three, or none. */
40
+ scale?: [string, string, string];
41
+ /** The action — pass a rendered link or button. */
42
+ action?: React.ReactNode;
43
+ className?: string;
44
+ };
45
+ declare function StatBanner({ tone, kicker, headline, body, figure, figureUnit, watermark, scale, action, className, }: StatBannerProps): react_jsx_runtime.JSX.Element;
46
+
47
+ export { StatBanner, type StatBannerProps };
@@ -0,0 +1,61 @@
1
+ import { cn } from '../chunk-FEK5XGZW.js';
2
+ import '../chunk-3GWWZKX7.js';
3
+ import { cva } from 'class-variance-authority';
4
+ import { jsxs, jsx } from 'react/jsx-runtime';
5
+
6
+ var bannerVariants = cva(
7
+ "relative flex flex-col gap-2 overflow-hidden rounded-[18px] p-5",
8
+ {
9
+ variants: {
10
+ tone: {
11
+ /** Dark ground, for the claim that needs the most weight. */
12
+ dark: "bg-[oklch(0.21_0.012_55)] text-white",
13
+ /** Tinted ground, for the supporting figure beside it. */
14
+ warm: "bg-accent-subtle text-foreground"
15
+ }
16
+ },
17
+ defaultVariants: { tone: "dark" }
18
+ }
19
+ );
20
+ function StatBanner({
21
+ tone,
22
+ kicker,
23
+ headline,
24
+ body,
25
+ figure,
26
+ figureUnit,
27
+ watermark,
28
+ scale,
29
+ action,
30
+ className
31
+ }) {
32
+ return /* @__PURE__ */ jsxs("div", { className: cn(bannerVariants({ tone }), className), children: [
33
+ watermark && /* @__PURE__ */ jsx(
34
+ "span",
35
+ {
36
+ "aria-hidden": true,
37
+ className: "pointer-events-none absolute -bottom-8 right-2 select-none font-serif text-[150px] font-bold leading-none opacity-[0.08]",
38
+ children: watermark
39
+ }
40
+ ),
41
+ /* @__PURE__ */ jsxs("div", { className: "relative z-[1] flex flex-1 flex-col gap-2", children: [
42
+ kicker && /* @__PURE__ */ jsx("span", { className: "text-[10px] font-bold tracking-[0.11em] opacity-80", children: kicker }),
43
+ figure && /* @__PURE__ */ jsxs("div", { className: "text-[40px] font-extrabold leading-none tracking-[-0.03em] tabular-nums", children: [
44
+ figure,
45
+ figureUnit && /* @__PURE__ */ jsx("span", { className: "ml-1 text-lg font-extrabold", children: figureUnit })
46
+ ] }),
47
+ headline && /* @__PURE__ */ jsx("h3", { className: "font-serif text-xl font-semibold leading-[1.2] tracking-[-0.02em] lg:text-2xl", children: headline }),
48
+ body && /* @__PURE__ */ jsx("p", { className: "text-[13px] leading-[1.5] opacity-85", children: body }),
49
+ scale && /* @__PURE__ */ jsxs("div", { className: "mt-1 flex items-center gap-2 text-[11px] tabular-nums opacity-70", children: [
50
+ /* @__PURE__ */ jsx("span", { children: scale[0] }),
51
+ /* @__PURE__ */ jsx("span", { "aria-hidden": true, className: "h-px flex-1 bg-current opacity-30" }),
52
+ /* @__PURE__ */ jsx("span", { children: scale[1] }),
53
+ /* @__PURE__ */ jsx("span", { "aria-hidden": true, className: "h-px flex-1 bg-current opacity-30" }),
54
+ /* @__PURE__ */ jsx("span", { children: scale[2] })
55
+ ] }),
56
+ action && /* @__PURE__ */ jsx("div", { className: "mt-auto flex items-center pt-3", children: action })
57
+ ] })
58
+ ] });
59
+ }
60
+
61
+ export { StatBanner };
@@ -22,9 +22,9 @@ import { VariantProps } from 'class-variance-authority';
22
22
  * Weights are 400 / 600 / 700. 500 is not in the system.
23
23
  */
24
24
  declare const textVariants: (props?: ({
25
- variant?: "h2" | "h3" | "label" | "body" | "caption" | "h1" | "h4" | "small" | "display" | "body-lg" | "body-sm" | "overline" | null | undefined;
26
- color?: "inherit" | "default" | "destructive" | "muted" | "primary" | "primary-foreground" | null | undefined;
27
- weight?: "bold" | "medium" | "normal" | "semibold" | null | undefined;
25
+ variant?: "small" | "display" | "h2" | "h3" | "label" | "body" | "caption" | "h1" | "h4" | "body-lg" | "body-sm" | "overline" | null | undefined;
26
+ color?: "default" | "destructive" | "inherit" | "muted" | "primary" | "primary-foreground" | null | undefined;
27
+ weight?: "medium" | "bold" | "normal" | "semibold" | null | undefined;
28
28
  leading?: "none" | "normal" | "tight" | "snug" | "relaxed" | "loose" | null | undefined;
29
29
  tracking?: "normal" | "tight" | "wide" | "wider" | null | undefined;
30
30
  wrap?: "balance" | "pretty" | "nowrap" | null | undefined;
@@ -1,6 +1,14 @@
1
1
  import * as react_jsx_runtime from 'react/jsx-runtime';
2
2
  import * as React from 'react';
3
3
 
4
+ /**
5
+ * The mobile axis gutter, shared by `HourBand` and `TimelinePlayhead` so the
6
+ * hour chips and the current-time label sit in one column. 58px is the width
7
+ * of a five-glyph 24-hour label ("20:00") in the chip's mono type plus its
8
+ * padding; a 12-hour locale's "09:00 AM" overflows it, which is one of the
9
+ * reasons the events surfaces format 24-hour everywhere.
10
+ */
11
+ declare const TIMELINE_MOBILE_GUTTER_CLASS = "w-[58px]";
4
12
  type TimelineHourPuckProps = {
5
13
  /** The hour, already formatted and localised — or an em dash for a quiet day. */
6
14
  time: string;
@@ -8,6 +16,16 @@ type TimelineHourPuckProps = {
8
16
  isLive?: boolean;
9
17
  /** Shown under the puck when live, e.g. "NOW ON". Required to render it. */
10
18
  liveLabel?: string;
19
+ /**
20
+ * `station` (default): the bordered puck that sits ON the hairline, for the
21
+ * desktop spine. `chip`: a filled, fixed-width lozenge with the hairline
22
+ * starting BELOW it, for the mobile gutter — an app's time label, not a
23
+ * station on a rail. In `chip` the live label is visually hidden: the tinted
24
+ * chip plus a small on-air glyph are the visible signal (the glyph so the
25
+ * state never rests on colour alone once the pulse is reduced away) and the
26
+ * label stays for screen readers only.
27
+ */
28
+ variant?: "station" | "chip";
11
29
  className?: string;
12
30
  };
13
31
  /**
@@ -25,7 +43,7 @@ type TimelineHourPuckProps = {
25
43
  * centre this puck with `-translate-y-1/2`, and a two-item column would centre
26
44
  * the STACK, lifting the puck off the line it exists to sit on.
27
45
  */
28
- declare function TimelineHourPuck({ time, isLive, liveLabel, className }: TimelineHourPuckProps): react_jsx_runtime.JSX.Element;
46
+ declare function TimelineHourPuck({ time, isLive, liveLabel, variant, className, }: TimelineHourPuckProps): react_jsx_runtime.JSX.Element;
29
47
  type TimelineBandProps = {
30
48
  /** The lane column — usually a poster card, or empty for a quiet hour. */
31
49
  lane: React.ReactNode;
@@ -43,4 +61,4 @@ type TimelineBandSkeletonProps = {
43
61
  */
44
62
  declare function TimelineBandSkeleton({ className }: TimelineBandSkeletonProps): react_jsx_runtime.JSX.Element;
45
63
 
46
- export { TimelineBand, type TimelineBandProps, TimelineBandSkeleton, type TimelineBandSkeletonProps, TimelineHourPuck, type TimelineHourPuckProps };
64
+ export { TIMELINE_MOBILE_GUTTER_CLASS, TimelineBand, type TimelineBandProps, TimelineBandSkeleton, type TimelineBandSkeletonProps, TimelineHourPuck, type TimelineHourPuckProps };
@@ -1,4 +1,4 @@
1
- export { TimelineBand, TimelineBandSkeleton, TimelineHourPuck } from '../chunk-637L7XQ3.js';
1
+ export { TIMELINE_MOBILE_GUTTER_CLASS, TimelineBand, TimelineBandSkeleton, TimelineHourPuck } from '../chunk-3TNPSN4D.js';
2
2
  import '../chunk-WSRWFA6K.js';
3
3
  import '../chunk-QXHMPJ35.js';
4
4
  import '../chunk-FEK5XGZW.js';
@@ -0,0 +1,26 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+
3
+ /**
4
+ * TimelinePlayhead — the "you are here" mark on the mobile timeline: the
5
+ * current time in the axis gutter, a dashed rule across the content column,
6
+ * and a filled NOW pill at the far edge.
7
+ *
8
+ * It sits BETWEEN hour bands, at the point where the wall clock falls, so the
9
+ * reader can see at a glance which hours are behind them and which are ahead.
10
+ * The live hour band's tinted chip says "this hour is running"; the playhead
11
+ * says exactly how far into it we are. Both are needed: a band spans an hour
12
+ * and the hour is mostly not "now".
13
+ *
14
+ * The time label shares the gutter width with `HourBand` so it lines up under
15
+ * the hour chips. Both strings arrive formatted — the DS holds no clock.
16
+ */
17
+ type TimelinePlayheadProps = {
18
+ /** The current time, already formatted — "21:38". */
19
+ time: string;
20
+ /** The pill text, e.g. "NOW". */
21
+ label: string;
22
+ className?: string;
23
+ };
24
+ declare function TimelinePlayhead({ time, label, className }: TimelinePlayheadProps): react_jsx_runtime.JSX.Element;
25
+
26
+ export { TimelinePlayhead, type TimelinePlayheadProps };
@@ -0,0 +1,25 @@
1
+ import { TIMELINE_MOBILE_GUTTER_CLASS } from '../chunk-3TNPSN4D.js';
2
+ import '../chunk-WSRWFA6K.js';
3
+ import '../chunk-QXHMPJ35.js';
4
+ import { cn } from '../chunk-FEK5XGZW.js';
5
+ import '../chunk-3GWWZKX7.js';
6
+ import { jsxs, jsx } from 'react/jsx-runtime';
7
+
8
+ function TimelinePlayhead({ time, label, className }) {
9
+ return /* @__PURE__ */ jsxs("div", { className: cn("flex items-center gap-2.5 pt-3", className), children: [
10
+ /* @__PURE__ */ jsx(
11
+ "span",
12
+ {
13
+ className: cn(
14
+ TIMELINE_MOBILE_GUTTER_CLASS,
15
+ "shrink-0 text-center font-mono text-[11px] font-bold tabular-nums text-success"
16
+ ),
17
+ children: time
18
+ }
19
+ ),
20
+ /* @__PURE__ */ jsx("span", { "aria-hidden": true, className: "h-0 flex-1 border-t-[1.5px] border-dashed border-success/50" }),
21
+ /* @__PURE__ */ jsx("span", { className: "rounded-full bg-success px-2 py-0.5 text-[9.5px] font-extrabold tracking-[0.1em] text-success-foreground", children: label })
22
+ ] });
23
+ }
24
+
25
+ export { TimelinePlayhead };
@@ -0,0 +1,35 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+
3
+ /**
4
+ * VenueBusynessStrip — when a venue actually fills up, as a bar per hour.
5
+ *
6
+ * The API serves one score per hour of the venue's own trading day, so the
7
+ * strip has no fixed length: a bakery closing at three and a club open until
8
+ * four draw different numbers of bars, and padding either to 24 would put empty
9
+ * columns under hours the venue is not open.
10
+ *
11
+ * The rush is derived rather than passed: bars at or above `peakRatio` of the
12
+ * day's own maximum take the accent. A fixed score threshold would mark every
13
+ * hour at a busy venue and none at a quiet one — busyness is only meaningful
14
+ * relative to that venue's own day.
15
+ *
16
+ * `caption` is composed by the caller ("Usually not busy until 18:00 — the rush
17
+ * is nine to eleven"), because the sentence needs the venue's clock, its locale
18
+ * and a plural rule the DS does not own.
19
+ *
20
+ * Server-safe.
21
+ */
22
+ type VenueBusynessStripProps = {
23
+ busyness: Array<{
24
+ hour: number;
25
+ score: number;
26
+ }>;
27
+ /** Composed sentence beside the bars. */
28
+ caption?: string;
29
+ /** Share of the day's own peak at which an hour counts as the rush. */
30
+ peakRatio?: number;
31
+ className?: string;
32
+ };
33
+ declare function VenueBusynessStrip({ busyness, caption, peakRatio, className, }: VenueBusynessStripProps): react_jsx_runtime.JSX.Element | null;
34
+
35
+ export { VenueBusynessStrip, type VenueBusynessStripProps };
@@ -0,0 +1,37 @@
1
+ import { cn } from '../chunk-FEK5XGZW.js';
2
+ import '../chunk-3GWWZKX7.js';
3
+ import { jsxs, jsx } from 'react/jsx-runtime';
4
+
5
+ var BAR_MAX_HEIGHT = 22;
6
+ function VenueBusynessStrip({
7
+ busyness,
8
+ caption,
9
+ peakRatio = 0.8,
10
+ className
11
+ }) {
12
+ if (busyness.length === 0) return null;
13
+ const peak = Math.max(...busyness.map((entry) => entry.score));
14
+ if (peak <= 0) return null;
15
+ return /* @__PURE__ */ jsxs(
16
+ "div",
17
+ {
18
+ className: cn("flex items-center gap-2.5 text-[12.5px] text-muted-foreground", className),
19
+ children: [
20
+ /* @__PURE__ */ jsx("span", { "aria-hidden": true, className: "flex h-[22px] items-end gap-[3px]", children: busyness.map(({ hour, score }) => /* @__PURE__ */ jsx(
21
+ "i",
22
+ {
23
+ className: cn(
24
+ "w-[7px] rounded-t-sm bg-[oklch(0.88_0.02_45)]",
25
+ score >= peak * peakRatio && "bg-[var(--accent,currentColor)]"
26
+ ),
27
+ style: { height: Math.max(2, Math.round(score / peak * BAR_MAX_HEIGHT)) }
28
+ },
29
+ hour
30
+ )) }),
31
+ caption
32
+ ]
33
+ }
34
+ );
35
+ }
36
+
37
+ export { VenueBusynessStrip };
@@ -0,0 +1,36 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+ import { PlaceGradeDimensions } from './place-types.js';
3
+
4
+ /**
5
+ * VenueGradeDimensions — the per-dimension review scores as a row of bars.
6
+ *
7
+ * ## Why this renders what it is given rather than a fixed row
8
+ *
9
+ * A dimension the reviews never mention is ABSENT from the payload, not zero,
10
+ * and exactly one of `food` / `coffee_quality` / `drinks_quality` is scored per
11
+ * place — a coffee bar has no food score and a restaurant has no coffee one.
12
+ * So a component drawing seven fixed columns would invent readings for a third
13
+ * of the catalogue. It iterates `order`, skips what is missing, and lays out
14
+ * however many survive.
15
+ *
16
+ * Scores arrive 0–100 and display on the 0–10 scale the rest of the product
17
+ * grades on, so the bar width is the raw value and the numeral is that value
18
+ * over ten. `formatScore` is injected because a decimal separator is
19
+ * locale-dependent.
20
+ *
21
+ * Server-safe.
22
+ */
23
+ type VenueGradeDimensionKey = keyof PlaceGradeDimensions;
24
+ type VenueGradeDimensionsProps = {
25
+ dimensions: PlaceGradeDimensions;
26
+ /** Dimension key → translated label. Keys with no label fall back to the key. */
27
+ labels: Partial<Record<VenueGradeDimensionKey, string>>;
28
+ /** Override the canonical order, or narrow which dimensions show at all. */
29
+ order?: VenueGradeDimensionKey[];
30
+ /** 0–100 → display numeral, e.g. `(v) => (v / 10).toFixed(1)`. */
31
+ formatScore: (value: number) => string;
32
+ className?: string;
33
+ };
34
+ declare function VenueGradeDimensions({ dimensions, labels, order, formatScore, className, }: VenueGradeDimensionsProps): react_jsx_runtime.JSX.Element | null;
35
+
36
+ export { type VenueGradeDimensionKey, VenueGradeDimensions, type VenueGradeDimensionsProps };
@@ -0,0 +1,3 @@
1
+ export { VenueGradeDimensions } from '../chunk-AXV5O2IZ.js';
2
+ import '../chunk-FEK5XGZW.js';
3
+ import '../chunk-3GWWZKX7.js';
@@ -0,0 +1,32 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+ import { CitedText } from './place-types.js';
3
+
4
+ /**
5
+ * VenueQuotes — the digest's standout lines, as pull-quotes.
6
+ *
7
+ * These are `reviews_digest.highlights`: one to three short positives already
8
+ * resolved into the request locale. They are the only place on a hub where a
9
+ * reviewer speaks in their own register, so they are set in serif italic
10
+ * against the sans everything else uses.
11
+ *
12
+ * `onCite` turns a quote into a button that opens the reviews behind it —
13
+ * `review_ids` is what makes the claim checkable, and a quote nobody can trace
14
+ * is just copy. Without the handler the quote renders as inert prose, which is
15
+ * correct on surfaces that have nowhere to open.
16
+ *
17
+ * No directive: inert quotes render on the server; passing `onCite` needs a
18
+ * client caller.
19
+ */
20
+ type VenueQuotesProps = {
21
+ quotes: CitedText[];
22
+ /** Opens the reviews behind one quote. Omit to render inert prose. */
23
+ onCite?: (quote: CitedText) => void;
24
+ /** Accessible name for the cite action, e.g. `(n) => "Read 5 reviews"`. */
25
+ citeLabel?: (reviewCount: number) => string;
26
+ /** Cap on how many render. The digest ships at most three. */
27
+ limit?: number;
28
+ className?: string;
29
+ };
30
+ declare function VenueQuotes({ quotes, onCite, citeLabel, limit, className }: VenueQuotesProps): react_jsx_runtime.JSX.Element | null;
31
+
32
+ export { VenueQuotes, type VenueQuotesProps };
@@ -0,0 +1,3 @@
1
+ export { VenueQuotes } from '../chunk-62ES6N32.js';
2
+ import '../chunk-FEK5XGZW.js';
3
+ import '../chunk-3GWWZKX7.js';
@@ -0,0 +1,39 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+ import { MenuItemLabels, MenuItemLinkProps } from './menu-item.js';
3
+ import { MenuItemPreview } from './menu-types.js';
4
+ import 'react';
5
+ import './place-types.js';
6
+
7
+ /**
8
+ * VenueSignatureBlock — "what it is known for": the one dish or drink a venue's
9
+ * reviews single out, on a tinted band.
10
+ *
11
+ * ## It is a `<MenuItem>` with a kicker
12
+ *
13
+ * It used to be its own layout — swatch, label, name, price, all hand-placed —
14
+ * and it drifted from the row every other surface renders. It is now the
15
+ * production row on an accent ground, so a dish looks like itself here, in the
16
+ * "known for" list, and on the venue's own menu.
17
+ *
18
+ * The kicker sits ABOVE the row rather than inside it. Inline at 9.5px it
19
+ * competed with the dish name for the same line, which is how it ended up
20
+ * stacked one word per line.
21
+ */
22
+ type VenueSignatureBlockProps = {
23
+ item: MenuItemPreview;
24
+ labels: MenuItemLabels;
25
+ formatPrice: (amount: number) => string;
26
+ formatCount?: (value: number) => string;
27
+ /** Small caps kicker, e.g. "WHAT IT IS KNOWN FOR". */
28
+ kicker: string;
29
+ /** The row's category mark, as a CSS `background-image` value. */
30
+ markGradient?: string;
31
+ /** Reviewers' reason, in place of the item description. */
32
+ subtitle?: string;
33
+ href?: string;
34
+ linkComponent?: React.ComponentType<MenuItemLinkProps>;
35
+ className?: string;
36
+ };
37
+ declare function VenueSignatureBlock({ item, labels, formatPrice, formatCount, kicker, markGradient, subtitle, href, linkComponent, className, }: VenueSignatureBlockProps): react_jsx_runtime.JSX.Element;
38
+
39
+ export { VenueSignatureBlock, type VenueSignatureBlockProps };
@@ -0,0 +1,6 @@
1
+ export { VenueSignatureBlock } from '../chunk-THOVPZNN.js';
2
+ import '../chunk-TH6W2PWG.js';
3
+ import '../chunk-Q6QH52LJ.js';
4
+ import '../chunk-UKBNQO6Q.js';
5
+ import '../chunk-FEK5XGZW.js';
6
+ import '../chunk-3GWWZKX7.js';
@@ -0,0 +1,74 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+ import { VenueGradeDimensionKey } from './venue-grade-dimensions.js';
3
+ import { MenuItemLabels } from './menu-item.js';
4
+ import { MenuVenueSignature, MenuLinkComponent, MenuLinkProps } from './menu-types.js';
5
+ import { PlacePreview, CitedText } from './place-types.js';
6
+ import 'react';
7
+
8
+ /**
9
+ * VenueSpotlight — one venue given real space: photograph, what it is known
10
+ * for, what reviewers say, how it grades, and when it fills up.
11
+ *
12
+ * The photograph IS the point of this surface. It is the one module on a menu
13
+ * hub that shows an image, and it can, because venue photography is real and
14
+ * licensed where item photography is generated. Absent a photo it degrades to
15
+ * the same diagonal texture the hero uses, never to a bare icon.
16
+ *
17
+ * ## Composed from exported parts
18
+ *
19
+ * `VenueSignatureBlock`, `VenueQuotes`, `VenueGradeDimensions` and
20
+ * `VenueBusynessStrip` are each exported on their own. This composite is the
21
+ * arrangement the hub wants; a consumer filming a single panel, or building a
22
+ * narrower variant, takes the parts instead. Everything here is layout — no
23
+ * part is reachable only through the composite.
24
+ *
25
+ * No directive: it renders on the server unless `onCite` is passed through to
26
+ * the quotes.
27
+ */
28
+ /** @deprecated Alias of `MenuLinkProps`; kept so existing imports keep working. */
29
+ type VenueSpotlightLinkProps = MenuLinkProps;
30
+ type VenueSpotlightLabels = {
31
+ /** Overlay on the photograph, e.g. "venue photograph". */
32
+ photoCredit?: string;
33
+ /** Composed open state, e.g. "Kitchen open until 23:00". Absent when closed or unknown. */
34
+ openUntil?: string;
35
+ /** Kicker over the signature, e.g. "WHAT IT IS KNOWN FOR". */
36
+ signatureKicker: string;
37
+ /** Composed review count, e.g. "318 reviews". */
38
+ reviewCount?: string;
39
+ /** Dimension key → translated label. */
40
+ dimensions: Partial<Record<VenueGradeDimensionKey, string>>;
41
+ /** Tag code → translated label. Missing codes render the raw code. */
42
+ tags?: Record<string, string>;
43
+ /** Composed value verdict, e.g. "Good value". */
44
+ valueVerdict?: string;
45
+ /** Composed busyness sentence. */
46
+ busyness?: string;
47
+ /** Accessible name for a quote's cite action. */
48
+ cite?: (reviewCount: number) => string;
49
+ };
50
+ type VenueSpotlightProps = {
51
+ place: PlacePreview;
52
+ href: string;
53
+ labels: VenueSpotlightLabels;
54
+ /** Composed meta line, e.g. "pub · Centru". Clause order is locale-dependent. */
55
+ meta?: string;
56
+ signature?: MenuVenueSignature;
57
+ /** CSS gradient for the signature swatch. */
58
+ signatureGradient?: string;
59
+ /** Passed through to the signature `<MenuItem>`. */
60
+ itemLabels: MenuItemLabels;
61
+ formatPrice: (amount: number) => string;
62
+ formatCount?: (value: number) => string;
63
+ /** 0–100 → display numeral for the dimension bars. */
64
+ formatScore: (value: number) => string;
65
+ /** Opens the reviews behind a quote. */
66
+ onCite?: (quote: CitedText) => void;
67
+ /** The action in the footer — pass a rendered link or button. */
68
+ action?: React.ReactNode;
69
+ linkComponent?: MenuLinkComponent;
70
+ className?: string;
71
+ };
72
+ declare function VenueSpotlight({ place, href, labels, meta, signature, signatureGradient, itemLabels, formatPrice, formatCount, formatScore, onCite, action, linkComponent: LinkComponent, className, }: VenueSpotlightProps): react_jsx_runtime.JSX.Element;
73
+
74
+ export { VenueSpotlight, type VenueSpotlightLabels, type VenueSpotlightLinkProps, type VenueSpotlightProps };