@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,118 @@
1
+ import { cn } from '../chunk-FEK5XGZW.js';
2
+ import { __objRest, __spreadValues } from '../chunk-3GWWZKX7.js';
3
+ import { jsxs, Fragment, jsx } from 'react/jsx-runtime';
4
+
5
+ function DefaultLink(_a) {
6
+ var _b = _a, { href } = _b, props = __objRest(_b, ["href"]);
7
+ return /* @__PURE__ */ jsx("a", __spreadValues({ href }, props));
8
+ }
9
+ function VenueWall({
10
+ places,
11
+ hrefs,
12
+ kicker,
13
+ kickerIcon,
14
+ headline,
15
+ body,
16
+ overflowLabel,
17
+ tileUrls,
18
+ action,
19
+ linkComponent: LinkComponent = DefaultLink,
20
+ className
21
+ }) {
22
+ return /* @__PURE__ */ jsxs(
23
+ "div",
24
+ {
25
+ className: cn(
26
+ "relative flex flex-col gap-4 overflow-hidden rounded-[20px] bg-[oklch(0.21_0.012_55)] p-5 text-white lg:p-8",
27
+ className
28
+ ),
29
+ children: [
30
+ (tileUrls == null ? void 0 : tileUrls.length) ? /* @__PURE__ */ jsxs(Fragment, { children: [
31
+ /* @__PURE__ */ jsx("div", { "aria-hidden": true, className: "absolute inset-0 grid auto-rows-fr grid-cols-4 gap-0.5 lg:grid-cols-6", children: Array.from({ length: 18 }, (_, cell) => (
32
+ // eslint-disable-next-line @next/next/no-img-element
33
+ /* @__PURE__ */ jsx(
34
+ "img",
35
+ {
36
+ src: tileUrls[cell % tileUrls.length],
37
+ alt: "",
38
+ loading: "lazy",
39
+ decoding: "async",
40
+ className: "h-full w-full object-cover"
41
+ },
42
+ cell
43
+ )
44
+ )) }),
45
+ /* @__PURE__ */ jsx(
46
+ "div",
47
+ {
48
+ "aria-hidden": true,
49
+ className: "absolute inset-0 bg-[oklch(0.16_0.012_55_/_0.9)]"
50
+ }
51
+ )
52
+ ] }) : /* @__PURE__ */ jsx(
53
+ "div",
54
+ {
55
+ "aria-hidden": true,
56
+ className: "absolute inset-0 bg-[repeating-linear-gradient(135deg,oklch(0.6_0.02_55)_0_9px,oklch(0.68_0.016_58)_9px_18px)] opacity-[0.18]"
57
+ }
58
+ ),
59
+ /* @__PURE__ */ jsxs("div", { className: "relative z-[1] flex flex-col gap-3", children: [
60
+ kicker && /* @__PURE__ */ jsxs("span", { className: "inline-flex w-max items-center gap-1.5 rounded-full bg-white/15 px-3 py-1.5 text-[11px] font-extrabold tracking-[0.11em]", children: [
61
+ kickerIcon,
62
+ kicker
63
+ ] }),
64
+ headline && /* @__PURE__ */ jsx("h3", { className: "max-w-[640px] font-serif text-[28px] font-semibold leading-[1.1] tracking-[-0.025em] lg:text-[40px]", children: headline }),
65
+ body && /* @__PURE__ */ jsx("p", { className: "hidden max-w-[540px] text-sm leading-[1.55] opacity-85 lg:block", children: body })
66
+ ] }),
67
+ /* @__PURE__ */ jsxs("div", { "aria-hidden": true, className: "relative z-[1] flex items-center gap-3 lg:hidden", children: [
68
+ /* @__PURE__ */ jsx("div", { className: "flex", children: places.slice(0, 5).map((place, index) => /* @__PURE__ */ jsx(
69
+ "span",
70
+ {
71
+ className: cn(
72
+ "h-9 w-9 shrink-0 overflow-hidden rounded-full bg-white/20 ring-2 ring-[oklch(0.21_0.012_55)]",
73
+ index > 0 && "-ml-2.5"
74
+ ),
75
+ children: place.thumbnail_url && // eslint-disable-next-line @next/next/no-img-element
76
+ /* @__PURE__ */ jsx("img", { src: place.thumbnail_url, alt: "", className: "h-full w-full object-cover", loading: "lazy" })
77
+ },
78
+ place.slug
79
+ )) }),
80
+ overflowLabel && /* @__PURE__ */ jsx("span", { className: "text-[13px] font-medium opacity-85", children: overflowLabel })
81
+ ] }),
82
+ /* @__PURE__ */ jsxs("ul", { className: "relative z-[1] hidden flex-wrap items-center gap-2 lg:flex", children: [
83
+ places.map((place) => {
84
+ const avatar = /* @__PURE__ */ jsxs(Fragment, { children: [
85
+ /* @__PURE__ */ jsx("span", { className: "h-7 w-7 shrink-0 overflow-hidden rounded-full bg-white/20", children: place.thumbnail_url && // eslint-disable-next-line @next/next/no-img-element
86
+ /* @__PURE__ */ jsx(
87
+ "img",
88
+ {
89
+ src: place.thumbnail_url,
90
+ alt: "",
91
+ className: "h-full w-full object-cover",
92
+ loading: "lazy"
93
+ }
94
+ ) }),
95
+ /* @__PURE__ */ jsx("span", { className: "truncate text-[12.5px] font-medium", children: place.name })
96
+ ] });
97
+ const chipClasses = "inline-flex max-w-[220px] items-center gap-2 rounded-full bg-white/10 py-1 pl-1 pr-3.5 backdrop-blur-[6px]";
98
+ return /* @__PURE__ */ jsx("li", { children: (hrefs == null ? void 0 : hrefs[place.slug]) ? /* @__PURE__ */ jsx(
99
+ LinkComponent,
100
+ {
101
+ href: hrefs[place.slug],
102
+ className: cn(
103
+ chipClasses,
104
+ "transition-colors hover:bg-white/20 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-white/60"
105
+ ),
106
+ children: avatar
107
+ }
108
+ ) : /* @__PURE__ */ jsx("span", { className: chipClasses, children: avatar }) }, place.slug);
109
+ }),
110
+ overflowLabel && /* @__PURE__ */ jsx("li", { className: "inline-flex h-9 items-center rounded-full bg-white/10 px-4 text-[12.5px] opacity-80", children: overflowLabel })
111
+ ] }),
112
+ action && /* @__PURE__ */ jsx("div", { className: "relative z-[1] mt-1", children: action })
113
+ ]
114
+ }
115
+ );
116
+ }
117
+
118
+ export { VenueWall };
@@ -0,0 +1,70 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+
3
+ /**
4
+ * WeekdayThemeBanner — the day's editorial theme, with the whole week beside it.
5
+ *
6
+ * Showing all seven days is the point: a banner that only says "it is Burger
7
+ * Thursday" is an assertion, while one that shows Wednesday was salads and
8
+ * Friday is pizza reads as a standing rhythm the reader can come back for. The
9
+ * other six days are also the only affordance for browsing to one.
10
+ *
11
+ * ## The active day is the caller's, not the component's
12
+ *
13
+ * `activeDay` is an index into `days`, resolved by the caller from the READER's
14
+ * clock. Resolving it here would use the server's, and a server in UTC and a
15
+ * reader in Bucharest disagree about which day it is for two hours out of every
16
+ * twenty-four.
17
+ *
18
+ * No directive: with `dayHref` the week rail is links and renders on the
19
+ * server; with `onDayChange` it is buttons and needs a client caller.
20
+ */
21
+ type WeekdayThemeBannerLinkProps = {
22
+ href: string;
23
+ className?: string;
24
+ children: React.ReactNode;
25
+ };
26
+ type WeekdayThemeDay = {
27
+ /** Short code, e.g. "THU". Already localised. */
28
+ code: string;
29
+ /** Theme name, e.g. "Burger Thursday". */
30
+ name: string;
31
+ /** Short form for the rail, e.g. "Burger". Falls back to `name`. */
32
+ shortName?: string;
33
+ /** Items under the theme. Omit where there is no count to show. */
34
+ count?: number;
35
+ };
36
+ type WeekdayThemeBannerProps = {
37
+ days: WeekdayThemeDay[];
38
+ /** Index into `days` for the reader's own today. */
39
+ activeDay: number;
40
+ /** Kicker over the title, e.g. "TODAY IS THURSDAY". Composed by the caller. */
41
+ kicker?: string;
42
+ kickerIcon?: React.ReactNode;
43
+ /** The banner's body copy for the active day. */
44
+ body?: string;
45
+ /** Heading over the week rail, e.g. "THE WEEK". */
46
+ weekLabel?: string;
47
+ /** The primary action — pass a rendered button or link. */
48
+ action?: React.ReactNode;
49
+ /** Note beside the action, e.g. "Bruno picks nine, ranked on mentions". */
50
+ actionNote?: string;
51
+ /** Destination per day index. Renders the rail as links. */
52
+ dayHref?: (day: WeekdayThemeDay, index: number) => string;
53
+ /** Selects a day in place. Renders the rail as buttons. */
54
+ onDayChange?: (index: number) => void;
55
+ /**
56
+ * The day's photograph.
57
+ *
58
+ * This is the one editorial slot on a menu hub that carries a plated dish,
59
+ * and it can because these are art-directed assets shipped with the app, not
60
+ * generated item photos pulled off a menu row. Without one the banner falls
61
+ * back to its flat ground and still reads — but the flat version is the
62
+ * degraded state, not the design.
63
+ */
64
+ imageUrl?: string;
65
+ linkComponent?: React.ComponentType<WeekdayThemeBannerLinkProps>;
66
+ className?: string;
67
+ };
68
+ declare function WeekdayThemeBanner({ days, activeDay, kicker, kickerIcon, body, weekLabel, action, actionNote, dayHref, onDayChange, imageUrl, linkComponent: LinkComponent, className, }: WeekdayThemeBannerProps): react_jsx_runtime.JSX.Element | null;
69
+
70
+ export { WeekdayThemeBanner, type WeekdayThemeBannerLinkProps, type WeekdayThemeBannerProps, type WeekdayThemeDay };
@@ -0,0 +1,127 @@
1
+ import { cn } from '../chunk-FEK5XGZW.js';
2
+ import { __objRest, __spreadValues } from '../chunk-3GWWZKX7.js';
3
+ import { jsxs, Fragment, jsx } from 'react/jsx-runtime';
4
+
5
+ function DefaultLink(_a) {
6
+ var _b = _a, { href } = _b, props = __objRest(_b, ["href"]);
7
+ return /* @__PURE__ */ jsx("a", __spreadValues({ href }, props));
8
+ }
9
+ function WeekdayThemeBanner({
10
+ days,
11
+ activeDay,
12
+ kicker,
13
+ kickerIcon,
14
+ body,
15
+ weekLabel,
16
+ action,
17
+ actionNote,
18
+ dayHref,
19
+ onDayChange,
20
+ imageUrl,
21
+ linkComponent: LinkComponent = DefaultLink,
22
+ className
23
+ }) {
24
+ const active = days[activeDay];
25
+ if (!active) return null;
26
+ return /* @__PURE__ */ jsxs(
27
+ "div",
28
+ {
29
+ className: cn(
30
+ "relative grid overflow-hidden rounded-[18px] bg-[linear-gradient(120deg,oklch(0.32_0.06_45),oklch(0.24_0.04_40))] text-white lg:grid-cols-[1fr_auto]",
31
+ className
32
+ ),
33
+ children: [
34
+ imageUrl && /* @__PURE__ */ jsxs(Fragment, { children: [
35
+ /* @__PURE__ */ jsx(
36
+ "img",
37
+ {
38
+ src: imageUrl,
39
+ alt: "",
40
+ "aria-hidden": true,
41
+ className: "absolute inset-0 h-full w-full object-cover"
42
+ }
43
+ ),
44
+ /* @__PURE__ */ jsx(
45
+ "div",
46
+ {
47
+ "aria-hidden": true,
48
+ className: "absolute inset-0 bg-[linear-gradient(100deg,oklch(0.18_0.03_45_/_0.94)_0%,oklch(0.18_0.03_45_/_0.82)_45%,oklch(0.18_0.03_45_/_0.66)_100%)]"
49
+ }
50
+ )
51
+ ] }),
52
+ !imageUrl && /* @__PURE__ */ jsx(
53
+ "span",
54
+ {
55
+ "aria-hidden": true,
56
+ className: "pointer-events-none absolute bottom-0 right-[28%] select-none font-serif text-[140px] font-bold leading-none text-white/[0.06]",
57
+ children: active.code
58
+ }
59
+ ),
60
+ /* @__PURE__ */ jsxs("div", { className: "relative z-[1] flex flex-col gap-2.5 p-4 lg:gap-3 lg:p-6", children: [
61
+ kicker && /* @__PURE__ */ jsxs("span", { className: "inline-flex w-max items-center gap-1.5 rounded-full bg-white/15 px-2.5 py-1 text-[10.5px] font-bold tracking-[0.09em]", children: [
62
+ kickerIcon,
63
+ kicker
64
+ ] }),
65
+ /* @__PURE__ */ jsx("h3", { className: "font-serif text-[24px] font-semibold tracking-[-0.02em] lg:text-[36px]", children: active.name }),
66
+ body && /* @__PURE__ */ jsx("p", { className: "max-w-[520px] text-[13.5px] leading-[1.5] opacity-90", children: body }),
67
+ (action || actionNote) && /* @__PURE__ */ jsxs("div", { className: "mt-1 flex flex-wrap items-center gap-3", children: [
68
+ action,
69
+ actionNote && /* @__PURE__ */ jsx("span", { className: "text-xs opacity-75", children: actionNote })
70
+ ] })
71
+ ] }),
72
+ /* @__PURE__ */ jsxs(
73
+ "div",
74
+ {
75
+ className: cn(
76
+ "relative z-[1] hidden border-t border-white/15 p-4 lg:block lg:min-w-[240px] lg:border-l lg:border-t-0 lg:p-5",
77
+ // Over a photograph the rail needs its own ground, or seven lines of
78
+ // small type land on whatever the plate happens to be.
79
+ imageUrl && "bg-black/45 backdrop-blur-[2px]"
80
+ ),
81
+ children: [
82
+ weekLabel && /* @__PURE__ */ jsx("div", { className: "mb-2 text-[10px] font-bold tracking-[0.12em] opacity-70", children: weekLabel }),
83
+ /* @__PURE__ */ jsx("ul", { className: "grid grid-cols-2 gap-x-4 sm:grid-cols-4 lg:grid-cols-1", children: days.map((day, index) => {
84
+ var _a;
85
+ const isActive = index === activeDay;
86
+ const content = /* @__PURE__ */ jsxs(Fragment, { children: [
87
+ /* @__PURE__ */ jsx("b", { className: "w-8 shrink-0 font-mono text-[11px] font-medium opacity-70", children: day.code }),
88
+ /* @__PURE__ */ jsx("span", { className: "min-w-0 flex-1 truncate", children: (_a = day.shortName) != null ? _a : day.name }),
89
+ day.count != null && /* @__PURE__ */ jsx("span", { className: "shrink-0 tabular-nums opacity-70", children: day.count })
90
+ ] });
91
+ const rowClasses = cn(
92
+ "flex w-full items-center gap-2 rounded transition-opacity",
93
+ isActive ? "-mx-1.5 bg-white/10 px-1.5 py-1.5 text-[14.5px] font-extrabold" : "py-[5px] text-[12.5px] opacity-70 hover:opacity-100"
94
+ );
95
+ return /* @__PURE__ */ jsx("li", { children: dayHref ? /* @__PURE__ */ jsx(
96
+ LinkComponent,
97
+ {
98
+ href: dayHref(day, index),
99
+ className: cn(
100
+ rowClasses,
101
+ "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-white/60"
102
+ ),
103
+ children: content
104
+ }
105
+ ) : onDayChange ? /* @__PURE__ */ jsx(
106
+ "button",
107
+ {
108
+ type: "button",
109
+ "aria-pressed": isActive,
110
+ onClick: () => onDayChange(index),
111
+ className: cn(
112
+ rowClasses,
113
+ "text-left focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-white/60"
114
+ ),
115
+ children: content
116
+ }
117
+ ) : /* @__PURE__ */ jsx("span", { className: rowClasses, children: content }) }, day.code);
118
+ }) })
119
+ ]
120
+ }
121
+ )
122
+ ]
123
+ }
124
+ );
125
+ }
126
+
127
+ export { WeekdayThemeBanner };
@@ -1,6 +1,6 @@
1
1
  import { useMediaQuery, DESKTOP_MEDIA_QUERY } from '../chunk-OPVKL4VS.js';
