@streamscloud/blocks 0.1.0

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 (91) hide show
  1. package/dist/content/analytics/index.d.ts +1 -0
  2. package/dist/content/analytics/index.js +1 -0
  3. package/dist/content/analytics/types.d.ts +9 -0
  4. package/dist/content/analytics/types.js +1 -0
  5. package/dist/content/content-viewer/attachments-horizontal.svelte +210 -0
  6. package/dist/content/content-viewer/attachments-horizontal.svelte.d.ts +15 -0
  7. package/dist/content/content-viewer/cmp.content-viewer.svelte +143 -0
  8. package/dist/content/content-viewer/cmp.content-viewer.svelte.d.ts +23 -0
  9. package/dist/content/content-viewer/content-texts.svelte +96 -0
  10. package/dist/content/content-viewer/content-texts.svelte.d.ts +14 -0
  11. package/dist/content/content-viewer/content-viewer-localization.d.ts +4 -0
  12. package/dist/content/content-viewer/content-viewer-localization.js +16 -0
  13. package/dist/content/content-viewer/heading.svelte +66 -0
  14. package/dist/content/content-viewer/heading.svelte.d.ts +11 -0
  15. package/dist/content/content-viewer/index.d.ts +1 -0
  16. package/dist/content/content-viewer/index.js +1 -0
  17. package/dist/content/content-viewer/media/content-media.svelte +53 -0
  18. package/dist/content/content-viewer/media/content-media.svelte.d.ts +12 -0
  19. package/dist/content/content-viewer/ui-manager.svelte.d.ts +10 -0
  20. package/dist/content/content-viewer/ui-manager.svelte.js +18 -0
  21. package/dist/content/controls/content-actions-generator.svelte.d.ts +18 -0
  22. package/dist/content/controls/content-actions-generator.svelte.js +27 -0
  23. package/dist/content/controls/content-actions-handler.svelte.d.ts +23 -0
  24. package/dist/content/controls/content-actions-handler.svelte.js +55 -0
  25. package/dist/content/controls/index.d.ts +1 -0
  26. package/dist/content/controls/index.js +1 -0
  27. package/dist/content/model/content-media-model.svelte.d.ts +20 -0
  28. package/dist/content/model/content-media-model.svelte.js +16 -0
  29. package/dist/content/model/content-model.d.ts +24 -0
  30. package/dist/content/model/content-model.js +32 -0
  31. package/dist/content/model/index.d.ts +3 -0
  32. package/dist/content/model/index.js +2 -0
  33. package/dist/content/model/types.d.ts +61 -0
  34. package/dist/content/model/types.js +1 -0
  35. package/dist/content/model/utils.d.ts +4 -0
  36. package/dist/content/model/utils.js +7 -0
  37. package/dist/content/sharing/index.d.ts +1 -0
  38. package/dist/content/sharing/index.js +1 -0
  39. package/dist/content/sharing/types.d.ts +5 -0
  40. package/dist/content/sharing/types.js +1 -0
  41. package/dist/content/social-interactions/index.d.ts +1 -0
  42. package/dist/content/social-interactions/index.js +1 -0
  43. package/dist/content/social-interactions/types.d.ts +8 -0
  44. package/dist/content/social-interactions/types.js +1 -0
  45. package/dist/core/enums.d.ts +4 -0
  46. package/dist/core/enums.js +1 -0
  47. package/dist/cta/cta-card/cmp.cta-card.svelte +259 -0
  48. package/dist/cta/cta-card/cmp.cta-card.svelte.d.ts +24 -0
  49. package/dist/cta/cta-card/index.d.ts +2 -0
  50. package/dist/cta/cta-card/index.js +1 -0
  51. package/dist/cta/cta-card/types.d.ts +18 -0
  52. package/dist/cta/cta-card/types.js +1 -0
  53. package/dist/feed-player/cmp.close-button.svelte +43 -0
  54. package/dist/feed-player/cmp.close-button.svelte.d.ts +19 -0
  55. package/dist/feed-player/cmp.feed-player.svelte +510 -0
  56. package/dist/feed-player/cmp.feed-player.svelte.d.ts +13 -0
  57. package/dist/feed-player/feed-player-localization.d.ts +16 -0
  58. package/dist/feed-player/feed-player-localization.js +70 -0
  59. package/dist/feed-player/index.d.ts +7 -0
  60. package/dist/feed-player/index.js +2 -0
  61. package/dist/feed-player/sidebar/article-tab.svelte +90 -0
  62. package/dist/feed-player/sidebar/article-tab.svelte.d.ts +10 -0
  63. package/dist/feed-player/sidebar/information-tab.svelte +85 -0
  64. package/dist/feed-player/sidebar/information-tab.svelte.d.ts +15 -0
  65. package/dist/feed-player/sidebar/panel-surface.svelte +13 -0
  66. package/dist/feed-player/sidebar/panel-surface.svelte.d.ts +7 -0
  67. package/dist/feed-player/sidebar/playlist-tab.svelte +90 -0
  68. package/dist/feed-player/sidebar/playlist-tab.svelte.d.ts +11 -0
  69. package/dist/feed-player/sidebar/post-card.svelte +92 -0
  70. package/dist/feed-player/sidebar/post-card.svelte.d.ts +14 -0
  71. package/dist/feed-player/sidebar/recommended-tab.svelte +161 -0
  72. package/dist/feed-player/sidebar/recommended-tab.svelte.d.ts +8 -0
  73. package/dist/feed-player/sidebar/sidebar-panel.svelte +69 -0
  74. package/dist/feed-player/sidebar/sidebar-panel.svelte.d.ts +26 -0
  75. package/dist/feed-player/sidebar/sidebar-tab-bar.svelte +44 -0
  76. package/dist/feed-player/sidebar/sidebar-tab-bar.svelte.d.ts +12 -0
  77. package/dist/feed-player/sidebar/types.d.ts +4 -0
  78. package/dist/feed-player/sidebar/types.js +1 -0
  79. package/dist/feed-player/types.d.ts +65 -0
  80. package/dist/feed-player/types.js +1 -0
  81. package/dist/index.d.ts +1 -0
  82. package/dist/index.js +1 -0
  83. package/dist/products/product-card/cmp.product-card.svelte +282 -0
  84. package/dist/products/product-card/cmp.product-card.svelte.d.ts +30 -0
  85. package/dist/products/product-card/index.d.ts +2 -0
  86. package/dist/products/product-card/index.js +1 -0
  87. package/dist/products/product-card/product-card-localization.d.ts +4 -0
  88. package/dist/products/product-card/product-card-localization.js +19 -0
  89. package/dist/products/product-card/types.d.ts +12 -0
  90. package/dist/products/product-card/types.js +1 -0
  91. package/package.json +92 -0
