@playpilot/tpi 5.6.0-beta.video.7 → 5.7.0-beta.display-2

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 (38) hide show
  1. package/dist/link-injections.js +9 -9
  2. package/events.md +8 -2
  3. package/package.json +1 -1
  4. package/src/lib/ads.ts +38 -0
  5. package/src/lib/constants.ts +2 -1
  6. package/src/lib/enums/ImageDimensions.ts +7 -0
  7. package/src/lib/enums/SplitTest.ts +23 -0
  8. package/src/lib/enums/TrackingEvent.ts +3 -0
  9. package/src/lib/image.ts +12 -0
  10. package/src/lib/linkInjection.ts +36 -2
  11. package/src/lib/splitTest.ts +19 -14
  12. package/src/lib/types/campaign.d.ts +45 -0
  13. package/src/lib/types/playlink.d.ts +2 -0
  14. package/src/lib/types/script.d.ts +5 -0
  15. package/src/main.ts +6 -0
  16. package/src/routes/+layout.svelte +4 -0
  17. package/src/routes/+page.svelte +13 -2
  18. package/src/routes/components/Ads/Display.svelte +140 -0
  19. package/src/routes/components/Ads/TopScroll.svelte +240 -0
  20. package/src/routes/components/DragHandle.svelte +105 -0
  21. package/src/routes/components/Modal.svelte +125 -30
  22. package/src/routes/components/Playlink.svelte +185 -0
  23. package/src/routes/components/Playlinks.svelte +34 -168
  24. package/src/routes/components/Popover.svelte +30 -2
  25. package/src/routes/components/Title.svelte +13 -43
  26. package/src/routes/components/TitleModal.svelte +24 -10
  27. package/src/routes/components/TitlePopover.svelte +12 -6
  28. package/src/tests/lib/ads.test.js +55 -0
  29. package/src/tests/lib/splitTest.test.js +97 -18
  30. package/src/tests/routes/+page.test.js +33 -0
  31. package/src/tests/routes/components/Ads/Display.test.js +109 -0
  32. package/src/tests/routes/components/Ads/TopScroll.test.js +104 -0
  33. package/src/tests/routes/components/Playlink.test.js +95 -0
  34. package/src/tests/routes/components/Playlinks.test.js +0 -30
  35. package/src/tests/routes/components/TitleModal.test.js +36 -0
  36. package/src/tests/routes/components/TitlePopover.test.js +20 -0
  37. package/src/routes/components/Ads/FloatingPlaylink.svelte +0 -62
  38. package/src/routes/components/Ads/VideoBackground.svelte +0 -28
