@streamscloud/embeddable 16.3.1 → 17.0.0-1775740482426

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 (35) hide show
  1. package/dist/ads/ad-card/cmp.ad-card.svelte +11 -9
  2. package/dist/feed-player/cmp.close-button.svelte +40 -0
  3. package/dist/feed-player/cmp.close-button.svelte.d.ts +19 -0
  4. package/dist/feed-player/cmp.feed-player.svelte +516 -0
  5. package/dist/feed-player/cmp.feed-player.svelte.d.ts +14 -0
  6. package/dist/feed-player/feed-player-localization.d.ts +16 -0
  7. package/dist/feed-player/feed-player-localization.js +70 -0
  8. package/dist/feed-player/index.d.ts +3 -0
  9. package/dist/feed-player/index.js +2 -0
  10. package/dist/feed-player/sidebar/article-tab.svelte +98 -0
  11. package/dist/feed-player/sidebar/article-tab.svelte.d.ts +19 -0
  12. package/dist/feed-player/sidebar/information-tab.svelte +102 -0
  13. package/dist/feed-player/sidebar/information-tab.svelte.d.ts +26 -0
  14. package/dist/feed-player/sidebar/playlist-tab.svelte +110 -0
  15. package/dist/feed-player/sidebar/playlist-tab.svelte.d.ts +19 -0
  16. package/dist/feed-player/sidebar/post-card.svelte +128 -0
  17. package/dist/feed-player/sidebar/post-card.svelte.d.ts +24 -0
  18. package/dist/feed-player/sidebar/recommended-tab.svelte +165 -0
  19. package/dist/feed-player/sidebar/recommended-tab.svelte.d.ts +24 -0
  20. package/dist/feed-player/sidebar/sidebar-panel.svelte +84 -0
  21. package/dist/feed-player/sidebar/sidebar-panel.svelte.d.ts +28 -0
  22. package/dist/feed-player/sidebar/sidebar-tab-bar.svelte +45 -0
  23. package/dist/feed-player/sidebar/sidebar-tab-bar.svelte.d.ts +20 -0
  24. package/dist/feed-player/sidebar/types.d.ts +4 -0
  25. package/dist/feed-player/sidebar/types.js +1 -0
  26. package/dist/feed-player/types.d.ts +62 -0
  27. package/dist/feed-player/types.js +1 -0
  28. package/dist/posts/attachments/cmp.attachments.svelte +7 -2
  29. package/dist/posts/post-viewer/cmp.post-viewer.svelte +10 -7
  30. package/dist/posts/post-viewer/media/post-media.svelte +1 -2
  31. package/dist/posts/posts-player/posts-player-view.svelte +2 -0
  32. package/dist/products/product-card/cmp.product-card.svelte +16 -12
  33. package/dist/streams/streams-player/streams-player-view.svelte +2 -0
  34. package/dist/ui/media-items/media-item-view/cmp.media-item-view.svelte +1 -1
  35. package/package.json +7 -3
