@playpilot/tpi 8.22.0-beta.2 → 8.23.0-beta.1
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.
- package/dist/editorial.mount.js +11 -11
- package/dist/link-injections.js +2 -2
- package/dist/mount.js +9 -9
- package/package.json +2 -2
- package/src/lib/api/api.ts +2 -3
- package/src/lib/modal.ts +3 -1
- package/src/lib/types/global.d.ts +2 -0
- package/src/lib/types/language.d.ts +1 -1
- package/src/routes/components/Explore/Routes/ExploreHome.svelte +36 -34
- package/src/routes/components/Explore/Routes/ExploreModal.svelte +3 -1
- package/src/routes/components/Modals/ModalCloseOverlay.svelte +65 -0
- package/src/routes/components/Modals/RailModal.svelte +8 -51
- package/src/routes/components/Modals/TitlesReelModal.svelte +188 -0
- package/src/routes/components/Playlinks/Playlinks.svelte +2 -38
- package/src/routes/components/Playlinks/PlaylinksDisclaimer.svelte +51 -0
- package/src/routes/components/Title.svelte +4 -142
- package/src/routes/components/TitleBackground.svelte +110 -0
- package/src/routes/components/TitleInfo.svelte +69 -0
- package/src/routes/components/YouTubeEmbed.svelte +13 -2
- package/src/routes/components/YouTubeEmbedBackground.svelte +3 -2
- package/src/routes/elements/+page.svelte +10 -0
- package/src/tests/routes/components/Explore/Routes/ExploreHome.test.js +3 -14
- package/src/tests/routes/components/Playlinks/Playlinks.test.js +0 -37
- package/src/tests/routes/components/Playlinks/PlaylinksDisclaimer.test.js +49 -0
- package/src/tests/routes/components/YouTubeEmbed.test.js +6 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@playpilot/tpi",
|
|
3
|
-
"version": "8.
|
|
3
|
+
"version": "8.23.0-beta.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"dev": "vite dev",
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
"svelte": "5.55.7",
|
|
39
39
|
"svelte-check": "^4.6.0",
|
|
40
40
|
"svelte-preprocess": "^6.0.5",
|
|
41
|
-
"svelte-tiny-slider": "^2.
|
|
41
|
+
"svelte-tiny-slider": "^2.8.0",
|
|
42
42
|
"typescript": "^6.0.3",
|
|
43
43
|
"typescript-eslint": "^8.62.0",
|
|
44
44
|
"vite": "^5.4.21",
|
package/src/lib/api/api.ts
CHANGED
|
@@ -7,10 +7,9 @@ type Options = {
|
|
|
7
7
|
headers?: Record<string, string>
|
|
8
8
|
method?: 'POST' | 'GET'
|
|
9
9
|
body?: null | Record<string, any>
|
|
10
|
-
baseUrl?: string
|
|
11
10
|
}
|
|
12
11
|
|
|
13
|
-
export async function api<T>(path: string, { headers = {}, method = 'GET', body = null
|
|
12
|
+
export async function api<T>(path: string, { headers = {}, method = 'GET', body = null }: Options = {}): Promise<T> {
|
|
14
13
|
// For GET methods we store all results in a cache object. This cache is super simple and has no invalidation at all.
|
|
15
14
|
// Once something is stored it's there forever since the page is only short lived.
|
|
16
15
|
// We never use the cache for editiorial mode since in the editor requests do change.
|
|
@@ -19,7 +18,7 @@ export async function api<T>(path: string, { headers = {}, method = 'GET', body
|
|
|
19
18
|
|
|
20
19
|
const baseHeaders: Record<string, string> = method === 'GET' ? {} : { 'Content-Type': 'application/json' }
|
|
21
20
|
|
|
22
|
-
const response = await fetch(
|
|
21
|
+
const response = await fetch(apiBaseUrl + path, {
|
|
23
22
|
method,
|
|
24
23
|
headers: new Headers({ ...baseHeaders, ...headers }),
|
|
25
24
|
// eslint-disable-next-line no-undef
|
package/src/lib/modal.ts
CHANGED
|
@@ -11,11 +11,12 @@ import { playFallbackViewTransition } from './viewTransition'
|
|
|
11
11
|
import { destroyLinkPopover } from './popover'
|
|
12
12
|
import { prefersReducedMotion } from 'svelte/motion'
|
|
13
13
|
import TitlesRailModal from '../routes/components/Modals/TitlesRailModal.svelte'
|
|
14
|
+
import TitlesReelModal from '../routes/components/Modals/TitlesReelModal.svelte'
|
|
14
15
|
|
|
15
16
|
type Modal = {
|
|
16
17
|
injection?: LinkInjection | null
|
|
17
18
|
component?: object
|
|
18
|
-
type: 'title' | 'participant' | 'titles-rail'
|
|
19
|
+
type: 'title' | 'participant' | 'titles-rail' | 'titles-reel'
|
|
19
20
|
data: TitleData | TitleData[] | ParticipantData | null
|
|
20
21
|
scrollPosition?: number
|
|
21
22
|
}
|
|
@@ -52,6 +53,7 @@ function getModalComponentByType(
|
|
|
52
53
|
{ type: Modal['type'], target: Element, data: Modal['data'], props?: Record<string, any> }): ReturnType<typeof mount> {
|
|
53
54
|
if (type === 'participant') return mount(ParticipantModal, { target, props: { participant: data as ParticipantData, ...props } })
|
|
54
55
|
if (type === 'titles-rail') return mount(TitlesRailModal, { target, props: { titles: data as TitleData[], ...props } })
|
|
56
|
+
if (type === 'titles-reel') return mount(TitlesReelModal, { target, props: { titles: data as TitleData[], ...props } })
|
|
55
57
|
return mount(TitleModal, { target, props: { title: data as TitleData, ...props } })
|
|
56
58
|
}
|
|
57
59
|
|
|
@@ -18,6 +18,8 @@ declare global {
|
|
|
18
18
|
declare module 'svelte/elements' {
|
|
19
19
|
interface SvelteWindowAttributes {
|
|
20
20
|
onupdateconsent?: (event: CustomEvent<{}>) => void
|
|
21
|
+
onplayembed?: (event: CustomEvent<{}>) => void
|
|
22
|
+
onpauseembed?: (event: CustomEvent<{}>) => void
|
|
21
23
|
}
|
|
22
24
|
}
|
|
23
25
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export type LanguageCode = 'en-US' | 'sv-SE' | '
|
|
1
|
+
export type LanguageCode = 'en-US' | 'sv-SE' | 'dk-DK'
|
|
@@ -2,23 +2,37 @@
|
|
|
2
2
|
import type { TitleData } from '$lib/types/title'
|
|
3
3
|
import { fetchTitles } from '$lib/api/titles'
|
|
4
4
|
import { t } from '$lib/localization'
|
|
5
|
+
import { Sorting } from '$lib/enums/Sorting'
|
|
5
6
|
import { track } from '$lib/tracking'
|
|
6
7
|
import { TrackingEvent } from '$lib/enums/TrackingEvent'
|
|
7
8
|
import TitlesRail from '../../Rails/TitlesRail.svelte'
|
|
8
9
|
import { mobileBreakpoint } from '$lib/constants'
|
|
9
|
-
import { getLanguage } from '$lib/language'
|
|
10
|
-
import type { LanguageCode } from '$lib/types/language'
|
|
11
|
-
import { api } from '$lib/api/api'
|
|
12
10
|
|
|
13
11
|
let expandedTitle: TitleData | null = $state(null)
|
|
14
12
|
let expandedRailKey: string | null = $state(null)
|
|
15
13
|
let windowWidth: number = $state(window.innerWidth)
|
|
16
14
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
}
|
|
15
|
+
const rails: { heading: string, params: Record<string, any>, properties: Record<string, any> }[] = [{
|
|
16
|
+
heading: 'List: Upcoming',
|
|
17
|
+
params: { from_playlist_sid: 'li42wf', region: null, no_region_filter: true },
|
|
18
|
+
properties: { expandable: true },
|
|
19
|
+
}, {
|
|
20
|
+
heading: 'List: Trending',
|
|
21
|
+
params: { ordering: Sorting.Popular },
|
|
22
|
+
properties: { expandable: true },
|
|
23
|
+
}, {
|
|
24
|
+
heading: 'List: New',
|
|
25
|
+
params: { from_playlist_sid: 'li42WR', include_playable_types: 'SVOD,FREE' },
|
|
26
|
+
properties: { aside: true, size: 'flexible' },
|
|
27
|
+
}, {
|
|
28
|
+
heading: 'List: Cinema',
|
|
29
|
+
params: { from_playlist_sid: 'li42WS', region: null, no_region_filter: true },
|
|
30
|
+
properties: { expandable: true },
|
|
31
|
+
}, {
|
|
32
|
+
heading: 'List: Demand',
|
|
33
|
+
params: { from_playlist_sid: 'licBS' },
|
|
34
|
+
properties: { aside: true, size: 'flexible' },
|
|
35
|
+
}]
|
|
22
36
|
|
|
23
37
|
async function getListTitles(params: Record<string, any> = {}): Promise<TitleData[]> {
|
|
24
38
|
try {
|
|
@@ -35,32 +49,20 @@
|
|
|
35
49
|
|
|
36
50
|
<div data-testid="explore-home"></div>
|
|
37
51
|
|
|
38
|
-
{#
|
|
39
|
-
|
|
40
|
-
<
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
{
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
size={windowWidth >= mobileBreakpoint ? (properties.size || 'flexible') : 'huge'}
|
|
53
|
-
minimumLength={7}
|
|
54
|
-
{...properties}
|
|
55
|
-
bind:expandedTitle
|
|
56
|
-
bind:expandedRailKey
|
|
57
|
-
onclick={() => track(TrackingEvent.ExploreTitleRailModalView, null, { rail: headings })}
|
|
58
|
-
onexpand={(title) => track(TrackingEvent.ExploreTitleRailExpandClick, title, { rail: headings })} />
|
|
59
|
-
</div>
|
|
60
|
-
{/each}
|
|
61
|
-
{:catch}
|
|
62
|
-
{t('An Error Occurred')}
|
|
63
|
-
{/await}
|
|
52
|
+
{#each rails as { heading, params, properties }}
|
|
53
|
+
<div class="rail">
|
|
54
|
+
<TitlesRail
|
|
55
|
+
heading={t(heading)}
|
|
56
|
+
titles={getListTitles(params)}
|
|
57
|
+
size={windowWidth >= mobileBreakpoint ? 'flexible' : 'huge'}
|
|
58
|
+
minimumLength={7}
|
|
59
|
+
{...properties}
|
|
60
|
+
bind:expandedTitle
|
|
61
|
+
bind:expandedRailKey
|
|
62
|
+
onclick={() => track(TrackingEvent.ExploreTitleRailModalView, null, { rail: heading })}
|
|
63
|
+
onexpand={(title) => track(TrackingEvent.ExploreTitleRailExpandClick, title, { rail: heading })} />
|
|
64
|
+
</div>
|
|
65
|
+
{/each}
|
|
64
66
|
|
|
65
67
|
<style lang="scss">
|
|
66
68
|
.rail {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import { fetchSimilarTitles, fetchTitleBySid } from '$lib/api/titles'
|
|
3
|
+
import { mobileBreakpoint } from '$lib/constants'
|
|
3
4
|
import { openModal } from '$lib/modal'
|
|
4
5
|
import type { TitleData } from '$lib/types/title'
|
|
5
6
|
|
|
@@ -14,6 +15,7 @@
|
|
|
14
15
|
async function openModalViaRoute(): Promise<void> {
|
|
15
16
|
const currentUrl = new URL(document.location.toString())
|
|
16
17
|
const sid = currentUrl.searchParams.get('sid')
|
|
18
|
+
const type = window.innerWidth > mobileBreakpoint ? 'titles-rail' : 'titles-reel'
|
|
17
19
|
|
|
18
20
|
if (!sid) return
|
|
19
21
|
|
|
@@ -24,7 +26,7 @@
|
|
|
24
26
|
).map(promise => (promise.status === 'fulfilled' ? promise.value : null))
|
|
25
27
|
|
|
26
28
|
openModal({
|
|
27
|
-
type
|
|
29
|
+
type,
|
|
28
30
|
data: [(title as TitleData), ...(railTitles as TitleData[])],
|
|
29
31
|
props: {
|
|
30
32
|
onclose: () => navigate('home'),
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { scale } from 'svelte/transition'
|
|
3
|
+
import { destroyAllModals } from '$lib/modal'
|
|
4
|
+
import RoundButton from '../RoundButton.svelte'
|
|
5
|
+
import IconClose from '../Icons/IconClose.svelte'
|
|
6
|
+
import IconArrow from '../Icons/IconArrow.svelte'
|
|
7
|
+
import Button from '../Button.svelte'
|
|
8
|
+
|
|
9
|
+
interface Props {
|
|
10
|
+
label?: string
|
|
11
|
+
onclose?: () => void
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const { label = '', onclose = () => null }: Props = $props()
|
|
15
|
+
</script>
|
|
16
|
+
|
|
17
|
+
{#if label}
|
|
18
|
+
<div class="back">
|
|
19
|
+
<Button variant="link" onclick={() => { onclose(); destroyAllModals() }}>
|
|
20
|
+
<IconArrow direction="left" />
|
|
21
|
+
{label}
|
|
22
|
+
</Button>
|
|
23
|
+
</div>
|
|
24
|
+
{/if}
|
|
25
|
+
|
|
26
|
+
<div class="close" transition:scale|global>
|
|
27
|
+
<RoundButton size="42px" onclick={() => { onclose(); destroyAllModals() }} aria-label="Close">
|
|
28
|
+
<IconClose size={24} />
|
|
29
|
+
</RoundButton>
|
|
30
|
+
</div>
|
|
31
|
+
|
|
32
|
+
<style lang="scss">
|
|
33
|
+
.back {
|
|
34
|
+
position: absolute;
|
|
35
|
+
top: margin(1.5);
|
|
36
|
+
left: var(--modal-close-overlay-left, margin(1));
|
|
37
|
+
font-family: theme(font-family);
|
|
38
|
+
transition: transform 50ms;
|
|
39
|
+
|
|
40
|
+
@include desktop {
|
|
41
|
+
left: margin(1);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
&:hover {
|
|
45
|
+
transform: scale(1.025);
|
|
46
|
+
|
|
47
|
+
:global(button) {
|
|
48
|
+
text-decoration-thickness: 2px;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
:global(button) {
|
|
53
|
+
text-decoration: underline;
|
|
54
|
+
color: white;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
.close {
|
|
59
|
+
--playpilot-button-background: transparent;
|
|
60
|
+
--playpilot-button-text-color: white;
|
|
61
|
+
position: absolute;
|
|
62
|
+
top: margin(1);
|
|
63
|
+
right: margin(1);
|
|
64
|
+
}
|
|
65
|
+
</style>
|
|
@@ -1,13 +1,10 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import { onMount, type Snippet } from 'svelte'
|
|
3
3
|
import TinySlider from 'svelte-tiny-slider'
|
|
4
|
-
import { fade
|
|
5
|
-
import { destroyAllModals } from '$lib/modal'
|
|
4
|
+
import { fade } from 'svelte/transition'
|
|
6
5
|
import Modal from './Modal.svelte'
|
|
7
|
-
import RoundButton from '../RoundButton.svelte'
|
|
8
|
-
import IconClose from '../Icons/IconClose.svelte'
|
|
9
6
|
import IconArrow from '../Icons/IconArrow.svelte'
|
|
10
|
-
import
|
|
7
|
+
import ModalCloseOverlay from './ModalCloseOverlay.svelte'
|
|
11
8
|
|
|
12
9
|
interface Props {
|
|
13
10
|
items: Record<string, any>[]
|
|
@@ -76,19 +73,8 @@
|
|
|
76
73
|
</TinySlider>
|
|
77
74
|
</div>
|
|
78
75
|
|
|
79
|
-
|
|
80
|
-
<
|
|
81
|
-
<Button variant="link" onclick={() => { onclose(); destroyAllModals() }}>
|
|
82
|
-
<IconArrow direction="left" />
|
|
83
|
-
Streaming guide
|
|
84
|
-
</Button>
|
|
85
|
-
</div>
|
|
86
|
-
{/if}
|
|
87
|
-
|
|
88
|
-
<div class="close" transition:scale|global>
|
|
89
|
-
<RoundButton size="42px" onclick={() => { onclose(); destroyAllModals() }} aria-label="Close">
|
|
90
|
-
<IconClose size={24} />
|
|
91
|
-
</RoundButton>
|
|
76
|
+
<div class="close">
|
|
77
|
+
<ModalCloseOverlay {onclose} label={window.location.href.includes('route=modal') ? 'Streaming guide' : ''} />
|
|
92
78
|
</div>
|
|
93
79
|
{/snippet}
|
|
94
80
|
</Modal>
|
|
@@ -178,39 +164,6 @@
|
|
|
178
164
|
}
|
|
179
165
|
}
|
|
180
166
|
|
|
181
|
-
.close {
|
|
182
|
-
--playpilot-button-background: transparent;
|
|
183
|
-
--playpilot-button-text-color: white;
|
|
184
|
-
position: absolute;
|
|
185
|
-
top: margin(1);
|
|
186
|
-
right: margin(1);
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
.back {
|
|
190
|
-
position: absolute;
|
|
191
|
-
top: margin(1.5);
|
|
192
|
-
left: calc((100vw - $size) / 2);
|
|
193
|
-
font-family: theme(font-family);
|
|
194
|
-
transition: transform 50ms;
|
|
195
|
-
|
|
196
|
-
@include desktop {
|
|
197
|
-
left: margin(1);
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
&:hover {
|
|
201
|
-
transform: scale(1.025);
|
|
202
|
-
|
|
203
|
-
:global(button) {
|
|
204
|
-
text-decoration-thickness: 2px;
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
:global(button) {
|
|
209
|
-
text-decoration: underline;
|
|
210
|
-
color: white;
|
|
211
|
-
}
|
|
212
|
-
}
|
|
213
|
-
|
|
214
167
|
.arrow {
|
|
215
168
|
--offset: #{margin(-1.25)};
|
|
216
169
|
--scale: 1;
|
|
@@ -254,4 +207,8 @@
|
|
|
254
207
|
right: var(--offset);
|
|
255
208
|
}
|
|
256
209
|
}
|
|
210
|
+
|
|
211
|
+
.close {
|
|
212
|
+
--modal-close-overlay-left: calc((100vw - $size) / 2)
|
|
213
|
+
}
|
|
257
214
|
</style>
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { TitleData } from '$lib/types/title'
|
|
3
|
+
import { onMount } from 'svelte'
|
|
4
|
+
import { heading } from '$lib/actions/heading'
|
|
5
|
+
import { t } from '$lib/localization'
|
|
6
|
+
import TinySlider from 'svelte-tiny-slider'
|
|
7
|
+
import Modal from './Modal.svelte'
|
|
8
|
+
import TitleBackground from '../TitleBackground.svelte'
|
|
9
|
+
import Description from '../Description.svelte'
|
|
10
|
+
import TitleInfo from '../TitleInfo.svelte'
|
|
11
|
+
import PlaylinksCompact from '../Playlinks/PlaylinksCompact.svelte'
|
|
12
|
+
import PlaylinksDisclaimer from '../Playlinks/PlaylinksDisclaimer.svelte'
|
|
13
|
+
import ModalCloseOverlay from './ModalCloseOverlay.svelte'
|
|
14
|
+
|
|
15
|
+
interface Props {
|
|
16
|
+
titles: TitleData[],
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const { titles }: Props = $props()
|
|
20
|
+
|
|
21
|
+
let interval: ReturnType<typeof setInterval> | null = null
|
|
22
|
+
|
|
23
|
+
onMount(() => {
|
|
24
|
+
pauseAndPlay(0)
|
|
25
|
+
|
|
26
|
+
return () => {
|
|
27
|
+
if (interval) clearInterval(interval)
|
|
28
|
+
}
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
function pauseAndPlay(index: number): void {
|
|
32
|
+
;[titles[index - 1], titles[index + 1]].forEach(title => {
|
|
33
|
+
window.dispatchEvent(new CustomEvent('pauseembed', { detail: title?.embeddable_url } ))
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
const currentTitle = titles[index]
|
|
37
|
+
|
|
38
|
+
if (interval) clearInterval(interval)
|
|
39
|
+
|
|
40
|
+
// This is on an interval because we don't know when the video loads, so we just continuously play and hope for the best.
|
|
41
|
+
interval = setInterval(() => {
|
|
42
|
+
window.dispatchEvent(new CustomEvent('playembed', { detail: currentTitle?.embeddable_url } ))
|
|
43
|
+
}, 500)
|
|
44
|
+
}
|
|
45
|
+
</script>
|
|
46
|
+
|
|
47
|
+
<div class="reel">
|
|
48
|
+
<Modal>
|
|
49
|
+
{#snippet dialog()}
|
|
50
|
+
<div class="slider">
|
|
51
|
+
<TinySlider vertical threshold={100} moveThreshold={20} onchange={pauseAndPlay}>
|
|
52
|
+
{#snippet children({ currentIndex, setIndex })}
|
|
53
|
+
{#each titles as title, index}
|
|
54
|
+
<!-- svelte-ignore a11y_click_events_have_key_events -->
|
|
55
|
+
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
|
56
|
+
<div class="item" onclick={() => setIndex(index)}>
|
|
57
|
+
{#if Math.abs(index - currentIndex) <= 2 || currentIndex === index}
|
|
58
|
+
<div class="content">
|
|
59
|
+
<div class="header">
|
|
60
|
+
<div class="heading" use:heading={2}>{title.title}</div>
|
|
61
|
+
|
|
62
|
+
<TitleInfo {title} />
|
|
63
|
+
</div>
|
|
64
|
+
|
|
65
|
+
<div class="details">
|
|
66
|
+
<Description text={title.description!} blurb={title.blurb} limit={80} />
|
|
67
|
+
|
|
68
|
+
<div class="playlinks-heading" use:heading={3}>{t('Where To Stream Online')}</div>
|
|
69
|
+
|
|
70
|
+
<PlaylinksCompact playlinks={title.providers} size={24} />
|
|
71
|
+
|
|
72
|
+
<PlaylinksDisclaimer playlinks={title.providers} />
|
|
73
|
+
</div>
|
|
74
|
+
</div>
|
|
75
|
+
|
|
76
|
+
<TitleBackground {title} useVideoBackground autoplay={false} inline />
|
|
77
|
+
|
|
78
|
+
<div class="gradient"></div>
|
|
79
|
+
{/if}
|
|
80
|
+
</div>
|
|
81
|
+
{/each}
|
|
82
|
+
{/snippet}
|
|
83
|
+
</TinySlider>
|
|
84
|
+
</div>
|
|
85
|
+
|
|
86
|
+
<ModalCloseOverlay label="Streaming guide" />
|
|
87
|
+
{/snippet}
|
|
88
|
+
</Modal>
|
|
89
|
+
</div>
|
|
90
|
+
|
|
91
|
+
<style lang="scss">
|
|
92
|
+
$modal-height: 90dvh;
|
|
93
|
+
$item-height: calc(90dvh - margin(7));
|
|
94
|
+
|
|
95
|
+
.reel {
|
|
96
|
+
--playpilot-detail-background-height: #{$item-height};
|
|
97
|
+
--playpilot-detail-background-border-radius: 0;
|
|
98
|
+
--playpilot-detail-background-width: 400%;
|
|
99
|
+
--background-mask-image: unset;
|
|
100
|
+
|
|
101
|
+
:global(*) {
|
|
102
|
+
box-sizing: border-box;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
:global(iframe) {
|
|
106
|
+
pointer-events: none;
|
|
107
|
+
z-index: 0;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
:global(.modal) {
|
|
111
|
+
padding: 0;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
.slider {
|
|
116
|
+
margin-top: 10dvh;
|
|
117
|
+
height: $modal-height;
|
|
118
|
+
width: 100%;
|
|
119
|
+
border-radius: theme(border-radius-large) theme(border-radius-large) 0 0;
|
|
120
|
+
overflow: hidden;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
.item {
|
|
124
|
+
position: relative;
|
|
125
|
+
width: 100dvw;
|
|
126
|
+
height: $item-height;
|
|
127
|
+
background: theme(detail-background, light);
|
|
128
|
+
overflow: hidden;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
.content {
|
|
132
|
+
position: relative;
|
|
133
|
+
display: flex;
|
|
134
|
+
flex-direction: column;
|
|
135
|
+
justify-content: space-between;
|
|
136
|
+
height: 100%;
|
|
137
|
+
color: theme(detail-text-color, text-color);
|
|
138
|
+
font-family: theme(detail-font-family, font-family);
|
|
139
|
+
font-weight: theme(detail-font-weight, normal);
|
|
140
|
+
font-size: theme(detail-font-size, font-size-base);
|
|
141
|
+
line-height: normal;
|
|
142
|
+
font-style: normal;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
.header {
|
|
146
|
+
position: relative;
|
|
147
|
+
z-index: 2;
|
|
148
|
+
padding: theme(reel-item-padding, margin(2));
|
|
149
|
+
font-size: theme(reel-header-font-size, font-size-small);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
.heading {
|
|
153
|
+
margin-bottom: margin(0.5);
|
|
154
|
+
color: theme(detail-title-text-color, text-color);
|
|
155
|
+
font-family: theme(detail-title-font-family, inherit);
|
|
156
|
+
font-weight: theme(detail-title-font-weight, lighter);
|
|
157
|
+
font-size: theme(detail-title-font-size, margin(1.25));
|
|
158
|
+
font-style: theme(detail-title-font-style, normal);
|
|
159
|
+
line-height: 1.2;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
.details {
|
|
163
|
+
z-index: 1;
|
|
164
|
+
position: relative;
|
|
165
|
+
padding: theme(reel-item-padding, margin(2));
|
|
166
|
+
background: linear-gradient(to bottom, transparent, theme(detail-background, light) 4rem);
|
|
167
|
+
font-size: theme(detail-font-size, font-size-base);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
.playlinks-heading {
|
|
171
|
+
margin: margin(0.5) 0;
|
|
172
|
+
color: theme(reel-playlinks-title-color, text-color-alt);
|
|
173
|
+
font-family: theme(reel-playlinks-title-font-family, inherit);
|
|
174
|
+
font-weight: theme(reel-playlinks-title-font-weight, lighter);
|
|
175
|
+
font-size: theme(reel-playlinks-title-font-size, font-size-small);
|
|
176
|
+
line-height: normal;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
.gradient {
|
|
180
|
+
z-index: 1;
|
|
181
|
+
position: absolute;
|
|
182
|
+
top: 0;
|
|
183
|
+
left: 0;
|
|
184
|
+
width: 100%;
|
|
185
|
+
height: margin(7);
|
|
186
|
+
background: linear-gradient(to bottom, rgba(0, 0, 0, 0.8), transparent);
|
|
187
|
+
}
|
|
188
|
+
</style>
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
import { getContext } from 'svelte'
|
|
11
11
|
import Playlink from './Playlink.svelte'
|
|
12
12
|
import Button from '../Button.svelte'
|
|
13
|
+
import PlaylinksDisclaimer from './PlaylinksDisclaimer.svelte'
|
|
13
14
|
|
|
14
15
|
type Category = PlaylinkCategory | '' | 'Other'
|
|
15
16
|
type CategorizedPlaylinks = Partial<Record<Category, PlaylinkData[]>>
|
|
@@ -79,16 +80,7 @@
|
|
|
79
80
|
|
|
80
81
|
<div class="heading" use:heading={3}>{t('Where To Stream Online')}</div>
|
|
81
82
|
|
|
82
|
-
{
|
|
83
|
-
<div class="disclaimer" data-testid="commission-disclaimer">
|
|
84
|
-
{window?.PlayPilotLinkInjections?.config?.playlinks_disclaimer_text || t('Commission Disclaimer')}
|
|
85
|
-
<a href="https://playpilot.com/" target="_blank" rel="sponsored">PlayPilot.com</a>
|
|
86
|
-
</div>
|
|
87
|
-
{:else}
|
|
88
|
-
<div class="empty" data-testid="playlinks-empty">
|
|
89
|
-
{t('Title Unavailable')}
|
|
90
|
-
</div>
|
|
91
|
-
{/if}
|
|
83
|
+
<PlaylinksDisclaimer playlinks={mergedPlaylinks} />
|
|
92
84
|
|
|
93
85
|
{#each sortedPlaylinks as [category, playlinks]}
|
|
94
86
|
{#if category && playlinks.length}
|
|
@@ -146,35 +138,7 @@
|
|
|
146
138
|
}
|
|
147
139
|
}
|
|
148
140
|
|
|
149
|
-
.disclaimer {
|
|
150
|
-
margin: theme(playlinks-disclaimer-margin, margin(0.5)) 0;
|
|
151
|
-
opacity: theme(playlinks-disclaimer-opacity, 0.65);
|
|
152
|
-
color: theme(playlinks-disclaimer-text-color, text-color-alt);
|
|
153
|
-
font-size: theme(playlinks-disclaimer-font-size, margin(0.625));
|
|
154
|
-
font-style: theme(playlinks-disclaimer-font-style, normal);
|
|
155
|
-
|
|
156
|
-
a {
|
|
157
|
-
color: inherit;
|
|
158
|
-
font-size: inherit;
|
|
159
|
-
font-style: inherit;
|
|
160
|
-
|
|
161
|
-
&:hover {
|
|
162
|
-
color: theme(playlinks-disclaimer-text-color-hover, text-color);
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
|
|
167
141
|
.show-more {
|
|
168
142
|
margin-top: margin(0.5);
|
|
169
143
|
}
|
|
170
|
-
|
|
171
|
-
.empty {
|
|
172
|
-
padding: margin(0.75);
|
|
173
|
-
margin-top: margin(0.5);
|
|
174
|
-
background: theme(playlink-background, lighter);
|
|
175
|
-
box-shadow: theme(playlink-shadow, shadow);
|
|
176
|
-
border-radius: theme(playlink-border-radius, border-radius);
|
|
177
|
-
white-space: initial;
|
|
178
|
-
line-height: 1.35;
|
|
179
|
-
}
|
|
180
144
|
</style>
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { t } from '$lib/localization'
|
|
3
|
+
import type { PlaylinkData } from '$lib/types/playlink'
|
|
4
|
+
|
|
5
|
+
interface Props {
|
|
6
|
+
playlinks: PlaylinkData[]
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const { playlinks }: Props = $props()
|
|
10
|
+
</script>
|
|
11
|
+
|
|
12
|
+
{#if playlinks.length}
|
|
13
|
+
<div class="disclaimer" data-testid="commission-disclaimer">
|
|
14
|
+
{window?.PlayPilotLinkInjections?.config?.playlinks_disclaimer_text || t('Commission Disclaimer')}
|
|
15
|
+
<a href="https://playpilot.com/" target="_blank" rel="sponsored">PlayPilot.com</a>
|
|
16
|
+
</div>
|
|
17
|
+
{:else}
|
|
18
|
+
<div class="empty" data-testid="playlinks-empty">
|
|
19
|
+
{t('Title Unavailable')}
|
|
20
|
+
</div>
|
|
21
|
+
{/if}
|
|
22
|
+
|
|
23
|
+
<style lang="scss">
|
|
24
|
+
.disclaimer {
|
|
25
|
+
margin: theme(playlinks-disclaimer-margin, margin(0.5)) 0;
|
|
26
|
+
opacity: theme(playlinks-disclaimer-opacity, 0.65);
|
|
27
|
+
color: theme(playlinks-disclaimer-text-color, text-color-alt);
|
|
28
|
+
font-size: theme(playlinks-disclaimer-font-size, margin(0.625));
|
|
29
|
+
font-style: theme(playlinks-disclaimer-font-style, normal);
|
|
30
|
+
|
|
31
|
+
a {
|
|
32
|
+
color: inherit;
|
|
33
|
+
font-size: inherit;
|
|
34
|
+
font-style: inherit;
|
|
35
|
+
|
|
36
|
+
&:hover {
|
|
37
|
+
color: theme(playlinks-disclaimer-text-color-hover, text-color);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
.empty {
|
|
43
|
+
padding: margin(0.75);
|
|
44
|
+
margin-top: margin(0.5);
|
|
45
|
+
background: theme(playlink-background, lighter);
|
|
46
|
+
box-shadow: theme(playlink-shadow, shadow);
|
|
47
|
+
border-radius: theme(playlink-border-radius, border-radius);
|
|
48
|
+
white-space: initial;
|
|
49
|
+
line-height: 1.35;
|
|
50
|
+
}
|
|
51
|
+
</style>
|