@@ -0,0 +1,185 @@
1
+ <script lang="ts">
2
+ import { removeImageUrlPrefix } from '$lib/image'
3
+ import { t } from '$lib/localization'
4
+ import type { PlaylinkData } from '$lib/types/playlink'
5
+
6
+ interface Props {
7
+ playlink: PlaylinkData
8
+ hideCategory?: boolean
9
+ onclick?: () => void
10
+ }
11
+
12
+ const { playlink, hideCategory = false, onclick = () => null }: Props = $props()
13
+
14
+ const { name, url, logo_url, highlighted, cta_text, action_text, extra_info: { category } } = $derived(playlink)
15
+
16
+ const categoryStrings = {
17
+ SVOD: t('Stream'),
18
+ BUY: t('Buy'),
19
+ RENT: t('Rent'),
20
+ TVOD: t('Rent Or Buy'),
21
+ }
22
+ </script>
23
+
24
+ <a href={url} target="_blank" class="playlink" class:highlighted={highlighted && cta_text} class:no-category={hideCategory} {onclick} data-playlink={name} rel="sponsored">
25
+ <div class="playlink-content">
26
+ <img src={removeImageUrlPrefix(logo_url)} alt="" height="36" width="36" />
27
+
28
+ <span class="name">{name}</span>
29
+
30
+ {#if !hideCategory}
31
+ <span class="category" data-testid="category">{categoryStrings[category] || t('Stream')}</span>
32
+ {/if}
33
+
34
+ {#if cta_text}
35
+ <span class="cta">{cta_text}</span>
36
+ {/if}
37
+
38
+ <div class="action">
39
+ {action_text || t('Watch')}
40
+ </div>
41
+ </div>
42
+ </a>
43
+
44
+ <style lang="scss">
45
+ $image-size: margin(2.25);
46
+
47
+ @keyframes sheen {
48
+ 0%, 20%, 60%, 80.01%, 100% {
49
+ background-position: -100% 0;
50
+ }
51
+
52
+ 80% {
53
+ background-position: 100% 0;
54
+ }
55
+ }
56
+
57
+ img {
58
+ height: $image-size;
59
+ width: $image-size;
60
+ border-radius: margin(0.5);
61
+ background: rgba(0, 0, 0, 0.25);
62
+ }
63
+
64
+ .playlink {
65
+ position: relative;
66
+ background: var(--playpilot-playlink-background, var(--playpilot-lighter));
67
+ box-shadow: var(--playpilot-playlink-shadow, var(--playpilot-shadow));
68
+ border-radius: var(--playpilot-playlink-border-radius, margin(0.5));
69
+ color: var(--playpilot-playlink-text-color, var(--playpilot-text-color)) !important;
70
+ font-weight: var(--playpilot-playlink-font-weight, inherit);
71
+ font-style: var(--playpilot-playlink-font-style, normal) !important;
72
+ text-decoration: none !important;
73
+ white-space: nowrap;
74
+ font-size: var(--playpilot-playlinks-font-size, margin(0.75));
75
+ line-height: 1;
76
+
77
+ &:hover,
78
+ &:active {
79
+ filter: var(--playpilot-playlink-hover-filter, brightness(1.1));
80
+ background: var(--playpilot-playlink-hover-background, var(--playpilot-playlink-background, var(--playpilot-lighter))) !important;
81
+ text-decoration: none !important;
82
+ }
83
+
84
+ &.highlighted {
85
+ grid-column: span 2;
86
+
87
+ &::before {
88
+ content: "";
89
+ z-index: 1;
90
+ display: block;
91
+ position: absolute;
92
+ top: 0;
93
+ right: 0;
94
+ bottom: 0;
95
+ left: 0;
96
+ border-radius: inherit;
97
+ background: linear-gradient(to left, currentColor 20%, transparent 50%, currentColor 80%);
98
+ background-size: 200% 100%;
99
+ background-position: -100% 0;
100
+ opacity: 0.35;
101
+ pointer-events: none;
102
+
103
+ @media (prefers-reduced-motion: no-preference) {
104
+ animation: sheen 4000ms ease-in-out infinite;
105
+ }
106
+ }
107
+
108
+ &::after {
109
+ content: "";
110
+ z-index: 2;
111
+ display: block;
112
+ position: absolute;
113
+ top: 2px;
114
+ right: 2px;
115
+ bottom: 2px;
116
+ left: 2px;
117
+ border-radius: inherit;
118
+ background: var(--playpilot-playlink-background, var(--playpilot-lighter));
119
+ }
120
+ }
121
+
122
+ img {
123
+ grid-area: image;
124
+ margin: 0;
125
+ }
126
+ }
127
+
128
+ .playlink-content {
129
+ position: relative;
130
+ z-index: 5;
131
+ display: grid;
132
+ grid-template-areas: "image name action" "image category action";
133
+ grid-template-columns: $image-size auto margin(6);
134
+ align-items: center;
135
+ gap: 0 margin(0.75);
136
+ padding: margin(0.75);
137
+
138
+ .highlighted & {
139
+ grid-template-areas: "image name action" "image category action" "image cta action";
140
+ gap: margin(0.25) margin(0.75);
141
+ }
142
+
143
+ .no-category & {
144
+ grid-template-areas: "image name action" "image cta action";
145
+ }
146
+ }
147
+
148
+ .name {
149
+ grid-area: name;
150
+ width: 100%;
151
+ overflow: hidden;
152
+ font-weight: var(--playpilot-playlink-font-weight, 500);
153
+ font-family: var(--playpilot-playlink-font-family, inherit);
154
+ text-overflow: ellipsis;
155
+ white-space: nowrap;
156
+ }
157
+
158
+ .category {
159
+ grid-area: category;
160
+ width: 100%;
161
+ font-size: var(--playpilot-playlinks-category-font-size, margin(0.625));
162
+ color: var(--playpilot-playlink-category-text-color, var(--playpilot-text-color-alt));
163
+ font-weight: var(--playpilot-playlink-category-font-weight, inherit);
164
+ font-family: var(--playpilot-playlink-category-font-family, inherit);
165
+ }
166
+
167
+ .cta {
168
+ grid-area: cta;
169
+ width: 100%;
170
+ font-size: var(--playpilot-playlinks-cta-font-size, var(--playpilot-playlinks-category-font-size, margin(0.625)));
171
+ color: var(--playpilot-playlink-cta-text-color, var(--playpilot-playlink-category-text-color, var(--playpilot-text-color)));
172
+ font-weight: var(--playpilot-playlink-cta-font-weight, var(--playpilot-playlink-category-font-weight, inherit));
173
+ font-family: var(--playpilot-playlink-cta-font-family, var(--playpilot-playlink-category-font-family, inherit));
174
+ }
175
+
176
+ .action {
177
+ grid-area: action;
178
+ margin-left: auto;
179
+ padding: margin(0.5);
180
+ border: var(--playpilot-playlinks-action-border, 1px solid currentColor);
181
+ border-radius: var(--playpilot-playlinks-action-border-radius, margin(2));
182
+ font-weight: var(--playpilot-playlinks-action-font-weight, 500);
183
+ color: var(--playpilot-playlinks-action-text-color, var(--playpilot-text-color));
184
+ }
185
+ </style>
@@ -6,8 +6,12 @@
6
6
  import type { PlaylinkData } from '$lib/types/playlink'
7
7
  import type { TitleData } from '$lib/types/title'
8
8
  import { heading } from '$lib/actions/heading'
9
+ import { isInSplitTestVariant, trackSplitTestAction } from '$lib/splitTest'
10
+ import { SplitTest } from '$lib/enums/SplitTest'
9
11
  import { getContext } from 'svelte'
10
- import { removeImageUrlPrefix } from '$lib/image'
12
+ import Playlink from './Playlink.svelte'
13
+ import Display from './Ads/Display.svelte'
14
+ import { campaignToPlaylink, getFirstAdOfType } from '$lib/ads'
11
15
 
12
16
  interface Props {
13
17
  playlinks: PlaylinkData[]
@@ -17,22 +21,21 @@
17
21
  const { playlinks, title }: Props = $props()
18
22
 
19
23
  const isModal = getContext('scope') === 'modal'
24
+ const displayAd = getFirstAdOfType('card')
25
+ const showDisplayAd = displayAd && isModal && isInSplitTestVariant(SplitTest.DisplayAdPosition, 0)
26
+ const showPlaylinkAd = displayAd && isModal && isInSplitTestVariant(SplitTest.DisplayAdPosition, 1)
20
27
 
21
28
  let outerWidth = $state(0)
22
29
 
23
- const list = $derived(outerWidth < 500)
30
+ // Grid turns into a list when the playlinks container is small enough
31
+ // It is also a list by default if a display ad is present, as that would
32
+ // otherwise break the layout in ways that don't make sense to fix.
33
+ const list = $derived(outerWidth < 500 || showDisplayAd || showPlaylinkAd)
24
34
 
25
- // Remove any playlinks without logos, as these are likely sub providers.
35
+ // Remove any playlinks without logos, these are likely sub providers.
26
36
  const filteredPlaylinks = $derived(playlinks.filter(playlink => !!playlink.logo_url))
27
37
  const mergedPlaylink = $derived(mergePlaylinks(filteredPlaylinks))
28
38
 
29
- const categoryStrings = {
30
- SVOD: t('Stream'),
31
- BUY: t('Buy'),
32
- RENT: t('Rent'),
33
- TVOD: t('Rent Or Buy'),
34
- }
35
-
36
39
  function onclick(playlink: string): void {
37
40
  track(isModal ? TrackingEvent.TitleModalPlaylinkClick : TrackingEvent.TitlePopoverPlaylinkClick, title, { playlink })
38
41
  }
@@ -48,42 +51,25 @@
48
51
  {/if}
49
52
 
50
53
  <div class="playlinks" class:list bind:clientWidth={outerWidth}>
51
- {#each mergedPlaylink as { name, url, logo_url, highlighted, cta_text, extra_info: { category } }}
52
- <a href={url} target="_blank" class="playlink" class:highlighted={highlighted && cta_text} onclick={() => onclick(name)} data-playlink={name} rel="sponsored">
53
- <div class="playlink-content">
54
- <img src={removeImageUrlPrefix(logo_url)} alt="" height="36" width="36" />
55
-
56
- <span class="name">{name}</span>
57
- <span class="category">{categoryStrings[category] || t('Stream')}</span>
58
-
59
- {#if cta_text}
60
- <span class="cta">{cta_text}</span>
61
- {/if}
62
-
63
- <div class="action">
64
- {t('Watch')}
65
- </div>
66
- </div>
67
- </a>
54
+ {#each mergedPlaylink as playlink, index}
55
+ <Playlink {playlink} onclick={() => onclick(playlink.name)} />
56
+
57
+ <!-- Insert display ad after the first playlink -->
58
+ {#if showDisplayAd && (index === 0)}
59
+ <Display campaign={displayAd} />
60
+ {:else if showPlaylinkAd && (index === 0)}
61
+ <Playlink playlink={campaignToPlaylink(displayAd)} onclick={() => trackSplitTestAction(SplitTest.DisplayAdPosition, 'click_playlink_ad')} hideCategory />
62
+ {/if}
68
63
  {/each}
69
64
 
70
- {#if !playlinks.length}
71
- <div class="playlink empty" data-testid="playlinks-empty">
65
+ {#if !mergedPlaylink.length}
66
+ <div class="empty" data-testid="playlinks-empty">
72
67
  {t('Title Unavailable')}
73
68
  </div>
74
69
  {/if}
75
70
  </div>
76
71
 
77
72
  <style lang="scss">
78
- $image-size: margin(2.25);
79
-
80
- img {
81
- height: $image-size;
82
- width: $image-size;
83
- border-radius: margin(0.5);
84
- background: rgba(0, 0, 0, 0.25);
85
- }
86
-
87
73
  .heading {
88
74
  margin: 0;
89
75
  color: var(--playpilot-playlinks-title-color, var(--playpilot-text-color-alt));
@@ -104,130 +90,11 @@
104
90
 
105
91
  &.list {
106
92
  grid-template-columns: 1fr;
107
- }
108
- }
109
-
110
-
111
- @keyframes sheen {
112
- 0%, 20%, 60%, 80.01%, 100% {
113
- background-position: -100% 0;
114
- }
115
-
116
- 80% {
117
- background-position: 100% 0;
118
- }
119
- }
120
-
121
- .playlink {
122
- position: relative;
123
- background: var(--playpilot-playlink-background, var(--playpilot-lighter));
124
- box-shadow: var(--playpilot-playlink-shadow, var(--playpilot-shadow));
125
- border-radius: var(--playpilot-playlink-border-radius, margin(0.5));
126
- color: var(--playpilot-playlink-text-color, var(--playpilot-text-color)) !important;
127
- font-weight: var(--playpilot-playlink-font-weight, inherit);
128
- font-style: var(--playpilot-playlink-font-style, normal) !important;
129
- text-decoration: none !important;
130
- white-space: nowrap;
131
- font-size: var(--playpilot-playlinks-font-size, margin(0.75));
132
- line-height: 1;
133
-
134
- &:hover,
135
- &:active {
136
- filter: var(--playpilot-playlink-hover-filter, brightness(1.1));
137
- background: var(--playpilot-playlink-hover-background, var(--playpilot-playlink-background, var(--playpilot-lighter))) !important;
138
- text-decoration: none !important;
139
- }
140
-
141
- &.highlighted {
142
- grid-column: span 2;
143
-
144
- .list & {
145
- grid-column: 1;
146
- }
147
-
148
- &::before {
149
- content: "";
150
- z-index: 1;
151
- display: block;
152
- position: absolute;
153
- top: 0;
154
- right: 0;
155
- bottom: 0;
156
- left: 0;
157
- border-radius: inherit;
158
- background: linear-gradient(to left, currentColor 20%, transparent 50%, currentColor 80%);
159
- background-size: 200% 100%;
160
- background-position: -100% 0;
161
- opacity: 0.35;
162
- pointer-events: none;
163
-
164
- @media (prefers-reduced-motion: no-preference) {
165
- animation: sheen 4000ms ease-in-out infinite;
166
- }
167
- }
168
93
 
169
- &::after {
170
- content: "";
171
- z-index: 2;
172
- display: block;
173
- position: absolute;
174
- top: 2px;
175
- right: 2px;
176
- bottom: 2px;
177
- left: 2px;
178
- border-radius: inherit;
179
- background: var(--playpilot-playlink-background, var(--playpilot-lighter));
94
+ :global(.playlink.highlighted) {
95
+ grid-column: span 1;
180
96
  }
181
97
  }
182
-
183
- img {
184
- grid-area: image;
185
- margin: 0;
186
- }
187
- }
188
-
189
- .playlink-content {
190
- position: relative;
191
- z-index: 5;
192
- display: grid;
193
- grid-template-areas: "image name action" "image category action";
194
- grid-template-columns: $image-size auto margin(4);
195
- align-items: center;
196
- gap: 0 margin(0.75);
197
- padding: margin(0.75);
198
-
199
- .highlighted & {
200
- grid-template-areas: "image name action" "image category action" "image cta action";
201
- gap: margin(0.25) margin(0.75);
202
- }
203
- }
204
-
205
- .name {
206
- grid-area: name;
207
- width: 100%;
208
- overflow: hidden;
209
- font-weight: var(--playpilot-playlink-font-weight, 500);
210
- font-family: var(--playpilot-playlink-font-family, inherit);
211
- text-overflow: ellipsis;
212
- white-space: nowrap;
213
- }
214
-
215
- .category {
216
- grid-area: category;
217
- width: 100%;
218
- font-size: var(--playpilot-playlinks-category-font-size, margin(0.625));
219
- color: var(--playpilot-playlink-category-text-color, var(--playpilot-text-color-alt));
220
- font-weight: var(--playpilot-playlink-category-font-weight, inherit);
221
- font-family: var(--playpilot-playlink-category-font-family, inherit);
222
- }
223
-
224
- .cta {
225
- grid-area: cta;
226
- width: 100%;
227
- font-size: var(--playpilot-playlinks-cta-font-size, var(--playpilot-playlinks-category-font-size, margin(0.625)));
228
- color: var(--playpilot-playlink-cta-text-color, var(--playpilot-playlink-category-text-color, var(--playpilot-text-color)));
229
- font-weight: var(--playpilot-playlink-cta-font-weight, var(--playpilot-playlink-category-font-weight, inherit));
230
- font-family: var(--playpilot-playlink-cta-font-family, var(--playpilot-playlink-category-font-family, inherit));
231
98
  }
232
99
 
233
100
  .disclaimer {
@@ -249,17 +116,16 @@
249
116
  }
250
117
 
251
118
  .empty {
119
+ grid-column: span 2;
120
+ padding: margin(0.75);
121
+ background: var(--playpilot-playlink-background, var(--playpilot-lighter));
122
+ box-shadow: var(--playpilot-playlink-shadow, var(--playpilot-shadow));
123
+ border-radius: var(--playpilot-playlink-border-radius, margin(0.5));
252
124
  white-space: initial;
253
125
  line-height: 1.35;
254
- }
255
126
 
256
- .action {
257
- grid-area: action;
258
- margin-left: auto;
259
- padding: margin(0.5);
260
- font-weight: var(--playpilot-playlinks-action-font-weight, 500);
261
- color: var(--playpilot-playlinks-action-text-color, var(--playpilot-text-color));
262
- border: var(--playpilot-playlinks-action-border, 1px solid currentColor);
263
- border-radius: var(--playpilot-playlinks-action-border-radius, margin(2));
127
+ .list & {
128
+ grid-column: 1;
129
+ }
264
130
  }
265
131
  </style>
@@ -1,17 +1,22 @@
1
1
  <script lang="ts">
2
+ import { SplitTest } from '$lib/enums/SplitTest'
3
+ import { isInSplitTestVariant } from '$lib/splitTest'
2
4
  import { onMount, setContext, tick, type Snippet } from 'svelte'
3
5
  import { prefersReducedMotion } from 'svelte/motion'
4
6
  import { fly } from 'svelte/transition'
5
7
 
6
8
  interface Props {
7
9
  children: Snippet
10
+ bubble?: Snippet | null
8
11
  maxHeight?: number
9
12
  }
10
13
 
11
- let { children, maxHeight = $bindable() }: Props = $props()
14
+ let { children, bubble, maxHeight = $bindable() }: Props = $props()
12
15
 
13
16
  setContext('scope', 'popover')
14
17
 
18
+ const inlineBubble = isInSplitTestVariant(SplitTest.TopScrollFormat, 1)
19
+
15
20
  let element: HTMLElement | null = $state(null)
16
21
  let flip = $state(false)
17
22
 
@@ -59,15 +64,24 @@
59
64
  }
60
65
  </script>
61
66
 
62
- <div class="popover" class:flip bind:this={element} style:--max-height={maxHeight ? maxHeight + 'px' : null} tabindex="-1" aria-hidden="true">
67
+ <div class="popover" class:flip class:inline={inlineBubble} class:has-bubble={!!bubble && inlineBubble} bind:this={element} style:--max-height={maxHeight ? maxHeight + 'px' : null} tabindex="-1" aria-hidden="true">
63
68
  <div class="dialog" transition:fly|global={{ duration: prefersReducedMotion.current ? 0 : 100, y: 10 }} data-view-transition-old>
64
69
  {@render children()}
65
70
  </div>
71
+
72
+ {#if bubble}
73
+ <div transition:fly|global={{ duration: prefersReducedMotion.current ? 0 : 100, y: 10 }}>
74
+ {@render bubble()}
75
+ </div>
76
+ {/if}
66
77
  </div>
67
78
 
68
79
  <style lang="scss">
69
80
  .popover {
70
81
  --offset: #{margin(0.5)};
82
+ display: flex;
83
+ flex-direction: column-reverse;
84
+ gap: margin(0.5);
71
85
  position: absolute;
72
86
  top: calc((var(--offset) - 1px) * -1); /* Add 1 pixel to account for rounding errors */
73
87
  left: 0;
@@ -78,9 +92,18 @@
78
92
  z-index: 2147483647; // As high as she goes
79
93
 
80
94
  &.flip {
95
+ flex-direction: column;
81
96
  top: auto;
82
97
  bottom: calc(var(--offset) + 1px); /* Add 1 pixel to account for rounding errors */
83
98
  transform: translateY(calc(100% + var(--offset)));
99
+
100
+ &.inline {
101
+ flex-direction: column-reverse;
102
+ }
103
+ }
104
+
105
+ &.inline {
106
+ gap: 0;
84
107
  }
85
108
  }
86
109
 
@@ -96,5 +119,10 @@
96
119
  overflow-y: overlay;
97
120
  overflow-x: hidden;
98
121
  view-transition-name: playpilot-title-content;
122
+
123
+ .has-bubble & {
124
+ border-top-left-radius: 0;
125
+ border-top-right-radius: 0;
126
+ }
99
127
  }
100
128
  </style>
@@ -7,28 +7,21 @@
7
7
  import type { TitleData } from '$lib/types/title'
8
8
  import { heading } from '$lib/actions/heading'
9
9
  import { removeImageUrlPrefix } from '$lib/image'
10
- import type { Snippet } from 'svelte'
11
- import FloatingPlaylink from './Ads/FloatingPlaylink.svelte'
12
- import VideoBackground from './Ads/VideoBackground.svelte'
13
10
 
14
11
  interface Props {
15
12
  title: TitleData
16
13
  small?: boolean
17
14
  compact?: boolean
18
- useVideoBackground?: boolean
19
- backgroundFormat?: '' | 'landscape' | 'portrait'
20
- children?: Snippet
21
- background?: Snippet | null
22
15
  }
23
16
 
24
- const { title, small = false, compact = false, useVideoBackground = false, backgroundFormat = '' }: Props = $props()
17
+ const { title, small = false, compact = false }: Props = $props()
25
18
 
26
19
  let posterLoaded = $state(false)
27
20
  let backgroundLoaded = $state(false)
28
21
  let useBackgroundFallback = $state(false)
29
22
  </script>
30
23
 
31
- <div class="content {backgroundFormat}" class:small class:compact data-playpilot-link-injections-title>
24
+ <div class="content" class:small class:compact data-playpilot-link-injections-title>
32
25
  <div class="header">
33
26
  {#if !compact}
34
27
  <div class="top">
@@ -67,26 +60,18 @@
67
60
  <Description text={title.description} blurb={title.blurb} />
68
61
  {/if}
69
62
  </div>
70
-
71
- {#if useVideoBackground}
72
- <FloatingPlaylink playlink={title.providers.find(p => p.name === 'Apple TV+')!} />
73
- {/if}
74
63
  </div>
75
64
 
76
- <div class="background {backgroundFormat}" class:faded={compact}>
77
- {#if useVideoBackground}
78
- <VideoBackground />
79
- {:else}
80
- {#key useBackgroundFallback}
81
- <img
82
- src={removeImageUrlPrefix(useBackgroundFallback ? title.standing_poster : title.medium_poster)}
83
- alt=""
84
- class:loaded={backgroundLoaded}
85
- class:blur={useBackgroundFallback}
86
- onload={() => backgroundLoaded = true}
87
- onerror={() => useBackgroundFallback = true} />
88
- {/key}
89
- {/if}
65
+ <div class="background" class:faded={compact}>
66
+ {#key useBackgroundFallback}
67
+ <img
68
+ src={removeImageUrlPrefix(useBackgroundFallback ? title.standing_poster : title.medium_poster)}
69
+ alt=""
70
+ class:loaded={backgroundLoaded}
71
+ class:blur={useBackgroundFallback}
72
+ onload={() => backgroundLoaded = true}
73
+ onerror={() => useBackgroundFallback = true} />
74
+ {/key}
90
75
  </div>
91
76
 
92
77
  <style lang="scss">
@@ -142,14 +127,6 @@
142
127
  .compact & {
143
128
  padding: 0 0 margin(1);
144
129
  }
145
-
146
- .landscape & {
147
- padding-top: margin(6);
148
- }
149
-
150
- .portrait & {
151
- padding-top: margin(12);
152
- }
153
130
  }
154
131
 
155
132
  .top {
@@ -206,6 +183,7 @@
206
183
  left: 0;
207
184
  width: 100%;
208
185
  height: margin(12);
186
+ border-radius: var(--playpilot-detail-background-border-radius);
209
187
  overflow: hidden;
210
188
  background: var(--playpilot-detail-background, var(--playpilot-lighter));
211
189
  z-index: 0;
@@ -222,14 +200,6 @@
222
200
  z-index: 1;
223
201
  }
224
202
 
225
- &.landscape {
226
- height: margin(18);
227
- }
228
-
229
- &.portrait {
230
- height: margin(24);
231
- }
232
-
233
203
  img {
234
204
  width: 100%;
235
205
  height: 100%;
@@ -2,10 +2,14 @@
2
2
  import { TrackingEvent } from '$lib/enums/TrackingEvent'
3
3
  import { track } from '$lib/tracking'
4
4
  import type { TitleData } from '$lib/types/title'
5
- import { trackSplitTestAction } from '$lib/splitTest'
6
5
  import { onMount } from 'svelte'
7
6
  import Modal from './Modal.svelte'
8
7
  import Title from './Title.svelte'
8
+ import TopScroll from './Ads/TopScroll.svelte'
9
+ import Display from './Ads/Display.svelte'
10
+ import { getFirstAdOfType } from '$lib/ads'
11
+ import { isInSplitTestVariant } from '$lib/splitTest'
12
+ import { SplitTest } from '$lib/enums/SplitTest'
9
13
 
10
14
  interface Props {
11
15
  onclose: () => void,
@@ -14,20 +18,18 @@
14
18
 
15
19
  const { onclose, title }: Props = $props()
16
20
 
21
+ const topScrollAd = getFirstAdOfType('top_scroll')
22
+ const displayAd = getFirstAdOfType('card')
23
+
24
+ const isInDisplayAdFormatSplitTest = isInSplitTestVariant(SplitTest.DisplayAdPosition, 1) || isInSplitTestVariant(SplitTest.DisplayAdPosition, 2)
25
+
17
26
  track(TrackingEvent.TitleModalView, title)
18
27
 
19
28
  let hasTrackedScrolling = false
20
29
 
21
- // This is all placeholder stuff that hardcodes a video ad for Apple TV+.
22
- const useVideoBackground = title.providers.some(p => p.name === 'Apple TV+')
23
- const backgroundFormat = useVideoBackground ? (Math.random() > 0.5 ? 'landscape' : 'portrait') : ''
24
- const mobileSize = useVideoBackground ? (backgroundFormat === 'landscape' ? 'tall' : 'taller') : ''
25
-
26
30
  onMount(() => {
27
31
  const openTimestamp = Date.now()
28
32
 
29
- trackSplitTestAction('initial_test', 'modal')
30
-
31
33
  return () => track(TrackingEvent.TitleModalClose, title, { time_spent: Date.now() - openTimestamp })
32
34
  })
33
35
 
@@ -39,6 +41,18 @@
39
41
  }
40
42
  </script>
41
43
 
42
- <Modal {onclose} {onscroll} {mobileSize}>
43
- <Title {title} {useVideoBackground} {backgroundFormat} />
44
+ {#snippet bubble()}
45
+ {#if topScrollAd}
46
+ <TopScroll campaign={topScrollAd} />
47
+ {/if}
48
+ {/snippet}
49
+
50
+ {#snippet prepend()}
51
+ {#if displayAd}
52
+ <Display campaign={displayAd} />
53
+ {/if}
54
+ {/snippet}
55
+
56
+ <Modal {onclose} {onscroll} prepend={isInDisplayAdFormatSplitTest ? prepend : null} bubble={topScrollAd ? bubble : null}>
57
+ <Title {title} />
44
58
  </Modal>