@bearmenu/ui 0.8.6 → 0.8.8

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 (88) hide show
  1. package/dist/{chunk-637L7XQ3.js → chunk-3TNPSN4D.js} +28 -9
  2. package/dist/chunk-AHL45DD2.js +145 -0
  3. package/dist/chunk-IYDH5IU6.js +66 -0
  4. package/dist/chunk-OPVKL4VS.js +18 -0
  5. package/dist/components/alert.d.ts +2 -2
  6. package/dist/components/badge.d.ts +1 -1
  7. package/dist/components/button.d.ts +1 -1
  8. package/dist/components/card.d.ts +2 -2
  9. package/dist/components/category-deck.js +10 -1
  10. package/dist/components/command.d.ts +15 -15
  11. package/dist/components/coverflow.d.ts +11 -3
  12. package/dist/components/coverflow.js +34 -51
  13. package/dist/components/drift-rail.d.ts +24 -10
  14. package/dist/components/drift-rail.js +1 -1
  15. package/dist/components/event-card.d.ts +8 -0
  16. package/dist/components/event-card.js +68 -35
  17. package/dist/components/event-compact-row.d.ts +34 -6
  18. package/dist/components/event-compact-row.js +120 -38
  19. package/dist/components/event-day-header.js +3 -1
  20. package/dist/components/event-day-separator.d.ts +7 -1
  21. package/dist/components/event-day-separator.js +3 -2
  22. package/dist/components/event-hub-hero.d.ts +25 -4
  23. package/dist/components/event-hub-hero.js +89 -19
  24. package/dist/components/event-list-row.d.ts +19 -6
  25. package/dist/components/event-list-row.js +78 -34
  26. package/dist/components/event-types.d.ts +5 -1
  27. package/dist/components/hour-band.d.ts +38 -40
  28. package/dist/components/hour-band.js +55 -27
  29. package/dist/components/hour-group.js +1 -1
  30. package/dist/components/item.d.ts +2 -2
  31. package/dist/components/poster-tape.js +1 -1
  32. package/dist/components/poster-wall.d.ts +3 -3
  33. package/dist/components/poster-wall.js +73 -17
  34. package/dist/components/section-header.d.ts +16 -1
  35. package/dist/components/section-header.js +26 -7
  36. package/dist/components/snap-rail.d.ts +34 -0
  37. package/dist/components/snap-rail.js +51 -0
  38. package/dist/components/spinner.d.ts +2 -2
  39. package/dist/components/tag.d.ts +2 -2
  40. package/dist/components/text.d.ts +3 -3
  41. package/dist/components/timeline-band.d.ts +20 -2
  42. package/dist/components/timeline-band.js +1 -1
  43. package/dist/components/timeline-playhead.d.ts +26 -0
  44. package/dist/components/timeline-playhead.js +25 -0
  45. package/dist/components/weekend-panel.d.ts +5 -4
  46. package/dist/components/weekend-panel.js +85 -71
  47. package/dist/hooks/use-drag-swipe.d.ts +44 -0
  48. package/dist/hooks/use-drag-swipe.js +2 -0
  49. package/dist/hooks/use-media-query.d.ts +17 -0
  50. package/dist/hooks/use-media-query.js +2 -0
  51. package/package.json +1 -1
  52. package/src/components/category-deck.tsx +19 -8
  53. package/src/components/coverflow.test.tsx +28 -1
  54. package/src/components/coverflow.tsx +37 -43
  55. package/src/components/drift-rail.tsx +167 -42
  56. package/src/components/event-card.test.tsx +11 -0
  57. package/src/components/event-card.tsx +67 -14
  58. package/src/components/event-compact-row.stories.tsx +30 -0
  59. package/src/components/event-compact-row.test.tsx +100 -0
  60. package/src/components/event-compact-row.tsx +166 -40
  61. package/src/components/event-day-header.tsx +3 -1
  62. package/src/components/event-day-separator.tsx +8 -1
  63. package/src/components/event-hub-hero.stories.tsx +30 -0
  64. package/src/components/event-hub-hero.tsx +102 -30
  65. package/src/components/event-list-row.stories.tsx +34 -0
  66. package/src/components/event-list-row.test.tsx +53 -0
  67. package/src/components/event-list-row.tsx +135 -87
  68. package/src/components/event-types.ts +5 -1
  69. package/src/components/hour-band.stories.tsx +28 -5
  70. package/src/components/hour-band.tsx +90 -70
  71. package/src/components/poster-wall.test.tsx +28 -0
  72. package/src/components/poster-wall.tsx +96 -23
  73. package/src/components/section-header.stories.tsx +24 -0
  74. package/src/components/section-header.test.tsx +28 -0
  75. package/src/components/section-header.tsx +45 -7
  76. package/src/components/snap-rail.stories.tsx +79 -0
  77. package/src/components/snap-rail.test.tsx +18 -0
  78. package/src/components/snap-rail.tsx +67 -0
  79. package/src/components/timeline-band.tsx +52 -12
  80. package/src/components/timeline-playhead.stories.tsx +30 -0
  81. package/src/components/timeline-playhead.test.tsx +11 -0
  82. package/src/components/timeline-playhead.tsx +46 -0
  83. package/src/components/weekend-panel.test.tsx +43 -0
  84. package/src/components/weekend-panel.tsx +142 -108
  85. package/src/hooks/use-drag-swipe.test.tsx +66 -0
  86. package/src/hooks/use-drag-swipe.ts +105 -0
  87. package/src/hooks/use-media-query.ts +31 -0
  88. package/dist/chunk-GPH6L4DX.js +0 -55
