@flamingo-stack/openframe-frontend-core 0.0.396-snapshot.20260709212121 → 0.0.396

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 (120) hide show
  1. package/dist/{chunk-IVXOR2IA.js → chunk-2PTEK3D6.js} +2 -2
  2. package/dist/{chunk-DB6CUWTL.cjs → chunk-6DGQPKVA.cjs} +26 -26
  3. package/dist/{chunk-DB6CUWTL.cjs.map → chunk-6DGQPKVA.cjs.map} +1 -1
  4. package/dist/{chunk-OAENYOKC.cjs → chunk-AODSKL5I.cjs} +9 -9
  5. package/dist/{chunk-OAENYOKC.cjs.map → chunk-AODSKL5I.cjs.map} +1 -1
  6. package/dist/{chunk-M6NPDNVB.cjs → chunk-CCV6Z3CL.cjs} +26 -26
  7. package/dist/{chunk-M6NPDNVB.cjs.map → chunk-CCV6Z3CL.cjs.map} +1 -1
  8. package/dist/{chunk-JNU3YPLP.js → chunk-EKYE2VJU.js} +4 -4
  9. package/dist/{chunk-J6TAK6HE.js → chunk-FAUC7EA3.js} +4 -4
  10. package/dist/{chunk-2OOZASQE.js → chunk-GA3R77TB.js} +2 -2
  11. package/dist/{chunk-VTR4QDL2.js → chunk-H4WIS6RJ.js} +2 -2
  12. package/dist/{chunk-HPQKX5IW.cjs → chunk-HEH47UTC.cjs} +15 -15
  13. package/dist/{chunk-HPQKX5IW.cjs.map → chunk-HEH47UTC.cjs.map} +1 -1
  14. package/dist/{chunk-XNAYZHPK.cjs → chunk-HNXZFKDQ.cjs} +62 -62
  15. package/dist/{chunk-XNAYZHPK.cjs.map → chunk-HNXZFKDQ.cjs.map} +1 -1
  16. package/dist/{chunk-T5F54MN4.cjs → chunk-HWJUWOXK.cjs} +12 -12
  17. package/dist/{chunk-T5F54MN4.cjs.map → chunk-HWJUWOXK.cjs.map} +1 -1
  18. package/dist/{chunk-K5FRWPWF.js → chunk-J2UHW4UM.js} +2 -2
  19. package/dist/{chunk-55SNCMFS.cjs → chunk-KRMGVMIO.cjs} +31 -31
  20. package/dist/{chunk-55SNCMFS.cjs.map → chunk-KRMGVMIO.cjs.map} +1 -1
  21. package/dist/{chunk-Z6PAD2UN.cjs → chunk-NAFW3LKI.cjs} +7 -7
  22. package/dist/{chunk-Z6PAD2UN.cjs.map → chunk-NAFW3LKI.cjs.map} +1 -1
  23. package/dist/{chunk-TTHTO5H5.js → chunk-NFRHBR7W.js} +4 -4
  24. package/dist/{chunk-3E4BDAHJ.js → chunk-OP3H7VB2.js} +2 -2
  25. package/dist/{chunk-ZFK4HHRX.js → chunk-PQXUY6SB.js} +2 -2
  26. package/dist/{chunk-43C5ECNT.cjs → chunk-RUYZ5QMK.cjs} +37 -37
  27. package/dist/{chunk-43C5ECNT.cjs.map → chunk-RUYZ5QMK.cjs.map} +1 -1
  28. package/dist/{chunk-D4F7ZLJG.js → chunk-STEM5CPV.js} +4 -4
  29. package/dist/{chunk-CK5UZLIN.js → chunk-SV7IJPIH.js} +5000 -5313
  30. package/dist/chunk-SV7IJPIH.js.map +1 -0
  31. package/dist/{chunk-WCDM226S.js → chunk-TIC3UE6L.js} +2 -2
  32. package/dist/{chunk-BB7TZ6KS.cjs → chunk-VSPNWVIE.cjs} +59 -59
  33. package/dist/{chunk-BB7TZ6KS.cjs.map → chunk-VSPNWVIE.cjs.map} +1 -1
  34. package/dist/{chunk-SBWTMJPQ.cjs → chunk-XQILJ7FF.cjs} +5 -5
  35. package/dist/{chunk-SBWTMJPQ.cjs.map → chunk-XQILJ7FF.cjs.map} +1 -1
  36. package/dist/{chunk-ZUKBC2ZX.cjs → chunk-Y36KVLBX.cjs} +798 -1111
  37. package/dist/chunk-Y36KVLBX.cjs.map +1 -0
  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/entity-cards/blog-card.d.ts +3 -6
  41. package/dist/components/chat/entity-cards/blog-card.d.ts.map +1 -1
  42. package/dist/components/chat/entity-cards/case-study-card.d.ts +3 -7
  43. package/dist/components/chat/entity-cards/case-study-card.d.ts.map +1 -1
  44. package/dist/components/chat/entity-cards/customer-interview-card.d.ts +3 -6
  45. package/dist/components/chat/entity-cards/customer-interview-card.d.ts.map +1 -1
  46. package/dist/components/chat/entity-cards/index.d.ts +0 -3
  47. package/dist/components/chat/entity-cards/index.d.ts.map +1 -1
  48. package/dist/components/chat/entity-cards/investor-update-card.d.ts +3 -6
  49. package/dist/components/chat/entity-cards/investor-update-card.d.ts.map +1 -1
  50. package/dist/components/chat/entity-cards/onboarding-guide-card.d.ts +2 -4
  51. package/dist/components/chat/entity-cards/onboarding-guide-card.d.ts.map +1 -1
  52. package/dist/components/chat/entity-cards/program-card.d.ts +5 -17
  53. package/dist/components/chat/entity-cards/program-card.d.ts.map +1 -1
  54. package/dist/components/chat/index.cjs +2 -10
  55. package/dist/components/chat/index.cjs.map +1 -1
  56. package/dist/components/chat/index.js +1 -9
  57. package/dist/components/chat/types/entities/program-types.d.ts +0 -23
  58. package/dist/components/chat/types/entities/program-types.d.ts.map +1 -1
  59. package/dist/components/contact/index.cjs +3 -3
  60. package/dist/components/contact/index.js +2 -2
  61. package/dist/components/docs/index.cjs +5 -5
  62. package/dist/components/docs/index.js +4 -4
  63. package/dist/components/embeds/index.cjs +3 -3
  64. package/dist/components/embeds/index.js +2 -2
  65. package/dist/components/faq/index.cjs +3 -3
  66. package/dist/components/faq/index.js +2 -2
  67. package/dist/components/features/cards-strip.d.ts.map +1 -1
  68. package/dist/components/features/index.cjs +2 -2
  69. package/dist/components/features/index.js +1 -1
  70. package/dist/components/features/video-bites-strip.d.ts.map +1 -1
  71. package/dist/components/help-center-pages/index.cjs +20 -20
  72. package/dist/components/help-center-pages/index.js +11 -11
  73. package/dist/components/index.cjs +120 -128
  74. package/dist/components/index.cjs.map +1 -1
  75. package/dist/components/index.js +9 -17
  76. package/dist/components/index.js.map +1 -1
  77. package/dist/components/navigation/index.cjs +2 -2
  78. package/dist/components/navigation/index.js +1 -1
  79. package/dist/components/onboarding-guides/index.cjs +5 -5
  80. package/dist/components/onboarding-guides/index.js +4 -4
  81. package/dist/components/related-content/index.cjs +3 -3
  82. package/dist/components/related-content/index.js +2 -2
  83. package/dist/components/tickets/index.cjs +5 -5
  84. package/dist/components/tickets/index.js +4 -4
  85. package/dist/components/ui/index.cjs +2 -10
  86. package/dist/components/ui/index.cjs.map +1 -1
  87. package/dist/components/ui/index.js +1 -9
  88. package/dist/index.cjs +2 -10
  89. package/dist/index.cjs.map +1 -1
  90. package/dist/index.js +1 -9
  91. package/package.json +1 -1
  92. package/src/components/chat/entity-cards/blog-card.tsx +7 -40
  93. package/src/components/chat/entity-cards/case-study-card.tsx +6 -32
  94. package/src/components/chat/entity-cards/customer-interview-card.tsx +6 -31
  95. package/src/components/chat/entity-cards/index.ts +0 -18
  96. package/src/components/chat/entity-cards/investor-update-card.tsx +2 -28
  97. package/src/components/chat/entity-cards/onboarding-guide-card.tsx +1 -30
  98. package/src/components/chat/entity-cards/program-card.tsx +17 -69
  99. package/src/components/chat/types/entities/program-types.ts +0 -34
  100. package/src/components/features/cards-strip.tsx +8 -122
  101. package/src/components/features/video-bites-strip.tsx +0 -4
  102. package/dist/chunk-CK5UZLIN.js.map +0 -1
  103. package/dist/chunk-ZUKBC2ZX.cjs.map +0 -1
  104. package/dist/components/chat/entity-cards/entity-portrait-card.d.ts +0 -59
  105. package/dist/components/chat/entity-cards/entity-portrait-card.d.ts.map +0 -1
  106. package/dist/components/chat/entity-cards/use-cover-image-fallback.d.ts +0 -26
  107. package/dist/components/chat/entity-cards/use-cover-image-fallback.d.ts.map +0 -1
  108. package/src/components/chat/entity-cards/entity-portrait-card.tsx +0 -201
  109. package/src/components/chat/entity-cards/use-cover-image-fallback.ts +0 -50
  110. /package/dist/{chunk-IVXOR2IA.js.map → chunk-2PTEK3D6.js.map} +0 -0
  111. /package/dist/{chunk-JNU3YPLP.js.map → chunk-EKYE2VJU.js.map} +0 -0
  112. /package/dist/{chunk-J6TAK6HE.js.map → chunk-FAUC7EA3.js.map} +0 -0
  113. /package/dist/{chunk-2OOZASQE.js.map → chunk-GA3R77TB.js.map} +0 -0
  114. /package/dist/{chunk-VTR4QDL2.js.map → chunk-H4WIS6RJ.js.map} +0 -0
  115. /package/dist/{chunk-K5FRWPWF.js.map → chunk-J2UHW4UM.js.map} +0 -0
  116. /package/dist/{chunk-TTHTO5H5.js.map → chunk-NFRHBR7W.js.map} +0 -0
  117. /package/dist/{chunk-3E4BDAHJ.js.map → chunk-OP3H7VB2.js.map} +0 -0
  118. /package/dist/{chunk-ZFK4HHRX.js.map → chunk-PQXUY6SB.js.map} +0 -0
  119. /package/dist/{chunk-D4F7ZLJG.js.map → chunk-STEM5CPV.js.map} +0 -0
  120. /package/dist/{chunk-WCDM226S.js.map → chunk-TIC3UE6L.js.map} +0 -0
