@revenuecat/purchases-ui-js 4.0.0 → 4.2.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.
- package/dist/components/carousel/Carousel.stories.svelte +29 -0
- package/dist/components/carousel/Carousel.svelte +74 -8
- package/dist/components/paywall/BackgroundVideoSurface.svelte +162 -0
- package/dist/components/paywall/BackgroundVideoSurface.svelte.d.ts +12 -0
- package/dist/components/paywall/Paywall.stories.svelte +134 -10
- package/dist/components/paywall/Paywall.svelte +36 -6
- package/dist/components/paywall/Sheet.svelte +66 -3
- package/dist/components/paywall/ViewportBackdrop.svelte +92 -0
- package/dist/components/{layout/Main → paywall}/ViewportBackdrop.svelte.d.ts +1 -1
- package/dist/components/paywall/fixtures/sheet-video-stacking-paywall.d.ts +6 -0
- package/dist/components/paywall/fixtures/sheet-video-stacking-paywall.js +42 -0
- package/dist/components/stack/Stack.stories.svelte +74 -0
- package/dist/components/stack/Stack.svelte +58 -3
- package/dist/components/tabs/Tabs.stories.svelte +27 -0
- package/dist/components/tabs/Tabs.svelte +69 -4
- package/dist/components/tabs/tabs-video-stacking-story-args.d.ts +3 -0
- package/dist/components/tabs/tabs-video-stacking-story-args.js +131 -0
- package/dist/components/video/Video.svelte +185 -20
- package/dist/components/workflows/Screen.stories.svelte +3 -6
- package/dist/index.d.ts +0 -1
- package/dist/index.js +0 -1
- package/dist/stories/video-background-story-fixture.d.ts +3 -0
- package/dist/stories/video-background-story-fixture.js +36 -0
- package/dist/types/background.d.ts +9 -1
- package/dist/ui/molecules/button.svelte +2 -2
- package/dist/utils/background-utils.d.ts +30 -1
- package/dist/utils/background-utils.js +53 -5
- package/dist/utils/document-background.d.ts +3 -0
- package/dist/utils/document-background.js +59 -0
- package/dist/utils/match-media-compat.d.ts +4 -0
- package/dist/utils/match-media-compat.js +11 -0
- package/dist/utils/video-background-host.d.ts +31 -0
- package/dist/utils/video-background-host.js +25 -0
- package/dist/utils/video-inline-playback.d.ts +25 -0
- package/dist/utils/video-inline-playback.js +78 -0
- package/dist/web-components/index.css +1 -1
- package/package.json +2 -2
- package/dist/components/layout/Main/Main.stories.svelte +0 -263
- package/dist/components/layout/Main/Main.stories.svelte.d.ts +0 -19
- package/dist/components/layout/Main/Main.svelte +0 -117
- package/dist/components/layout/Main/Main.svelte.d.ts +0 -11
- package/dist/components/layout/Main/ViewportBackdrop.svelte +0 -65
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
import { viewportDecorator } from "../../stories/viewport-decorator";
|
|
5
5
|
import type { CarouselProps } from "../../types/components/carousel";
|
|
6
6
|
import { DEFAULT_SPACING } from "../../utils/constants";
|
|
7
|
+
import { STORY_BACKGROUND_VIDEO_FILL } from "../../stories/video-background-story-fixture";
|
|
7
8
|
import { defineMeta } from "@storybook/addon-svelte-csf";
|
|
8
9
|
import Carousel from "./Carousel.svelte";
|
|
9
10
|
|
|
@@ -442,6 +443,34 @@
|
|
|
442
443
|
});
|
|
443
444
|
</script>
|
|
444
445
|
|
|
446
|
+
<Story
|
|
447
|
+
name="Surface — video background (stacking / z-order)"
|
|
448
|
+
parameters={{
|
|
449
|
+
chromatic: { disableSnapshot: true },
|
|
450
|
+
docs: {
|
|
451
|
+
description: {
|
|
452
|
+
story:
|
|
453
|
+
"**Z-order:** video layer → tinted `::before` → carousel clip (slides + controls). Rounded corners should clip the backdrop; Chromatic skips motion snapshots.",
|
|
454
|
+
},
|
|
455
|
+
},
|
|
456
|
+
}}
|
|
457
|
+
args={{
|
|
458
|
+
background: STORY_BACKGROUND_VIDEO_FILL,
|
|
459
|
+
shape: {
|
|
460
|
+
type: "rectangle",
|
|
461
|
+
corners: {
|
|
462
|
+
bottom_leading: 20,
|
|
463
|
+
bottom_trailing: 20,
|
|
464
|
+
top_leading: 20,
|
|
465
|
+
top_trailing: 20,
|
|
466
|
+
},
|
|
467
|
+
},
|
|
468
|
+
auto_advance: null,
|
|
469
|
+
loop: false,
|
|
470
|
+
initial_page_index: 0,
|
|
471
|
+
}}
|
|
472
|
+
/>
|
|
473
|
+
|
|
445
474
|
<Story
|
|
446
475
|
name="No loop - first page"
|
|
447
476
|
args={{
|
|
@@ -2,8 +2,16 @@
|
|
|
2
2
|
import { getColorModeContext } from "../../stores/color-mode";
|
|
3
3
|
import { getPaywallContext } from "../../stores/paywall";
|
|
4
4
|
import { getSelectedStateContext } from "../../stores/selected";
|
|
5
|
+
import BackgroundVideoSurface from "../paywall/BackgroundVideoSurface.svelte";
|
|
5
6
|
import type { CarouselProps } from "../../types/components/carousel";
|
|
6
|
-
import {
|
|
7
|
+
import {
|
|
8
|
+
mapBackground,
|
|
9
|
+
resolveBackgroundVideoForSurface,
|
|
10
|
+
} from "../../utils/background-utils";
|
|
11
|
+
import {
|
|
12
|
+
rcVideoBackgroundHostClass,
|
|
13
|
+
videoBackgroundHostStyles,
|
|
14
|
+
} from "../../utils/video-background-host";
|
|
7
15
|
import {
|
|
8
16
|
css,
|
|
9
17
|
mapBorder,
|
|
@@ -33,7 +41,7 @@
|
|
|
33
41
|
page_peek,
|
|
34
42
|
page_control,
|
|
35
43
|
initial_page_index,
|
|
36
|
-
loop,
|
|
44
|
+
loop: carouselPagingLoop,
|
|
37
45
|
auto_advance,
|
|
38
46
|
} = $derived.by(() => {
|
|
39
47
|
return {
|
|
@@ -46,13 +54,26 @@
|
|
|
46
54
|
const colorMode = $derived(getColorMode());
|
|
47
55
|
const { emitComponentInteraction } = getPaywallContext();
|
|
48
56
|
|
|
57
|
+
const hasVideoBg = $derived(background?.type === "video");
|
|
58
|
+
const videoSurface = $derived.by(() => {
|
|
59
|
+
if (!hasVideoBg) return null;
|
|
60
|
+
const bg = background;
|
|
61
|
+
if (!bg || bg.type !== "video") return null;
|
|
62
|
+
return resolveBackgroundVideoForSurface(colorMode, bg);
|
|
63
|
+
});
|
|
64
|
+
const borderRadiusCssForBg = $derived(mapBorderRadius(shape));
|
|
65
|
+
|
|
49
66
|
const carouselStyle = $derived(
|
|
50
67
|
css({
|
|
51
68
|
margin: mapSpacing(margin),
|
|
52
69
|
padding: mapSpacing(padding),
|
|
70
|
+
...videoBackgroundHostStyles({
|
|
71
|
+
hasVideoBg,
|
|
72
|
+
positionRelative: true,
|
|
73
|
+
}),
|
|
53
74
|
...mapBackground(colorMode, null, background),
|
|
54
75
|
...mapBorder(colorMode, border),
|
|
55
|
-
"border-radius":
|
|
76
|
+
"border-radius": borderRadiusCssForBg,
|
|
56
77
|
"box-shadow": mapShadow(colorMode, shadow),
|
|
57
78
|
"transition-duration": `${auto_advance?.ms_transition_time ?? 0}ms`,
|
|
58
79
|
}),
|
|
@@ -67,7 +88,7 @@
|
|
|
67
88
|
);
|
|
68
89
|
|
|
69
90
|
const prevPages = $derived.by(() => {
|
|
70
|
-
if (!
|
|
91
|
+
if (!carouselPagingLoop) {
|
|
71
92
|
return [];
|
|
72
93
|
}
|
|
73
94
|
|
|
@@ -85,7 +106,7 @@
|
|
|
85
106
|
});
|
|
86
107
|
|
|
87
108
|
const nextPages = $derived.by(() => {
|
|
88
|
-
if (!
|
|
109
|
+
if (!carouselPagingLoop) {
|
|
89
110
|
return [];
|
|
90
111
|
}
|
|
91
112
|
|
|
@@ -138,7 +159,7 @@
|
|
|
138
159
|
}
|
|
139
160
|
}
|
|
140
161
|
|
|
141
|
-
const loopOffset = $derived(
|
|
162
|
+
const loopOffset = $derived(carouselPagingLoop ? 2 : 0);
|
|
142
163
|
|
|
143
164
|
function setSliderTranslate(translate: number) {
|
|
144
165
|
if (slider !== null) {
|
|
@@ -238,7 +259,7 @@
|
|
|
238
259
|
return;
|
|
239
260
|
}
|
|
240
261
|
|
|
241
|
-
const [min, max] =
|
|
262
|
+
const [min, max] = carouselPagingLoop
|
|
242
263
|
? [startTranslation - carouselWidth, startTranslation + carouselWidth]
|
|
243
264
|
: [-(pages.length - 1) * pageWidth, 0];
|
|
244
265
|
const translation = getTranslation(slider);
|
|
@@ -252,7 +273,29 @@
|
|
|
252
273
|
}
|
|
253
274
|
</script>
|
|
254
275
|
|
|
255
|
-
<div
|
|
276
|
+
<div
|
|
277
|
+
class={[
|
|
278
|
+
"carousel",
|
|
279
|
+
"rc-gradient-border",
|
|
280
|
+
hasVideoBg ? rcVideoBackgroundHostClass("carousel") : "",
|
|
281
|
+
]
|
|
282
|
+
.filter(Boolean)
|
|
283
|
+
.join(" ")}
|
|
284
|
+
style={carouselStyle}
|
|
285
|
+
>
|
|
286
|
+
{#if videoSurface}
|
|
287
|
+
<div class="carousel-bg-video-mount">
|
|
288
|
+
<BackgroundVideoSurface
|
|
289
|
+
url={videoSurface.url}
|
|
290
|
+
urlLowRes={videoSurface.url_low_res}
|
|
291
|
+
objectFit={videoSurface.fit}
|
|
292
|
+
objectPosition={videoSurface.position}
|
|
293
|
+
poster={videoSurface.posterWebp}
|
|
294
|
+
mute={videoSurface.mute}
|
|
295
|
+
loop={videoSurface.loop}
|
|
296
|
+
/>
|
|
297
|
+
</div>
|
|
298
|
+
{/if}
|
|
256
299
|
<div class="carousel-clip">
|
|
257
300
|
<div
|
|
258
301
|
bind:this={slider}
|
|
@@ -324,6 +367,29 @@
|
|
|
324
367
|
border-radius: inherit;
|
|
325
368
|
}
|
|
326
369
|
|
|
370
|
+
.carousel-bg-video-mount {
|
|
371
|
+
position: absolute;
|
|
372
|
+
inset: 0;
|
|
373
|
+
z-index: 1;
|
|
374
|
+
border-radius: inherit;
|
|
375
|
+
overflow: hidden;
|
|
376
|
+
pointer-events: none;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
.carousel.rc-carousel-video-bg::before {
|
|
380
|
+
content: "";
|
|
381
|
+
position: absolute;
|
|
382
|
+
inset: 0;
|
|
383
|
+
background: var(--overlay);
|
|
384
|
+
pointer-events: none;
|
|
385
|
+
z-index: 2;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
.carousel.rc-carousel-video-bg .carousel-clip {
|
|
389
|
+
position: relative;
|
|
390
|
+
z-index: 3;
|
|
391
|
+
}
|
|
392
|
+
|
|
327
393
|
.slider {
|
|
328
394
|
display: flex;
|
|
329
395
|
flex-direction: row;
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { onMount, untrack } from "svelte";
|
|
3
|
+
import { subscribeCompatMediaChange } from "../../utils/match-media-compat";
|
|
4
|
+
import {
|
|
5
|
+
subscribeDecorativeVideoAutoplayAttempts,
|
|
6
|
+
tryDecorativeAutoplayWithMuteFallback,
|
|
7
|
+
} from "../../utils/video-inline-playback";
|
|
8
|
+
|
|
9
|
+
interface Props {
|
|
10
|
+
url: string;
|
|
11
|
+
urlLowRes?: string | null;
|
|
12
|
+
objectFit: string;
|
|
13
|
+
objectPosition: string;
|
|
14
|
+
poster?: string | null;
|
|
15
|
+
mute: boolean;
|
|
16
|
+
loop: boolean;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
let {
|
|
20
|
+
url,
|
|
21
|
+
urlLowRes = null,
|
|
22
|
+
objectFit,
|
|
23
|
+
objectPosition,
|
|
24
|
+
poster = null,
|
|
25
|
+
mute,
|
|
26
|
+
loop,
|
|
27
|
+
}: Props = $props();
|
|
28
|
+
|
|
29
|
+
let videoElement = $state<HTMLVideoElement | null>(null);
|
|
30
|
+
let hasError = $state(false);
|
|
31
|
+
let reduceMotion = $state(false);
|
|
32
|
+
/** When unmuted autoplay fails, retry muted (Safari autoplay policy). */
|
|
33
|
+
let playbackMutedOverride = $state(false);
|
|
34
|
+
|
|
35
|
+
onMount(() => {
|
|
36
|
+
if (typeof window === "undefined") return;
|
|
37
|
+
const mq = window.matchMedia("(prefers-reduced-motion: reduce)");
|
|
38
|
+
reduceMotion = mq.matches;
|
|
39
|
+
const onChange = () => {
|
|
40
|
+
reduceMotion = mq.matches;
|
|
41
|
+
};
|
|
42
|
+
return subscribeCompatMediaChange(mq, onChange);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
$effect(() => {
|
|
46
|
+
url;
|
|
47
|
+
urlLowRes;
|
|
48
|
+
untrack(() => {
|
|
49
|
+
hasError = false;
|
|
50
|
+
playbackMutedOverride = false;
|
|
51
|
+
});
|
|
52
|
+
queueMicrotask(() => {
|
|
53
|
+
videoElement?.load();
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
const showPosterOnly = $derived(reduceMotion || hasError);
|
|
58
|
+
|
|
59
|
+
const mediaStyle = $derived(
|
|
60
|
+
`object-fit:${objectFit};object-position:${objectPosition}`,
|
|
61
|
+
);
|
|
62
|
+
|
|
63
|
+
$effect(() => {
|
|
64
|
+
if (showPosterOnly || !videoElement) {
|
|
65
|
+
untrack(() => {
|
|
66
|
+
playbackMutedOverride = false;
|
|
67
|
+
});
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const el = videoElement;
|
|
72
|
+
let cancelled = false;
|
|
73
|
+
const isCancelled = () => cancelled;
|
|
74
|
+
|
|
75
|
+
const tryPlay = () => {
|
|
76
|
+
tryDecorativeAutoplayWithMuteFallback(el, {
|
|
77
|
+
cancelled: isCancelled,
|
|
78
|
+
preferMuted: mute,
|
|
79
|
+
getMutedOverride: () => playbackMutedOverride,
|
|
80
|
+
setMutedOverride: (v) =>
|
|
81
|
+
untrack(() => {
|
|
82
|
+
playbackMutedOverride = v;
|
|
83
|
+
}),
|
|
84
|
+
});
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
const removeMediaListeners = subscribeDecorativeVideoAutoplayAttempts(
|
|
88
|
+
el,
|
|
89
|
+
tryPlay,
|
|
90
|
+
isCancelled,
|
|
91
|
+
);
|
|
92
|
+
|
|
93
|
+
return () => {
|
|
94
|
+
cancelled = true;
|
|
95
|
+
removeMediaListeners();
|
|
96
|
+
};
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
function onVideoError() {
|
|
100
|
+
hasError = true;
|
|
101
|
+
}
|
|
102
|
+
</script>
|
|
103
|
+
|
|
104
|
+
{#if showPosterOnly && poster}
|
|
105
|
+
<div class="rc-bg-video" aria-hidden="true">
|
|
106
|
+
<img src={poster} alt="" class="fill" style={mediaStyle} />
|
|
107
|
+
</div>
|
|
108
|
+
{:else if showPosterOnly}
|
|
109
|
+
<!-- No poster/fallback asset for reduced motion or failed load -->
|
|
110
|
+
{:else}
|
|
111
|
+
<!-- svelte-ignore a11y_media_has_caption decorative background -->
|
|
112
|
+
<div class="rc-bg-video" aria-hidden="true">
|
|
113
|
+
<video
|
|
114
|
+
bind:this={videoElement}
|
|
115
|
+
class="fill"
|
|
116
|
+
style={mediaStyle}
|
|
117
|
+
{loop}
|
|
118
|
+
muted={mute || playbackMutedOverride}
|
|
119
|
+
playsinline
|
|
120
|
+
autoplay
|
|
121
|
+
preload="auto"
|
|
122
|
+
poster={poster ?? undefined}
|
|
123
|
+
onerror={onVideoError}
|
|
124
|
+
>
|
|
125
|
+
{#if urlLowRes}
|
|
126
|
+
<!-- Narrow viewports use low-res asset first; larger screens use primary URL. -->
|
|
127
|
+
<source src={urlLowRes} media="(max-width: 600px)" />
|
|
128
|
+
{/if}
|
|
129
|
+
<source src={url} />
|
|
130
|
+
</video>
|
|
131
|
+
</div>
|
|
132
|
+
{/if}
|
|
133
|
+
|
|
134
|
+
<style>
|
|
135
|
+
.rc-bg-video {
|
|
136
|
+
position: absolute;
|
|
137
|
+
inset: 0;
|
|
138
|
+
overflow: hidden;
|
|
139
|
+
z-index: 1;
|
|
140
|
+
pointer-events: none;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Fill the layer with absolute edges (percent heights are unreliable under nested
|
|
145
|
+
* flex/absolute hosts). Beat Paywall's global `.paywall video { max-width: 100% }`
|
|
146
|
+
* so `object-fit: cover|contain` applies to the full backdrop box (e.g. carousel).
|
|
147
|
+
*/
|
|
148
|
+
.rc-bg-video :is(video, img).fill {
|
|
149
|
+
position: absolute;
|
|
150
|
+
inset: 0;
|
|
151
|
+
width: 100%;
|
|
152
|
+
height: 100%;
|
|
153
|
+
max-width: none;
|
|
154
|
+
max-height: none;
|
|
155
|
+
display: block;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
.rc-bg-video video.fill {
|
|
159
|
+
transform: translateZ(0);
|
|
160
|
+
-webkit-transform: translateZ(0);
|
|
161
|
+
}
|
|
162
|
+
</style>
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
interface Props {
|
|
2
|
+
url: string;
|
|
3
|
+
urlLowRes?: string | null;
|
|
4
|
+
objectFit: string;
|
|
5
|
+
objectPosition: string;
|
|
6
|
+
poster?: string | null;
|
|
7
|
+
mute: boolean;
|
|
8
|
+
loop: boolean;
|
|
9
|
+
}
|
|
10
|
+
declare const BackgroundVideoSurface: import("svelte").Component<Props, {}, "">;
|
|
11
|
+
type BackgroundVideoSurface = ReturnType<typeof BackgroundVideoSurface>;
|
|
12
|
+
export default BackgroundVideoSurface;
|
|
@@ -3,7 +3,6 @@
|
|
|
3
3
|
import type { ComponentProps } from "svelte";
|
|
4
4
|
|
|
5
5
|
import Paywall from "./Paywall.svelte";
|
|
6
|
-
import Main from "../layout/Main/Main.svelte";
|
|
7
6
|
import {
|
|
8
7
|
alignmentPaywallData,
|
|
9
8
|
calmPaywallData,
|
|
@@ -28,6 +27,7 @@
|
|
|
28
27
|
import { BACKGROUND_PAYWALL } from "./fixtures/background-paywall";
|
|
29
28
|
import { OVERRIDE_PAYWALL } from "./fixtures/override-paywall";
|
|
30
29
|
import { SHEET_PAYWALL } from "./fixtures/sheet-paywall";
|
|
30
|
+
import { SHEET_PAYWALL_VIDEO_STACKING } from "./fixtures/sheet-video-stacking-paywall";
|
|
31
31
|
import { STACK_PAYWALL } from "./fixtures/stack-paywall";
|
|
32
32
|
import { VARIABLES } from "./fixtures/variables";
|
|
33
33
|
import { CUSTOM_VARIABLES_PAYWALL } from "./fixtures/custom-variables-paywall";
|
|
@@ -63,9 +63,7 @@
|
|
|
63
63
|
|
|
64
64
|
{#snippet template(props: ComponentProps<typeof Paywall>)}
|
|
65
65
|
<div class="paywall-story-frame">
|
|
66
|
-
<
|
|
67
|
-
<Paywall {...props} />
|
|
68
|
-
</Main>
|
|
66
|
+
<Paywall {...props} />
|
|
69
67
|
</div>
|
|
70
68
|
{/snippet}
|
|
71
69
|
|
|
@@ -96,6 +94,28 @@
|
|
|
96
94
|
}}
|
|
97
95
|
/>
|
|
98
96
|
|
|
97
|
+
<Story
|
|
98
|
+
name="Sheet — video background (stacking / z-order)"
|
|
99
|
+
parameters={{
|
|
100
|
+
chromatic: { disableSnapshot: true },
|
|
101
|
+
docs: {
|
|
102
|
+
description: {
|
|
103
|
+
story:
|
|
104
|
+
"**Z-order:** `BackgroundVideoSurface` under the sheet panel (`z-index: 1`), CSS `::before` tint (`z-index: 2`), inner `Stack` (`z-index: 3`). Backdrop dim is outside the sheet chrome. Motion is disabled in Chromatic.",
|
|
105
|
+
},
|
|
106
|
+
},
|
|
107
|
+
}}
|
|
108
|
+
decorators={[viewportDecorator(500, 500, 0)]}
|
|
109
|
+
play={async ({ canvasElement }) => {
|
|
110
|
+
const button = canvasElement.querySelector("button");
|
|
111
|
+
button?.click();
|
|
112
|
+
await waitForAnimations();
|
|
113
|
+
}}
|
|
114
|
+
args={{
|
|
115
|
+
paywallData: SHEET_PAYWALL_VIDEO_STACKING,
|
|
116
|
+
}}
|
|
117
|
+
/>
|
|
118
|
+
|
|
99
119
|
<Story
|
|
100
120
|
name="Background - Color"
|
|
101
121
|
decorators={[viewportDecorator(500, 500, 0)]}
|
|
@@ -234,6 +254,116 @@
|
|
|
234
254
|
}}
|
|
235
255
|
/>
|
|
236
256
|
|
|
257
|
+
<Story
|
|
258
|
+
name="Background - Video fill"
|
|
259
|
+
decorators={[viewportDecorator(500, 500, 0)]}
|
|
260
|
+
parameters={{ chromatic: { disableSnapshot: true } }}
|
|
261
|
+
args={{
|
|
262
|
+
paywallData: BACKGROUND_PAYWALL({
|
|
263
|
+
type: "video",
|
|
264
|
+
fit_mode: "fill",
|
|
265
|
+
mute_audio: true,
|
|
266
|
+
loop: true,
|
|
267
|
+
color_overlay: null,
|
|
268
|
+
fallback_image: {
|
|
269
|
+
light: {
|
|
270
|
+
width: 640,
|
|
271
|
+
height: 360,
|
|
272
|
+
original:
|
|
273
|
+
"https://placehold.co/640x360/1a1a2e/ffffff.webp?text=Poster",
|
|
274
|
+
heic: "https://placehold.co/640x360/1a1a2e/ffffff.webp?text=Poster",
|
|
275
|
+
heic_low_res:
|
|
276
|
+
"https://placehold.co/640x360/1a1a2e/ffffff.webp?text=Poster",
|
|
277
|
+
webp: "https://placehold.co/640x360/1a1a2e/ffffff.webp?text=Poster",
|
|
278
|
+
webp_low_res:
|
|
279
|
+
"https://placehold.co/640x360/1a1a2e/ffffff.webp?text=Poster",
|
|
280
|
+
},
|
|
281
|
+
},
|
|
282
|
+
value: {
|
|
283
|
+
light: {
|
|
284
|
+
width: 1920,
|
|
285
|
+
height: 1080,
|
|
286
|
+
url: "https://videos.pexels.com/video-files/10288594/10288594-hd_1920_1080_25fps.mp4",
|
|
287
|
+
url_low_res:
|
|
288
|
+
"https://videos.pexels.com/video-files/10288594/10288594-hd_1920_1080_25fps.mp4",
|
|
289
|
+
},
|
|
290
|
+
},
|
|
291
|
+
}),
|
|
292
|
+
}}
|
|
293
|
+
/>
|
|
294
|
+
|
|
295
|
+
<Story
|
|
296
|
+
name="Background - Video fit"
|
|
297
|
+
decorators={[viewportDecorator(500, 500, 0)]}
|
|
298
|
+
parameters={{ chromatic: { disableSnapshot: true } }}
|
|
299
|
+
args={{
|
|
300
|
+
paywallData: BACKGROUND_PAYWALL({
|
|
301
|
+
type: "video",
|
|
302
|
+
fit_mode: "fit",
|
|
303
|
+
mute_audio: true,
|
|
304
|
+
loop: true,
|
|
305
|
+
color_overlay: null,
|
|
306
|
+
fallback_image: null,
|
|
307
|
+
value: {
|
|
308
|
+
light: {
|
|
309
|
+
width: 1920,
|
|
310
|
+
height: 1080,
|
|
311
|
+
url: "https://videos.pexels.com/video-files/10288594/10288594-hd_1920_1080_25fps.mp4",
|
|
312
|
+
url_low_res:
|
|
313
|
+
"https://videos.pexels.com/video-files/10288594/10288594-hd_1920_1080_25fps.mp4",
|
|
314
|
+
},
|
|
315
|
+
},
|
|
316
|
+
}),
|
|
317
|
+
}}
|
|
318
|
+
/>
|
|
319
|
+
|
|
320
|
+
<Story
|
|
321
|
+
name="Background - Video with tint overlay"
|
|
322
|
+
decorators={[viewportDecorator(500, 500, 0)]}
|
|
323
|
+
parameters={{ chromatic: { disableSnapshot: true } }}
|
|
324
|
+
args={{
|
|
325
|
+
paywallData: BACKGROUND_PAYWALL({
|
|
326
|
+
type: "video",
|
|
327
|
+
fit_mode: "fill",
|
|
328
|
+
mute_audio: true,
|
|
329
|
+
loop: true,
|
|
330
|
+
color_overlay: {
|
|
331
|
+
light: {
|
|
332
|
+
type: "linear",
|
|
333
|
+
degrees: 180,
|
|
334
|
+
points: [
|
|
335
|
+
{ percent: 0, color: "#00000066" },
|
|
336
|
+
{ percent: 100, color: "#1a0a3080" },
|
|
337
|
+
],
|
|
338
|
+
},
|
|
339
|
+
},
|
|
340
|
+
fallback_image: {
|
|
341
|
+
light: {
|
|
342
|
+
width: 640,
|
|
343
|
+
height: 360,
|
|
344
|
+
original:
|
|
345
|
+
"https://placehold.co/640x360/2d1b4e/ffffff.webp?text=Poster",
|
|
346
|
+
heic: "https://placehold.co/640x360/2d1b4e/ffffff.webp?text=Poster",
|
|
347
|
+
heic_low_res:
|
|
348
|
+
"https://placehold.co/640x360/2d1b4e/ffffff.webp?text=Poster",
|
|
349
|
+
webp: "https://placehold.co/640x360/2d1b4e/ffffff.webp?text=Poster",
|
|
350
|
+
webp_low_res:
|
|
351
|
+
"https://placehold.co/640x360/2d1b4e/ffffff.webp?text=Poster",
|
|
352
|
+
},
|
|
353
|
+
},
|
|
354
|
+
value: {
|
|
355
|
+
light: {
|
|
356
|
+
width: 1920,
|
|
357
|
+
height: 1080,
|
|
358
|
+
url: "https://videos.pexels.com/video-files/10288594/10288594-hd_1920_1080_25fps.mp4",
|
|
359
|
+
url_low_res:
|
|
360
|
+
"https://videos.pexels.com/video-files/10288594/10288594-hd_1920_1080_25fps.mp4",
|
|
361
|
+
},
|
|
362
|
+
},
|
|
363
|
+
}),
|
|
364
|
+
}}
|
|
365
|
+
/>
|
|
366
|
+
|
|
237
367
|
<Story
|
|
238
368
|
name="Primary"
|
|
239
369
|
args={{
|
|
@@ -510,10 +640,4 @@
|
|
|
510
640
|
background-color: var(--rc-purchases-ui-bg-color, Canvas);
|
|
511
641
|
color-scheme: light dark;
|
|
512
642
|
}
|
|
513
|
-
|
|
514
|
-
/* Allow chromatic to capture the full content snapshot */
|
|
515
|
-
.paywall-story-frame :global(.paywall-content-scroll) {
|
|
516
|
-
flex: 1 1 auto;
|
|
517
|
-
overflow-y: visible;
|
|
518
|
-
}
|
|
519
643
|
</style>
|
|
@@ -32,13 +32,17 @@
|
|
|
32
32
|
type PackageInfo,
|
|
33
33
|
type VariableDictionary,
|
|
34
34
|
} from "../../types/variables";
|
|
35
|
+
import { paywallRootBackgroundModel } from "../../utils/background-utils";
|
|
36
|
+
import { css } from "../../utils/base-utils";
|
|
35
37
|
import { STICKY_OVERLAY_Z_INDEX } from "../../utils/constants";
|
|
38
|
+
import { applyDocumentBackground } from "../../utils/document-background";
|
|
36
39
|
import { registerFonts } from "../../utils/font-utils";
|
|
37
40
|
import { findSelectedPackageId } from "../../utils/style-utils";
|
|
38
41
|
import { onMount } from "svelte";
|
|
39
42
|
import { derived, readable, writable } from "svelte/store";
|
|
40
43
|
import Stack from "../stack/Stack.svelte";
|
|
41
44
|
import Sheet from "./Sheet.svelte";
|
|
45
|
+
import ViewportBackdrop from "./ViewportBackdrop.svelte";
|
|
42
46
|
import {
|
|
43
47
|
type PackageInfoStore,
|
|
44
48
|
setPackageInfoContext,
|
|
@@ -128,13 +132,39 @@
|
|
|
128
132
|
customVariables = {},
|
|
129
133
|
}: Props = $props();
|
|
130
134
|
|
|
131
|
-
setColorModeContext(() => preferredColorMode);
|
|
135
|
+
const getColorMode = setColorModeContext(() => preferredColorMode);
|
|
136
|
+
|
|
137
|
+
const viewportBackdropModel = $derived(
|
|
138
|
+
paywallRootBackgroundModel(paywallData, getColorMode()),
|
|
139
|
+
);
|
|
140
|
+
|
|
141
|
+
const instanceId: symbol = Symbol();
|
|
142
|
+
$effect(() =>
|
|
143
|
+
applyDocumentBackground(instanceId, viewportBackdropModel, paywallData),
|
|
144
|
+
);
|
|
132
145
|
|
|
133
146
|
const { default_locale, components_config, components_localizations } =
|
|
134
147
|
paywallData;
|
|
135
148
|
const { base } = components_config;
|
|
136
149
|
const defaultPackageId = findSelectedPackageId(base);
|
|
137
150
|
|
|
151
|
+
// Solid colours paint here so the paywall has its own bg without requiring
|
|
152
|
+
// consumer CSS. Gradients and images are deliberately skipped — ViewportBackdrop
|
|
153
|
+
// paints those at viewport scale, and rendering them on both surfaces would
|
|
154
|
+
// anchor the gradient stops to two different boxes and produce a visible seam
|
|
155
|
+
// at the paywall edge.
|
|
156
|
+
const paywallStyle = $derived.by((): string => {
|
|
157
|
+
const m = viewportBackdropModel;
|
|
158
|
+
if (
|
|
159
|
+
m.kind === "style" &&
|
|
160
|
+
m.style.background &&
|
|
161
|
+
!m.style.background.includes("gradient")
|
|
162
|
+
) {
|
|
163
|
+
return css(m.style);
|
|
164
|
+
}
|
|
165
|
+
return "";
|
|
166
|
+
});
|
|
167
|
+
|
|
138
168
|
const { getLocalizedString } = setLocalizationContext(() => ({
|
|
139
169
|
defaultLocale: default_locale,
|
|
140
170
|
selectedLocale,
|
|
@@ -300,7 +330,8 @@
|
|
|
300
330
|
</script>
|
|
301
331
|
|
|
302
332
|
<svelte:boundary onerror={onError}>
|
|
303
|
-
<div class={paywallClass}>
|
|
333
|
+
<div class={paywallClass} style={paywallStyle}>
|
|
334
|
+
<ViewportBackdrop model={viewportBackdropModel} />
|
|
304
335
|
{#if header}
|
|
305
336
|
<div
|
|
306
337
|
class="header-wrapper"
|
|
@@ -345,10 +376,9 @@
|
|
|
345
376
|
.paywall {
|
|
346
377
|
position: relative;
|
|
347
378
|
display: flex;
|
|
348
|
-
flex: 1 1 auto;
|
|
349
379
|
flex-direction: column;
|
|
350
380
|
align-items: stretch;
|
|
351
|
-
|
|
381
|
+
height: 100%;
|
|
352
382
|
|
|
353
383
|
transition-property: filter, transform;
|
|
354
384
|
transition-duration: 0.1s;
|
|
@@ -370,13 +400,13 @@
|
|
|
370
400
|
.paywall-content {
|
|
371
401
|
z-index: 2;
|
|
372
402
|
width: 100%;
|
|
373
|
-
flex: 1;
|
|
403
|
+
flex: 1 1 auto;
|
|
374
404
|
display: flex;
|
|
375
405
|
flex-direction: column;
|
|
376
406
|
min-height: 0;
|
|
377
407
|
|
|
378
408
|
& > :global(.paywall-content-scroll) {
|
|
379
|
-
flex: 1 1
|
|
409
|
+
flex: 1 1 auto;
|
|
380
410
|
min-height: 0;
|
|
381
411
|
overflow-y: auto;
|
|
382
412
|
}
|