@@ -2,22 +2,36 @@ import * as react_jsx_runtime from 'react/jsx-runtime';
2
2
 
3
3
  /**
4
4
  * DriftRail — an infinite horizontal marquee for logos, tag chips or small
5
- * poster tiles. The caller passes one set of children; the component renders
6
- * a second, `aria-hidden` copy right after it so a `translateX(-50%)` loop is
7
- * seamless. Because the loop depends on the duplicate being pixel-identical
8
- * to the first pass, keep `children` a fixed, ordered list — no per-render
9
- * randomisation.
5
+ * poster tiles that the reader can take hold of. The caller passes one set of
6
+ * children; the component renders a second, `aria-hidden` copy right after it
7
+ * so the loop is seamless. Because the loop depends on the duplicate being
8
+ * pixel-identical to the first pass, keep `children` a fixed, ordered list —
9
+ * no per-render randomisation.
10
10
  *
11
- * Motion is scoped to this instance via a generated id (no shared stylesheet,
12
- * no class collisions between multiple rails on one page) and is paused two
13
- * ways, per the source design: an `IntersectionObserver` stops the loop the
14
- * moment the rail scrolls off-screen, and `prefers-reduced-motion: reduce`
15
- * removes the animation outright rather than merely slowing it.
11
+ * IT IS A SCROLLER THAT DRIFTS, NOT AN ANIMATION. It used to be a CSS
12
+ * `translateX` keyframe, which is cheap but cannot be grabbed: a finger on it
13
+ * did nothing, and a marquee that ignores the hand on it reads as a broken
14
+ * scroller. Now the rail is a real horizontal scroll container with the
15
+ * scrollbar hidden, and a `requestAnimationFrame` loop advances `scrollLeft`
16
+ * at `width / durationSeconds` per second. When the scroll position passes
17
+ * the first copy it is wrapped back by exactly one copy's width — the two
18
+ * copies are identical, so the jump is invisible — and the same wrap holds
19
+ * while the reader scrolls, in either direction, so there is no end to hit.
20
+ *
21
+ * THE READER WINS. A touch scrolls natively (momentum and all); a mouse drag
22
+ * scrolls the rail by the drag distance; a wheel works because it is a
23
+ * scroller. Any of those stops the drift, and it resumes a few seconds after
24
+ * the last interaction. Hover pauses it too (`pauseOnHover`), and an
25
+ * `IntersectionObserver` stops the loop off-screen so the page is not doing
26
+ * work nobody can see. `prefers-reduced-motion` removes the drift entirely;
27
+ * the rail stays scrollable by hand, which is more than the static strip it
28
+ * used to collapse to.
16
29
  */