@@ -17,7 +17,6 @@ import Image from '../../../embed-shims/next-image'
17
17
  import { Card } from '../../ui/card'
18
18
  import { cn } from '../../../utils/cn'
19
19
  import type { CaseStudy } from '../../../types/case-study'
20
- import { EntityPortraitCard } from './entity-portrait-card'
21
20
  import { useEntityCardLink } from './use-entity-card-link'
22
21
  import { useEntityCardPlaceholder } from './use-entity-card-placeholder'
23
22
  import {
@@ -33,7 +32,10 @@ import {
33
32
  COMPACT_CARD_TITLE,
34
33
  COMPACT_CARD_TITLE_ROW,
35
34
  } from '../utils/compact-card-classes'
36
- import { hideOnError } from './use-cover-image-fallback'
35
+
36
+ const hideOnError = (e: React.SyntheticEvent<HTMLImageElement>) => {
37
+ ;(e.currentTarget as HTMLImageElement).style.display = 'none'
38
+ }
37
39
 
38
40
  export interface CaseStudyCardProps {
39
41
  study: CaseStudy
@@ -46,15 +48,11 @@ export interface CaseStudyCardProps {
46
48
  targetPlatform?: string | null
47
49
  /** OG placeholder URL, used when `study.featured_image` is missing. */
48
50
  placeholderUrl?: string | null
49
- size?: 'default' | 'sm' | 'portrait'
50
- /** Portrait density: render the content-type chip. Mixed rails only; single-type rails pass false. Default true. */
51
- showTypeBadge?: boolean
51
+ size?: 'default' | 'sm'
52
52
  className?: string
53
53
  }
54
54
 
55
- /** `portrait` shares the default skeleton shape the portrait anatomy uses the
56
- * same zone boxes (media aspect → 72px title → 60px person footer). */
57
- export function CaseStudyCardSkeleton({ size = 'default' }: { size?: 'default' | 'sm' | 'portrait' }) {
55
+ export function CaseStudyCardSkeleton({ size = 'default' }: { size?: 'default' | 'sm' }) {
58
56
  if (size === 'sm') {
59
57
  return (
60
58
  <span className={COMPACT_CARD_SKELETON_OUTER}>
@@ -100,7 +98,6 @@ export function CaseStudyCard({
100
98
  targetPlatform,
101
99
  placeholderUrl: placeholderUrlProp,
102
100
  size = 'default',
103
- showTypeBadge = true,
104
101
  className,
105
102
  }: CaseStudyCardProps) {
106
103
  const { target, rel } = useEntityCardLink({
@@ -148,29 +145,6 @@ export function CaseStudyCard({
148
145
  )
149
146
  }
150
147
 
151
- if (size === 'portrait') {
152
- // Rail/strip density — shared <EntityPortraitCard> shell.
153
- return (
154
- <EntityPortraitCard
155
- href={href}
156
- target={target}
157
- rel={rel}
158
- typeLabel={showTypeBadge ? 'Case Study' : undefined}
159
- imageUrl={study.featured_image}
160
- placeholderUrl={placeholderUrl}
161
- imageAlt={study.msp?.name || study.title}
162
- title={study.title}
163
- person={{
164
- name: study.user?.full_name || 'Anonymous',
165
- avatarUrl: study.user?.avatar_url,
166
- subtitle: study.msp?.name ?? null,
167
- iconOverlayUrl: study.msp?.icon_url ?? null,
168
- }}
169
- className={className}
170
- />
171
- )
172
- }
173
-
174
148
  return (
175
149
  <a href={href} target={target} rel={rel} className={cn('block h-full', className)}>
176
150
  <Card className="bg-ods-card border border-ods-border hover:border-ods-accent transition-colors p-6 flex flex-col gap-6 overflow-hidden">
@@ -13,7 +13,6 @@ import { Card } from '../../ui/card'
13
13
  import { cn } from '../../../utils/cn'
14
14
  import { Video } from 'lucide-react'
15
15
  import type { CustomerInterview } from '../../../types/customer-interview'
16
- import { EntityPortraitCard } from './entity-portrait-card'
17
16
  import { useEntityCardLink } from './use-entity-card-link'
18
17
  import { useEntityCardPlaceholder } from './use-entity-card-placeholder'
19
18
  import {
@@ -29,7 +28,10 @@ import {
29
28
  COMPACT_CARD_TITLE,
30
29
  COMPACT_CARD_TITLE_ROW,
31
30
  } from '../utils/compact-card-classes'
32
- import { hideOnError } from './use-cover-image-fallback'
31
+
32
+ const hideOnError = (e: React.SyntheticEvent<HTMLImageElement>) => {
33
+ ;(e.currentTarget as HTMLImageElement).style.display = 'none'
34
+ }
33
35
 
34
36
  export interface CustomerInterviewCardProps {
35
37
  interview: CustomerInterview
@@ -41,14 +43,11 @@ export interface CustomerInterviewCardProps {
41
43
  targetPlatform?: string | null
42
44
  /** OG placeholder URL fallback when `interview.featured_image` is missing. */
43
45
  placeholderUrl?: string | null
44
- size?: 'default' | 'sm' | 'portrait'
45
- /** Portrait density: render the content-type chip. Mixed rails only; single-type rails pass false. Default true. */
46
- showTypeBadge?: boolean
46
+ size?: 'default' | 'sm'
47
47
  className?: string
48
48
  }
49
49
 
50
- /** `portrait` shares the default skeleton shape (same zone boxes). */
51
- export function CustomerInterviewCardSkeleton({ size = 'default' }: { size?: 'default' | 'sm' | 'portrait' }) {
50
+ export function CustomerInterviewCardSkeleton({ size = 'default' }: { size?: 'default' | 'sm' }) {
52
51
  if (size === 'sm') {
53
52
  return (
54
53
  <span className={COMPACT_CARD_SKELETON_OUTER}>
@@ -99,7 +98,6 @@ export function CustomerInterviewCard({
99
98
  targetPlatform,
100
99
  placeholderUrl: placeholderUrlProp,
101
100
  size = 'default',
102
- showTypeBadge = true,
103
101
  className,
104
102
  }: CustomerInterviewCardProps) {
105
103
  const { target, rel } = useEntityCardLink({
@@ -152,29 +150,6 @@ export function CustomerInterviewCard({
152
150
  )
153
151
  }
154
152
 
155
- if (size === 'portrait') {
156
- // Rail/strip density — shared <EntityPortraitCard> shell.
157
- return (
158
- <EntityPortraitCard
159
- href={href}
160
- target={target}
161
- rel={rel}
162
- typeLabel={showTypeBadge ? 'Customer Interview' : undefined}
163
- imageUrl={interview.featured_image}
164
- placeholderUrl={placeholderUrl}
165
- imageAlt={interview.title}
166
- title={interview.title}
167
- person={{
168
- name: interview.user?.full_name || 'Customer',
169
- avatarUrl: interview.user?.avatar_url,
170
- subtitle: interview.msp?.name ?? null,
171
- iconOverlayUrl: interview.msp?.icon_url ?? null,
172
- }}
173
- className={className}
174
- />
175
- )
176
- }
177
-
178
153
  return (
179
154
  <a href={href} target={target} rel={rel} className={cn('block h-full', className)}>
180
155
  <Card className="bg-ods-card border border-ods-border hover:border-ods-accent transition-colors p-6 flex flex-col gap-6 overflow-hidden">
@@ -16,16 +16,6 @@ export {
16
16
  type EntityAuthorCardProps,
17
17
  } from './entity-author-card'
18
18
  export { BlogImagePlaceholder } from './blog-image-placeholder'
19
- export {
20
- EntityPortraitCard,
21
- type EntityPortraitCardProps,
22
- type EntityPortraitPerson,
23
- } from './entity-portrait-card'
24
- export {
25
- useCoverImageFallback,
26
- hideOnError,
27
- type CoverImageFallback,
28
- } from './use-cover-image-fallback'
29
19
  export { AdminContentCard } from './admin-content-card'
30
20
  export { WhatIShippedCard, WhatIShippedCardSkeleton } from './what-i-shipped-card'
31
21
  export type { WhatIShippedCardData, WhatIShippedCardProps } from './what-i-shipped-card'
@@ -143,11 +133,3 @@ export {
143
133
  type ChatCardRenderOptions,
144
134
  type ChatCardDispatchExtras,
145
135
  } from './dispatch'
146
-
147
- // Program author-ref + video-bites profile adapter — re-exported through the
148
- // PACKAGE-EXPORTED chat barrel so the hub can value-import them (deep paths
149
- // into chat/types/* are not in the package exports map and 404 at runtime).
150
- export {
151
- programItemToStripProfile,
152
- type ProgramAuthorRef,
153
- } from '../types/entities/program-types'
@@ -11,7 +11,6 @@
11
11
  import React from 'react'
12
12
  import { Calendar } from 'lucide-react'
13
13
  import { AdminContentCard } from './admin-content-card'
14
- import { EntityPortraitCard } from './entity-portrait-card'
15
14
  import { formatInvestorUpdatePeriod, type InvestorUpdate } from '../types/entities/investor-update'
16
15
  import { useEntityCardLink } from './use-entity-card-link'
17
16
  import { useEntityCardPlaceholder } from './use-entity-card-placeholder'
@@ -39,14 +38,11 @@ export interface InvestorUpdateCardProps {
39
38
  targetPlatform?: string | null
40
39
  /** OG placeholder URL used when `update.featured_image` is missing. */
41
40
  placeholderUrl?: string | null
42
- size?: 'default' | 'sm' | 'portrait'
43
- /** Portrait density: render the content-type chip. Mixed rails only; single-type rails pass false. Default true. */
44
- showTypeBadge?: boolean
41
+ size?: 'default' | 'sm'
45
42
  className?: string
46
43
  }
47
44
 
48
- /** `portrait` shares the default skeleton shape (same zone boxes). */
49
- export function InvestorUpdateCardSkeleton({ size = 'default' }: { size?: 'default' | 'sm' | 'portrait' }) {
45
+ export function InvestorUpdateCardSkeleton({ size = 'default' }: { size?: 'default' | 'sm' }) {
50
46
  if (size === 'sm') {
51
47
  return (
52
48
  <span className={COMPACT_CARD_SKELETON_OUTER}>
@@ -87,7 +83,6 @@ export function InvestorUpdateCard({
87
83
  targetPlatform,
88
84
  placeholderUrl: placeholderUrlProp,
89
85
  size = 'default',
90
- showTypeBadge = true,
91
86
  className,
92
87
  }: InvestorUpdateCardProps) {
93
88
  const { target, rel } = useEntityCardLink({
@@ -149,27 +144,6 @@ export function InvestorUpdateCard({
149
144
  )
150
145
  }
151
146
 
152
- if (size === 'portrait') {
153
- // Rail/strip density — shared <EntityPortraitCard> shell. The reporting
154
- // period is the meaningful footer line (no fabricated author — no avatar
155
- // renders an initial circle).
156
- const periodText = formatInvestorUpdatePeriod(update.period_start, update.period_end)
157
- return (
158
- <EntityPortraitCard
159
- href={href}
160
- target={target}
161
- rel={rel}
162
- typeLabel={showTypeBadge ? 'Investor Update' : undefined}
163
- imageUrl={update.featured_image}
164
- placeholderUrl={placeholderUrl}
165
- imageAlt={update.title ?? 'Investor update'}
166
- title={update.title || `Update #${update.update_number ?? '?'}`}
167
- person={periodText ? { name: periodText } : null}
168
- className={className}
169
- />
170
- )
171
- }
172
-
173
147
  return (
174
148
  <a href={href} target={target} rel={rel} className={`block h-full ${className ?? ''}`}>
175
149
  <AdminContentCard
@@ -24,7 +24,6 @@ import { Clock, ExternalLink, GraduationCap, Play } from 'lucide-react'
24
24
  import { cn } from '../../../utils/cn'
25
25
  import { BlogImagePlaceholder } from './blog-image-placeholder'
26
26
  import { EntityAuthorCard } from './entity-author-card'
27
- import { EntityPortraitCard } from './entity-portrait-card'
28
27
  import { formatDurationMMSS } from '../../../utils/format'
29
28
  import type { OnboardingGuide } from '../types/entities/onboarding-guide'
30
29
  import { useEntityCardLink } from './use-entity-card-link'
@@ -53,10 +52,7 @@ export interface OnboardingGuideCardProps {
53
52
  targetPlatform?: string | null
54
53
  /** OG placeholder URL used by the catalog + sm variants when no cover. */
55
54
  placeholderUrl?: string | null
56
- size?: 'catalog' | 'default' | 'sm' | 'portrait'
57
- /** Portrait density: render the content-type chip. Mixed rails only; single-type rails pass false. Default true. */
58
- showTypeBadge?: boolean
59
- | 'default' | 'sm' | 'portrait'
55
+ size?: 'catalog' | 'default' | 'sm'
60
56
  className?: string
61
57
  }
62
58
 
@@ -173,7 +169,6 @@ export function OnboardingGuideCard({
173
169
  targetPlatform,
174
170
  placeholderUrl: placeholderUrlProp,
175
171
  size = 'default',
176
- showTypeBadge = true,
177
172
  className,
178
173
  }: OnboardingGuideCardProps) {
179
174
  const { target, rel } = useEntityCardLink({
@@ -188,30 +183,6 @@ export function OnboardingGuideCard({
188
183
  aspect: size === 'sm' ? 'square' : 'wide',
189
184
  })
190
185
 
191
- if (size === 'portrait') {
192
- // Rail/strip density — shared <EntityPortraitCard> shell. Same cover
193
- // chain as the catalog variant.
194
- const coverImage =
195
- guide.featured_image ||
196
- guide.main_video_thumbnail ||
197
- guide.og_image_url ||
198
- null
199
- return (
200
- <EntityPortraitCard
201
- href={href}
202
- target={target}
203
- rel={rel}
204
- typeLabel={showTypeBadge ? 'Guide' : undefined}
205
- imageUrl={coverImage}
206
- placeholderUrl={placeholderUrl}
207
- imageAlt={guide.title}
208
- title={guide.title}
209
- person={guide.section ? { name: guide.section } : null}
210
- className={className}
211
- />
212
- )
213
- }
214
-
215
186
  if (size === 'catalog') {
216
187
  const coverImage =
217
188
  guide.featured_image ||
@@ -2,18 +2,8 @@
2
2
 
3
3
  /**
4
4
  * ProgramCard (pure presentation). Generic card for podcasts / webinars /
5
- * events. Three densities — `default` (wide horizontal detail, archive
6
- * pages), `sm` (compact horizontal for chat-inline), and `portrait`
7
- * (vertical rail/strip card).
8
- *
9
- * `portrait` exists because mixed-content rails MUST share ONE card anatomy
10
- * (2026 card-UI practice: a rail mixes content types, never card layouts —
11
- * mixing orientations/aspects in one scroller is the anti-pattern). It
12
- * renders the SAME three zones as the other portrait entity cards
13
- * (CaseStudyCard et al.): wide media slot → fixed 72px title zone → fixed
14
- * 60px person/meta footer, p-6 / gap-6. The program's identity lives in the
15
- * Azeret Mono title and the date · duration meta line — not in a different
16
- * layout.
5
+ * events. Two densities — `default` (vertical detail) and `sm` (compact
6
+ * horizontal for chat-inline).
17
7
  *
18
8
  * The card writes NO click logic — callers wrap with their own anchor
19
9
  * and pass the resolved detail URL via `href`.
@@ -44,19 +34,16 @@ import {
44
34
  COMPACT_CARD_TITLE,
45
35
  COMPACT_CARD_TITLE_ROW,
46
36
  } from '../utils/compact-card-classes'
47
- import {
48
- programItemToStripProfile,
49
- type BaseProgramItem,
50
- type ProgramAuthorRef,
51
- type ProgramConfig,
52
- type ProgramMedia,
53
- type ProgramHost,
37
+ import type {
38
+ BaseProgramItem,
39
+ ProgramConfig,
40
+ ProgramMedia,
41
+ ProgramHost,
54
42
  } from '../types/entities/program-types'
55
- import { EntityPortraitCard } from './entity-portrait-card'
56
43
  import { useEntityCardLink } from './use-entity-card-link'
57
44
  import { useEntityCardPlaceholder } from './use-entity-card-placeholder'
58
45
 
59
- type CardSize = 'default' | 'sm' | 'portrait'
46
+ type CardSize = 'default' | 'sm'
60
47
 
61
48
  /**
62
49
  * Format a Date with date-fns pinned to UTC. `date-fns` `format()` reads the
@@ -128,8 +115,6 @@ export interface ProgramCardProps<T extends BaseProgramItem> {
128
115
  media?: ProgramMedia[]
129
116
  renderMeta?: (item: T) => React.ReactNode
130
117
  size?: CardSize
131
- /** Portrait density: render the content-type chip. Mixed rails only; single-type rails pass false. Default true. */
132
- showTypeBadge?: boolean
133
118
  /** Detail URL resolved by the caller. */
134
119
  href: string
135
120
  /** When `_blank`, opens in a new tab. Set by chat dispatch via
@@ -227,7 +212,6 @@ export function ProgramCard<T extends BaseProgramItem>({
227
212
  media = [],
228
213
  renderMeta,
229
214
  size = 'default',
230
- showTypeBadge = true,
231
215
  href,
232
216
  target: targetProp,
233
217
  rel: relProp,
@@ -253,60 +237,24 @@ export function ProgramCard<T extends BaseProgramItem>({
253
237
  const accentColor = 'var(--color-accent-primary)'
254
238
  const isScheduled = 'status' in item && (item as any).status === 'scheduled'
255
239
 
256
- // Compact per-type meta (duration / location / start time) — shared by the
257
- // `sm` and `portrait` densities.
258
- const compactTypeMeta = (): string | null => {
240
+ if (size === 'sm') {
241
+ const itemDate = (() => {
242
+ try { return formatUtc(new Date(item.date), 'MMM d, yyyy') } catch { return '' }
243
+ })()
244
+ const compactCover = coverImage || placeholderUrl || null
245
+ let typeMeta: string | null = null
259
246
  if (config.type === 'podcast' && 'duration_seconds' in item && !isScheduled) {
260
247
  const dur = (item as any).duration_seconds
261
- if (typeof dur === 'number' && dur > 0) return formatDurationCompact(dur)
248
+ if (typeof dur === 'number' && dur > 0) typeMeta = formatDurationCompact(dur)
262
249
  } else if (config.type === 'event' && 'location_name' in item) {
263
250
  const loc = (item as any).location_name
264
- if (typeof loc === 'string' && loc.trim().length > 0) return loc
251
+ if (typeof loc === 'string' && loc.trim().length > 0) typeMeta = loc
265
252
  } else if (config.type === 'webinar' && 'start_at' in item) {
266
253
  const w = item as any
267
254
  const time = formatTimeWithTimezone(w.start_at, w.timezone ?? null)
268
255
  const dur = formatDurationFromRange(w.start_at, w.end_at)
269
- return dur ? `${time} · ${dur}` : time
256
+ typeMeta = dur ? `${time} · ${dur}` : time
270
257
  }
271
- return null
272
- }
273
- const compactDate = (() => {
274
- try { return formatUtc(new Date(item.date), 'MMM d, yyyy') } catch { return '' }
275
- })()
276
-
277
- if (size === 'portrait') {
278
- // Rail/strip density — mapped onto the shared <EntityPortraitCard> shell
279
- // (media aspect-[1200/630] → h-[72px] title → h-[60px] footer, p-6/gap-6).
280
- // Person = author-first via programItemToStripProfile (author → primary
281
- // host); the date · duration meta line fills the subtitle when the
282
- // profile has no job title.
283
- const profile = programItemToStripProfile(item as { author?: ProgramAuthorRef | null; hosts?: ProgramHost[] | null })
284
- const dateMeta = [compactDate, compactTypeMeta()].filter(Boolean).join(' · ')
285
- return (
286
- <EntityPortraitCard
287
- href={href}
288
- target={target}
289
- rel={rel}
290
- typeLabel={showTypeBadge ? config.labels.singular : undefined}
291
- imageUrl={coverImage}
292
- placeholderUrl={placeholderUrl}
293
- imageAlt={item.title}
294
- title={item.title}
295
- titleClassName="font-['Azeret_Mono'] font-semibold text-lg leading-6"
296
- person={
297
- profile
298
- ? { name: profile.name, avatarUrl: profile.avatarUrl, subtitle: profile.subtitle || dateMeta }
299
- : { name: config.labels.singular, subtitle: dateMeta }
300
- }
301
- className={className}
302
- />
303
- )
304
- }
305
-
306
- if (size === 'sm') {
307
- const itemDate = compactDate
308
- const compactCover = coverImage || placeholderUrl || null
309
- const typeMeta = compactTypeMeta()
310
258
  const subtitleParts = [itemDate, typeMeta, config.labels?.singular].filter(
311
259
  (s): s is string => typeof s === 'string' && s.length > 0,
312
260
  )
@@ -17,10 +17,6 @@
17
17
  */
18
18
 
19
19
  import type { ReactNode } from 'react';
20
- import {
21
- toStripProfile,
22
- type VideoBiteStripProfile,
23
- } from '../../../features/video-bites-shared';
24
20
 
25
21
  // ============================================================================
26
22
  // HOST TYPES
@@ -38,36 +34,6 @@ export interface ProgramHost {
38
34
  role?: string; // e.g., "Host", "Speaker", "Guest"
39
35
  }
40
36
 
41
- /**
42
- * Client-safe author subset hydrated onto program items from the entity's
43
- * `author_id → profiles` FK join (podcast episodes / webinars). Deliberately
44
- * excludes `email` — this shape is serialized into public payloads.
45
- */
46
- export interface ProgramAuthorRef {
47
- id?: string | null;
48
- full_name?: string | null;
49
- avatar_url?: string | null;
50
- job_title?: string | null;
51
- }
52
-
53
- /**
54
- * Video-bites overlay profile for a program item: internal author first
55
- * (`author_id → profiles`), then the provider-synced primary host (Livestorm
56
- * speakers / Podbean hosts). Host `role` is a raw provider enum (e.g.
57
- * `team_member`) — never display copy, so it is not used as the subtitle.
58
- * Routes through `toStripProfile` (the SSoT — null on missing names).
59
- */
60
- export function programItemToStripProfile(item: {
61
- author?: ProgramAuthorRef | null;
62
- hosts?: ProgramHost[] | null;
63
- }): VideoBiteStripProfile | null {
64
- const fromAuthor = toStripProfile(item.author);
65
- if (fromAuthor) return fromAuthor;
66
- const host = Array.isArray(item.hosts) ? item.hosts[0] : null;
67
- if (!host?.name) return null;
68
- return toStripProfile({ full_name: host.name, avatar_url: host.avatar_url ?? null });
69
- }
70
-
71
37
  // ============================================================================
72
38
  // DATABASE ROW TYPES
73
39
  // ============================================================================