@@ -0,0 +1,12 @@
1
+ import type { ContentViewerMediaModel } from '../../model/content-media-model.svelte';
2
+ type Props = {
3
+ id: string;
4
+ media: ContentViewerMediaModel;
5
+ autoplay?: true | false | 'on-appearance';
6
+ on: {
7
+ videoProgress?: (progress: number) => void;
8
+ };
9
+ };
10
+ declare const ContentMedia: import("svelte").Component<Props, {}, "">;
11
+ type ContentMedia = ReturnType<typeof ContentMedia>;
12
+ export default ContentMedia;
@@ -0,0 +1,10 @@
1
+ export declare class ContentViewerUiManager {
2
+ readonly showAttachments: boolean;
3
+ readonly showControls: boolean;
4
+ readonly infoPaddingInline: number;
5
+ isMobileView: boolean;
6
+ controlsPanelWidth: number;
7
+ enableControls: boolean;
8
+ enableAttachments: boolean;
9
+ canShowAttachments: boolean;
10
+ }
@@ -0,0 +1,18 @@
1
+ const INFO_PADDING_INLINE = 20;
2
+ const INFO_PADDING_INLINE_MOBILE = 10;
3
+ export class ContentViewerUiManager {
4
+ showAttachments = $derived.by(() => {
5
+ return this.canShowAttachments && this.enableAttachments;
6
+ });
7
+ showControls = $derived.by(() => {
8
+ return this.enableControls;
9
+ });
10
+ infoPaddingInline = $derived.by(() => {
11
+ return this.isMobileView ? INFO_PADDING_INLINE_MOBILE : INFO_PADDING_INLINE;
12
+ });
13
+ isMobileView = $state(false);
14
+ controlsPanelWidth = $state(0);
15
+ enableControls = $state(false);
16
+ enableAttachments = $state(false);
17
+ canShowAttachments = $state(true);
18
+ }
@@ -0,0 +1,18 @@
1
+ import type { ContentModel } from '../model';
2
+ import type { IContentSharingHandler } from '../sharing';
3
+ import type { IContentSocialInteractionsHandler } from '../social-interactions';
4
+ import { ContentActionsHandler } from './content-actions-handler.svelte';
5
+ export declare class ContentActionsGenerator {
6
+ private props;
7
+ private handlers;
8
+ constructor(props: ContentActionsGeneratorProps);
9
+ getContentActionsHandler(model: ContentModel): ContentActionsHandler;
10
+ }
11
+ type ContentActionsGeneratorProps = {
12
+ readonly socialInteractionsHandler: IContentSocialInteractionsHandler | undefined;
13
+ readonly sharingHandler: IContentSharingHandler | undefined;
14
+ readonly on: {
15
+ attachmentClicked: () => void;
16
+ };
17
+ };
18
+ export {};
@@ -0,0 +1,27 @@
1
+ import { ContentActionsHandler } from './content-actions-handler.svelte';
2
+ export class ContentActionsGenerator {
3
+ props;
4
+ handlers = {};
5
+ constructor(props) {
6
+ this.props = props;
7
+ }
8
+ getContentActionsHandler(model) {
9
+ let handler = this.handlers[model.id];
10
+ if (!handler) {
11
+ const props = this.props;
12
+ handler = new ContentActionsHandler({
13
+ content: model,
14
+ get socialInteractionsHandler() {
15
+ return props.socialInteractionsHandler ?? null;
16
+ },
17
+ get shareClicked() {
18
+ const sh = props.sharingHandler;
19
+ return sh ? () => sh.share(model.id) : undefined;
20
+ },
21
+ attachmentClicked: props.on.attachmentClicked
22
+ });
23
+ this.handlers[model.id] = handler;
24
+ }
25
+ return handler;
26
+ }
27
+ }
@@ -0,0 +1,23 @@
1
+ import type { ContentModel } from '../model';
2
+ import type { IContentSocialInteractionsHandler } from '../social-interactions';
3
+ import type { IconColor } from '@streamscloud/kit/ui/icon';
4
+ export declare class ContentActionsHandler {
5
+ readonly actions: ContentAction[];
6
+ private content;
7
+ private props;
8
+ private isLikedStore;
9
+ constructor(props: ContentActionsHandlerProps);
10
+ private initIsLikedStore;
11
+ }
12
+ export type ContentActionsHandlerProps = {
13
+ content: ContentModel;
14
+ readonly socialInteractionsHandler: IContentSocialInteractionsHandler | null;
15
+ readonly shareClicked: (() => void) | undefined;
16
+ attachmentClicked: () => void;
17
+ };
18
+ type ContentAction = {
19
+ icon: string;
20
+ iconColor?: IconColor;
21
+ callback: () => void;
22
+ };
23
+ export {};
@@ -0,0 +1,55 @@
1
+ import IconHeartFilled from '@fluentui/svg-icons/icons/heart_32_filled.svg?raw';
2
+ import IconHeart from '@fluentui/svg-icons/icons/heart_32_regular.svg?raw';
3
+ import IconShare from '@fluentui/svg-icons/icons/share_48_regular.svg?raw';
4
+ import IconShoppingBag from '@fluentui/svg-icons/icons/shopping_bag_32_regular.svg?raw';
5
+ import IconSpeaker2 from '@fluentui/svg-icons/icons/speaker_2_32_regular.svg?raw';
6
+ import IconSpeakerMute from '@fluentui/svg-icons/icons/speaker_mute_32_regular.svg?raw';
7
+ import { MediaVolumeManager } from '@streamscloud/kit/ui/media-playback';
8
+ export class ContentActionsHandler {
9
+ actions = $derived.by(() => {
10
+ const result = [];
11
+ if (this.content.attachments) {
12
+ result.push({ icon: IconShoppingBag, callback: this.props.attachmentClicked });
13
+ }
14
+ if (this.content.enableSocialInteractions && this.props.socialInteractionsHandler) {
15
+ result.push({
16
+ icon: this.isLikedStore.isLiked ? IconHeartFilled : IconHeart,
17
+ iconColor: this.isLikedStore.isLiked ? 'danger' : undefined,
18
+ callback: () => this.props.socialInteractionsHandler?.toggleLike(this.content.id)
19
+ });
20
+ }
21
+ const shareClicked = this.props.shareClicked;
22
+ if (shareClicked) {
23
+ result.push({
24
+ icon: IconShare,
25
+ callback: () => shareClicked()
26
+ });
27
+ }
28
+ if (this.content.media && !this.content.media.currentItem.isImage) {
29
+ result.push({
30
+ icon: MediaVolumeManager.isMuted ? IconSpeakerMute : IconSpeaker2,
31
+ callback: () => (MediaVolumeManager.isMuted = !MediaVolumeManager.isMuted)
32
+ });
33
+ }
34
+ return result;
35
+ });
36
+ content;
37
+ props;
38
+ isLikedStore = $state.raw({
39
+ get isLiked() {
40
+ return false;
41
+ }
42
+ });
43
+ constructor(props) {
44
+ this.content = props.content;
45
+ this.props = props;
46
+ void this.initIsLikedStore();
47
+ }
48
+ async initIsLikedStore() {
49
+ const handler = this.props.socialInteractionsHandler;
50
+ if (!handler) {
51
+ return;
52
+ }
53
+ this.isLikedStore = await handler.getIsLiked(this.content.id);
54
+ }
55
+ }
@@ -0,0 +1 @@
1
+ export { ContentActionsGenerator } from './content-actions-generator.svelte';
@@ -0,0 +1 @@
1
+ export { ContentActionsGenerator } from './content-actions-generator.svelte';
@@ -0,0 +1,20 @@
1
+ import type { ContentModelMediaFit, IContentMediaItemModel } from './types';
2
+ export declare class ContentViewerMediaModel {
3
+ readonly currentItem: {
4
+ isImage: false;
5
+ url: string;
6
+ thumbnailUrl: string;
7
+ } | {
8
+ isImage: boolean;
9
+ url: string;
10
+ };
11
+ currentIndex: number;
12
+ readonly isGallery: boolean;
13
+ items: IContentMediaItemModel[];
14
+ mediaFit: ContentModelMediaFit;
15
+ constructor(init: {
16
+ media: IContentMediaItemModel[];
17
+ mediaFit: ContentModelMediaFit;
18
+ mediaIndex?: number;
19
+ });
20
+ }
@@ -0,0 +1,16 @@
1
+ export class ContentViewerMediaModel {
2
+ currentItem = $derived.by(() => {
3
+ const item = this.items[this.currentIndex];
4
+ return item ? item : { isImage: true, url: '' };
5
+ });
6
+ currentIndex = $state(0);
7
+ isGallery = $derived.by(() => this.items.length > 1);
8
+ items;
9
+ mediaFit;
10
+ constructor(init) {
11
+ const { media, mediaFit, mediaIndex } = init;
12
+ this.items = media;
13
+ this.mediaFit = mediaFit;
14
+ this.currentIndex = mediaIndex || 0;
15
+ }
16
+ }
@@ -0,0 +1,24 @@
1
+ import type { ContentType } from '../../core/enums';
2
+ import { ContentViewerMediaModel } from './content-media-model.svelte';
3
+ import type { IContentCtaCardModel, IContentHeadingModel, IContentModel, IContentProductCardModel } from './types';
4
+ export declare class ContentModel {
5
+ id: string;
6
+ media: ContentViewerMediaModel;
7
+ contentType: ContentType | null;
8
+ articleId: string | null;
9
+ texts: {
10
+ kicker: string | null;
11
+ title: string | null;
12
+ text: string | null;
13
+ readMoreUrl: string | null;
14
+ };
15
+ heading: IContentHeadingModel | null;
16
+ enableSocialInteractions: boolean;
17
+ attachments: false | {
18
+ products: IContentProductCardModel[];
19
+ ctas: IContentCtaCardModel[];
20
+ };
21
+ constructor(init: IContentModel & {
22
+ mediaIndex?: number;
23
+ });
24
+ }
@@ -0,0 +1,32 @@
1
+ import { ContentViewerMediaModel } from './content-media-model.svelte';
2
+ export class ContentModel {
3
+ id;
4
+ media;
5
+ contentType;
6
+ articleId;
7
+ texts;
8
+ heading;
9
+ enableSocialInteractions;
10
+ attachments;
11
+ constructor(init) {
12
+ this.id = init.id;
13
+ this.contentType = init.contentType;
14
+ this.articleId = init.articleId;
15
+ this.media = new ContentViewerMediaModel({ media: init.media, mediaFit: init.mediaFit, mediaIndex: init.mediaIndex });
16
+ this.texts = {
17
+ kicker: init.kicker,
18
+ title: init.title,
19
+ text: init.text,
20
+ readMoreUrl: init.readMoreUrl ?? null
21
+ };
22
+ this.heading = init.heading;
23
+ this.enableSocialInteractions = init.enableSocialInteractions;
24
+ this.attachments =
25
+ init.products.length || init.ctas.length
26
+ ? {
27
+ products: init.products,
28
+ ctas: init.ctas
29
+ }
30
+ : false;
31
+ }
32
+ }
@@ -0,0 +1,3 @@
1
+ export { ContentModel } from './content-model';
2
+ export type { ContentModelMediaFit, IContentCtaCardModel, IContentHeadingModel, IContentMediaItemModel, IContentModel, IContentProductCardModel } from './types';
3
+ export { getContentCoverImage } from './utils';
@@ -0,0 +1,2 @@
1
+ export { ContentModel } from './content-model';
2
+ export { getContentCoverImage } from './utils';
@@ -0,0 +1,61 @@
1
+ import { type ContentType, type CtaType, type Currency } from '../../core/enums';
2
+ export type IContentModel = {
3
+ id: string;
4
+ contentType: ContentType | null;
5
+ media: IContentMediaItemModel[];
6
+ mediaFit: ContentModelMediaFit;
7
+ kicker: string | null;
8
+ title: string | null;
9
+ text: string | null;
10
+ viewsCount: number;
11
+ displayDate: string;
12
+ readMoreUrl?: string;
13
+ heading: IContentHeadingModel | null;
14
+ enableSocialInteractions: boolean;
15
+ articleId: string | null;
16
+ products: IContentProductCardModel[];
17
+ ctas: IContentCtaCardModel[];
18
+ };
19
+ export type IContentMediaItemModel = {
20
+ isImage: true;
21
+ url: string;
22
+ } | {
23
+ isImage: false;
24
+ url: string;
25
+ thumbnailUrl: string;
26
+ };
27
+ export type IContentHeadingModel = {
28
+ image: string | null;
29
+ name: string;
30
+ displayDate: string;
31
+ viewsCount: number;
32
+ };
33
+ export type ContentModelMediaFit = 'cover' | 'contain';
34
+ export type IContentProductCardModel = {
35
+ id: string;
36
+ title: string;
37
+ shortDescription: string | null;
38
+ link: string | null;
39
+ image: string | null;
40
+ brandName: string | null;
41
+ price: number;
42
+ currency: Currency;
43
+ salePrice: number | null;
44
+ };
45
+ export type IContentCtaCardModel = {
46
+ id: string;
47
+ type: CtaType;
48
+ image: string | null;
49
+ title: string;
50
+ description: string | null;
51
+ price: number | null;
52
+ priceInfoLabel: string | null;
53
+ currency: Currency | null;
54
+ ctaButton: {
55
+ background: string;
56
+ textColor: string;
57
+ text: string;
58
+ url: string;
59
+ border: string;
60
+ } | null;
61
+ };
@@ -0,0 +1 @@
1
+ import {} from '../../core/enums';
@@ -0,0 +1,4 @@
1
+ import type { IContentMediaItemModel } from './types';
2
+ export declare const getContentCoverImage: (content: {
3
+ media: IContentMediaItemModel[];
4
+ }) => string;
@@ -0,0 +1,7 @@
1
+ export const getContentCoverImage = (content) => {
2
+ const firstItem = content.media[0];
3
+ if (!firstItem) {
4
+ return '';
5
+ }
6
+ return firstItem.isImage ? firstItem.url : firstItem.thumbnailUrl;
7
+ };
@@ -0,0 +1 @@
1
+ export type { IContentSharingHandler } from './types';
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,5 @@
1
+ export interface IContentSharingHandler {
2
+ share: (contentId: string) => PromiseLike<void>;
3
+ }
4
+ type PromiseLike<T> = T | Promise<T>;
5
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export type { IContentSocialInteractionsHandler } from './types';
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,8 @@
1
+ export interface IContentSocialInteractionsHandler {
2
+ getIsLiked: (contentId: string) => PromiseLike<{
3
+ readonly isLiked: boolean;
4
+ }>;
5
+ toggleLike: (contentId: string) => PromiseLike<void>;
6
+ }
7
+ type PromiseLike<T> = T | Promise<T>;
8
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,4 @@
1
+ export type ContentType = 'ARTICLE' | 'MEDIA' | 'MOMENT' | 'SHORT_VIDEO' | 'VIDEO';
2
+ export type CtaType = 'BANNER_RESPONSIVE' | 'MEDIA_DIALOG_PROMOTION' | 'STORY';
3
+ export type Currency = 'EUR' | 'NOK' | 'USD';
4
+ export type MediaType = 'AUDIO' | 'IMAGE' | 'VIDEO';
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,259 @@
1
+ <script lang="ts">import { toPriceRepresentation } from '@streamscloud/kit/core/utils';
2
+ import { Button } from '@streamscloud/kit/ui/button';
3
+ import { Image } from '@streamscloud/kit/ui/image';
4
+ import { LineClamp } from '@streamscloud/kit/ui/line-clamp';
5
+ let { cta, inert = false, on } = $props();
6
+ const trackImpression = (node) => {
7
+ if (on?.impression) {
8
+ const observer = new IntersectionObserver((entries) => {
9
+ entries.forEach((entry) => {
10
+ if (entry.isIntersecting && entry.intersectionRatio >= 0.5) {
11
+ on.impression?.(cta.id);
12
+ observer.unobserve(entry.target);
13
+ }
14
+ });
15
+ }, { threshold: 0.5 });
16
+ observer.observe(node);
17
+ return {
18
+ destroy() {
19
+ observer.disconnect();
20
+ }
21
+ };
22
+ }
23
+ };
24
+ const handleCtaClick = () => {
25
+ if (on?.click) {
26
+ on.click(cta.id);
27
+ }
28
+ if (cta.ctaButton?.url) {
29
+ window.open(cta.ctaButton.url, '_blank', 'noopener noreferrer');
30
+ }
31
+ };
32
+ </script>
33
+
34
+ <div
35
+ class="cta-card"
36
+ style:--_--cta-card--cta--background={cta.ctaButton?.background}
37
+ style:--_--cta-card--cta--text-color={cta.ctaButton?.textColor}
38
+ inert={inert}
39
+ use:trackImpression>
40
+ <div class="cta-card__image">
41
+ <Image src={cta.image} alt={cta.title} />
42
+ </div>
43
+ <div class="cta-card__info">
44
+ <div class="cta-card__content">
45
+ {#if cta.title}
46
+ <div class="cta-card__title">
47
+ <LineClamp maxLines={2}>{cta.title}</LineClamp>
48
+ </div>
49
+ {/if}
50
+ {#if cta.description}
51
+ <div class="cta-card__description">
52
+ <LineClamp maxLines={2}>{cta.description}</LineClamp>
53
+ </div>
54
+ {/if}
55
+ </div>
56
+ <div class="cta-card__price-container">
57
+ {#if cta.price && cta.currency}
58
+ <div class="cta-card__price">
59
+ {#if cta.priceInfoLabel}
60
+ <span class="cta-card__price-info-label">{cta.priceInfoLabel}</span>
61
+ {/if}
62
+ {toPriceRepresentation({ amount: cta.price, currency: cta.currency })}
63
+ </div>
64
+ {/if}
65
+ </div>
66
+ <div class="cta-card__button-container">
67
+ {#if cta.ctaButton && cta.ctaButton.url && cta.ctaButton.text}
68
+ <div class="cta-card__button">
69
+ <Button type="button" size="md" variant="secondary" fullWidth on={{ click: handleCtaClick }}>
70
+ <span class="cta-card__button-text">{cta.ctaButton.text}</span>
71
+ </Button>
72
+ </div>
73
+ {/if}
74
+ </div>
75
+ </div>
76
+ </div>
77
+
78
+ <!--
79
+ @component
80
+ Call-to-action card with image, primary text, secondary text, and CTA button.
81
+
82
+ ### CSS Custom Properties
83
+ | Property | Description | Default |
84
+ |---|---|---|
85
+ | `--sc-blocks--cta-card--background-color` | Card background | `rgb(from light-dark(white, black) r g b / 90%)` |
86
+ | `--sc-blocks--cta-card--border-color` | Card border color | `--sc-kit--color--border` |
87
+ | `--sc-blocks--cta-card--price-color` | Price color | `inherit` |
88
+ | `--sc-blocks--cta-card--text--primary` | Primary text color | `--sc-kit--color--text--primary` |
89
+ | `--sc-blocks--cta-card--text-secondary` | Secondary text color | `--sc-kit--color--text--secondary` |
90
+ -->
91
+
92
+ <style>.cta-card {
93
+ --_cta-card--background-color: var(
94
+ --sc-blocks--cta-card--background-color,
95
+ rgb(from light-dark(#ffffff, #000000) r g b / 90%)
96
+ );
97
+ --_cta-card--border-color: var(--sc-blocks--cta-card--border-color, var(--sc-kit--color--border));
98
+ --_cta-card--price-color: var(--sc-blocks--cta-card--price-color, inherit);
99
+ --_cta-card--text--primary: var(--sc-blocks--cta-card--text--primary, var(--sc-kit--color--text--primary));
100
+ --_cta-card--text-secondary: var(--sc-blocks--cta-card--text-secondary, var(--sc-kit--color--text--secondary));
101
+ --_cta-card--cta--background: var(--_--cta-card--cta--background, var(--sc-kit--color--bg--panel));
102
+ --_cta-card--cta--text-color: var(--_--cta-card--cta--text-color, var(--sc-kit--color--text--primary));
103
+ width: 100%;
104
+ height: max-content;
105
+ display: flex;
106
+ flex-direction: column;
107
+ position: relative;
108
+ container-type: inline-size;
109
+ aspect-ratio: 9/16;
110
+ background-color: var(--_cta-card--background-color);
111
+ border: 1px solid var(--_cta-card--border-color);
112
+ border-radius: var(--sc-kit--radius--lg);
113
+ padding: var(--sc-kit--space--3) var(--sc-kit--space--3) 1.125rem;
114
+ justify-content: flex-start;
115
+ gap: var(--sc-kit--space--2);
116
+ /* Set 'container-type: inline-size;' to reference container*/
117
+ }
118
+ @container (width < 230px) {
119
+ .cta-card {
120
+ padding: var(--sc-kit--space--2) var(--sc-kit--space--2) var(--sc-kit--space--3);
121
+ }
122
+ }
123
+ .cta-card__image {
124
+ border-radius: var(--sc-kit--radius--sm);
125
+ overflow: hidden;
126
+ aspect-ratio: 1/1;
127
+ width: 100%;
128
+ --sc-kit--image--object-fit: cover;
129
+ }
130
+ .cta-card__info {
131
+ display: grid;
132
+ grid-template-rows: 0.7fr auto auto;
133
+ grid-template-areas: "content" "price" "button";
134
+ flex: 1 1 auto;
135
+ min-height: 0;
136
+ }
137
+ .cta-card__content {
138
+ grid-area: content;
139
+ display: flex;
140
+ flex-direction: column;
141
+ gap: 0.1875rem;
142
+ align-self: start;
143
+ /* Set 'container-type: inline-size;' to reference container*/
144
+ }
145
+ @container (width < 230px) {
146
+ .cta-card__content {
147
+ gap: 0.0938rem;
148
+ }
149
+ }
150
+ .cta-card__price-container {
151
+ grid-area: price;
152
+ display: flex;
153
+ justify-content: flex-end;
154
+ align-items: center;
155
+ height: 3rem;
156
+ overflow: hidden;
157
+ min-width: 0;
158
+ /* Set 'container-type: inline-size;' to reference container*/
159
+ }
160
+ @container (width < 230px) {
161
+ .cta-card__price-container {
162
+ height: 2rem;
163
+ }
164
+ }
165
+ .cta-card__button-container {
166
+ grid-area: button;
167
+ display: flex;
168
+ justify-content: center;
169
+ align-items: end;
170
+ margin-top: 0.625rem;
171
+ min-width: 0;
172
+ /* Set 'container-type: inline-size;' to reference container*/
173
+ }
174
+ @container (width < 230px) {
175
+ .cta-card__button-container {
176
+ margin-top: 0.375rem;
177
+ }
178
+ }
179
+ .cta-card__button-text {
180
+ display: block;
181
+ text-overflow: ellipsis;
182
+ max-width: 100%;
183
+ white-space: nowrap;
184
+ overflow: hidden;
185
+ }
186
+ .cta-card__title {
187
+ color: var(--_cta-card--text--primary);
188
+ font-weight: var(--sc-kit--font-weight--bold);
189
+ font-size: 1.125rem;
190
+ line-height: 1.375rem;
191
+ min-height: 1.375rem;
192
+ /* Set 'container-type: inline-size;' to reference container*/
193
+ }
194
+ @container (width < 230px) {
195
+ .cta-card__title {
196
+ font-size: var(--sc-kit--font-size--sm);
197
+ line-height: 0.875rem;
198
+ min-height: 0.875rem;
199
+ }
200
+ }
201
+ .cta-card__description {
202
+ font-weight: var(--sc-kit--font-weight--regular);
203
+ color: var(--_cta-card--text-secondary);
204
+ font-size: 0.9375rem;
205
+ line-height: 1.375rem;
206
+ min-height: 1.375rem;
207
+ /* Set 'container-type: inline-size;' to reference container*/
208
+ }
209
+ @container (width < 230px) {
210
+ .cta-card__description {
211
+ font-size: 0.625rem;
212
+ line-height: 0.875rem;
213
+ min-height: 0.875rem;
214
+ }
215
+ }
216
+ .cta-card__price {
217
+ font-size: 1.6875rem;
218
+ font-weight: var(--sc-kit--font-weight--bold);
219
+ text-align: right;
220
+ display: flex;
221
+ align-items: end;
222
+ gap: 0.625rem;
223
+ min-width: 0;
224
+ white-space: nowrap;
225
+ color: var(--_cta-card--price-color);
226
+ /* Set 'container-type: inline-size;' to reference container*/
227
+ }
228
+ @container (width < 230px) {
229
+ .cta-card__price {
230
+ font-size: 1.25rem;
231
+ }
232
+ }
233
+ .cta-card__price-info-label {
234
+ font-weight: var(--sc-kit--font-weight--regular);
235
+ font-size: var(--sc-kit--font-size--sm);
236
+ line-height: 1.0625rem;
237
+ text-align: right;
238
+ color: var(--_cta-card--text-secondary);
239
+ white-space: nowrap;
240
+ overflow: hidden;
241
+ text-overflow: ellipsis;
242
+ flex: 1 1 auto;
243
+ min-width: 0;
244
+ /* Set 'container-type: inline-size;' to reference container*/
245
+ }
246
+ @container (width < 230px) {
247
+ .cta-card__price-info-label {
248
+ font-size: 0.625rem;
249
+ line-height: 0.875rem;
250
+ }
251
+ }
252
+ .cta-card__button {
253
+ width: 100%;
254
+ --sc-kit--button--background: var(--_cta-card--cta--background);
255
+ --sc-kit--button--background--hover: var(--_cta-card--cta--background);
256
+ --sc-kit--button--background--active: var(--_cta-card--cta--background);
257
+ --sc-kit--button--color: var(--_cta-card--cta--text-color);
258
+ --sc-kit--button--border: none;
259
+ }</style>