17
30
  type DriftRailProps = {
18
31
  children: React.ReactNode;
19
32
  /** Runs the loop right-to-left instead of left-to-right. */
20
33
  reverse?: boolean;
34
+ /** Seconds for one full pass of the children. */
21
35
  durationSeconds?: number;
22
36
  /** Pauses the loop while the pointer hovers the rail. */
23
37
  pauseOnHover?: boolean;
@@ -1,3 +1,3 @@
1
- export { DriftRail } from '../chunk-GPH6L4DX.js';
1
+ export { DriftRail } from '../chunk-AHL45DD2.js';
2
2
  import '../chunk-FEK5XGZW.js';
3
3
  import '../chunk-3GWWZKX7.js';
@@ -61,6 +61,14 @@ type EventCardProps = {
61
61
  liveStatus?: string;
62
62
  /** Hide the day/time line — the timeline supplies its own hour heading. */
63
63
  hideWhen?: boolean;
64
+ /**
65
+ * Already over — dims the poster, mutes the title and shows the `endedLabel`
66
+ * chip. Never row-level opacity, which composites the text below AA. Same
67
+ * contract as `EventCompactRow`.
68
+ */
69
+ isEnded?: boolean;
70
+ /** Chip text for an ended event, e.g. "Ended". Required to render the chip. */
71
+ endedLabel?: string;
64
72
  /** Overrides the day label when the card appears outside its own day section. */
65
73
  dayLabel?: string;
66
74
  /** Append the entry fee to the meta line. Off by default, matching the design's base card. */
@@ -25,6 +25,8 @@ var EventCard = memo(function EventCard2({
25
25
  tags,
26
26
  liveStatus,
27
27
  hideWhen,
28
+ isEnded,
29
+ endedLabel,
28
30
  dayLabel,
29
31
  showPrice,
30
32
  href,
@@ -36,6 +38,8 @@ var EventCard = memo(function EventCard2({
36
38
  }) {
37
39
  var _a;
38
40
  const [imgError, setImgError] = useState(false);
41
+ const showEnded = isEnded && !!endedLabel;
42
+ const time = hideWhen ? "" : formatters.time(event.starts_at);
39
43
  const fee = resolveEventEntryFee(event);
40
44
  const showPoster = !!event.poster_url && !imgError;
41
45
  const venue = (_a = event.place_name) != null ? _a : event.location;
@@ -45,6 +49,7 @@ var EventCard = memo(function EventCard2({
45
49
  {
46
50
  className: cn(
47
51
  "relative overflow-hidden rounded-xl bg-muted",
52
+ isEnded && "opacity-45",
48
53
  posterAspect === "3/4" ? "aspect-[3/4]" : posterAspect === "4/5" ? "aspect-[4/5]" : "aspect-[4/3]"
49
54
  ),
50
55
  children: [
@@ -65,10 +70,19 @@ var EventCard = memo(function EventCard2({
65
70
  ) : fallback,
66
71
  /* @__PURE__ */ jsxs("div", { className: "absolute inset-x-2 top-2 flex items-start justify-between gap-2", children: [
67
72
  badge ? /* @__PURE__ */ jsx("span", { className: cn(PILL_BASE, "bg-black/55 text-white"), children: badge }) : /* @__PURE__ */ jsx("span", {}),
68
- fee.kind === "free" && /* @__PURE__ */ jsxs("span", { className: cn(PILL_BASE, "bg-success font-bold text-success-foreground"), children: [
69
- /* @__PURE__ */ jsx(Gift, { className: "h-3 w-3", "aria-hidden": true }),
70
- labels.free
71
- ] })
73
+ fee.kind === "free" && /* @__PURE__ */ jsxs(
74
+ "span",
75
+ {
76
+ className: cn(
77
+ PILL_BASE,
78
+ "bg-success font-bold text-success-foreground"
79
+ ),
80
+ children: [
81
+ /* @__PURE__ */ jsx(Gift, { className: "h-3 w-3", "aria-hidden": true }),
82
+ labels.free
83
+ ]
84
+ }
85
+ )
72
86
  ] }),
73
87
  tagLabel && /* @__PURE__ */ jsx("div", { className: "absolute inset-x-2 bottom-2 flex gap-1", children: /* @__PURE__ */ jsx("span", { className: cn(PILL_BASE, "bg-black/55 text-white"), children: tagLabel }) })
74
88
  ]
@@ -79,7 +93,8 @@ var EventCard = memo(function EventCard2({
79
93
  {
80
94
  className: cn(
81
95
  "mt-2.5 leading-[1.35]",
82
- emphasis ? "text-lg font-bold tracking-[-0.02em]" : "text-sm font-semibold"
96
+ emphasis ? "text-lg font-bold tracking-[-0.02em]" : "text-sm font-semibold",
97
+ isEnded && "text-muted-foreground"
83
98
  ),
84
99
  children: event.title
85
100
  }
@@ -90,8 +105,10 @@ var EventCard = memo(function EventCard2({
90
105
  ] }),
91
106
  !hideWhen && /* @__PURE__ */ jsxs("div", { className: "mt-[3px] flex items-center gap-1.5 text-xs text-muted-foreground", children: [
92
107
  /* @__PURE__ */ jsx("span", { children: dayLabel != null ? dayLabel : formatters.day(event.starts_at) }),
93
- /* @__PURE__ */ jsx("span", { className: "text-border", "aria-hidden": true, children: "\xB7" }),
94
- /* @__PURE__ */ jsx("span", { children: formatters.time(event.starts_at) }),
108
+ time && /* @__PURE__ */ jsxs(Fragment, { children: [
109
+ /* @__PURE__ */ jsx("span", { className: "text-border", "aria-hidden": true, children: "\xB7" }),
110
+ /* @__PURE__ */ jsx("span", { children: time })
111
+ ] }),
95
112
  showPrice && fee.kind === "paid" && /* @__PURE__ */ jsxs(Fragment, { children: [
96
113
  /* @__PURE__ */ jsx("span", { className: "text-border", "aria-hidden": true, children: "\xB7" }),
97
114
  /* @__PURE__ */ jsx("span", { children: formatters.price(fee.amountRon) })
@@ -101,7 +118,8 @@ var EventCard = memo(function EventCard2({
101
118
  liveStatus && /* @__PURE__ */ jsxs("div", { className: "mt-1 flex items-center gap-1.5 text-xs font-bold text-success", children: [
102
119
  /* @__PURE__ */ jsx(Radio, { className: "h-3 w-3 shrink-0", "aria-hidden": true }),
103
120
  liveStatus
104
- ] })
121
+ ] }),
122
+ showEnded && /* @__PURE__ */ jsx("div", { className: "mt-1.5", children: /* @__PURE__ */ jsx(Tag, { size: "sm", variant: "default", className: "font-extrabold", children: endedLabel }) })
105
123
  ] });
106
124
  const rootClasses = cn(
107
125
  "flex min-w-0 flex-col text-left",
@@ -120,7 +138,15 @@ var EventCard = memo(function EventCard2({
120
138
  );
121
139
  }
122
140
  if (onClick) {
123
- return /* @__PURE__ */ jsx("button", { type: "button", className: rootClasses, onClick: (e) => onClick(event, e), children: body });
141
+ return /* @__PURE__ */ jsx(
142
+ "button",
143
+ {
144
+ type: "button",
145
+ className: rootClasses,
146
+ onClick: (e) => onClick(event, e),
147
+ children: body
148
+ }
149
+ );
124
150
  }
125
151
  return /* @__PURE__ */ jsx("div", { className: rootClasses, children: body });
126
152
  });
@@ -130,32 +156,39 @@ function EventCardSkeleton({
130
156
  hideWhen,
131
157
  className
132
158
  }) {
133
- return /* @__PURE__ */ jsxs("div", { "aria-hidden": "true", className: cn("flex min-w-0 flex-col text-left", className), children: [
134
- /* @__PURE__ */ jsx(
135
- Skeleton,
136
- {
137
- className: cn(
138
- posterAspect === "3/4" ? "aspect-[3/4]" : posterAspect === "4/5" ? "aspect-[4/5]" : "aspect-[4/3]"
139
- )
140
- }
141
- ),
142
- emphasis ? /* @__PURE__ */ jsxs("div", { className: "mt-2.5 space-y-2", children: [
143
- /* @__PURE__ */ jsx(Skeleton, { className: "h-5 w-full" }),
144
- /* @__PURE__ */ jsx(Skeleton, { className: "h-5 w-full" }),
145
- /* @__PURE__ */ jsx(Skeleton, { className: "h-5 w-1/2" })
146
- ] }) : /* @__PURE__ */ jsxs("div", { className: "mt-2.5 space-y-1.5", children: [
147
- /* @__PURE__ */ jsx(Skeleton, { className: "h-3.5 w-full" }),
148
- /* @__PURE__ */ jsx(Skeleton, { className: "h-3.5 w-2/3" })
149
- ] }),
150
- /* @__PURE__ */ jsxs("div", { className: "mt-0.5 flex items-center gap-1", children: [
151
- /* @__PURE__ */ jsx(Skeleton, { className: "h-3 w-3 shrink-0 rounded-full" }),
152
- /* @__PURE__ */ jsx(Skeleton, { className: "h-3 w-24" })
153
- ] }),
154
- !hideWhen && /* @__PURE__ */ jsxs("div", { className: "mt-[3px] flex items-center gap-1.5", children: [
155
- /* @__PURE__ */ jsx(Skeleton, { className: "h-3 w-16" }),
156
- /* @__PURE__ */ jsx(Skeleton, { className: "h-3 w-10" })
157
- ] })
158
- ] });
159
+ return /* @__PURE__ */ jsxs(
160
+ "div",
161
+ {
162
+ "aria-hidden": "true",
163
+ className: cn("flex min-w-0 flex-col text-left", className),
164
+ children: [
165
+ /* @__PURE__ */ jsx(
166
+ Skeleton,
167
+ {
168
+ className: cn(
169
+ posterAspect === "3/4" ? "aspect-[3/4]" : posterAspect === "4/5" ? "aspect-[4/5]" : "aspect-[4/3]"
170
+ )
171
+ }
172
+ ),
173
+ emphasis ? /* @__PURE__ */ jsxs("div", { className: "mt-2.5 space-y-2", children: [
174
+ /* @__PURE__ */ jsx(Skeleton, { className: "h-5 w-full" }),
175
+ /* @__PURE__ */ jsx(Skeleton, { className: "h-5 w-full" }),
176
+ /* @__PURE__ */ jsx(Skeleton, { className: "h-5 w-1/2" })
177
+ ] }) : /* @__PURE__ */ jsxs("div", { className: "mt-2.5 space-y-1.5", children: [
178
+ /* @__PURE__ */ jsx(Skeleton, { className: "h-3.5 w-full" }),
179
+ /* @__PURE__ */ jsx(Skeleton, { className: "h-3.5 w-2/3" })
180
+ ] }),
181
+ /* @__PURE__ */ jsxs("div", { className: "mt-0.5 flex items-center gap-1", children: [
182
+ /* @__PURE__ */ jsx(Skeleton, { className: "h-3 w-3 shrink-0 rounded-full" }),
183
+ /* @__PURE__ */ jsx(Skeleton, { className: "h-3 w-24" })
184
+ ] }),
185
+ !hideWhen && /* @__PURE__ */ jsxs("div", { className: "mt-[3px] flex items-center gap-1.5", children: [
186
+ /* @__PURE__ */ jsx(Skeleton, { className: "h-3 w-16" }),
187
+ /* @__PURE__ */ jsx(Skeleton, { className: "h-3 w-10" })
188
+ ] })
189
+ ]
190
+ }
191
+ );
159
192
  }
160
193
 
161
194
  export { EventCard, EventCardSkeleton };
@@ -14,10 +14,25 @@ import { EventPreview, EventCardLabels, EventFormatters } from './event-types.js
14
14
  *
15
15
  * THIS ROW DOES NOT DRAW DIVIDERS; ITS CONTAINER DOES. `<HourGroup>` already
16
16
  * draws the rule under the hour heading, so a row-level top separator here
17
- * doubled it directly beneath every hour. `<EventListRow>` and
18
- * `<EventListRowWide>` are different: they stack in a plain list with no
19
- * other rule around them, so their own top separator is the only one there —
20
- * do not carry this rule over to them.
17
+ * doubled it directly beneath every hour; `<HourBand>` rules between its rows
18
+ * with a wrapper. `<EventListRow>` and `<EventListRowWide>` are different:
19
+ * they stack in a plain list with no other rule around them, so their own top
20
+ * separator is the only one there — do not carry this rule over to them.
21
+ *
22
+ * STATE LIVES ON THE META LINE. `isLive` adds the on-air chip and `isEnded`
23
+ * the ended chip beside the time and venue, so a row's state is read in the
24
+ * same glance as its facts. Ended dims the POSTER and moves the title to
25
+ * `--muted-foreground` — never row-level opacity, which composites the text
26
+ * below AA.
27
+ *
28
+ * THE PRICE IS ON THE LAST ROW, right-aligned beside the tags, not on the
29
+ * title line. Two bold things on one line compete — the title and a price
30
+ * both in 700 read as one shout — and the title is the thing the reader is
31
+ * scanning for. `showPrice` renders it (free in `--success`, paid in
32
+ * `--foreground`); without it the row keeps its trailing free pill for the
33
+ * hub's venue block. This is ONE row for the timeline and the list view:
34
+ * the list used to have its own, and the reader met two anatomies for the
35
+ * same object.
21
36
  *
22
37
  * Same contract as the rest: DS-owned view model, injected labels/formatters,
23
38
  * plain `<img>`, anchor whenever `href` is given.
@@ -32,7 +47,8 @@ type EventCompactRowProps = {
32
47
  event: EventPreview;
33
48
  labels: EventCardLabels;
34
49
  formatters: EventFormatters;
35
- /** Drops the leading time from the sub line. */
50
+ /** Drops the leading time from the sub line. An empty `formatters.time`
51
+ * result (hour unknown) drops it the same way, without the prop. */
36
52
  hideTime?: boolean;
37
53
  /**
38
54
  * Replaces the default "time · venue" sub-line entirely. For surfaces where
@@ -50,6 +66,18 @@ type EventCompactRowProps = {
50
66
  * rather than truncate, so a chip's own text is never cut mid-word. Cap
51
67
  * the count at the call site — this row does not. */
52
68
  tags?: string[];
69
+ /** Currently running — adds the `labels.liveNow` chip to the meta line. */
70
+ isLive?: boolean;
71
+ /** Already over — dims the poster, mutes the title, adds the `endedLabel` chip. */
72
+ isEnded?: boolean;
73
+ /** Chip text for an ended event, e.g. "Ended". Required to render the chip. */
74
+ endedLabel?: string;
75
+ /**
76
+ * Prints the fee on the meta line — `labels.free` in `--success`, a paid
77
+ * amount via `formatters.price` — instead of the trailing free pill. An
78
+ * unknown fee prints nothing, same as every sibling row.
79
+ */
80
+ showPrice?: boolean;
53
81
  size?: "sm" | "default";
54
82
  href?: string;
55
83
  linkComponent?: React.ComponentType<EventCompactRowLinkProps>;
@@ -67,6 +95,6 @@ type EventCompactRowSkeletonProps = {
67
95
  className?: string;
68
96
  };
69
97
  /** Mirrors `<EventCompactRow>`'s geometry exactly so nothing shifts when data lands. */
70
- declare function EventCompactRowSkeleton({ size, className }: EventCompactRowSkeletonProps): react_jsx_runtime.JSX.Element;
98
+ declare function EventCompactRowSkeleton({ size, className, }: EventCompactRowSkeletonProps): react_jsx_runtime.JSX.Element;
71
99
 
72
100
  export { EventCompactRow, type EventCompactRowLinkProps, type EventCompactRowProps, EventCompactRowSkeleton, type EventCompactRowSkeletonProps };
@@ -6,6 +6,7 @@ import { Skeleton } from '../chunk-QXHMPJ35.js';
6
6
  import { cn } from '../chunk-FEK5XGZW.js';
7
7
  import { __objRest, __spreadValues } from '../chunk-3GWWZKX7.js';
8
8
  import { memo, useState } from 'react';
9
+ import { Radio } from 'lucide-react';
9
10
  import { jsxs, Fragment, jsx } from 'react/jsx-runtime';
10
11
 
11
12
  function DefaultLink(_a) {
@@ -19,6 +20,10 @@ var EventCompactRow = memo(function EventCompactRow2({
19
20
  hideTime,
20
21
  subtitle,
21
22
  tags,
23
+ isLive,
24
+ isEnded,
25
+ endedLabel,
26
+ showPrice,
22
27
  size = "default",
23
28
  href,
24
29
  linkComponent: LinkComponent = DefaultLink,
@@ -33,50 +38,108 @@ var EventCompactRow = memo(function EventCompactRow2({
33
38
  const fee = resolveEventEntryFee(event);
34
39
  const showPoster = !!event.poster_url && !imgError;
35
40
  const venue = (_a = event.place_name) != null ? _a : event.location;
41
+ const time = formatters.time(event.starts_at);
42
+ const priceText = fee.kind === "free" ? labels.free : fee.kind === "paid" ? formatters.price(fee.amountRon) : void 0;
43
+ const showEnded = isEnded && !!endedLabel;
44
+ const separator = /* @__PURE__ */ jsx("span", { "aria-hidden": true, className: "text-border", children: "\xB7" });
36
45
  const body = /* @__PURE__ */ jsxs(Fragment, { children: [
37
- /* @__PURE__ */ jsx("div", { className: "relative aspect-[4/5] w-16 shrink-0 overflow-hidden rounded-[9px] bg-muted", children: showPoster ? (
38
- // eslint-disable-next-line @next/next/no-img-element
39
- /* @__PURE__ */ jsx(
40
- "img",
41
- {
42
- src: event.poster_url,
43
- alt: "",
44
- loading: priority ? "eager" : "lazy",
45
- decoding: "async",
46
- className: "h-full w-full object-cover",
47
- onError: () => setImgError(true)
48
- }
49
- )
50
- ) : (
51
- // A bare `bg-muted` rectangle was survivable at 34px. At 64x80 beside
52
- // real posters it reads as a broken image rather than an absent one —
53
- // so this row finally joins every sibling in the family in having a
54
- // designed fallback.
55
- fallback
56
- ) }),
46
+ /* @__PURE__ */ jsx(
47
+ "div",
48
+ {
49
+ className: cn(
50
+ "relative aspect-[4/5] w-16 shrink-0 overflow-hidden rounded-[10px] bg-muted",
51
+ isEnded && "opacity-45"
52
+ ),
53
+ children: showPoster ? (
54
+ // eslint-disable-next-line @next/next/no-img-element
55
+ /* @__PURE__ */ jsx(
56
+ "img",
57
+ {
58
+ src: event.poster_url,
59
+ alt: "",
60
+ loading: priority ? "eager" : "lazy",
61
+ decoding: "async",
62
+ className: "h-full w-full object-cover",
63
+ onError: () => setImgError(true)
64
+ }
65
+ )
66
+ ) : (
67
+ // A bare `bg-muted` rectangle was survivable at 34px. At 58x72 beside
68
+ // real posters it reads as a broken image rather than an absent one —
69
+ // so this row finally joins every sibling in the family in having a
70
+ // designed fallback.
71
+ fallback
72
+ )
73
+ }
74
+ ),
57
75
  /* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
58
76
  /* @__PURE__ */ jsx(
59
77
  "div",
60
78
  {
61
79
  className: cn(
62
- "font-semibold leading-[1.35]",
80
+ "leading-[1.3]",
63
81
  "transition-colors duration-150 motion-reduce:transition-none",
64
- isSm ? "text-[13.5px]" : "text-[13px]",
82
+ isSm ? "text-[14.5px] lg:text-[13.5px]" : "text-[14px] lg:text-[13px]",
83
+ isEnded ? "font-semibold text-muted-foreground" : "font-bold text-foreground lg:font-semibold",
65
84
  (href || onClick) && "group-hover:text-primary"
66
85
  ),
67
86
  children: event.title
68
87
  }
69
88
  ),
70
- /* @__PURE__ */ jsx("div", { className: "truncate text-[11.5px] text-muted-foreground", children: subtitle ? subtitle : /* @__PURE__ */ jsxs(Fragment, { children: [
71
- !hideTime && /* @__PURE__ */ jsxs(Fragment, { children: [
72
- /* @__PURE__ */ jsx("span", { className: "font-mono tabular-nums", children: formatters.time(event.starts_at) }),
73
- /* @__PURE__ */ jsx("span", { "aria-hidden": true, children: " \xB7 " })
74
- ] }),
75
- venue
76
- ] }) }),
77
- tags && tags.length > 0 && /* @__PURE__ */ jsx(TagGroup, { className: "mt-1", children: tags.map((tag) => /* @__PURE__ */ jsx(Tag, { size: "sm", children: tag }, tag)) })
89
+ /* @__PURE__ */ jsxs(
90
+ "div",
91
+ {
92
+ className: cn(
93
+ "mt-1 flex items-center gap-1.5 text-[11.5px] text-muted-foreground",
94
+ isLive || showEnded ? "flex-wrap" : "truncate"
95
+ ),
96
+ children: [
97
+ subtitle ? subtitle : /* @__PURE__ */ jsxs(Fragment, { children: [
98
+ !hideTime && time && /* @__PURE__ */ jsxs(Fragment, { children: [
99
+ /* @__PURE__ */ jsx("span", { className: "font-mono tabular-nums", children: time }),
100
+ separator
101
+ ] }),
102
+ /* @__PURE__ */ jsx("span", { className: "min-w-0 truncate", children: venue })
103
+ ] }),
104
+ isLive && /* @__PURE__ */ jsxs(
105
+ Tag,
106
+ {
107
+ size: "sm",
108
+ variant: "success",
109
+ className: "shrink-0 whitespace-nowrap font-extrabold",
110
+ children: [
111
+ /* @__PURE__ */ jsx(Radio, { className: "h-2.5 w-2.5", "aria-hidden": true }),
112
+ labels.liveNow
113
+ ]
114
+ }
115
+ ),
116
+ showEnded && /* @__PURE__ */ jsx(
117
+ Tag,
118
+ {
119
+ size: "sm",
120
+ variant: "default",
121
+ className: "shrink-0 whitespace-nowrap font-extrabold",
122
+ children: endedLabel
123
+ }
124
+ )
125
+ ]
126
+ }
127
+ ),
128
+ (tags && tags.length > 0 || showPrice && priceText) && /* @__PURE__ */ jsxs("div", { className: "mt-1.5 flex items-center gap-2", children: [
129
+ tags && tags.length > 0 ? /* @__PURE__ */ jsx(TagGroup, { className: "min-w-0 flex-1", children: tags.map((tag) => /* @__PURE__ */ jsx(Tag, { size: "sm", variant: "quiet", children: tag }, tag)) }) : /* @__PURE__ */ jsx("span", { className: "flex-1" }),
130
+ showPrice && priceText && /* @__PURE__ */ jsx(
131
+ "span",
132
+ {
133
+ className: cn(
134
+ "shrink-0 text-[13px] tabular-nums",
135
+ fee.kind === "free" ? "font-semibold text-success" : "font-bold text-foreground"
136
+ ),
137
+ children: priceText
138
+ }
139
+ )
140
+ ] })
78
141
  ] }),
79
- fee.kind === "free" && /* @__PURE__ */ jsx(
142
+ !showPrice && fee.kind === "free" && /* @__PURE__ */ jsx(
80
143
  "span",
81
144
  {
82
145
  className: cn(
@@ -88,7 +151,7 @@ var EventCompactRow = memo(function EventCompactRow2({
88
151
  )
89
152
  ] });
90
153
  const rootClasses = cn(
91
- "group relative -mx-2.5 flex w-full items-start gap-[11px] rounded-lg px-2.5 py-2 text-left",
154
+ "group relative -mx-2.5 flex w-full items-start gap-3 rounded-lg px-2.5 py-[11px] text-left lg:gap-[11px] lg:py-2",
92
155
  "transition-colors duration-150 motion-reduce:transition-none",
93
156
  (href || onClick) && "cursor-pointer hover:bg-foreground/5 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
94
157
  className
@@ -105,25 +168,44 @@ var EventCompactRow = memo(function EventCompactRow2({
105
168
  );
106
169
  }
107
170
  if (onClick) {
108
- return /* @__PURE__ */ jsx("button", { type: "button", className: rootClasses, onClick: (e) => onClick(event, e), children: body });
171
+ return /* @__PURE__ */ jsx(
172
+ "button",
173
+ {
174
+ type: "button",
175
+ className: rootClasses,
176
+ onClick: (e) => onClick(event, e),
177
+ children: body
178
+ }
179
+ );
109
180
  }
110
181
  return /* @__PURE__ */ jsx("div", { className: rootClasses, children: body });
111
182
  });
112
- function EventCompactRowSkeleton({ size = "default", className }) {
183
+ function EventCompactRowSkeleton({
184
+ size = "default",
185
+ className
186
+ }) {
113
187
  const isSm = size === "sm";
114
188
  return /* @__PURE__ */ jsxs(
115
189
  "div",
116
190
  {
117
191
  "aria-hidden": "true",
118
192
  className: cn(
119
- "relative -mx-2.5 flex w-full items-start gap-[11px] rounded-lg px-2.5 py-2 text-left",
193
+ "relative -mx-2.5 flex w-full items-start gap-3 rounded-lg px-2.5 py-[11px] text-left lg:gap-[11px] lg:py-2",
120
194
  className
121
195
  ),
122
196
  children: [
123
- /* @__PURE__ */ jsx(Skeleton, { className: "aspect-[4/5] w-16 shrink-0 rounded-[9px]" }),
197
+ /* @__PURE__ */ jsx(Skeleton, { className: "aspect-[4/5] w-16 shrink-0 rounded-[10px]" }),
124
198
  /* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
125
- /* @__PURE__ */ jsx(Skeleton, { className: cn(isSm ? "h-[13.5px]" : "h-[13px]", "w-2/5") }),
126
- /* @__PURE__ */ jsx(Skeleton, { className: "mt-0.5 h-[11.5px] w-1/3" })
199
+ /* @__PURE__ */ jsx(
200
+ Skeleton,
201
+ {
202
+ className: cn(
203
+ isSm ? "h-[14.5px] lg:h-[13.5px]" : "h-[14px] lg:h-[13px]",
204
+ "w-3/5"
205
+ )
206
+ }
207
+ ),
208
+ /* @__PURE__ */ jsx(Skeleton, { className: "mt-1 h-[11.5px] w-1/3" })
127
209
  ] })
128
210
  ]
129
211
  }
@@ -27,7 +27,9 @@ function EventDayHeader({
27
27
  {
28
28
  className: cn(
29
29
  "font-black leading-[0.82] tracking-[-0.05em] tabular-nums",
30
- large ? "text-[58px]" : "text-[40px]"
30
+ // `sm` is the phone scale: 30px, the design's app day head. 40px
31
+ // was a page-scale number that took a third of a 390px row.
32
+ large ? "text-[58px]" : "text-[30px] leading-[0.86]"
31
33
  ),
32
34
  children: dayNumber
33
35
  }
@@ -25,6 +25,12 @@ import { ElementType } from 'react';
25
25
  * `<Separator>` above the label), not the weight.
26
26
  */
27
27
  type EventDaySeparatorProps = {
28
+ /**
29
+ * `always` (default) draws the trailing rule at every width. `lg` drops it
30
+ * below `lg`, where the list is an app sheet whose day heads are bold
31
+ * labels alone — the rows below rule themselves — and keeps it from `lg`.
32
+ */
33
+ rule?: "always" | "lg";
28
34
  label: string;
29
35
  count?: string;
30
36
  size?: "sm" | "default";
@@ -32,7 +38,7 @@ type EventDaySeparatorProps = {
32
38
  as?: ElementType;
33
39
  className?: string;
34
40
  };
35
- declare function EventDaySeparator({ label, count, size, as: Tag, className, }: EventDaySeparatorProps): react_jsx_runtime.JSX.Element;
41
+ declare function EventDaySeparator({ label, count, size, as: Tag, className, rule, }: EventDaySeparatorProps): react_jsx_runtime.JSX.Element;
36
42
  type EventDaySeparatorSkeletonProps = {
37
43
  /** Matches `EventDaySeparatorProps["size"]`. */
38
44
  size?: "sm" | "default";
@@ -9,7 +9,8 @@ function EventDaySeparator({
9
9
  count,
10
10
  size = "default",
11
11
  as: Tag = "h2",
12
- className
12
+ className,
13
+ rule = "always"
13
14
  }) {
14
15
  return /* @__PURE__ */ jsxs(
15
16
  "div",
@@ -22,7 +23,7 @@ function EventDaySeparator({
22
23
  children: [
23
24
  /* @__PURE__ */ jsx(Tag, { className: cn("font-semibold", size === "sm" ? "text-[13px]" : "text-sm"), children: label }),
24
25
  count && /* @__PURE__ */ jsx("span", { className: cn("text-muted-foreground", size === "sm" ? "text-[11.5px]" : "text-xs"), children: count }),
25
- /* @__PURE__ */ jsx(Separator, { className: "flex-1" })
26
+ /* @__PURE__ */ jsx(Separator, { className: cn("flex-1", rule === "lg" && "hidden lg:block") })
26
27
  ]
27
28
  }
28
29
  );
@@ -2,9 +2,20 @@ import * as react_jsx_runtime from 'react/jsx-runtime';
2
2
  import * as React from 'react';
3
3
 
4
4
  /**
5
- * EventHubHero — the hub's opening block: an eyebrow, a big headline, an
6
- * intro line and a stats row on the left; an arbitrary slot (the poster
7
- * stack, in the source design) on the right. Stacks on mobile.
5
+ * EventHubHero — the hub's opening block. On desktop: an eyebrow, a big
6
+ * headline, an intro paragraph and a stats row on the left; an arbitrary slot
7
+ * (the poster stack, in the source design) on the right.
8
+ *
9
+ * BELOW `lg` IT IS A MASTHEAD, NOT A HERO. One headline line, the eyebrow
10
+ * demoted to a sub-line beneath it, no intro, and the figures as a swipeable
11
+ * rail of chips that bleeds to the screen edge. The first real content lands
12
+ * roughly 250px sooner than under the web-shaped hero — on a phone the page
13
+ * should show, not narrate. Same markup at every width; only the classes
14
+ * change, so the copy is authored once.
15
+ *
16
+ * A stat with `tone: "live"` is the one that is happening right now: its
17
+ * chip takes a `--success` border and value, the same live colour every
18
+ * events surface uses.
8
19
  *
9
20
  * All copy and stat values arrive pre-formatted — the DS holds no `Intl`
10
21
  * locale and does not format numbers.
@@ -12,6 +23,8 @@ import * as React from 'react';
12
23
  type EventHubHeroStat = {
13
24
  value: string;
14
25
  label: string;
26
+ /** `live` marks the figure that is happening right now. */
27
+ tone?: "default" | "live";
15
28
  };
16
29
  type EventHubHeroProps = {
17
30
  /** Small caps-style label above the title, e.g. "Updated today". */
@@ -19,11 +32,19 @@ type EventHubHeroProps = {
19
32
  title: string;
20
33
  intro: string;
21
34
  stats: EventHubHeroStat[];
35
+ /**
36
+ * Accessible name for the stats rail, e.g. "Key figures". When given, the
37
+ * rail is a named, focusable region so a keyboard can reach chips clipped
38
+ * by the phone's edge. Optional so existing consumers keep compiling — but
39
+ * pass it: without a name the rail cannot be focusable, and a phone reader
40
+ * on a keyboard cannot reach the last figures.
41
+ */
42
+ statsLabel?: string;
22
43
  /** The right-column slot — the poster stack in the source design. */
23
44
  aside?: React.ReactNode;
24
45
  className?: string;
25
46
  };
26
- declare function EventHubHero({ eyebrow, title, intro, stats, aside, className }: EventHubHeroProps): react_jsx_runtime.JSX.Element;
47
+ declare function EventHubHero({ eyebrow, title, intro, stats, statsLabel, aside, className, }: EventHubHeroProps): react_jsx_runtime.JSX.Element;
27
48
  type EventHubHeroSkeletonProps = {
28
49
  /** The right-column slot, e.g. a `PosterStack`-shaped placeholder. Mirrors `aside` on the real component. */
29
50
  aside?: React.ReactNode;