2
- import { MotionPlayPause } from '../chunk-TT3WFS6S.js';
3
- import '../chunk-2WDJR6K7.js';
2
+ import { MotionPlayPause } from '../chunk-YOKHBAIH.js';
3
+ import '../chunk-G7IFL4Z4.js';
4
4
  import { useDragSwipe } from '../chunk-IYDH5IU6.js';
5
5
  import { Skeleton } from '../chunk-QXHMPJ35.js';
6
6
  import { cn } from '../chunk-FEK5XGZW.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bearmenu/ui",
3
- "version": "0.8.8",
3
+ "version": "0.8.10",
4
4
  "description": "BearMenu design system — shared UI tokens, primitives, and components",
5
5
  "license": "UNLICENSED",
6
6
  "repository": {
@@ -0,0 +1,227 @@
1
+ import type { Meta, StoryObj } from "@storybook/react";
2
+
3
+ /**
4
+ * Foundations / Colour dictionary — every colour a menu surface paints, in one
5
+ * table grouped by the forest element it is, with the reason beside it. A page,
6
+ * not a component: the values live in the consuming app (see the notes under
7
+ * the table), this is where a person goes to understand them.
8
+ */
9
+
10
+ type Chip = { label: string; value: string };
11
+
12
+ type Row = {
13
+ element: string;
14
+ why: string;
15
+ /** The three `--menu-field-<family>-<n>` tints, when this element is a card-field family. */
16
+ field?: { family: string; tints: [string, string, string]; draws: string };
17
+ /** `FAMILY_HUES` entries painted by the hub's family cards (chips, price band, photo tint). */
18
+ familyCards: Chip[];
19
+ /** `INTENT_GRADIENTS` entries painted by the hub's intent tiles. */
20
+ intents: Chip[];
21
+ };
22
+
23
+ const ROWS: Row[] = [
24
+ {
25
+ element: "Wheat and honey",
26
+ why: "Grain and what goes on it — crust, pasta, pizza base, pastry, yolk — is this gold before it is cooked. Honey in the comb is the same colour, and so is morning light on toast, which is why brunch and beer (roasted malt) sit here too.",
27
+ field: { family: "larder", tints: ["oklch(0.505 0.11 74)", "oklch(0.555 0.105 80)", "oklch(0.465 0.115 68)"], draws: "bread, pasta, pizza, eggs, sides, sauces, spirits" },
28
+ familyCards: [{ label: "Breakfast & brunch", value: "oklch(0.62 0.14 82)" }, { label: "Bakeries", value: "oklch(0.58 0.11 72)" }, { label: "Beer", value: "oklch(0.62 0.13 86)" }],
29
+ intents: [{ label: "Brunch", value: "oklch(0.62 0.14 82)" }],
30
+ },
31
+ {
32
+ element: "Lake water",
33
+ why: "Fish, shellfish and soup come out of water, and a cocktail is a glass of it. A forest has a lake, not a sea: dark, blue-green, in shade — so the hue stays on the green side (198–220) and never reads as sky or as a pool.",
34
+ field: { family: "sea", tints: ["oklch(0.485 0.095 205)", "oklch(0.535 0.09 198)", "oklch(0.445 0.095 212)"], draws: "fish, shellfish, soups, cocktails" },
35
+ familyCards: [{ label: "Cocktails", value: "oklch(0.47 0.10 205)" }],
36
+ intents: [{ label: "Cocktail bars", value: "oklch(0.47 0.10 205)" }],
37
+ },
38
+ {
39
+ element: "Ember and terracotta",
40
+ why: "Meat is the family cooked over fire: the red of the coals, the crust on beef, cured and smoked meat. Red wine shares the glass and the colour. Terracotta is the same fire one step warmer — the oven's brick and the tomato — which is why Italian sits beside Romanian here.",
41
+ field: { family: "meat", tints: ["oklch(0.49 0.13 28)", "oklch(0.54 0.13 34)", "oklch(0.45 0.13 22)"], draws: "beef, game, cured meat, burgers, red wine" },
42
+ familyCards: [{ label: "Romanian", value: "oklch(0.48 0.16 25)" }, { label: "Italian", value: "oklch(0.56 0.15 38)" }],
43
+ intents: [{ label: "Burgers", value: "oklch(0.48 0.16 25)" }, { label: "Italian tonight", value: "oklch(0.56 0.15 38)" }],
44
+ },
45
+ {
46
+ element: "Maple and amber",
47
+ why: "Sugar turns this colour when it meets heat: caramel, syrup, the top of a crème brûlée, the maple leaf in autumn — and a pizza crust out of the oven, which is why the Pizza card wears it. Beer's amber is the same chemistry, malt roasted, so beer items draw this field even though the beer card wears honey.",
48
+ field: { family: "sweet", tints: ["oklch(0.505 0.14 50)", "oklch(0.555 0.135 58)", "oklch(0.465 0.14 44)"], draws: "desserts, pastry, beer" },
49
+ familyCards: [{ label: "Pizza", value: "oklch(0.52 0.14 58)" }],
50
+ intents: [],
51
+ },
52
+ {
53
+ element: "Fern and pine",
54
+ why: "The leaf itself. Salads, vegetables and the non-alcoholic glass — juice, lemonade, the herb in it — are the green things on the menu and take the forest's own green: pine-deep rather than lime, so it stays a forest and not a lawn.",
55
+ field: { family: "verdant", tints: ["oklch(0.505 0.115 148)", "oklch(0.555 0.11 138)", "oklch(0.465 0.115 156)"], draws: "salads, vegetables, non-alcoholic" },
56
+ familyCards: [{ label: "Vegetarian", value: "oklch(0.52 0.14 148)" }, { label: "Vegan", value: "oklch(0.55 0.12 135)" }],
57
+ intents: [{ label: "Vegan", value: "oklch(0.52 0.14 148)" }],
58
+ },
59
+ {
60
+ element: "Bark and walnut",
61
+ why: "The roasted bean is the colour of tree bark, and a coffee house is made of both — wood and roast. Walnut is the same brown darker: a lit doorway at night, which is what \"open right now\" looks like from the street. Coffee items themselves draw the non-alcoholic field (pine), not this.",
62
+ familyCards: [{ label: "Coffee", value: "oklch(0.44 0.10 52)" }],
63
+ intents: [{ label: "Coffee", value: "oklch(0.44 0.10 52)" }, { label: "Open right now", value: "oklch(0.44 0.08 62)" }],
64
+ },
65
+ {
66
+ element: "Rosehip",
67
+ why: "The forest's berry: the jam in the tart, the fruit on the cake, the glass of red. A red with no purple in it — the magenta and violet it replaced belong to no place a bear has been.",
68
+ familyCards: [{ label: "Wine by the glass", value: "oklch(0.48 0.14 12)" }],
69
+ intents: [{ label: "Patisseries", value: "oklch(0.52 0.15 12)" }],
70
+ },
71
+ {
72
+ element: "Moss in sun",
73
+ why: "The sunlit forest floor. Yellow-green because a terrace is the one intent about being outdoors in daylight; deeper than lime so it is ground, not a highlighter.",
74
+ familyCards: [],
75
+ intents: [{ label: "On a terrace", value: "oklch(0.60 0.13 122)" }],
76
+ },
77
+ {
78
+ element: "Stone",
79
+ why: "No colour of its own. An untagged dish gets it because there is no ingredient to be coloured by; Centru gets it because the town is the one intent that is a place and not a taste. Never a section mark — as a mark, grey reads as absent.",
80
+ field: { family: "neutral", tints: ["oklch(0.505 0.02 60)", "oklch(0.555 0.022 66)", "oklch(0.465 0.018 54)"], draws: "an untagged dish only" },
81
+ familyCards: [],
82
+ intents: [{ label: "In Centru", value: "oklch(0.50 0.03 70)" }],
83
+ },
84
+ ];
85
+
86
+ const NOT_THESE: Chip[] = [
87
+ { label: "sky blue", value: "oklch(0.5 0.13 240)" },
88
+ { label: "teal", value: "oklch(0.55 0.12 190)" },
89
+ { label: "violet", value: "oklch(0.5 0.11 300)" },
90
+ { label: "magenta", value: "oklch(0.52 0.13 340)" },
91
+ { label: "olive-yellow", value: "oklch(0.58 0.12 95)" },
92
+ ];
93
+
94
+ const darker = (value: string) =>
95
+ value.replace(/oklch\(([\d.]+) /, (_m, l: string) => `oklch(${(Number(l) - 0.09).toFixed(3)} `);
96
+
97
+ function Swatch({ label, value, angle = 135, tall }: Chip & { angle?: number; tall?: boolean }) {
98
+ return (
99
+ <div className="flex w-[104px] shrink-0 flex-col gap-1">
100
+ <span
101
+ aria-hidden
102
+ className={tall ? "block h-14 rounded-lg" : "block h-9 rounded-lg"}
103
+ style={{ backgroundImage: `linear-gradient(${angle}deg, ${value}, ${darker(value)})` }}
104
+ />
105
+ <span className="text-[11px] font-semibold leading-tight">{label}</span>
106
+ <code className="text-[9.5px] leading-tight text-muted-foreground">{value.replace("oklch(", "").replace(")", "")}</code>
107
+ </div>
108
+ );
109
+ }
110
+
111
+ const TH = "px-3 py-2 text-left text-[11px] font-bold uppercase tracking-[0.08em] text-muted-foreground";
112
+ const TD = "px-3 py-3 align-top";
113
+
114
+ function ColourDictionaryPage() {
115
+ return (
116
+ <article className="mx-auto max-w-[1440px] px-6 py-7 text-foreground">
117
+ <p className="text-[11px] font-bold uppercase tracking-[0.09em] text-muted-foreground">Foundations</p>
118
+ <h1 className="mt-0.5 text-[26px] font-extrabold tracking-[-0.02em]">The menu colour dictionary</h1>
119
+ <p className="mt-1 max-w-[90ch] text-[14px] leading-[1.55] text-foreground/85">
120
+ Every hue is a forest object, at full strength — the floor and canopy of a forest and what a bear eats off it.
121
+ Every colour a menu surface paints is in this table, grouped by the element it is, with why it belongs.
122
+ <span className="ml-2 inline-flex items-center gap-2 align-middle text-[12px] text-muted-foreground">
123
+ Not in the forest, so not in the palette:
124
+ {NOT_THESE.map((c) => (
125
+ <span key={c.label} className="inline-flex items-center gap-1 line-through">
126
+ <span aria-hidden className="inline-block h-3.5 w-5 rounded-sm" style={{ background: c.value }} />
127
+ {c.label}
128
+ </span>
129
+ ))}
130
+ </span>
131
+ </p>
132
+
133
+ <div className="mt-5 overflow-x-auto rounded-xl border border-border">
134
+ <table className="w-full min-w-[1180px] border-collapse text-[13px]">
135
+ <thead className="bg-muted/60">
136
+ <tr>
137
+ <th className={`${TH} w-[290px]`}>Element · why it belongs</th>
138
+ <th className={TH}>Card fields · the three tints</th>
139
+ <th className={TH}>Family cards · chips, price band, photo tint</th>
140
+ <th className={TH}>Intent tiles</th>
141
+ </tr>
142
+ </thead>
143
+ <tbody>
144
+ {ROWS.map((row) => (
145
+ <tr key={row.element} className="border-t border-border">
146
+ <td className={TD}>
147
+ <div className="text-[15px] font-extrabold tracking-[-0.01em]">{row.element}</div>
148
+ <p className="mt-1 text-[12.5px] leading-[1.5] text-foreground/85">{row.why}</p>
149
+ </td>
150
+ <td className={TD}>
151
+ {row.field ? (
152
+ <div className="flex flex-col gap-2">
153
+ <div className="flex gap-2">
154
+ {row.field.tints.map((tint, index) => (
155
+ <Swatch key={tint} label={`${row.field!.family}-${index}`} value={tint} tall />
156
+ ))}
157
+ </div>
158
+ <div className="text-[11.5px] text-muted-foreground">
159
+ <span className="font-semibold text-foreground/80">draws</span> {row.field.draws}
160
+ </div>
161
+ </div>
162
+ ) : (
163
+ <span className="text-[12px] text-muted-foreground">—</span>
164
+ )}
165
+ </td>
166
+ <td className={TD}>
167
+ {row.familyCards.length ? (
168
+ <div className="flex gap-2">
169
+ {row.familyCards.map((c) => (
170
+ <Swatch key={c.label} {...c} angle={150} />
171
+ ))}
172
+ </div>
173
+ ) : (
174
+ <span className="text-[12px] text-muted-foreground">—</span>
175
+ )}
176
+ </td>
177
+ <td className={TD}>
178
+ {row.intents.length ? (
179
+ <div className="flex gap-2">
180
+ {row.intents.map((c) => (
181
+ <Swatch key={c.label} {...c} angle={150} />
182
+ ))}
183
+ </div>
184
+ ) : (
185
+ <span className="text-[12px] text-muted-foreground">—</span>
186
+ )}
187
+ </td>
188
+ </tr>
189
+ ))}
190
+ </tbody>
191
+ </table>
192
+ </div>
193
+
194
+ <div className="mt-8 grid gap-8 md:grid-cols-3">
195
+ <section>
196
+ <h2 className="text-[15px] font-extrabold">How a colour is chosen</h2>
197
+ <ol className="mt-2 list-decimal space-y-1.5 pl-4 text-[13px] leading-[1.55] text-foreground/85">
198
+ <li><b>What is the family made of, or where is it consumed?</b> Grain, fire, water, sugar, leaf; a night out, a morning, the town.</li>
199
+ <li><b>Which forest element already has that colour — literally?</b> Wheat is gold, embers are red, lake water is blue-green. If nothing in a forest has it, it does not go in; that is how violet and magenta left.</li>
200
+ <li><b>Take it at full strength.</b> An early pass pulled every chroma down to feel earthy and read as faded. Keep lightness 0.44–0.62 so white type at 0.26–0.30 alpha stays readable.</li>
201
+ </ol>
202
+ </section>
203
+ <section>
204
+ <h2 className="text-[15px] font-extrabold">The colour names a family, never an item</h2>
205
+ <p className="mt-2 text-[13px] leading-[1.55] text-foreground/85">
206
+ Sixteen appetizers draw one colour. Two dishes in one family differ only by one of the three tints, keyed on the parent concept so the same dish is the same tint on every menu and after every re-sort. A category's section mark always uses tint <code>-0</code>. Where a family has both a card and an intent tile, they share the value so the two doors to one page agree.
207
+ </p>
208
+ </section>
209
+ <section>
210
+ <h2 className="text-[15px] font-extrabold">Where the values live</h2>
211
+ <p className="mt-2 text-[13px] leading-[1.55] text-foreground/85">
212
+ The design system owns the typography, the falloff and the layering (<code>MenuField</code>, <code>MenuFamilyCard</code>, <code>MenuIntentTiles</code>) and takes a finished colour as a prop. The palette lives in the fe app: the eighteen <code>--menu-field-*</code> tokens in <code>app/globals.css</code>, <code>FAMILY_HUES</code> and <code>INTENT_GRADIENTS</code> in <code>hub-gradients.ts</code>, category → family in <code>category-colors.ts</code>. The Storybook mocks carry the same values so post-generator films what the app paints.
213
+ </p>
214
+ </section>
215
+ </div>
216
+ </article>
217
+ );
218
+ }
219
+
220
+ const meta: Meta<typeof ColourDictionaryPage> = {
221
+ title: "Foundations/Colour dictionary",
222
+ component: ColourDictionaryPage,
223
+ parameters: { layout: "fullscreen" },
224
+ };
225
+ export default meta;
226
+
227
+ export const Page: StoryObj<typeof ColourDictionaryPage> = {};