@flamingo-stack/openframe-frontend-core 0.0.394 → 0.0.395

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 (114) hide show
  1. package/dist/{chunk-FIOOW474.cjs → chunk-3VJPMRNT.cjs} +7 -7
  2. package/dist/{chunk-FIOOW474.cjs.map → chunk-3VJPMRNT.cjs.map} +1 -1
  3. package/dist/{chunk-SSZL4QYX.js → chunk-45Z6B6CO.js} +4 -4
  4. package/dist/{chunk-N6SH4ZYN.cjs → chunk-4YVUL4T7.cjs} +12 -12
  5. package/dist/{chunk-N6SH4ZYN.cjs.map → chunk-4YVUL4T7.cjs.map} +1 -1
  6. package/dist/{chunk-DFCPMBYN.cjs → chunk-5U72NXGG.cjs} +9 -9
  7. package/dist/{chunk-DFCPMBYN.cjs.map → chunk-5U72NXGG.cjs.map} +1 -1
  8. package/dist/{chunk-HXDBJR4A.cjs → chunk-BEPZ5YAN.cjs} +59 -59
  9. package/dist/{chunk-HXDBJR4A.cjs.map → chunk-BEPZ5YAN.cjs.map} +1 -1
  10. package/dist/{chunk-DRSAMA27.cjs → chunk-J2UDV3FH.cjs} +62 -62
  11. package/dist/{chunk-DRSAMA27.cjs.map → chunk-J2UDV3FH.cjs.map} +1 -1
  12. package/dist/{chunk-PVMISZQT.cjs → chunk-M7WTWBCY.cjs} +37 -37
  13. package/dist/{chunk-PVMISZQT.cjs.map → chunk-M7WTWBCY.cjs.map} +1 -1
  14. package/dist/{chunk-WAKXAMRC.js → chunk-MPN3MV5H.js} +4 -4
  15. package/dist/{chunk-GAD4OYKN.js → chunk-NJPMHDFG.js} +2 -2
  16. package/dist/{chunk-5J3LR56Z.js → chunk-NN73KMIK.js} +2 -2
  17. package/dist/{chunk-IEKEUSPV.cjs → chunk-OSOKWP5R.cjs} +31 -31
  18. package/dist/{chunk-IEKEUSPV.cjs.map → chunk-OSOKWP5R.cjs.map} +1 -1
  19. package/dist/{chunk-AGTM2H7T.js → chunk-PFF6Q6N5.js} +2 -2
  20. package/dist/{chunk-AQJ5S6RS.js → chunk-PJ2YFSMY.js} +2 -2
  21. package/dist/{chunk-R6PS3AXI.cjs → chunk-PNMX75W4.cjs} +1171 -1057
  22. package/dist/chunk-PNMX75W4.cjs.map +1 -0
  23. package/dist/{chunk-LP3FQTCB.cjs → chunk-RNWLVI3V.cjs} +26 -26
  24. package/dist/{chunk-LP3FQTCB.cjs.map → chunk-RNWLVI3V.cjs.map} +1 -1
  25. package/dist/{chunk-NYBMBTBL.js → chunk-RWBTSF2E.js} +5295 -5181
  26. package/dist/chunk-RWBTSF2E.js.map +1 -0
  27. package/dist/{chunk-CJMM2WZP.cjs → chunk-T6FEE3R2.cjs} +5 -5
  28. package/dist/{chunk-CJMM2WZP.cjs.map → chunk-T6FEE3R2.cjs.map} +1 -1
  29. package/dist/{chunk-G6CNEEZ3.js → chunk-TTEAYVLT.js} +2 -2
  30. package/dist/{chunk-DXIDOGF4.js → chunk-TWLPG5IQ.js} +2 -2
  31. package/dist/{chunk-A7IGCQUD.js → chunk-WHJEU7V7.js} +4 -4
  32. package/dist/{chunk-4FT3KQD3.cjs → chunk-Y4I4462K.cjs} +15 -15
  33. package/dist/{chunk-4FT3KQD3.cjs.map → chunk-Y4I4462K.cjs.map} +1 -1
  34. package/dist/{chunk-FVFPGGDA.js → chunk-YJE3HL6Q.js} +4 -4
  35. package/dist/{chunk-2J6AOHJI.js → chunk-ZB5HSWX4.js} +2 -2
  36. package/dist/{chunk-YIJGUS7V.cjs → chunk-ZCKVP4SU.cjs} +26 -26
  37. package/dist/{chunk-YIJGUS7V.cjs.map → chunk-ZCKVP4SU.cjs.map} +1 -1
  38. package/dist/components/case-studies/index.cjs +8 -8
  39. package/dist/components/case-studies/index.js +2 -2
  40. package/dist/components/chat/index.cjs +2 -2
  41. package/dist/components/chat/index.js +1 -1
  42. package/dist/components/contact/index.cjs +3 -3
  43. package/dist/components/contact/index.js +2 -2
  44. package/dist/components/docs/index.cjs +5 -5
  45. package/dist/components/docs/index.js +4 -4
  46. package/dist/components/embeds/index.cjs +3 -3
  47. package/dist/components/embeds/index.js +2 -2
  48. package/dist/components/faq/index.cjs +3 -3
  49. package/dist/components/faq/index.js +2 -2
  50. package/dist/components/features/cards-strip.d.ts +114 -0
  51. package/dist/components/features/cards-strip.d.ts.map +1 -0
  52. package/dist/components/features/index.cjs +6 -2
  53. package/dist/components/features/index.cjs.map +1 -1
  54. package/dist/components/features/index.d.ts +1 -0
  55. package/dist/components/features/index.d.ts.map +1 -1
  56. package/dist/components/features/index.js +5 -1
  57. package/dist/components/features/video-bites-strip.d.ts +15 -26
  58. package/dist/components/features/video-bites-strip.d.ts.map +1 -1
  59. package/dist/components/help-center-pages/index.cjs +20 -20
  60. package/dist/components/help-center-pages/index.js +11 -11
  61. package/dist/components/index.cjs +124 -120
  62. package/dist/components/index.cjs.map +1 -1
  63. package/dist/components/index.js +13 -9
  64. package/dist/components/index.js.map +1 -1
  65. package/dist/components/navigation/index.cjs +2 -2
  66. package/dist/components/navigation/index.js +1 -1
  67. package/dist/components/onboarding-guides/index.cjs +5 -5
  68. package/dist/components/onboarding-guides/index.js +4 -4
  69. package/dist/components/related-content/index.cjs +3 -3
  70. package/dist/components/related-content/index.js +2 -2
  71. package/dist/components/tickets/index.cjs +5 -5
  72. package/dist/components/tickets/index.js +4 -4
  73. package/dist/components/ui/index.cjs +2 -2
  74. package/dist/components/ui/index.js +1 -1
  75. package/dist/index.cjs +6 -2
  76. package/dist/index.cjs.map +1 -1
  77. package/dist/index.js +5 -1
  78. package/dist/types/blog.d.ts +9 -10
  79. package/dist/types/blog.d.ts.map +1 -1
  80. package/dist/types/case-study.d.ts +2 -5
  81. package/dist/types/case-study.d.ts.map +1 -1
  82. package/dist/types/customer-interview.d.ts +2 -5
  83. package/dist/types/customer-interview.d.ts.map +1 -1
  84. package/dist/types/entity-platform.d.ts +22 -0
  85. package/dist/types/entity-platform.d.ts.map +1 -0
  86. package/dist/types/index.d.ts +1 -0
  87. package/dist/types/index.d.ts.map +1 -1
  88. package/package.json +1 -2
  89. package/src/components/features/cards-strip.tsx +587 -0
  90. package/src/components/features/index.ts +3 -0
  91. package/src/components/features/video-bites-strip.tsx +53 -375
  92. package/src/stories/CardsStrip.stories.tsx +101 -0
  93. package/src/types/blog.ts +9 -10
  94. package/src/types/case-study.ts +2 -5
  95. package/src/types/customer-interview.ts +2 -5
  96. package/src/types/entity-platform.ts +21 -0
  97. package/src/types/index.ts +1 -0
  98. package/dist/chunk-NYBMBTBL.js.map +0 -1
  99. package/dist/chunk-R6PS3AXI.cjs.map +0 -1
  100. package/dist/components/carousel.d.ts +0 -19
  101. package/dist/components/carousel.d.ts.map +0 -1
  102. package/src/components/.carousel.md +0 -50
  103. package/src/components/carousel.tsx +0 -262
  104. /package/dist/{chunk-SSZL4QYX.js.map → chunk-45Z6B6CO.js.map} +0 -0
  105. /package/dist/{chunk-WAKXAMRC.js.map → chunk-MPN3MV5H.js.map} +0 -0
  106. /package/dist/{chunk-GAD4OYKN.js.map → chunk-NJPMHDFG.js.map} +0 -0
  107. /package/dist/{chunk-5J3LR56Z.js.map → chunk-NN73KMIK.js.map} +0 -0
  108. /package/dist/{chunk-AGTM2H7T.js.map → chunk-PFF6Q6N5.js.map} +0 -0
  109. /package/dist/{chunk-AQJ5S6RS.js.map → chunk-PJ2YFSMY.js.map} +0 -0
  110. /package/dist/{chunk-G6CNEEZ3.js.map → chunk-TTEAYVLT.js.map} +0 -0
  111. /package/dist/{chunk-DXIDOGF4.js.map → chunk-TWLPG5IQ.js.map} +0 -0
  112. /package/dist/{chunk-A7IGCQUD.js.map → chunk-WHJEU7V7.js.map} +0 -0
  113. /package/dist/{chunk-FVFPGGDA.js.map → chunk-YJE3HL6Q.js.map} +0 -0
  114. /package/dist/{chunk-2J6AOHJI.js.map → chunk-ZB5HSWX4.js.map} +0 -0
