@bearmenu/ui 0.8.8 → 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 (113) hide show
  1. package/dist/chunk-62ES6N32.js +27 -0
  2. package/dist/chunk-AXV5O2IZ.js +58 -0
  3. package/dist/chunk-Q6QH52LJ.js +38 -0
  4. package/dist/chunk-TH6W2PWG.js +213 -0
  5. package/dist/chunk-THOVPZNN.js +37 -0
  6. package/dist/chunk-YS6BHKZO.js +92 -0
  7. package/dist/components/alert.d.ts +2 -2
  8. package/dist/components/badge.d.ts +1 -1
  9. package/dist/components/button.d.ts +1 -1
  10. package/dist/components/command.d.ts +7 -7
  11. package/dist/components/concept-compare.d.ts +73 -0
  12. package/dist/components/concept-compare.js +162 -0
  13. package/dist/components/event-card.js +1 -1
  14. package/dist/components/event-compact-row.d.ts +1 -1
  15. package/dist/components/event-compact-row.js +1 -1
  16. package/dist/components/event-detail-panel.d.ts +171 -25
  17. package/dist/components/event-detail-panel.js +471 -174
  18. package/dist/components/event-list-row-wide.js +1 -1
  19. package/dist/components/event-list-row.js +1 -1
  20. package/dist/components/hub-surface.d.ts +27 -0
  21. package/dist/components/hub-surface.js +20 -0
  22. package/dist/components/menu-family-card.d.ts +59 -0
  23. package/dist/components/menu-family-card.js +121 -0
  24. package/dist/components/menu-field.d.ts +57 -0
  25. package/dist/components/menu-field.js +3 -0
  26. package/dist/components/menu-hub-hero.d.ts +65 -0
  27. package/dist/components/menu-hub-hero.js +103 -0
  28. package/dist/components/menu-intent-tiles.d.ts +31 -0
  29. package/dist/components/menu-intent-tiles.js +50 -0
  30. package/dist/components/menu-item.d.ts +127 -0
  31. package/dist/components/menu-item.js +5 -0
  32. package/dist/components/menu-mocks.d.ts +55 -0
  33. package/dist/components/menu-mocks.js +379 -0
  34. package/dist/components/menu-types.d.ts +245 -0
  35. package/dist/components/menu-types.js +1 -0
  36. package/dist/components/motion-icon-set.js +15 -3
  37. package/dist/components/open-now-list.d.ts +40 -0
  38. package/dist/components/open-now-list.js +69 -0
  39. package/dist/components/place-types.d.ts +112 -1
  40. package/dist/components/ranked-venue-list.d.ts +49 -0
  41. package/dist/components/ranked-venue-list.js +87 -0
  42. package/dist/components/signature-chip.d.ts +37 -0
  43. package/dist/components/signature-chip.js +4 -0
  44. package/dist/components/signature-line-list.d.ts +45 -0
  45. package/dist/components/signature-line-list.js +99 -0
  46. package/dist/components/snap-rail.d.ts +24 -4
  47. package/dist/components/snap-rail.js +66 -8
  48. package/dist/components/spinner.d.ts +2 -2
  49. package/dist/components/stat-banner.d.ts +47 -0
  50. package/dist/components/stat-banner.js +61 -0
  51. package/dist/components/tag.d.ts +2 -2
  52. package/dist/components/text.d.ts +3 -3
  53. package/dist/components/venue-busyness-strip.d.ts +35 -0
  54. package/dist/components/venue-busyness-strip.js +37 -0
  55. package/dist/components/venue-grade-dimensions.d.ts +36 -0
  56. package/dist/components/venue-grade-dimensions.js +3 -0
  57. package/dist/components/venue-quotes.d.ts +32 -0
  58. package/dist/components/venue-quotes.js +3 -0
  59. package/dist/components/venue-signature-block.d.ts +39 -0
  60. package/dist/components/venue-signature-block.js +6 -0
  61. package/dist/components/venue-spotlight.d.ts +74 -0
  62. package/dist/components/venue-spotlight.js +138 -0
  63. package/dist/components/venue-wall.d.ts +41 -0
  64. package/dist/components/venue-wall.js +118 -0
  65. package/dist/components/weekday-theme-banner.d.ts +70 -0
  66. package/dist/components/weekday-theme-banner.js +127 -0
  67. package/package.json +1 -1
  68. package/src/components/colour-dictionary.stories.tsx +227 -0
  69. package/src/components/concept-compare.stories.tsx +104 -0
  70. package/src/components/concept-compare.tsx +276 -0
  71. package/src/components/event-compact-row.tsx +1 -1
  72. package/src/components/event-detail-panel.stories.tsx +205 -97
  73. package/src/components/event-detail-panel.test.tsx +181 -0
  74. package/src/components/event-detail-panel.tsx +703 -185
  75. package/src/components/hub-surface.stories.tsx +68 -0
  76. package/src/components/hub-surface.tsx +40 -0
  77. package/src/components/menu-family-card.stories.tsx +112 -0
  78. package/src/components/menu-family-card.tsx +211 -0
  79. package/src/components/menu-field.stories.tsx +74 -0
  80. package/src/components/menu-field.tsx +199 -0
  81. package/src/components/menu-hub-hero.stories.tsx +92 -0
  82. package/src/components/menu-hub-hero.tsx +187 -0
  83. package/src/components/menu-intent-tiles.stories.tsx +54 -0
  84. package/src/components/menu-intent-tiles.tsx +76 -0
  85. package/src/components/menu-item.tsx +416 -0
  86. package/src/components/menu-mocks.ts +417 -0
  87. package/src/components/menu-types.ts +259 -0
  88. package/src/components/motion-icon-set.tsx +30 -3
  89. package/src/components/open-now-list.stories.tsx +76 -0
  90. package/src/components/open-now-list.tsx +127 -0
  91. package/src/components/place-types.ts +112 -0
  92. package/src/components/ranked-venue-list.stories.tsx +80 -0
  93. package/src/components/ranked-venue-list.tsx +177 -0
  94. package/src/components/signature-chip.tsx +77 -0
  95. package/src/components/signature-line-list.stories.tsx +85 -0
  96. package/src/components/signature-line-list.tsx +136 -0
  97. package/src/components/snap-rail.tsx +90 -9
  98. package/src/components/stat-banner.stories.tsx +100 -0
  99. package/src/components/stat-banner.tsx +117 -0
  100. package/src/components/venue-busyness-strip.stories.tsx +66 -0
  101. package/src/components/venue-busyness-strip.tsx +64 -0
  102. package/src/components/venue-grade-dimensions.stories.tsx +83 -0
  103. package/src/components/venue-grade-dimensions.tsx +91 -0
  104. package/src/components/venue-quotes.stories.tsx +59 -0
  105. package/src/components/venue-quotes.tsx +64 -0
  106. package/src/components/venue-signature-block.stories.tsx +68 -0
  107. package/src/components/venue-signature-block.tsx +71 -0
  108. package/src/components/venue-spotlight.stories.tsx +141 -0
  109. package/src/components/venue-spotlight.tsx +236 -0
  110. package/src/components/venue-wall.stories.tsx +78 -0
  111. package/src/components/venue-wall.tsx +185 -0
  112. package/src/components/weekday-theme-banner.stories.tsx +113 -0
  113. package/src/components/weekday-theme-banner.tsx +231 -0