@@ -0,0 +1,70 @@
1
+ import { AppLocale } from '@streamscloud/kit/core/locale';
2
+ export class FeedPlayerLocalization {
3
+ get tabInformation() {
4
+ return loc.tabInformation[AppLocale.current];
5
+ }
6
+ get tabProduct() {
7
+ return loc.tabProduct[AppLocale.current];
8
+ }
9
+ get tabArticle() {
10
+ return loc.tabArticle[AppLocale.current];
11
+ }
12
+ get tabRecommended() {
13
+ return loc.tabRecommended[AppLocale.current];
14
+ }
15
+ get tabPlaylist() {
16
+ return loc.tabPlaylist[AppLocale.current];
17
+ }
18
+ get show() {
19
+ return loc.show[AppLocale.current];
20
+ }
21
+ get relatedPosts() {
22
+ return loc.relatedPosts[AppLocale.current];
23
+ }
24
+ get suggestedPlaylist() {
25
+ return loc.suggestedPlaylist[AppLocale.current];
26
+ }
27
+ get suggestedProducts() {
28
+ return loc.suggestedProducts[AppLocale.current];
29
+ }
30
+ get showList() {
31
+ return loc.showList[AppLocale.current];
32
+ }
33
+ get updatedLabel() {
34
+ return loc.updatedLabel[AppLocale.current];
35
+ }
36
+ postsCount(count) {
37
+ return loc.postsCount[AppLocale.current](count);
38
+ }
39
+ postOf(current, total) {
40
+ return loc.postOf[AppLocale.current](current, total);
41
+ }
42
+ viewsLabel(count) {
43
+ return loc.viewsLabel[AppLocale.current](count);
44
+ }
45
+ }
46
+ const loc = {
47
+ tabInformation: { en: 'Information', no: 'Informasjon' },
48
+ tabProduct: { en: 'Product', no: 'Produkt' },
49
+ tabArticle: { en: 'Article', no: 'Artikkel' },
50
+ tabRecommended: { en: 'Recommended', no: 'Anbefalt' },
51
+ tabPlaylist: { en: 'Playlist', no: 'Spilleliste' },
52
+ show: { en: 'Show', no: 'Vis' },
53
+ relatedPosts: { en: 'Related Posts', no: 'Relaterte innlegg' },
54
+ suggestedPlaylist: { en: 'Suggested Playlist', no: 'Foreslått spilleliste' },
55
+ suggestedProducts: { en: 'Suggested Products', no: 'Foreslåtte produkter' },
56
+ showList: { en: 'Show list', no: 'Vis liste' },
57
+ updatedLabel: { en: 'Updated:', no: 'Oppdatert:' },
58
+ postsCount: {
59
+ en: (count) => `${count} Post${count !== 1 ? 's' : ''}`,
60
+ no: (count) => `${count} innlegg`
61
+ },
62
+ postOf: {
63
+ en: (current, total) => `Post ${current} of ${total}`,
64
+ no: (current, total) => `Innlegg ${current} av ${total}`
65
+ },
66
+ viewsLabel: {
67
+ en: (count) => `${new Intl.NumberFormat('en').format(count)} Views`,
68
+ no: (count) => `${new Intl.NumberFormat('nb').format(count)} visninger`
69
+ }
70
+ };
@@ -0,0 +1,3 @@
1
+ export { default as CloseButton } from './cmp.close-button.svelte';
2
+ export { default as FeedPlayer } from './cmp.feed-player.svelte';
3
+ export type { FeedPlayerPlaylistData, FeedPlayerPostPreview, FeedPlayerProps, FeedPlayerRecommendedData, FeedPlayerSettings, FeedPlayerSuggestedPlaylist, IFeedPlayerPlaylistHandler, IFeedPlayerRecommendedHandler } from './types';
@@ -0,0 +1,2 @@
1
+ export { default as CloseButton } from './cmp.close-button.svelte';
2
+ export { default as FeedPlayer } from './cmp.feed-player.svelte';
@@ -0,0 +1,98 @@
1
+ <script lang="ts">import { FeedPlayerLocalization } from '../feed-player-localization';
2
+ import { Image } from '@streamscloud/kit/ui/image';
3
+ const localization = new FeedPlayerLocalization();
4
+ let { model, on } = $props();
5
+ const heroImage = $derived(model.media.items[0]?.isImage ? model.media.items[0].url : null);
6
+ </script>
7
+
8
+ <div class="article-tab">
9
+ {#if heroImage}
10
+ <div class="article-tab__hero">
11
+ <Image src={heroImage} />
12
+ </div>
13
+ {/if}
14
+
15
+ <div class="article-tab__content">
16
+ {#if model.texts.kicker}
17
+ <div class="article-tab__category">{model.texts.kicker}</div>
18
+ {/if}
19
+
20
+ {#if model.texts.title}
21
+ <div class="article-tab__title">{model.texts.title}</div>
22
+ {/if}
23
+
24
+ {#if model.texts.text}
25
+ <div class="article-tab__text">{model.texts.text}</div>
26
+ {/if}
27
+
28
+ {#if model.articleId && on?.readMore}
29
+ <button type="button" class="article-tab__show-button" onclick={() => model.articleId && on?.readMore && on.readMore(model.articleId)}
30
+ >{localization.show}</button>
31
+ {/if}
32
+ </div>
33
+ </div>
34
+
35
+ <!--
36
+ @component
37
+ Article tab — preview of the article with hero image, title, text, and "Show" button.
38
+
39
+ ### CSS Custom Properties
40
+ | Property | Description | Default |
41
+ |---|---|---|
42
+ | `--article-tab--button-background` | Show button background | `#f9fafb` |
43
+ | `--article-tab--button-border` | Show button border | `#e5e7eb` |
44
+ -->
45
+
46
+ <style>.article-tab {
47
+ --_article-tab--button-background: var(--article-tab--button-background, #f9fafb);
48
+ --_article-tab--button-border: var(--article-tab--button-border, #e5e7eb);
49
+ display: flex;
50
+ flex-direction: column;
51
+ --sc-kit--image--object-fit: cover;
52
+ --sc-kit--image--border-radius: 0.375rem 0.375rem 0 0;
53
+ }
54
+ .article-tab__hero {
55
+ width: 100%;
56
+ aspect-ratio: 392/245;
57
+ flex-shrink: 0;
58
+ }
59
+ .article-tab__content {
60
+ display: flex;
61
+ flex-direction: column;
62
+ gap: 0.25rem;
63
+ padding: 1rem;
64
+ }
65
+ .article-tab__category {
66
+ font-size: 1rem;
67
+ font-weight: 500;
68
+ color: #9ca3af;
69
+ }
70
+ .article-tab__title {
71
+ font-size: 2rem;
72
+ font-weight: 600;
73
+ line-height: 1.2;
74
+ }
75
+ .article-tab__text {
76
+ font-size: 1.0625rem;
77
+ font-weight: 400;
78
+ line-height: 1.375rem;
79
+ display: -webkit-box;
80
+ -webkit-line-clamp: 10;
81
+ line-clamp: 10;
82
+ -webkit-box-orient: vertical;
83
+ overflow: hidden;
84
+ }
85
+ .article-tab__show-button {
86
+ margin-top: 0.5rem;
87
+ width: 100%;
88
+ height: 2.375rem;
89
+ display: flex;
90
+ align-items: center;
91
+ justify-content: center;
92
+ font-size: 0.875rem;
93
+ font-weight: 600;
94
+ background: var(--_article-tab--button-background);
95
+ border: 1px solid var(--_article-tab--button-border);
96
+ border-radius: 0.5rem;
97
+ cursor: pointer;
98
+ }</style>
@@ -0,0 +1,19 @@
1
+ import type { PostModel } from '../../posts/model';
2
+ type Props = {
3
+ model: PostModel;
4
+ on?: {
5
+ readMore?: (articleId: string) => void;
6
+ };
7
+ };
8
+ /**
9
+ * Article tab — preview of the article with hero image, title, text, and "Show" button.
10
+ *
11
+ * ### CSS Custom Properties
12
+ * | Property | Description | Default |
13
+ * |---|---|---|
14
+ * | `--article-tab--button-background` | Show button background | `#f9fafb` |
15
+ * | `--article-tab--button-border` | Show button border | `#e5e7eb` |
16
+ */
17
+ declare const ArticleTab: import("svelte").Component<Props, {}, "">;
18
+ type ArticleTab = ReturnType<typeof ArticleTab>;
19
+ export default ArticleTab;
@@ -0,0 +1,102 @@
1
+ <script lang="ts">import { AdCard } from '../../ads/ad-card';
2
+ import { ProductCard } from '../../products/product-card';
3
+ import IconCheckmarkCircle24 from '@fluentui/svg-icons/icons/checkmark_circle_24_regular.svg?raw';
4
+ import { Icon } from '@streamscloud/kit/ui/icon';
5
+ let { model, trackingParams, selectedProductId, on } = $props();
6
+ </script>
7
+
8
+ {#if model.attachments}
9
+ <div class="information-tab">
10
+ {#if model.attachments.ads.length}
11
+ <div class="information-tab__ads">
12
+ {#each model.attachments.ads as ad (ad.id)}
13
+ <div class="information-tab__ad-wrapper">
14
+ <AdCard
15
+ ad={ad}
16
+ {trackingParams}
17
+ on={{
18
+ click: on?.adClick,
19
+ impression: on?.adImpression
20
+ }} />
21
+ </div>
22
+ {/each}
23
+ </div>
24
+ {/if}
25
+
26
+ {#if model.attachments.products.length}
27
+ <div class="information-tab__products">
28
+ {#each model.attachments.products as product (product.id)}
29
+ <div
30
+ class="information-tab__product-wrapper"
31
+ onclick={() => on?.productSelect?.(product.id)}
32
+ onkeydown={() => {}}
33
+ role="none">
34
+ {#if selectedProductId === product.id}
35
+ <div class="information-tab__checkmark">
36
+ <Icon src={IconCheckmarkCircle24} />
37
+ </div>
38
+ {/if}
39
+ <ProductCard
40
+ product={product}
41
+ inert={true}
42
+ {trackingParams}
43
+ on={{
44
+ impression: on?.productImpression,
45
+ buy: on?.productBuy
46
+ }} />
47
+ </div>
48
+ {/each}
49
+ </div>
50
+ {/if}
51
+ </div>
52
+ {/if}
53
+
54
+ <!--
55
+ @component
56
+ Information tab — displays product and ad cards for the current post.
57
+
58
+ ### CSS Custom Properties
59
+ | Property | Description | Default |
60
+ |---|---|---|
61
+ | `--information-tab--checkmark-color` | Selected product checkmark color | `#0cce6b` |
62
+ | `--information-tab--gap` | Gap between cards | `16px` |
63
+ -->
64
+
65
+ <style>.information-tab {
66
+ --_information-tab--checkmark-color: var(--information-tab--checkmark-color, #ffffff);
67
+ --_information-tab--gap: var(--information-tab--gap, 1rem);
68
+ display: flex;
69
+ flex-direction: column;
70
+ gap: var(--_information-tab--gap);
71
+ container-type: inline-size;
72
+ }
73
+ .information-tab__ads {
74
+ display: flex;
75
+ flex-wrap: wrap;
76
+ justify-content: flex-start;
77
+ gap: var(--_information-tab--gap);
78
+ }
79
+ .information-tab__ad-wrapper {
80
+ width: calc(50% - var(--_information-tab--gap) / 2);
81
+ }
82
+ .information-tab__products {
83
+ display: flex;
84
+ flex-wrap: wrap;
85
+ justify-content: flex-start;
86
+ gap: var(--_information-tab--gap);
87
+ }
88
+ .information-tab__product-wrapper {
89
+ position: relative;
90
+ border-radius: 0.5rem;
91
+ cursor: pointer;
92
+ width: calc(50% - var(--_information-tab--gap) / 2);
93
+ }
94
+ .information-tab__checkmark {
95
+ --sc-kit--icon--size: 1.5rem;
96
+ --sc-kit--icon--color: var(--_information-tab--checkmark-color);
97
+ position: absolute;
98
+ top: 0.5rem;
99
+ right: 0.5rem;
100
+ z-index: 1;
101
+ filter: drop-shadow(1px 1px 1px rgba(0, 0, 0, 0.5));
102
+ }</style>
@@ -0,0 +1,26 @@
1
+ import type { TrackingParams } from '../../marketing-tracking';
2
+ import type { PostModel } from '../../posts/model';
3
+ type Props = {
4
+ model: PostModel;
5
+ trackingParams: TrackingParams;
6
+ selectedProductId: string | null;
7
+ on?: {
8
+ productImpression?: (productId: string) => void;
9
+ productBuy?: (productId: string) => void;
10
+ productSelect?: (productId: string) => void;
11
+ adClick?: (adId: string) => void;
12
+ adImpression?: (adId: string) => void;
13
+ };
14
+ };
15
+ /**
16
+ * Information tab — displays product and ad cards for the current post.
17
+ *
18
+ * ### CSS Custom Properties
19
+ * | Property | Description | Default |
20
+ * |---|---|---|
21
+ * | `--information-tab--checkmark-color` | Selected product checkmark color | `#0cce6b` |
22
+ * | `--information-tab--gap` | Gap between cards | `16px` |
23
+ */
24
+ declare const InformationTab: import("svelte").Component<Props, {}, "">;
25
+ type InformationTab = ReturnType<typeof InformationTab>;
26
+ export default InformationTab;
@@ -0,0 +1,110 @@
1
+ <script lang="ts">import { FeedPlayerLocalization } from '../feed-player-localization';
2
+ import { default as PostCard } from './post-card.svelte';
3
+ import { untrack } from 'svelte';
4
+ const { handler, activePostId, on } = $props();
5
+ const localization = new FeedPlayerLocalization();
6
+ let data = $state.raw(null);
7
+ $effect(() => {
8
+ void handler;
9
+ untrack(() => {
10
+ handler.getPlaylist().then((result) => {
11
+ data = result;
12
+ });
13
+ });
14
+ });
15
+ const currentPostIndex = $derived.by(() => {
16
+ if (!data)
17
+ return -1;
18
+ return data.posts.findIndex((p) => p.id === activePostId);
19
+ });
20
+ const formatDate = (dateString) => {
21
+ const date = new Date(dateString);
22
+ const now = new Date();
23
+ const diffMs = now.getTime() - date.getTime();
24
+ const diffDays = Math.floor(diffMs / 86400000);
25
+ if (diffDays === 0)
26
+ return 'Today';
27
+ if (diffDays === 1)
28
+ return 'Yesterday';
29
+ if (diffDays < 7)
30
+ return `${diffDays} days ago`;
31
+ return date.toLocaleDateString();
32
+ };
33
+ </script>
34
+
35
+ {#if data}
36
+ <div class="playlist-tab">
37
+ <div class="playlist-tab__header">
38
+ <div class="playlist-tab__header-left">
39
+ <div class="playlist-tab__name">{data.name}</div>
40
+ <div class="playlist-tab__updated">{localization.updatedLabel} {formatDate(data.updatedAt)}</div>
41
+ </div>
42
+ <div class="playlist-tab__post-indicator">{localization.postOf(currentPostIndex + 1, data.posts.length)}</div>
43
+ </div>
44
+
45
+ <div class="playlist-tab__list">
46
+ {#each data.posts as post (post.id)}
47
+ <PostCard
48
+ {post}
49
+ thumbnailWidth={146}
50
+ thumbnailHeight={260}
51
+ thumbnailRadius={8}
52
+ active={post.id === activePostId}
53
+ on={{ click: () => on?.postActivate?.(post.id) }} />
54
+ {/each}
55
+ </div>
56
+ </div>
57
+ {/if}
58
+
59
+ <!--
60
+ @component
61
+ Playlist tab — displays the current playlist's posts with active post highlighted.
62
+
63
+ ### CSS Custom Properties
64
+ | Property | Description | Default |
65
+ |---|---|---|
66
+ | `--playlist-tab--meta-color` | Header secondary text color | `#9ca3af` |
67
+ -->
68
+
69
+ <style>.playlist-tab {
70
+ --_playlist-tab--background: var(--playlist-tab--background, rgba(255, 255, 255, 0.58));
71
+ --_playlist-tab--meta-color: var(--playlist-tab--meta-color, #9ca3af);
72
+ display: flex;
73
+ flex-direction: column;
74
+ gap: 1rem;
75
+ background: var(--_playlist-tab--background);
76
+ padding: 1rem;
77
+ min-height: 100%;
78
+ }
79
+ .playlist-tab__header {
80
+ display: flex;
81
+ justify-content: space-between;
82
+ align-items: flex-start;
83
+ }
84
+ .playlist-tab__header-left {
85
+ display: flex;
86
+ flex-direction: column;
87
+ }
88
+ .playlist-tab__name {
89
+ font-size: 1rem;
90
+ font-weight: 500;
91
+ line-height: 1.5rem;
92
+ }
93
+ .playlist-tab__updated {
94
+ font-size: 0.75rem;
95
+ font-weight: 500;
96
+ color: var(--_playlist-tab--meta-color);
97
+ line-height: 1rem;
98
+ }
99
+ .playlist-tab__post-indicator {
100
+ font-size: 0.75rem;
101
+ font-weight: 500;
102
+ color: var(--_playlist-tab--meta-color);
103
+ line-height: 1rem;
104
+ white-space: nowrap;
105
+ }
106
+ .playlist-tab__list {
107
+ display: flex;
108
+ flex-direction: column;
109
+ gap: 1rem;
110
+ }</style>
@@ -0,0 +1,19 @@
1
+ import type { IFeedPlayerPlaylistHandler } from '../types';
2
+ type Props = {
3
+ handler: IFeedPlayerPlaylistHandler;
4
+ activePostId: string;
5
+ on?: {
6
+ postActivate?: (postId: string) => void;
7
+ };
8
+ };
9
+ /**
10
+ * Playlist tab — displays the current playlist's posts with active post highlighted.
11
+ *
12
+ * ### CSS Custom Properties
13
+ * | Property | Description | Default |
14
+ * |---|---|---|
15
+ * | `--playlist-tab--meta-color` | Header secondary text color | `#9ca3af` |
16
+ */
17
+ declare const PlaylistTab: import("svelte").Component<Props, {}, "">;
18
+ type PlaylistTab = ReturnType<typeof PlaylistTab>;
19
+ export default PlaylistTab;
@@ -0,0 +1,128 @@
1
+ <script lang="ts">import { FeedPlayerLocalization } from '../feed-player-localization';
2
+ import { Image } from '@streamscloud/kit/ui/image';
3
+ const { post, thumbnailWidth, thumbnailHeight, thumbnailRadius = 6, active = false, on } = $props();
4
+ const localization = new FeedPlayerLocalization();
5
+ const formatDuration = (seconds) => {
6
+ const m = Math.floor(seconds / 60);
7
+ const s = seconds % 60;
8
+ return `${m}:${String(s).padStart(2, '0')}`;
9
+ };
10
+ const formatDate = (dateString) => {
11
+ const date = new Date(dateString);
12
+ const now = new Date();
13
+ const diffMs = now.getTime() - date.getTime();
14
+ const diffDays = Math.floor(diffMs / 86400000);
15
+ if (diffDays === 0)
16
+ return 'Today';
17
+ if (diffDays === 1)
18
+ return 'Yesterday';
19
+ if (diffDays < 7)
20
+ return `${diffDays} days ago`;
21
+ return date.toLocaleDateString();
22
+ };
23
+ </script>
24
+
25
+ <div
26
+ class="post-card"
27
+ onclick={on?.click}
28
+ onkeydown={() => {}}
29
+ role="none">
30
+ <div
31
+ class="post-card__thumbnail"
32
+ class:post-card__thumbnail--active={active}
33
+ style:width="{thumbnailWidth}px"
34
+ style:height="{thumbnailHeight}px"
35
+ style:--_post-card--thumbnail-radius="{thumbnailRadius}px">
36
+ <Image src={post.thumbnailUrl} />
37
+ {#if post.durationSeconds != null}
38
+ <div class="post-card__duration">{formatDuration(post.durationSeconds)}</div>
39
+ {/if}
40
+ </div>
41
+ <div class="post-card__info">
42
+ {#if post.title}
43
+ <div class="post-card__title">{post.title}</div>
44
+ {/if}
45
+ {#if post.description}
46
+ <div class="post-card__description">{post.description}</div>
47
+ {/if}
48
+ <div class="post-card__meta">{formatDate(post.displayDate)} · {localization.viewsLabel(post.viewsCount)}</div>
49
+ </div>
50
+ </div>
51
+
52
+ <!--
53
+ @component
54
+ Horizontal post card with thumbnail, title, description, and meta info.
55
+
56
+ ### CSS Custom Properties
57
+ | Property | Description | Default |
58
+ |---|---|---|
59
+ | `--post-card--active-border-color` | Active post border color | `#0cce6b` |
60
+ | `--post-card--description-color` | Description text color | `#999` |
61
+ | `--post-card--meta-color` | Meta text color | `#999` |
62
+ -->
63
+
64
+ <style>.post-card {
65
+ --_post-card--active-border-color: var(--post-card--active-border-color, #0cce6b);
66
+ --_post-card--description-color: var(--post-card--description-color, #999);
67
+ --_post-card--meta-color: var(--post-card--meta-color, #999);
68
+ display: flex;
69
+ gap: 1rem;
70
+ align-items: flex-end;
71
+ cursor: pointer;
72
+ }
73
+ .post-card__thumbnail {
74
+ position: relative;
75
+ flex-shrink: 0;
76
+ border-radius: var(--_post-card--thumbnail-radius);
77
+ overflow: hidden;
78
+ --sc-kit--image--object-fit: cover;
79
+ --sc-kit--image--border-radius: var(--_post-card--thumbnail-radius);
80
+ }
81
+ .post-card__thumbnail--active {
82
+ outline: 2px solid var(--_post-card--active-border-color);
83
+ outline-offset: 0.125rem;
84
+ border-radius: calc(var(--_post-card--thumbnail-radius) + 3px);
85
+ }
86
+ .post-card__duration {
87
+ position: absolute;
88
+ bottom: 0.25rem;
89
+ right: 0.25rem;
90
+ background: rgba(0, 0, 0, 0.75);
91
+ border-radius: 0.25rem;
92
+ padding: 0.125rem 0.25rem;
93
+ font-size: 0.625rem;
94
+ font-weight: 400;
95
+ color: white;
96
+ line-height: 1;
97
+ }
98
+ .post-card__info {
99
+ display: flex;
100
+ flex-direction: column;
101
+ gap: 0.75rem;
102
+ min-width: 0;
103
+ height: 7.125rem;
104
+ }
105
+ .post-card__title {
106
+ font-size: 0.875rem;
107
+ font-weight: 600;
108
+ overflow: hidden;
109
+ text-overflow: ellipsis;
110
+ white-space: nowrap;
111
+ }
112
+ .post-card__description {
113
+ font-size: 0.75rem;
114
+ font-weight: 400;
115
+ color: var(--_post-card--description-color);
116
+ line-height: 0.9375rem;
117
+ display: -webkit-box;
118
+ -webkit-line-clamp: 3;
119
+ line-clamp: 3;
120
+ -webkit-box-orient: vertical;
121
+ overflow: hidden;
122
+ }
123
+ .post-card__meta {
124
+ font-size: 0.625rem;
125
+ font-weight: 400;
126
+ color: var(--_post-card--meta-color);
127
+ white-space: nowrap;
128
+ }</style>
@@ -0,0 +1,24 @@
1
+ import type { FeedPlayerPostPreview } from '../types';
2
+ type Props = {
3
+ post: FeedPlayerPostPreview;
4
+ thumbnailWidth: number;
5
+ thumbnailHeight: number;
6
+ thumbnailRadius?: number;
7
+ active?: boolean;
8
+ on?: {
9
+ click?: () => void;
10
+ };
11
+ };
12
+ /**
13
+ * Horizontal post card with thumbnail, title, description, and meta info.
14
+ *
15
+ * ### CSS Custom Properties
16
+ * | Property | Description | Default |
17
+ * |---|---|---|
18
+ * | `--post-card--active-border-color` | Active post border color | `#0cce6b` |
19
+ * | `--post-card--description-color` | Description text color | `#999` |
20
+ * | `--post-card--meta-color` | Meta text color | `#999` |
21
+ */
22
+ declare const PostCard: import("svelte").Component<Props, {}, "">;
23
+ type PostCard = ReturnType<typeof PostCard>;
24
+ export default PostCard;