@@ -1 +1 @@
1
- {"version":3,"file":"customer-interview.d.ts","sourceRoot":"","sources":["../../src/types/customer-interview.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"customer-interview.d.ts","sourceRoot":"","sources":["../../src/types/customer-interview.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Hydrated `entity_platforms` junction row — THE shape the hub's
3
+ * attachPlatformsToRows puts on every `<entity>_platforms[]` array
4
+ * (rows arrive featured-first with the platform record's fields spread on
5
+ * via PLATFORM_EMBED). One definition; the per-entity types
6
+ * (case_study_platforms / blog_post_platforms / customer_interview_platforms)
7
+ * all reference it so the shape can never drift per entity.
8
+ */
9
+ export interface EntityPlatformAssoc {
10
+ platform_id: string;
11
+ is_featured: boolean;
12
+ featured_order?: number | null;
13
+ /** Platform name — spread from the platform record. Feeds platform-aware
14
+ * URL composition (composeContentUrlFromPlatforms) without casts. */
15
+ name?: string;
16
+ display_name?: string;
17
+ /** Alias some admin fetches add (e.g. getCaseStudyForAdmin). */
18
+ platform_name?: string;
19
+ id?: string;
20
+ created_at?: string;
21
+ }
22
+ //# sourceMappingURL=entity-platform.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"entity-platform.d.ts","sourceRoot":"","sources":["../../src/types/entity-platform.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,MAAM,WAAW,mBAAmB;IAClC,WAAW,EAAE,MAAM,CAAA;IACnB,WAAW,EAAE,OAAO,CAAA;IACpB,cAAc,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC9B;0EACsE;IACtE,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,gEAAgE;IAChE,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,EAAE,CAAC,EAAE,MAAM,CAAA;IACX,UAAU,CAAC,EAAE,MAAM,CAAA;CACpB"}
@@ -9,6 +9,7 @@ export * from './auth';
9
9
  export * from './blog';
10
10
  export * from './entity-author';
11
11
  export * from './case-study';
12
+ export * from './entity-platform';
12
13
  export * from './customer-interview';
13
14
  export * from './customer-interview-ai.types';
14
15
  export * from './luma';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/types/index.ts"],"names":[],"mappings":"AAIA,cAAc,gBAAgB,CAAA;AAI9B,cAAc,cAAc,CAAA;AAC5B,YAAY,EAAE,QAAQ,EAAE,iBAAiB,EAAE,qBAAqB,EAAE,MAAM,cAAc,CAAA;AACtF,cAAc,YAAY,CAAA;AAC1B,cAAc,SAAS,CAAA;AACvB,YAAY,EAAE,cAAc,EAAE,cAAc,EAAE,YAAY,EAAE,cAAc,EAAE,cAAc,EAAE,cAAc,IAAI,gBAAgB,EAAE,aAAa,EAAE,MAAM,YAAY,CAAA;AAGjK,cAAc,YAAY,CAAA;AAG1B,cAAc,QAAQ,CAAA;AAGtB,cAAc,QAAQ,CAAA;AACtB,cAAc,iBAAiB,CAAA;AAC/B,cAAc,cAAc,CAAA;AAC5B,cAAc,sBAAsB,CAAA;AACpC,cAAc,+BAA+B,CAAA;AAC7C,cAAc,QAAQ,CAAA;AACtB,cAAc,mBAAmB,CAAA;AACjC,cAAc,YAAY,CAAA;AAC1B,cAAc,UAAU,CAAA;AACxB,cAAc,gBAAgB,CAAA;AAC9B,cAAc,oBAAoB,CAAA;AAGlC,cAAc,YAAY,CAAA;AAC1B,cAAc,WAAW,CAAA;AACzB,cAAc,QAAQ,CAAA;AACtB,cAAc,QAAQ,CAAA;AAGtB,cAAc,SAAS,CAAA;AACvB,cAAc,YAAY,CAAA;AAG1B,cAAc,OAAO,CAAA;AACrB,cAAc,eAAe,CAAA;AAC7B,cAAc,UAAU,CAAA;AACxB,cAAc,SAAS,CAAA;AAGvB,cAAc,cAAc,CAAA;AAG5B,cAAc,cAAc,CAAA;AAG5B,cAAc,eAAe,CAAA;AAG7B,cAAc,YAAY,CAAA;AAG1B,cAAc,aAAa,CAAA;AAE3B,cAAc,uBAAuB,CAAA;AACrC,cAAc,SAAS,CAAA;AACvB,cAAc,eAAe,CAAA;AAC7B,cAAc,QAAQ,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/types/index.ts"],"names":[],"mappings":"AAIA,cAAc,gBAAgB,CAAA;AAI9B,cAAc,cAAc,CAAA;AAC5B,YAAY,EAAE,QAAQ,EAAE,iBAAiB,EAAE,qBAAqB,EAAE,MAAM,cAAc,CAAA;AACtF,cAAc,YAAY,CAAA;AAC1B,cAAc,SAAS,CAAA;AACvB,YAAY,EAAE,cAAc,EAAE,cAAc,EAAE,YAAY,EAAE,cAAc,EAAE,cAAc,EAAE,cAAc,IAAI,gBAAgB,EAAE,aAAa,EAAE,MAAM,YAAY,CAAA;AAGjK,cAAc,YAAY,CAAA;AAG1B,cAAc,QAAQ,CAAA;AAGtB,cAAc,QAAQ,CAAA;AACtB,cAAc,iBAAiB,CAAA;AAC/B,cAAc,cAAc,CAAA;AAC5B,cAAc,mBAAmB,CAAA;AACjC,cAAc,sBAAsB,CAAA;AACpC,cAAc,+BAA+B,CAAA;AAC7C,cAAc,QAAQ,CAAA;AACtB,cAAc,mBAAmB,CAAA;AACjC,cAAc,YAAY,CAAA;AAC1B,cAAc,UAAU,CAAA;AACxB,cAAc,gBAAgB,CAAA;AAC9B,cAAc,oBAAoB,CAAA;AAGlC,cAAc,YAAY,CAAA;AAC1B,cAAc,WAAW,CAAA;AACzB,cAAc,QAAQ,CAAA;AACtB,cAAc,QAAQ,CAAA;AAGtB,cAAc,SAAS,CAAA;AACvB,cAAc,YAAY,CAAA;AAG1B,cAAc,OAAO,CAAA;AACrB,cAAc,eAAe,CAAA;AAC7B,cAAc,UAAU,CAAA;AACxB,cAAc,SAAS,CAAA;AAGvB,cAAc,cAAc,CAAA;AAG5B,cAAc,cAAc,CAAA;AAG5B,cAAc,eAAe,CAAA;AAG7B,cAAc,YAAY,CAAA;AAG1B,cAAc,aAAa,CAAA;AAE3B,cAAc,uBAAuB,CAAA;AACrC,cAAc,SAAS,CAAA;AACvB,cAAc,eAAe,CAAA;AAC7B,cAAc,QAAQ,CAAA"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@flamingo-stack/openframe-frontend-core",
3
- "version": "0.0.394",
3
+ "version": "0.0.395",
4
4
  "description": "Shared design system and components for all Flamingo platforms",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -323,7 +323,6 @@
323
323
  "class-variance-authority": "^0.7.0",
324
324
  "clsx": "^2.1.1",
325
325
  "cmdk": "^1.0.4",
326
- "embla-carousel-react": "^8.6.0",
327
326
  "file-type": "^22.0.1",
328
327
  "framer-motion": "^12.23.12",
329
328
  "graphql": "^16.12.0",
@@ -0,0 +1,587 @@
1
+ "use client";
2
+
3
+ /**
4
+ * <CardsStrip> — THE horizontal card-strip engine (single source of truth).
5
+ *
6
+ * Extracted verbatim from <VideoBitesStrip> so every strip surface shares ONE
7
+ * marquee/chevron/seam/measure implementation ("unify means delete
8
+ * variations"). Two input modes, one engine:
9
+ *
10
+ * MODE A — render-prop (`items` + `itemKey` + `renderCard`): full control.
11
+ * The card receives the strip context (controlled activation, clone flag,
12
+ * shared player-mount gate) and owns its own clone a11y. Used by
13
+ * <VideoBitesStrip>. The engine adds NO cell wrapper in this mode — the
14
+ * card's root is the track's direct child (`scrollByCard` measures
15
+ * `track.firstElementChild.offsetWidth`, and the `flex items-stretch gap-4`
16
+ * sizing contract depends on it).
17
+ *
18
+ * MODE B — children (organic): ANY card components as plain JSX children.
19
+ * Each child is auto-wrapped in a managed cell (fixed width, hover/keyboard
20
+ * marquee-pause, clone `aria-hidden` + `inert`, row-height stretch).
21
+ * Adding a new card type requires ZERO strip code. Contract:
22
+ * (a) pass an ARRAY of children (`{rows.map(...)}`) — `Children.toArray`
23
+ * does NOT flatten through a single <Fragment> child (it would become
24
+ * one giant cell; a dev-only warning fires);
25
+ * (b) when `autoScroll` is on, children must be ref-free / unique-DOM-id-
26
+ * free / mount-effect-free — the clone copy mounts the same element a
27
+ * second time (double-fired effects, duplicate ids, last-mounted ref).
28
+ *
29
+ * Activation invariant: hover/focus activation is wired in exactly ONE place —
30
+ * the managed cell (children mode) OR the card itself (render-prop mode,
31
+ * e.g. <VideoBiteCard>) — never both.
32
+ *
33
+ * Marquee state model (explicit — do not "simplify" into one timer):
34
+ * the rAF advances only when the pause-reason set {cardHovered, offViewport,
35
+ * tabHidden, reducedMotion} is empty AND `now > max(chevronSuppressUntil,
36
+ * userScrollSuppressUntil)`. cardHovered means the pointer is over a CARD
37
+ * (incl. its overlay) — leaving the card resumes the marquee immediately.
38
+ * Chevron clicks and manual wheel/touch each set their own suppress-until
39
+ * timestamp; any card-level leave-grace is a separate concern that never
40
+ * touches marquee state.
41
+ */
42
+
43
+ import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
44
+ import { cn } from '../../utils/cn';
45
+ import { Button } from '../ui/button';
46
+ import { SECTION_HEADING_CLASS } from '../layout/page-heading';
47
+ import { Chevron02LeftIcon } from '../icons-v2-generated/arrows/chevron-02-left-icon';
48
+ import { Chevron02RightIcon } from '../icons-v2-generated/arrows/chevron-02-right-icon';
49
+
50
+ // =============================================================================
51
+ // Types
52
+ // =============================================================================
53
+
54
+ /** Per-card strip context passed to `renderCard` (and used internally by the
55
+ * children-mode managed cell). */
56
+ export interface CardStripRenderCtx {
57
+ /** Item index (copy-independent — the clone copy repeats the same indices). */
58
+ index: number;
59
+ /** `${itemKey}__${copyIndex}__${index}` — unique per rendered card, even for
60
+ * duplicate items (the trailing index disambiguates). */
61
+ cardKey: string;
62
+ isClone: boolean;
63
+ isTouch: boolean;
64
+ /** Engine's (max-width: 767px) media query. */
65
+ isMobile: boolean;
66
+ /** activeKey === cardKey (controlled hover/focus activation). */
67
+ active: boolean;
68
+ onActivate: (key: string) => void;
69
+ onDeactivate: (key: string) => void;
70
+ /** Shared-by-index 500px near-viewport gate (opt-in — only meaningful when
71
+ * the card registers via `rootRef`). */
72
+ mounted: boolean;
73
+ /** Registers the card root with the mount-gate IntersectionObserver.
74
+ * May return a cleanup (React 19 ref contract). */
75
+ rootRef: (el: HTMLDivElement | null) => (() => void) | undefined;
76
+ }
77
+
78
+ interface CardsStripEngineProps {
79
+ /** Section heading. Omit `title` (or set `showTitle={false}`) to hide. */
80
+ title?: string;
81
+ showTitle?: boolean;
82
+ /** Custom node rendered between the heading and the strip. */
83
+ headerSlot?: React.ReactNode;
84
+ /** Marquee auto-scroll. Auto-disabled on no-overflow and
85
+ * prefers-reduced-motion. Pass `autoScroll={false}` for chevron-style
86
+ * entity strips — the default stays `true` for bites-contract parity. */
87
+ autoScroll?: boolean;
88
+ /** Marquee speed in px/s. Default 60 — marquee libraries (e.g. GSAP marquee)
89
+ * default around 100px/s; 60 keeps drift lively while cards stay easy to
90
+ * hover-target. 60px/s ≈ 1px per 60Hz frame, the smoothest integer step. */
91
+ autoScrollSpeed?: number;
92
+ /** Pause the marquee while a CARD is hovered (resumes as soon as the
93
+ * pointer leaves the card — strip whitespace/heading never pauses). */
94
+ pauseOnHover?: boolean;
95
+ /** Floating prev/next buttons. Hidden automatically when nothing overflows. */
96
+ showChevrons?: boolean;
97
+ /** Chevron aria-labels. */
98
+ prevLabel?: string;
99
+ nextLabel?: string;
100
+ /** Merged into the engine's wrapper ref (e.g. `useVideoWarmup().ref`). */
101
+ rootRef?: (el: HTMLDivElement | null) => void;
102
+ className?: string;
103
+ }
104
+
105
+ // The two mode interfaces hand-mirror each other's field names as `never` —
106
+ // INVARIANT: every field added to one mode gets a `never` twin in the other,
107
+ // or the XOR discrimination silently weakens (mixed-mode props become
108
+ // assignable). Same manual pattern as social-icon-row.tsx / chart.tsx.
109
+
110
+ /** MODE A — render-prop (advanced): full ctx control, no cell wrapper. */
111
+ interface RenderCardMode<T> {
112
+ items: ReadonlyArray<T>;
113
+ itemKey: (item: T, index: number) => string;
114
+ renderCard: (item: T, ctx: CardStripRenderCtx) => React.ReactNode;
115
+ children?: never;
116
+ cardWidthDesktop?: never;
117
+ cardWidthMobile?: never;
118
+ }
119
+
120
+ /** MODE B — children (organic): ANY card components, zero registration. */
121
+ interface ChildrenMode {
122
+ children: React.ReactNode;
123
+ /** Managed cell width in px per breakpoint (Figma: 400 desktop). */
124
+ cardWidthDesktop?: number;
125
+ cardWidthMobile?: number;
126
+ items?: never;
127
+ itemKey?: never;
128
+ renderCard?: never;
129
+ }
130
+
131
+ export type CardsStripProps<T = unknown> = CardsStripEngineProps & (RenderCardMode<T> | ChildrenMode);
132
+
133
+ // =============================================================================
134
+ // Constants
135
+ // =============================================================================
136
+
137
+ // Gap between cards (px) — keep in sync with the track's `gap-4`.
138
+ const TRACK_GAP_PX = 16;
139
+ // Marquee suppression windows after manual interaction.
140
+ const CHEVRON_SUPPRESS_MS = 4000;
141
+ const USER_SCROLL_SUPPRESS_MS = 3000;
142
+
143
+ /** Single source for the strip cell width cap (shared with <VideoBiteCard>). */
144
+ export const STRIP_CELL_MAX_WIDTH = '90vw';
145
+
146
+ const EMPTY_CHILDREN: ReadonlyArray<React.ReactNode> = [];
147
+
148
+ // =============================================================================
149
+ // Component
150
+ // =============================================================================
151
+
152
+ export function CardsStrip<T = unknown>(props: CardsStripProps<T>): React.ReactElement | null {
153
+ const {
154
+ title,
155
+ showTitle = true,
156
+ headerSlot,
157
+ autoScroll = true,
158
+ autoScrollSpeed = 60,
159
+ pauseOnHover = true,
160
+ showChevrons = true,
161
+ prevLabel = 'Previous items',
162
+ nextLabel = 'Next items',
163
+ rootRef,
164
+ className,
165
+ } = props;
166
+
167
+ // ---- input-mode normalization ---------------------------------------------
168
+ // Children mode feeds the SAME engine path as render-prop mode — one
169
+ // track/clone/measure/scroll implementation. Engine internals operate on the
170
+ // erased item type (`unknown`); the only assertions live at this seam.
171
+ const childrenMode = props.renderCard === undefined;
172
+ const childItems = useMemo(
173
+ () => (childrenMode ? React.Children.toArray(props.children) : EMPTY_CHILDREN),
174
+ [childrenMode, props.children],
175
+ );
176
+ const items: ReadonlyArray<unknown> = childrenMode ? childItems : props.items;
177
+ const cardWidthDesktop = childrenMode ? (props.cardWidthDesktop ?? 400) : 0;
178
+ const cardWidthMobile = childrenMode ? (props.cardWidthMobile ?? 320) : 0;
179
+
180
+ useEffect(() => {
181
+ if (process.env.NODE_ENV === 'production' || !childrenMode) return;
182
+ const only = childItems.length === 1 ? childItems[0] : null;
183
+ if (React.isValidElement(only) && only.type === React.Fragment) {
184
+ console.warn(
185
+ '[CardsStrip] A single <Fragment> child becomes ONE giant cell — pass an array of card elements ({rows.map(...)}) instead.',
186
+ );
187
+ }
188
+ }, [childrenMode, childItems]);
189
+
190
+ const getItemKey = (item: unknown, index: number): string => {
191
+ if (!childrenMode) {
192
+ // Seam assertion: T is erased inside the engine; the props union
193
+ // guarantees itemKey matches the items array.
194
+ return (props.itemKey as (item: unknown, index: number) => string)(item, index);
195
+ }
196
+ // Children.toArray always assigns element keys ('.0' / '.$slug' — stable
197
+ // across renders); explicit null-check because `&&`/`??` chaining yields
198
+ // the string "false" for non-element children (same isValidElement guard
199
+ // as steps.tsx).
200
+ return React.isValidElement(item) && item.key != null ? String(item.key) : String(index);
201
+ };
202
+
203
+ const wrapperRef = useRef<HTMLDivElement | null>(null);
204
+ const scrollerRef = useRef<HTMLDivElement | null>(null);
205
+ const trackRef = useRef<HTMLDivElement | null>(null);
206
+
207
+ // ---- environment ----------------------------------------------------------
208
+ const [reducedMotion, setReducedMotion] = useState(false);
209
+ const [isTouch, setIsTouch] = useState(false);
210
+ const [isMobile, setIsMobile] = useState(false);
211
+ useEffect(() => {
212
+ const subs: Array<() => void> = [];
213
+ const watch = (query: string, set: (v: boolean) => void) => {
214
+ const mq = window.matchMedia(query);
215
+ set(mq.matches);
216
+ const onChange = (e: MediaQueryListEvent) => set(e.matches);
217
+ mq.addEventListener('change', onChange);
218
+ subs.push(() => mq.removeEventListener('change', onChange));
219
+ };
220
+ watch('(prefers-reduced-motion: reduce)', setReducedMotion);
221
+ watch('(hover: none)', setIsTouch);
222
+ watch('(max-width: 767px)', setIsMobile);
223
+ return () => subs.forEach(fn => fn());
224
+ }, []);
225
+
226
+ // ---- overflow measurement ---------------------------------------------------
227
+ // `overflows` gates chevrons; `marqueeActive` additionally gates cloning + rAF.
228
+ const [overflows, setOverflows] = useState(false);
229
+ const singleCopyWidthRef = useRef(0);
230
+ const marqueeActive = autoScroll && !reducedMotion && overflows && items.length > 0;
231
+
232
+ const measure = useCallback(() => {
233
+ const scroller = scrollerRef.current;
234
+ const track = trackRef.current;
235
+ if (!scroller || !track) return;
236
+ // When clones are rendered the track is 2× one copy (+ one joining gap).
237
+ const copies = track.dataset.copies === '2' ? 2 : 1;
238
+ const singleCopy = copies === 2 ? (track.scrollWidth - TRACK_GAP_PX) / 2 + TRACK_GAP_PX : track.scrollWidth;
239
+ singleCopyWidthRef.current = singleCopy;
240
+ setOverflows(singleCopy > scroller.clientWidth + 1);
241
+ }, []);
242
+
243
+ useEffect(() => {
244
+ measure();
245
+ const scroller = scrollerRef.current;
246
+ const track = trackRef.current;
247
+ if (!scroller || !track || typeof ResizeObserver === 'undefined') return;
248
+ const ro = new ResizeObserver(measure);
249
+ ro.observe(scroller);
250
+ ro.observe(track);
251
+ return () => ro.disconnect();
252
+ }, [measure, items.length, marqueeActive, isMobile]);
253
+
254
+ // ---- pause-reason set + suppress timestamps ---------------------------------
255
+ // (Card hover — the only hover-based pause reason — lives in activeKeyRef below.)
256
+ const nearViewportRef = useRef(true);
257
+ const chevronSuppressUntilRef = useRef(0);
258
+ const userScrollSuppressUntilRef = useRef(0);
259
+
260
+ // Two-way viewport gate (unlike the fire-once `useNearViewport`): the rAF
261
+ // should stop again when the strip scrolls far off-screen.
262
+ useEffect(() => {
263
+ const el = wrapperRef.current;
264
+ if (!el || typeof IntersectionObserver === 'undefined') return;
265
+ const io = new IntersectionObserver(
266
+ entries => { nearViewportRef.current = entries[0]?.isIntersecting ?? true; },
267
+ { rootMargin: '200px' },
268
+ );
269
+ io.observe(el);
270
+ return () => io.disconnect();
271
+ }, []);
272
+
273
+ // ---- marquee rAF engine ------------------------------------------------------
274
+ // Position lives in a FLOAT ref, not in scrollLeft read-modify-write: reading
275
+ // scrollLeft back each frame returns a rounded value, so sub-pixel increments
276
+ // (speed/fps < 1px) get eaten by rounding — the old engine both stuttered AND
277
+ // ran measurably slower than the configured speed because of it.
278
+ //
279
+ // Chevron GLIDE also runs through this engine (glideRemainingRef): browser
280
+ // `scrollBy({behavior:'smooth'})` animates toward an ABSOLUTE target, so a
281
+ // seam warp mid-animation made it lunge a full copy-width to catch up, warp
282
+ // again, and oscillate — rapid chevron clicks left every card flickering.
283
+ // The rAF tween consumes a signed remaining-distance instead, wrapping
284
+ // modulo the copy width each frame, so warps are invisible to it.
285
+ const marqueePosRef = useRef(0);
286
+ const glideRemainingRef = useRef(0);
287
+ useEffect(() => {
288
+ if (!marqueeActive) return;
289
+ const scroller = scrollerRef.current;
290
+ if (!scroller) return;
291
+ let raf = 0;
292
+ let last = performance.now();
293
+ marqueePosRef.current = scroller.scrollLeft;
294
+ const wrap = (pos: number) => {
295
+ const half = singleCopyWidthRef.current;
296
+ if (half <= 0) return pos;
297
+ while (pos >= half) pos -= half;
298
+ while (pos < 0) pos += half;
299
+ return pos;
300
+ };
301
+ const tick = (now: number) => {
302
+ const dt = Math.min(now - last, 100) / 1000; // clamp tab-wake jumps
303
+ last = now;
304
+ const paused =
305
+ (pauseOnHover && activeKeyRef.current !== null) ||
306
+ !nearViewportRef.current ||
307
+ document.visibilityState === 'hidden' ||
308
+ now < Math.max(chevronSuppressUntilRef.current, userScrollSuppressUntilRef.current);
309
+ const glide = glideRemainingRef.current;
310
+ if (glide !== 0) {
311
+ // Ease-out toward the chevron target; runs even while "paused"
312
+ // (the chevron click itself sets the suppress window).
313
+ if (Math.abs(scroller.scrollLeft - marqueePosRef.current) > 1.5) {
314
+ marqueePosRef.current = scroller.scrollLeft;
315
+ }
316
+ const speed = Math.max(Math.abs(glide) * 6, 240); // px/s, proportional
317
+ const step = Math.sign(glide) * Math.min(Math.abs(glide), speed * dt);
318
+ glideRemainingRef.current = Math.abs(glide - step) < 0.5 ? 0 : glide - step;
319
+ marqueePosRef.current = wrap(marqueePosRef.current + step);
320
+ scroller.scrollLeft = marqueePosRef.current;
321
+ } else if (!paused) {
322
+ // Resync after external movement (user scroll, seam warp).
323
+ if (Math.abs(scroller.scrollLeft - marqueePosRef.current) > 1.5) {
324
+ marqueePosRef.current = scroller.scrollLeft;
325
+ }
326
+ marqueePosRef.current = wrap(marqueePosRef.current + autoScrollSpeed * dt);
327
+ scroller.scrollLeft = marqueePosRef.current;
328
+ } else {
329
+ marqueePosRef.current = scroller.scrollLeft;
330
+ }
331
+ raf = requestAnimationFrame(tick);
332
+ };
333
+ raf = requestAnimationFrame(tick);
334
+ return () => cancelAnimationFrame(raf);
335
+ }, [marqueeActive, autoScrollSpeed, pauseOnHover]);
336
+
337
+ // ---- hover / overlay state -----------------------------------------------------
338
+ // activeKey identifies the hovered/tap-activated CARD (copy-aware so hovering
339
+ // a clone near the wrap seam works too). It only drives per-card `active`
340
+ // state — any heavier behavior (players, overlays) is the card's concern.
341
+ const [activeKey, setActiveKey] = useState<string | null>(null);
342
+ // Ref mirror for the rAF loop: the marquee pauses ONLY while a CARD is
343
+ // hovered (activeKey set) — hovering strip whitespace/heading does not
344
+ // pause, and leaving a card resumes immediately.
345
+ const activeKeyRef = useRef<string | null>(null);
346
+ const activate = useCallback((key: string) => {
347
+ activeKeyRef.current = key;
348
+ setActiveKey(key);
349
+ }, []);
350
+ const deactivate = useCallback((key: string) => {
351
+ // Ref write hoisted OUT of the state updater: React re-invokes queued
352
+ // updaters at render time, so an impure updater could re-null the ref
353
+ // AFTER a same-batch activate(B) already set it (leave(A)+enter(B) in one
354
+ // dispatch when the pointer jumps directly between cells) — stranding the
355
+ // rAF pause check while a card is visibly hovered.
356
+ if (activeKeyRef.current === key) activeKeyRef.current = null;
357
+ setActiveKey(current => (current === key ? null : current));
358
+ }, []);
359
+
360
+ // ---- shared card-mount gate (by ITEM index, across both copies) -------------
361
+ // A card and its clone show identical content half a copy-width apart. If
362
+ // each card gated its own expensive content (e.g. video players), a seam warp
363
+ // instantly swapped the visible copy for cards whose content was NOT mounted
364
+ // yet — every wrap crossing flashed placeholders. Mounting by INDEX (near in
365
+ // EITHER copy → both copies mount) makes the warp pixel-identical. Opt-in:
366
+ // cards that never register via ctx.rootRef incur no per-card observation.
367
+ const [mountedIdx, setMountedIdx] = useState<ReadonlySet<number>>(() => new Set());
368
+ const cardObserverRef = useRef<IntersectionObserver | null>(null);
369
+ const cardElIdxRef = useRef(new Map<Element, number>());
370
+ const cardElNearRef = useRef(new Map<Element, boolean>());
371
+ useEffect(() => {
372
+ if (typeof IntersectionObserver === 'undefined') return;
373
+ const io = new IntersectionObserver(entries => {
374
+ for (const e of entries) cardElNearRef.current.set(e.target, e.isIntersecting);
375
+ const next = new Set<number>();
376
+ cardElNearRef.current.forEach((near, el) => {
377
+ if (!near) return;
378
+ const idx = cardElIdxRef.current.get(el);
379
+ if (idx !== undefined) next.add(idx);
380
+ });
381
+ setMountedIdx(prev => {
382
+ if (prev.size === next.size) {
383
+ let same = true;
384
+ next.forEach(i => { if (!prev.has(i)) same = false; });
385
+ if (same) return prev;
386
+ }
387
+ return next;
388
+ });
389
+ }, { rootMargin: '500px' });
390
+ cardObserverRef.current = io;
391
+ cardElIdxRef.current.forEach((_idx, el) => io.observe(el));
392
+ return () => { io.disconnect(); cardObserverRef.current = null; };
393
+ }, []);
394
+ // Stable per-index ref callbacks (a fresh closure per render would detach/
395
+ // re-attach the observer every render). React 19 ref-cleanup unregisters.
396
+ const cardRefFnsRef = useRef(new Map<number, (el: HTMLDivElement | null) => (() => void) | undefined>());
397
+ const getCardRef = useCallback((idx: number) => {
398
+ let fn = cardRefFnsRef.current.get(idx);
399
+ if (!fn) {
400
+ fn = (el: HTMLDivElement | null) => {
401
+ if (!el) return undefined;
402
+ cardElIdxRef.current.set(el, idx);
403
+ cardObserverRef.current?.observe(el);
404
+ return () => {
405
+ cardElIdxRef.current.delete(el);
406
+ cardElNearRef.current.delete(el);
407
+ cardObserverRef.current?.unobserve(el);
408
+ };
409
+ };
410
+ cardRefFnsRef.current.set(idx, fn);
411
+ }
412
+ return fn;
413
+ }, []);
414
+
415
+ // ---- manual navigation -----------------------------------------------------------
416
+ const scrollByCard = useCallback((dir: 1 | -1) => {
417
+ const scroller = scrollerRef.current;
418
+ const track = trackRef.current;
419
+ if (!scroller || !track) return;
420
+ const firstCard = track.firstElementChild as HTMLElement | null;
421
+ const step = (firstCard?.offsetWidth ?? 320) + TRACK_GAP_PX;
422
+ chevronSuppressUntilRef.current = performance.now() + CHEVRON_SUPPRESS_MS;
423
+ if (marqueeActiveRef.current) {
424
+ // Wrap-aware glide via the rAF engine (see engine comment) — clicks
425
+ // accumulate distance instead of racing browser smooth-scroll targets.
426
+ glideRemainingRef.current += dir * step;
427
+ } else {
428
+ // No clones/seam without the marquee — native smooth scroll is safe.
429
+ scroller.scrollBy({ left: dir * step, behavior: 'smooth' });
430
+ }
431
+ }, []);
432
+
433
+ const onUserScrollIntent = useCallback(() => {
434
+ userScrollSuppressUntilRef.current = performance.now() + USER_SCROLL_SUPPRESS_MS;
435
+ }, []);
436
+
437
+ // Never-ending strip: warp across the clone seam on EVERY scroll (manual
438
+ // wheel/drag/chevron included — the rAF only wraps while the marquee runs,
439
+ // so without this a user scrolling during a pause/suppress window hits the
440
+ // physical track edges). Both copies render identical content, so a ±half
441
+ // jump is pixel-invisible. Backward warp only when arriving AT the left
442
+ // edge from the right (never on initial rest at 0).
443
+ const lastScrollLeftRef = useRef(0);
444
+ const marqueeActiveRef = useRef(false);
445
+ marqueeActiveRef.current = marqueeActive;
446
+ const onSeamWarp = useCallback(() => {
447
+ const scroller = scrollerRef.current;
448
+ if (!scroller || !marqueeActiveRef.current) return;
449
+ const half = singleCopyWidthRef.current;
450
+ if (half <= 0) return;
451
+ let sl = scroller.scrollLeft;
452
+ if (sl >= half) {
453
+ sl -= half;
454
+ scroller.scrollLeft = sl;
455
+ marqueePosRef.current = sl;
456
+ } else if (sl <= 0 && lastScrollLeftRef.current > 0.5) {
457
+ sl += half;
458
+ scroller.scrollLeft = sl;
459
+ marqueePosRef.current = sl;
460
+ }
461
+ lastScrollLeftRef.current = sl;
462
+ }, []);
463
+
464
+ // ---- render ----------------------------------------------------------------
465
+
466
+ const renderItem = (item: unknown, ctx: CardStripRenderCtx): React.ReactNode => {
467
+ if (!childrenMode) {
468
+ // Seam assertion (see normalization comment above).
469
+ return (props.renderCard as (item: unknown, ctx: CardStripRenderCtx) => React.ReactNode)(item, ctx);
470
+ }
471
+ // Managed cell — the ONLY place cell width / activation / clone a11y are
472
+ // encoded for children mode.
473
+ return (
474
+ // OUTER cell: width + activation handlers (never inert — an inert cell
475
+ // would retarget pointer events to the track and defeat hover-pause on
476
+ // clones).
477
+ <div
478
+ className="shrink-0 self-stretch"
479
+ style={{ width: ctx.isMobile ? cardWidthMobile : cardWidthDesktop, maxWidth: STRIP_CELL_MAX_WIDTH }}
480
+ onPointerEnter={ctx.isTouch ? undefined : () => ctx.onActivate(ctx.cardKey)}
481
+ onPointerLeave={ctx.isTouch ? undefined : () => ctx.onDeactivate(ctx.cardKey)}
482
+ // Keyboard pause parity (focus bubbles from the card's anchor; WCAG 2.2.2).
483
+ onFocus={() => ctx.onActivate(ctx.cardKey)}
484
+ onBlur={e => {
485
+ // Intra-cell focus moves must not blip the pause off.
486
+ if (e.currentTarget.contains(e.relatedTarget as Node | null)) return;
487
+ ctx.onDeactivate(ctx.cardKey);
488
+ }}
489
+ >
490
+ {/* INNER: clone a11y + uniform stretch — cards untouched. `inert` on
491
+ the clone strips anchor focusability without modifying any card
492
+ (WCAG aria-hidden-focus); clone content is pointer/focus-dead by
493
+ design while the outer cell still hover-pauses. */}
494
+ <div
495
+ className="h-full [&>*]:h-full [&>a>*]:h-full"
496
+ aria-hidden={ctx.isClone || undefined}
497
+ inert={ctx.isClone || undefined}
498
+ >
499
+ {item as React.ReactNode}
500
+ </div>
501
+ </div>
502
+ );
503
+ };
504
+
505
+ if (items.length === 0) return null;
506
+
507
+ const copies = marqueeActive ? 2 : 1;
508
+
509
+ return (
510
+ <div
511
+ ref={node => { wrapperRef.current = node; rootRef?.(node); }}
512
+ className={cn('flex flex-col gap-6 w-full min-w-0', className)}
513
+ >
514
+ {showTitle && title && (
515
+ <h2 className={`${SECTION_HEADING_CLASS} break-words`}>{title}</h2>
516
+ )}
517
+ {headerSlot}
518
+
519
+ <div className="relative">
520
+ <div
521
+ ref={scrollerRef}
522
+ className="overflow-x-auto [scrollbar-width:none] [&::-webkit-scrollbar]:hidden"
523
+ onWheel={onUserScrollIntent}
524
+ onTouchStart={onUserScrollIntent}
525
+ onPointerDown={onUserScrollIntent}
526
+ onScroll={onSeamWarp}
527
+ >
528
+ <div ref={trackRef} data-copies={copies} className="flex w-max items-stretch gap-4">
529
+ {Array.from({ length: copies }, (_, copyIndex) =>
530
+ items.map((item, i) => {
531
+ const key = `${getItemKey(item, i)}__${copyIndex}__${i}`;
532
+ const isClone = copyIndex === 1;
533
+ return (
534
+ // Clone copy a11y (aria-hidden + no focusable descendants —
535
+ // WCAG) is the card's job in render-prop mode (via
536
+ // ctx.isClone) and the managed cell's job in children mode.
537
+ // The engine itself adds no wrapper here in render-prop mode
538
+ // so the card root stays the track's direct child.
539
+ <React.Fragment key={key}>
540
+ {renderItem(item, {
541
+ index: i,
542
+ cardKey: key,
543
+ isClone,
544
+ isTouch,
545
+ isMobile,
546
+ active: activeKey === key,
547
+ onActivate: activate,
548
+ onDeactivate: deactivate,
549
+ mounted: mountedIdx.has(i),
550
+ rootRef: getCardRef(i),
551
+ })}
552
+ </React.Fragment>
553
+ );
554
+ }),
555
+ )}
556
+ </div>
557
+ </div>
558
+
559
+ {showChevrons && overflows && (
560
+ <>
561
+ {/* Shared design-system Button: outline surface (ods-card + border,
562
+ the Figma button-icon look) + the square `icon` size, which owns
563
+ the centered 24px glyph. Icon-only → aria-label required. */}
564
+ <Button
565
+ type="button"
566
+ variant="outline"
567
+ size="icon"
568
+ aria-label={prevLabel}
569
+ onClick={() => scrollByCard(-1)}
570
+ className="absolute left-2 top-1/2 -translate-y-1/2 z-10"
571
+ leftIcon={<Chevron02LeftIcon />}
572
+ />
573
+ <Button
574
+ type="button"
575
+ variant="outline"
576
+ size="icon"
577
+ aria-label={nextLabel}
578
+ onClick={() => scrollByCard(1)}
579
+ className="absolute right-2 top-1/2 -translate-y-1/2 z-10"
580
+ leftIcon={<Chevron02RightIcon />}
581
+ />
582
+ </>
583
+ )}
584
+ </div>
585
+ </div>
586
+ );
587
+ }