@@ -1,6 +1,6 @@
1
1
  import { Separator } from '../chunk-WSRWFA6K.js';
2
- import { Tag } from '../chunk-ZDAHKI3A.js';
3
2
  import { resolveEventEntryFee } from '../chunk-DNSXMHYX.js';
3
+ import { Tag } from '../chunk-ZDAHKI3A.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
1
  import { Separator } from '../chunk-WSRWFA6K.js';
2
2
  import { TagGroup } from '../chunk-6XMYEZVR.js';
3
- import { Tag } from '../chunk-ZDAHKI3A.js';
4
3
  import { resolveEventEntryFee } from '../chunk-DNSXMHYX.js';
4
+ import { Tag } from '../chunk-ZDAHKI3A.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,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,20 @@
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
+ "lg:overflow-visible lg:rounded-none lg:bg-transparent lg:p-0 lg:shadow-none",
13
+ className
14
+ ),
15
+ children
16
+ }
17
+ );
18
+ }
19
+
20
+ export { HubSurface };
@@ -0,0 +1,59 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+ import { MenuItemLabels } from './menu-item.js';
3
+ import { MenuFamilyPreview, MenuLinkComponent, MenuLinkProps } from './menu-types.js';
4
+ import 'react';
5
+ import './place-types.js';
6
+
7
+ /**
8
+ * MenuFamilyCard — a browsable grouping of venues: a cuisine on the dishes tab,
9
+ * a drink family on the drinks tab.
10
+ *
11
+ * ## The count does the scanning, the item answers the question
12
+ *
13
+ * The card is two halves with two jobs. The dark panel is for scanning a row of
14
+ * four: one number, one name, and the groups the family consists of, over a
15
+ * mosaic of its venues. The white half underneath answers what you would
16
+ * actually order — the production `<MenuItem>`, with its price, portion and
17
+ * mention count, so the recommendation is the review census rather than an
18
+ * editorial pick.
19
+ *
20
+ * ## Both halves count VENUES
21
+ *
22
+ * Cuisine exists on places and nowhere else — no listing endpoint will tell you
23
+ * how many dishes are Italian — so a card counting dishes cannot be built and
24
+ * this one does not try. The groups line is a plain list without counts: at
25
+ * four cards across, four chips each carrying a number turned the panel into a
26
+ * table, and the numbers were kitchens, which nobody reads a chip as.
27
+ *
28
+ * The mosaic holds VENUE photographs, which are real. It dims them hard — this
29
+ * is a ground for a number, not a gallery, and at this size a legible photo
30
+ * competes with the count for the same glance.
31
+ */
32
+ /** @deprecated Alias of `MenuLinkProps`; kept so existing imports keep working. */
33
+ type MenuFamilyCardLinkProps = MenuLinkProps;
34
+ type MenuFamilyCardLabels = {
35
+ /** Unit under the count, e.g. "KITCHENS". */
36
+ venueUnit: string;
37
+ /** Kicker over the featured item, e.g. "MOST NAMED". */
38
+ mostNamed: string;
39
+ /** Footer caption, e.g. "kitchen price band". */
40
+ priceBand: string;
41
+ };
42
+ type MenuFamilyCardProps = {
43
+ family: MenuFamilyPreview;
44
+ labels: MenuFamilyCardLabels;
45
+ /** Passed through to the featured `<MenuItem>`. */
46
+ itemLabels: MenuItemLabels;
47
+ formatPrice: (amount: number) => string;
48
+ formatCount?: (value: number) => string;
49
+ href: string;
50
+ /** Finished CSS colour for the featured item's category mark. */
51
+ hue?: string;
52
+ /** Composed price band, e.g. "32–84 lei". */
53
+ priceBandLabel?: string;
54
+ linkComponent?: MenuLinkComponent;
55
+ className?: string;
56
+ };
57
+ declare function MenuFamilyCard({ family, labels, itemLabels, formatPrice, formatCount, href, hue, priceBandLabel, linkComponent: LinkComponent, className, }: MenuFamilyCardProps): react_jsx_runtime.JSX.Element;
58
+
59
+ export { MenuFamilyCard, type MenuFamilyCardLabels, type MenuFamilyCardLinkProps, type MenuFamilyCardProps };
@@ -0,0 +1,121 @@
1
+ import { MenuItem } from '../chunk-TH6W2PWG.js';
2
+ import '../chunk-Q6QH52LJ.js';
3
+ import '../chunk-UKBNQO6Q.js';
4
+ import { cn } from '../chunk-FEK5XGZW.js';
5
+ import { __objRest, __spreadValues } from '../chunk-3GWWZKX7.js';
6
+ import { Wallet } from 'lucide-react';
7
+ import { jsxs, jsx } from 'react/jsx-runtime';
8
+
9
+ function DefaultLink(_a) {
10
+ var _b = _a, { href } = _b, props = __objRest(_b, ["href"]);
11
+ return /* @__PURE__ */ jsx("a", __spreadValues({ href }, props));
12
+ }
13
+ var MOSAIC_CELLS = 4;
14
+ function MenuFamilyCard({
15
+ family,
16
+ labels,
17
+ itemLabels,
18
+ formatPrice,
19
+ formatCount,
20
+ href,
21
+ hue,
22
+ priceBandLabel,
23
+ linkComponent: LinkComponent = DefaultLink,
24
+ className
25
+ }) {
26
+ var _a, _b, _c;
27
+ const photos = (_a = family.venue_photo_urls) != null ? _a : [];
28
+ const groups = ((_b = family.chips) != null ? _b : []).map((chip) => chip.label);
29
+ return /* @__PURE__ */ jsxs(
30
+ "div",
31
+ {
32
+ className: cn(
33
+ "flex h-full flex-col overflow-hidden rounded-[14px] border border-border bg-card",
34
+ className
35
+ ),
36
+ children: [
37
+ /* @__PURE__ */ jsxs(
38
+ LinkComponent,
39
+ {
40
+ href,
41
+ className: "relative block overflow-hidden bg-[oklch(0.21_0.012_55)] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ring",
42
+ children: [
43
+ /* @__PURE__ */ jsx("span", { "aria-hidden": true, className: "absolute inset-0 grid grid-cols-2 grid-rows-2 gap-px", children: Array.from(
44
+ { length: MOSAIC_CELLS },
45
+ (_, cell) => photos.length ? (
46
+ // eslint-disable-next-line @next/next/no-img-element
47
+ /* @__PURE__ */ jsx(
48
+ "img",
49
+ {
50
+ src: photos[cell % photos.length],
51
+ alt: "",
52
+ loading: "lazy",
53
+ decoding: "async",
54
+ className: "h-full w-full object-cover"
55
+ },
56
+ cell
57
+ )
58
+ ) : /* @__PURE__ */ jsx(
59
+ "span",
60
+ {
61
+ className: "bg-[repeating-linear-gradient(135deg,oklch(0.6_0.02_55)_0_9px,oklch(0.68_0.016_58)_9px_18px)] opacity-[0.18]"
62
+ },
63
+ cell
64
+ )
65
+ ) }),
66
+ hue && /* @__PURE__ */ jsx(
67
+ "span",
68
+ {
69
+ "aria-hidden": true,
70
+ className: "absolute inset-0 opacity-90 mix-blend-color",
71
+ style: { backgroundColor: hue }
72
+ }
73
+ ),
74
+ /* @__PURE__ */ jsx(
75
+ "span",
76
+ {
77
+ "aria-hidden": true,
78
+ className: "absolute inset-0 bg-[linear-gradient(180deg,oklch(0.16_0.012_55_/_0.9)_0%,oklch(0.16_0.012_55_/_0.62)_45%,oklch(0.16_0.012_55_/_0.94)_100%)]"
79
+ }
80
+ ),
81
+ /* @__PURE__ */ jsxs("span", { className: "relative flex min-h-[260px] flex-col justify-between p-3.5 text-white", children: [
82
+ /* @__PURE__ */ jsxs("span", { className: "block", children: [
83
+ /* @__PURE__ */ jsx("b", { className: "block text-[34px] font-extrabold leading-none tracking-[-0.03em] tabular-nums", children: family.venue_count }),
84
+ /* @__PURE__ */ jsx("span", { className: "mt-1 block text-[9.5px] font-bold tracking-[0.12em] opacity-80", children: labels.venueUnit })
85
+ ] }),
86
+ /* @__PURE__ */ jsxs("span", { className: "block", children: [
87
+ /* @__PURE__ */ jsx("span", { className: "block text-[17px] font-bold tracking-[-0.01em]", children: family.name }),
88
+ groups.length > 0 && /* @__PURE__ */ jsx("span", { className: "mt-0.5 block truncate text-[11.5px] opacity-75", children: groups.join(" \xB7 ") }),
89
+ priceBandLabel && /* @__PURE__ */ jsxs("span", { className: "mt-2 inline-flex items-center gap-1.5 rounded-full bg-white/15 px-2.5 py-1 text-[11px] backdrop-blur-[2px]", children: [
90
+ /* @__PURE__ */ jsx(Wallet, { className: "h-3 w-3 shrink-0", "aria-hidden": true }),
91
+ /* @__PURE__ */ jsx("span", { className: "opacity-80", children: labels.priceBand }),
92
+ /* @__PURE__ */ jsx("b", { className: "font-semibold tabular-nums", children: priceBandLabel })
93
+ ] })
94
+ ] })
95
+ ] })
96
+ ]
97
+ }
98
+ ),
99
+ family.signature && /* @__PURE__ */ jsxs("div", { className: "flex flex-1 flex-col px-2 pb-2 pt-3", children: [
100
+ /* @__PURE__ */ jsx("span", { className: "px-2 text-[9.5px] font-bold uppercase tracking-[0.09em] text-muted-foreground", children: labels.mostNamed }),
101
+ /* @__PURE__ */ jsx(
102
+ MenuItem,
103
+ {
104
+ className: "min-h-0",
105
+ item: family.signature.item,
106
+ labels: itemLabels,
107
+ formatPrice,
108
+ formatCount,
109
+ markGradient: hue,
110
+ subtitle: family.signature.why,
111
+ href: (_c = family.signature.href) != null ? _c : href,
112
+ linkComponent: LinkComponent
113
+ }
114
+ )
115
+ ] })
116
+ ]
117
+ }
118
+ );
119
+ }
120
+
121
+ export { MenuFamilyCard };
@@ -0,0 +1,57 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+
3
+ /**
4
+ * MenuField — the photoless cover surface for a dish or drink.
5
+ *
6
+ * Menu-item photography is AI-generated and never shown, so the item's own name
7
+ * repeats across a shallow diagonal over a gradient. The type IS the cover.
8
+ *
9
+ * The rows fade SYMMETRICALLY out from a single strong row at the centre. That
10
+ * shape is what keeps the type reading as texture in the colour rather than as
11
+ * a watermark stamped on top of it: the eye lands on one legible line and the
12
+ * rest falls away in both directions. A uniform wash — or a random per-row
13
+ * alpha — reads as noise at every alpha high enough to see.
14
+ *
15
+ * The alpha curve is IDENTICAL for every hue. Scaling it per hue to even out
16
+ * how white type reads over different grounds also moves where rows fall below
17
+ * visibility, so a damped hue lost rows and one card showed five bands where
18
+ * its neighbour showed three. Correct contrast in the `gradient` you pass, not
19
+ * in the curve.
20
+ *
21
+ * ## Why `gradient` is a prop
22
+ *
23
+ * Resolving a category slug to a hue needs the app's category enums, which the
24
+ * DS does not take. Callers pass a finished CSS gradient — fe from its own
25
+ * category-colour table, post-generator from whatever it is filming. The DS
26
+ * owns the typography and the falloff; the consumer owns the palette.
27
+ *
28
+ * Server-safe: no state, no effects. The row geometry is a 13-iteration loop
29
+ * computed on every render, which is cheaper than the hook that would let it be
30
+ * skipped.
31
+ */
32
+ type MenuFieldProps = {
33
+ /** Item name — rendered as the repeating ghost. */
34
+ name: string;
35
+ /** CSS `background-image` value. See the note above on why this is injected. */
36
+ gradient: string;
37
+ /**
38
+ * Deterministic stagger seed. Pass the PARENT CONCEPT SLUG so one dish breaks
39
+ * its lines identically at every venue and across re-sorts. Defaults to
40
+ * `name`, which is stable but shared by every venue's version of a dish.
41
+ */
42
+ seedKey?: string;
43
+ /**
44
+ * `repeat` (default) — the name as texture, thirteen fading rows. Right at
45
+ * swatch and thumbnail sizes, where any single line would be unreadable.
46
+ *
47
+ * `display` — the name ONCE, set large and bled off the edges. Right when the
48
+ * tile is the primary object on its row and the name is the thing being sold;
49
+ * the repeat treatment at that size reads as wallpaper rather than as a dish.
50
+ * Long names clip on purpose — the caption underneath carries the full text.
51
+ */
52
+ variant?: "repeat" | "display";
53
+ className?: string;
54
+ };
55
+ declare function MenuField({ name, gradient, seedKey, variant, className, }: MenuFieldProps): react_jsx_runtime.JSX.Element;
56
+
57
+ export { MenuField, type MenuFieldProps };
@@ -0,0 +1,3 @@
1
+ export { MenuField } from '../chunk-YS6BHKZO.js';
2
+ import '../chunk-FEK5XGZW.js';
3
+ import '../chunk-3GWWZKX7.js';
@@ -0,0 +1,65 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+
3
+ /**
4
+ * MenuHubHero — the claim that opens a menu hub, proven by live figures.
5
+ *
6
+ * A statement rather than a count ("Nobody should have to walk in blind"),
7
+ * over a dark ground, with the catalogue's real numbers under it and one proof
8
+ * line that demonstrates what the catalogue can do that a single menu cannot.
9
+ *
10
+ * ## Why the ground is a texture and not a photograph
11
+ *
12
+ * Menu-item photography is AI-generated and never shown, and one venue
13
+ * photograph behind a claim about every venue in the city argues the wrong
14
+ * thing. The tile grid reads as "many menus" without being any of them.
15
+ * Callers that DO have a real, licensed image for the slot can pass
16
+ * `backgroundUrl` — venue photography, never a plate.
17
+ *
18
+ * ## Figures
19
+ *
20
+ * `figures` is a list, not four fixed slots: the drinks tab has different ones
21
+ * and the count changes when a number turns out to have nothing behind it.
22
+ * Every value is a pre-formatted string because thousands separators are
23
+ * locale-dependent and the DS owns no locale.
24
+ *
25
+ * Server-safe.
26
+ */
27
+ type MenuHubHeroFigure = {
28
+ /** Pre-formatted, e.g. "19,062". */
29
+ value: string;
30
+ label: string;
31
+ };
32
+ type MenuHubHeroProps = {
33
+ /** Small caps line above the headline, e.g. "397 MENUS IN CLUJ-NAPOCA, READ IN FULL". */
34
+ kicker?: string;
35
+ /** Icon for the kicker, resolved by the caller. */
36
+ kickerIcon?: React.ReactNode;
37
+ headline: string;
38
+ body?: string;
39
+ figures?: MenuHubHeroFigure[];
40
+ /**
41
+ * The proof line — one demonstrated fact, e.g. "A margherita: 87 kitchens
42
+ * make one, 10 to 120 lei". Composed by the caller; clause order is
43
+ * locale-dependent.
44
+ */
45
+ proof?: React.ReactNode;
46
+ proofIcon?: React.ReactNode;
47
+ /** The action beside the proof line. Pass a rendered link or button. */
48
+ action?: React.ReactNode;
49
+ /** Real, licensed photograph for the ground. Venue imagery only — see above. */
50
+ backgroundUrl?: string;
51
+ /**
52
+ * Tiles for the mosaic ground — generic, cinematic, licensed stills, served
53
+ * from the consumer's own origin.
54
+ *
55
+ * A mosaic rather than one photograph because the hero's claim is about EVERY
56
+ * kitchen in the city: a single image makes it about one of them. Given fewer
57
+ * tiles than cells the list repeats, so a caller can ship six and still fill
58
+ * a 1440px hero.
59
+ */
60
+ tileUrls?: string[];
61
+ className?: string;
62
+ };
63
+ declare function MenuHubHero({ kicker, kickerIcon, headline, body, figures, proof, proofIcon, action, backgroundUrl, tileUrls, className, }: MenuHubHeroProps): react_jsx_runtime.JSX.Element;
64
+
65
+ export { MenuHubHero, type MenuHubHeroFigure, type MenuHubHeroProps };
@@ -0,0 +1,103 @@
1
+ import { cn } from '../chunk-FEK5XGZW.js';
2
+ import '../chunk-3GWWZKX7.js';
3
+ import { jsxs, jsx } from 'react/jsx-runtime';
4
+
5
+ var TEXTURE_CELLS = 28;
6
+ function MenuHubHero({
7
+ kicker,
8
+ kickerIcon,
9
+ headline,
10
+ body,
11
+ figures,
12
+ proof,
13
+ proofIcon,
14
+ action,
15
+ backgroundUrl,
16
+ tileUrls,
17
+ className
18
+ }) {
19
+ return /* @__PURE__ */ jsxs(
20
+ "div",
21
+ {
22
+ className: cn(
23
+ "relative flex min-h-[260px] overflow-hidden rounded-[20px] bg-[oklch(0.21_0.012_55)] lg:min-h-[440px]",
24
+ className
25
+ ),
26
+ children: [
27
+ backgroundUrl ? (
28
+ // eslint-disable-next-line @next/next/no-img-element
29
+ /* @__PURE__ */ jsx(
30
+ "img",
31
+ {
32
+ src: backgroundUrl,
33
+ alt: "",
34
+ "aria-hidden": true,
35
+ className: "absolute inset-0 h-full w-full object-cover opacity-[0.42]"
36
+ }
37
+ )
38
+ ) : /* @__PURE__ */ jsx(
39
+ "div",
40
+ {
41
+ "aria-hidden": true,
42
+ className: "absolute inset-0 grid auto-rows-fr grid-cols-4 gap-0.5 lg:grid-cols-7",
43
+ children: Array.from({ length: TEXTURE_CELLS }, (_, cell) => {
44
+ const url = (tileUrls == null ? void 0 : tileUrls.length) ? tileUrls[cell % tileUrls.length] : void 0;
45
+ return url ? (
46
+ // eslint-disable-next-line @next/next/no-img-element
47
+ /* @__PURE__ */ jsx(
48
+ "img",
49
+ {
50
+ src: url,
51
+ alt: "",
52
+ loading: "lazy",
53
+ decoding: "async",
54
+ className: "h-full w-full object-cover"
55
+ },
56
+ cell
57
+ )
58
+ ) : /* @__PURE__ */ jsx(
59
+ "div",
60
+ {
61
+ className: "bg-[repeating-linear-gradient(135deg,oklch(0.6_0.02_55)_0_9px,oklch(0.68_0.016_58)_9px_18px)] opacity-[0.42]"
62
+ },
63
+ cell
64
+ );
65
+ })
66
+ }
67
+ ),
68
+ /* @__PURE__ */ jsx(
69
+ "div",
70
+ {
71
+ "aria-hidden": true,
72
+ className: cn(
73
+ "absolute inset-0",
74
+ (tileUrls == null ? void 0 : tileUrls.length) || backgroundUrl ? "bg-[linear-gradient(100deg,oklch(0.16_0.012_55_/_0.96)_0%,oklch(0.16_0.012_55_/_0.88)_52%,oklch(0.16_0.012_55_/_0.72)_100%)]" : "bg-[linear-gradient(100deg,oklch(0.18_0.012_55_/_0.95)_0%,oklch(0.18_0.012_55_/_0.72)_52%,oklch(0.18_0.012_55_/_0.3)_100%)]"
75
+ )
76
+ }
77
+ ),
78
+ /* @__PURE__ */ jsxs("div", { className: "relative z-[1] flex w-full flex-col justify-center gap-3 p-5 text-white lg:gap-4 lg:px-12 lg:py-11", children: [
79
+ kicker && /* @__PURE__ */ jsxs("span", { className: "inline-flex w-max items-center gap-[7px] rounded-full bg-white/15 px-3 py-1.5 text-[11px] font-extrabold tracking-[0.11em]", children: [
80
+ kickerIcon,
81
+ kicker
82
+ ] }),
83
+ /* @__PURE__ */ jsx("h1", { className: "max-w-[640px] font-serif text-[32px] font-semibold leading-[1.04] tracking-[-0.025em] lg:text-[50px]", children: headline }),
84
+ body && /* @__PURE__ */ jsx("p", { className: "hidden max-w-[540px] text-sm leading-[1.6] opacity-[0.84] lg:block lg:text-[15.5px]", children: body }),
85
+ figures && figures.length > 0 && // Two by two on the phone: four figures in one row wrapped to three and an orphan.
86
+ /* @__PURE__ */ jsx("div", { className: "grid grid-cols-2 gap-x-6 gap-y-3 pt-3 lg:flex lg:flex-wrap lg:gap-x-8 lg:gap-y-4 lg:pt-1.5", children: figures.map((figure) => /* @__PURE__ */ jsxs("div", { children: [
87
+ /* @__PURE__ */ jsx("div", { className: "text-2xl font-black leading-none tracking-[-0.03em] tabular-nums lg:text-[32px]", children: figure.value }),
88
+ /* @__PURE__ */ jsx("div", { className: "mt-[5px] text-xs opacity-[0.72]", children: figure.label })
89
+ ] }, figure.label)) }),
90
+ (proof || action) && /* @__PURE__ */ jsxs("div", { className: "mt-auto flex flex-wrap items-center gap-3 pt-5", children: [
91
+ proof && /* @__PURE__ */ jsxs("span", { className: "inline-flex min-h-11 items-center gap-2.5 rounded-full bg-white/10 px-4 py-2 text-[13px] backdrop-blur-[6px]", children: [
92
+ proofIcon,
93
+ proof
94
+ ] }),
95
+ action
96
+ ] })
97
+ ] })
98
+ ]
99
+ }
100
+ );
101
+ }
102
+
103
+ export { MenuHubHero };
@@ -0,0 +1,31 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+ import { MenuIntentTile, MenuLinkComponent, MenuLinkProps } from './menu-types.js';
3
+ import 'react';
4
+ import './place-types.js';
5
+
6
+ /**
7
+ * MenuIntentTiles — the sentence people arrive with, as six coloured tiles.
8
+ *
9
+ * An intent is a family plus a constraint: Italian *tonight*, burgers, coffee.
10
+ * Each tile is a destination, so `href` is required on the view model — a
11
+ * category page that does not exist is filtered out by the caller rather than
12
+ * rendered as a tile that links nowhere. Roughly thirty-two of thirty-eight
13
+ * cuisines have a page, so that case is normal, not exceptional.
14
+ *
15
+ * The icon is a watermark bleeding off the bottom-right corner at low opacity,
16
+ * not a badge: it gives the tile a silhouette to be recognised by in a rail
17
+ * without competing with the label. It is `aria-hidden` — the label already
18
+ * says what the tile is.
19
+ *
20
+ * Server-safe.
21
+ */
22
+ /** @deprecated Alias of `MenuLinkProps`; kept so existing imports keep working. */
23
+ type MenuIntentTilesLinkProps = MenuLinkProps;
24
+ type MenuIntentTilesProps = {
25
+ tiles: MenuIntentTile[];
26
+ linkComponent?: MenuLinkComponent;
27
+ className?: string;
28
+ };
29
+ declare function MenuIntentTiles({ tiles, linkComponent: LinkComponent, className, }: MenuIntentTilesProps): react_jsx_runtime.JSX.Element;
30
+
31
+ export { MenuIntentTiles, type MenuIntentTilesLinkProps, type MenuIntentTilesProps };
@@ -0,0 +1,50 @@
1
+ import { cn } from '../chunk-FEK5XGZW.js';
2
+ import { __objRest, __spreadValues } from '../chunk-3GWWZKX7.js';
3
+ import { ChevronRight } from 'lucide-react';
4
+ import { jsx, jsxs } from 'react/jsx-runtime';
5
+
6
+ function DefaultLink(_a) {
7
+ var _b = _a, { href } = _b, props = __objRest(_b, ["href"]);
8
+ return /* @__PURE__ */ jsx("a", __spreadValues({ href }, props));
9
+ }
10
+ function MenuIntentTiles({
11
+ tiles,
12
+ linkComponent: LinkComponent = DefaultLink,
13
+ className
14
+ }) {
15
+ return /* @__PURE__ */ jsx(
16
+ "div",
17
+ {
18
+ className: cn(
19
+ "grid grid-cols-2 gap-3 lg:grid-cols-6 lg:gap-3.5",
20
+ className
21
+ ),
22
+ children: tiles.map((tile) => /* @__PURE__ */ jsxs(
23
+ LinkComponent,
24
+ {
25
+ href: tile.href,
26
+ className: "relative flex min-h-[110px] flex-col overflow-hidden rounded-2xl bg-muted p-3.5 text-white focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 lg:min-h-[124px] lg:px-3.5 lg:pb-3.5 lg:pt-[15px]",
27
+ style: tile.gradient ? { backgroundImage: tile.gradient } : void 0,
28
+ children: [
29
+ tile.icon && /* @__PURE__ */ jsx(
30
+ "span",
31
+ {
32
+ "aria-hidden": true,
33
+ className: "pointer-events-none absolute -bottom-2.5 -right-1.5 z-0 opacity-[0.22] [&>svg]:h-[62px] [&>svg]:w-[62px]",
34
+ children: tile.icon
35
+ }
36
+ ),
37
+ /* @__PURE__ */ jsxs("span", { className: "relative z-[1] flex h-full flex-col", children: [
38
+ /* @__PURE__ */ jsx("span", { className: "text-[14.5px] font-extrabold leading-[1.2] tracking-[-0.015em]", children: tile.label }),
39
+ tile.caption && /* @__PURE__ */ jsx("span", { className: "mt-[3px] text-[11.5px] leading-[1.35]", children: tile.caption }),
40
+ /* @__PURE__ */ jsx(ChevronRight, { className: "mt-auto h-4 w-4 opacity-85", "aria-hidden": true })
41
+ ] })
42
+ ]
43
+ },
44
+ tile.slug
45
+ ))
46
+ }
47
+ );
48
+ }
49
+
50
+ export { MenuIntentTiles };