@vectoriox/iox-ui 5.73.4 → 5.74.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/fesm2022/vectoriox-iox-ui-media-core.mjs +93 -12
- package/fesm2022/vectoriox-iox-ui-media-core.mjs.map +1 -1
- package/fesm2022/vectoriox-iox-ui-scene-core.mjs +14 -10
- package/fesm2022/vectoriox-iox-ui-scene-core.mjs.map +1 -1
- package/media-core/index.d.ts +34 -3
- package/package.json +1 -1
- package/scene-core/index.d.ts +6 -3
- package/src/media-core/frames.ts +98 -10
- package/src/media-core/video.ts +8 -2
- package/src/scene-core/scene.ts +15 -10
- package/styles/iox-design.css +5 -2
package/scene-core/index.d.ts
CHANGED
|
@@ -85,7 +85,7 @@ declare function normalizeTracks(tracks: unknown): SceneTrack[];
|
|
|
85
85
|
*/
|
|
86
86
|
declare function revealClipPath(showing: number, from?: SceneRevealFrom, inlineSign?: 1 | -1): string;
|
|
87
87
|
/** A numeric track's value at progress `p`. */
|
|
88
|
-
declare function valueAt(keys: ReadonlyArray<SceneKey>, p: number): number;
|
|
88
|
+
declare function valueAt(keys: ReadonlyArray<SceneKey>, p: number, ease?: (t: number) => number): number;
|
|
89
89
|
/**
|
|
90
90
|
* A colour track's value at progress `p`. Mixed by the browser, never computed here, so a design token stays a live
|
|
91
91
|
* reference (a re-theme reaches a Scene, as it reaches every saved page) and the path is perceptual.
|
|
@@ -104,7 +104,10 @@ declare function sceneFrame(tracks: ReadonlyArray<SceneTrack>, p: number, option
|
|
|
104
104
|
declare const SCENE_WORD_DIM = 0.16;
|
|
105
105
|
/** How many of a layer's words are lit at `p`, in %; null when it has no such track. */
|
|
106
106
|
declare function sceneWordsLit(tracks: ReadonlyArray<SceneTrack>, p: number): number | null;
|
|
107
|
-
/**
|
|
107
|
+
/**
|
|
108
|
+
* How far a layer's pictures have played at `p`, in %; null when it has no such track. A film runs straight between its
|
|
109
|
+
* keys, where everything else eases: eased over a 20-screen Scene it would crawl for its first screens and rush the middle.
|
|
110
|
+
*/
|
|
108
111
|
declare function scenePlayed(tracks: ReadonlyArray<SceneTrack>, p: number): number | null;
|
|
109
112
|
/** Word `index` of `count`, with `lit` % lit: words light one after the other, each easing in as the last finishes. */
|
|
110
113
|
declare function sceneWordOpacity(index: number, count: number, lit: number): number;
|
|
@@ -130,7 +133,7 @@ declare function applySceneFirstFrame(el: HTMLElement | null | undefined, node:
|
|
|
130
133
|
*/
|
|
131
134
|
declare const SCENE_FLOW_RULES: string;
|
|
132
135
|
/** Under reduced motion the driver never starts, so the same rules hold for the whole visit. */
|
|
133
|
-
declare const SCENE_REDUCED_MOTION_CSS = "@media (prefers-reduced-motion: reduce){.iox-role-scene{height:auto!important}.iox-role-scene-stage{position:relative!important;top:auto!important;height:auto!important;overflow:visible!important;container-type:normal!important}[data-iox-tracks]{transform:none!important;opacity:1!important;clip-path:none!important;pointer-events:auto!important}[data-iox-video-frames]{height:auto!important}[data-iox-video-frames]>.iox-video__poster{position:static!important;height:auto!important}}";
|
|
136
|
+
declare const SCENE_REDUCED_MOTION_CSS = "@media (prefers-reduced-motion: reduce){.iox-role-scene{height:auto!important}.iox-role-scene-stage{position:relative!important;top:auto!important;height:auto!important;overflow:visible!important;container-type:normal!important}[data-iox-tracks]{transform:none!important;opacity:1!important;clip-path:none!important;pointer-events:auto!important}[data-iox-video-frames]{height:auto!important}[data-iox-video-frames]>.iox-video__poster,[data-iox-video-frames]>picture>.iox-video__poster{position:static!important;height:auto!important}}";
|
|
134
137
|
|
|
135
138
|
declare function sceneKeyPosition(p: number): number;
|
|
136
139
|
interface SceneTrackDef {
|
package/src/media-core/frames.ts
CHANGED
|
@@ -25,10 +25,18 @@ export interface FrameSequence {
|
|
|
25
25
|
wide: FrameSetInfo;
|
|
26
26
|
/** Absent = phones play the wide set. */
|
|
27
27
|
phone?: FrameSetInfo;
|
|
28
|
+
/**
|
|
29
|
+
* A phone film of its own, framed for a tall screen, lives under its own name: a replacement that fails half way leaves
|
|
30
|
+
* the one playing untouched. Absent = the phone set is the wide film made smaller, under `base`.
|
|
31
|
+
*/
|
|
32
|
+
phoneBase?: string;
|
|
28
33
|
/** What the sequence weighs in storage, both sets. Shown to the author. */
|
|
29
34
|
bytes?: number;
|
|
30
35
|
}
|
|
31
36
|
|
|
37
|
+
/** At or under this width a screen is a phone. Scene's own limit says the same: scene-core reads media-core, never the reverse. */
|
|
38
|
+
export const FRAMES_PHONE_MAX_WIDTH = 767;
|
|
39
|
+
|
|
32
40
|
export enum FrameExtension {
|
|
33
41
|
Webp = 'webp',
|
|
34
42
|
Jpeg = 'jpg',
|
|
@@ -58,11 +66,13 @@ export function normalizeFrameSequence(raw: unknown): FrameSequence | null {
|
|
|
58
66
|
const wide = setInfo(value.wide);
|
|
59
67
|
if (!wide || typeof value.base !== 'string' || !SAFE_BASE.test(value.base) || !ALL_EXTENSIONS.has(String(value.ext))) return null;
|
|
60
68
|
const phone = setInfo(value.phone);
|
|
69
|
+
const phoneBase = phone && typeof value.phoneBase === 'string' && SAFE_BASE.test(value.phoneBase) ? value.phoneBase.replace(/\/+$/, '') : '';
|
|
61
70
|
return {
|
|
62
71
|
base: value.base.replace(/\/+$/, ''),
|
|
63
72
|
ext: value.ext as FrameExtension,
|
|
64
73
|
wide,
|
|
65
74
|
...(phone ? { phone } : {}),
|
|
75
|
+
...(phoneBase ? { phoneBase } : {}),
|
|
66
76
|
...(Number.isFinite(value.bytes) && (value.bytes as number) > 0 ? { bytes: value.bytes } : {}),
|
|
67
77
|
};
|
|
68
78
|
}
|
|
@@ -78,7 +88,8 @@ export function frameFileName(index: number, ext: FrameExtension): string {
|
|
|
78
88
|
|
|
79
89
|
/** Picture `index` (from 0) of a set. */
|
|
80
90
|
export function frameUrl(sequence: FrameSequence, set: FrameSet, index: number): string {
|
|
81
|
-
|
|
91
|
+
const base = set === FrameSet.Phone && sequence.phoneBase ? sequence.phoneBase : sequence.base;
|
|
92
|
+
return `${base}/${set}/${frameFileName(index, sequence.ext)}`;
|
|
82
93
|
}
|
|
83
94
|
|
|
84
95
|
/** The first picture of each set, for a poster's `srcset`; '' when there is only one set. */
|
|
@@ -92,6 +103,42 @@ export function frameSetFor(sequence: FrameSequence, phone: boolean): { set: Fra
|
|
|
92
103
|
return phone && sequence.phone ? { set: FrameSet.Phone, info: sequence.phone } : { set: FrameSet.Wide, info: sequence.wide };
|
|
93
104
|
}
|
|
94
105
|
|
|
106
|
+
/** Where `played` % stands in a set of `count`: 2.5 is half way from the third picture to the fourth. */
|
|
107
|
+
export function framePosition(played: number, count: number): number {
|
|
108
|
+
return (Math.min(100, Math.max(0, played)) / 100) * (count - 1);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/** On a Video whose pictures are drawing: the poster has done its job and steps aside. */
|
|
112
|
+
export const FRAMES_SHOWING_ATTRIBUTE = 'data-iox-frames-showing';
|
|
113
|
+
|
|
114
|
+
/** How long a stopped scroll waits before settling on a whole picture, and how fast it gets there per frame. */
|
|
115
|
+
const REST_AFTER_MS = 140;
|
|
116
|
+
const REST_RATE = 0.25;
|
|
117
|
+
|
|
118
|
+
/** How finely two neighbours mix. Finer than the eye reads, and a scroll that moves less than a step draws nothing. */
|
|
119
|
+
export const FRAME_BLEND_STEPS = 32;
|
|
120
|
+
|
|
121
|
+
/** What to draw: picture `from`, with `to` over it at `mix` when the moment falls between two that have arrived. */
|
|
122
|
+
export interface FrameBlend {
|
|
123
|
+
from: number;
|
|
124
|
+
to: number;
|
|
125
|
+
mix: number;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* The moment between two pictures. A long Scene gives a picture 50px of scroll or more, so a slow scroll would otherwise
|
|
130
|
+
* show the film stepping. Blending the two neighbours keeps it moving under the finger, at the cost of one more drawImage.
|
|
131
|
+
* A neighbour still loading never blends: the nearest arrived picture stands in, and null means none has.
|
|
132
|
+
*/
|
|
133
|
+
export function frameBlendAt(loaded: ReadonlyArray<unknown>, position: number): FrameBlend | null {
|
|
134
|
+
const from = Math.floor(position);
|
|
135
|
+
const to = from + 1;
|
|
136
|
+
const mix = Math.round((position - from) * FRAME_BLEND_STEPS) / FRAME_BLEND_STEPS;
|
|
137
|
+
if (mix > 0 && mix < 1 && loaded[from] && loaded[to]) return { from, to, mix };
|
|
138
|
+
const nearest = nearestLoadedFrame(loaded, Math.round(position));
|
|
139
|
+
return nearest < 0 ? null : { from: nearest, to: nearest, mix: 0 };
|
|
140
|
+
}
|
|
141
|
+
|
|
95
142
|
/** The picture at `played` % of a set of `count`. */
|
|
96
143
|
export function frameIndexAt(played: number, count: number): number {
|
|
97
144
|
if (count <= 1) return 0;
|
|
@@ -207,6 +254,9 @@ export interface FramePlayerOptions {
|
|
|
207
254
|
* Browser only. It writes and never reads, so the driver can call it in its write phase.
|
|
208
255
|
*/
|
|
209
256
|
export class FramePlayer {
|
|
257
|
+
private played: number | null = null;
|
|
258
|
+
private restTimer: number | null = null;
|
|
259
|
+
private restFrame: number | null = null;
|
|
210
260
|
private readonly set: FrameSet;
|
|
211
261
|
private readonly info: FrameSetInfo;
|
|
212
262
|
private readonly images: Array<HTMLImageElement | null>;
|
|
@@ -215,8 +265,8 @@ export class FramePlayer {
|
|
|
215
265
|
private observer: IntersectionObserver | null = null;
|
|
216
266
|
private canvas: HTMLCanvasElement | null = null;
|
|
217
267
|
private context: CanvasRenderingContext2D | null = null;
|
|
218
|
-
private
|
|
219
|
-
private drawn =
|
|
268
|
+
private position = 0;
|
|
269
|
+
private drawn: FrameBlend | null = null;
|
|
220
270
|
private nextToLoad = 0;
|
|
221
271
|
private loading = 0;
|
|
222
272
|
private started = false;
|
|
@@ -244,8 +294,34 @@ export class FramePlayer {
|
|
|
244
294
|
|
|
245
295
|
/** Stand at `played` %. */
|
|
246
296
|
seek(played: number): void {
|
|
247
|
-
|
|
297
|
+
if (played === this.played) return;
|
|
298
|
+
this.played = played;
|
|
299
|
+
this.stopResting();
|
|
300
|
+
this.position = framePosition(played, this.info.count);
|
|
248
301
|
this.draw();
|
|
302
|
+
this.restTimer = this.host.setTimeout?.(() => this.rest(), REST_AFTER_MS) ?? null;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
/** A stopped scroll must not stand on two pictures at once: a moving subject would show twice. It settles on the nearer. */
|
|
306
|
+
private rest(): void {
|
|
307
|
+
this.restTimer = null;
|
|
308
|
+
const target = Math.round(this.position);
|
|
309
|
+
const step = (): void => {
|
|
310
|
+
this.restFrame = null;
|
|
311
|
+
if (this.destroyed) return;
|
|
312
|
+
const left = target - this.position;
|
|
313
|
+
this.position = Math.abs(left) < 1 / FRAME_BLEND_STEPS ? target : this.position + left * REST_RATE;
|
|
314
|
+
this.draw();
|
|
315
|
+
if (this.position !== target) this.restFrame = this.host.requestAnimationFrame(step);
|
|
316
|
+
};
|
|
317
|
+
step();
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
private stopResting(): void {
|
|
321
|
+
if (this.restTimer !== null) this.host.clearTimeout?.(this.restTimer);
|
|
322
|
+
if (this.restFrame !== null) this.host.cancelAnimationFrame?.(this.restFrame);
|
|
323
|
+
this.restTimer = null;
|
|
324
|
+
this.restFrame = null;
|
|
249
325
|
}
|
|
250
326
|
|
|
251
327
|
/** Whether a re-render took the canvas away with the markup it sat in. */
|
|
@@ -255,6 +331,8 @@ export class FramePlayer {
|
|
|
255
331
|
|
|
256
332
|
destroy(): void {
|
|
257
333
|
this.destroyed = true;
|
|
334
|
+
this.stopResting();
|
|
335
|
+
this.el.removeAttribute(FRAMES_SHOWING_ATTRIBUTE);
|
|
258
336
|
this.observer?.disconnect();
|
|
259
337
|
this.observer = null;
|
|
260
338
|
for (const image of this.pending) {
|
|
@@ -314,13 +392,23 @@ export class FramePlayer {
|
|
|
314
392
|
}
|
|
315
393
|
|
|
316
394
|
private draw(): void {
|
|
317
|
-
const
|
|
318
|
-
|
|
319
|
-
|
|
395
|
+
const blend = frameBlendAt(this.images, this.position);
|
|
396
|
+
const last = this.drawn;
|
|
397
|
+
if (!blend || (last && last.from === blend.from && last.to === blend.to && last.mix === blend.mix)) return;
|
|
398
|
+
const image = this.images[blend.from]!;
|
|
320
399
|
if (!this.ensureCanvas(image)) return;
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
400
|
+
const context = this.context!;
|
|
401
|
+
const { width, height } = this.canvas!;
|
|
402
|
+
context.clearRect(0, 0, width, height);
|
|
403
|
+
context.drawImage(image, 0, 0, width, height);
|
|
404
|
+
if (blend.mix > 0) {
|
|
405
|
+
context.globalAlpha = blend.mix;
|
|
406
|
+
context.drawImage(this.images[blend.to]!, 0, 0, width, height);
|
|
407
|
+
context.globalAlpha = 1;
|
|
408
|
+
}
|
|
409
|
+
// Over a transparent film the poster would show through: a second, stiller subject behind the moving one.
|
|
410
|
+
if (!this.drawn) this.el.setAttribute(FRAMES_SHOWING_ATTRIBUTE, '');
|
|
411
|
+
this.drawn = blend;
|
|
324
412
|
}
|
|
325
413
|
|
|
326
414
|
private ensureCanvas(image: HTMLImageElement): boolean {
|
package/src/media-core/video.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { ConsentCategory, ConsentDecision, hasConsent } from '@vectoriox/iox-ui/consent-core';
|
|
2
2
|
|
|
3
|
-
import { FrameSequence, FrameSet, frameUrl, normalizeFrameSequence, phoneFrameSrcset } from './frames';
|
|
3
|
+
import { FRAMES_PHONE_MAX_WIDTH, FrameSequence, FrameSet, frameUrl, normalizeFrameSequence, phoneFrameSrcset } from './frames';
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* VIDEO (CC3 — complex-components plan §7b, board 5). A LEAF type with two sources:
|
|
@@ -441,7 +441,13 @@ export function videoInnerHtml(plan: VideoPlan, options: VideoMarkupOptions): st
|
|
|
441
441
|
const srcset = own ? phoneFrameSrcset(plan.frames!) : '';
|
|
442
442
|
// Its real size, so where a Scene flows (reduced motion, no script) the picture holds its height before it loads.
|
|
443
443
|
const size = own ? ` width="${plan.frames!.wide.width}" height="${plan.frames!.wide.height}"` : '';
|
|
444
|
-
|
|
444
|
+
const image = (attrs: string): string => `<img class="${VideoClass.Poster}" src="${esc(plan.poster)}"${attrs} alt="${esc(plan.title)}" decoding="async">`;
|
|
445
|
+
// A phone film is framed for a tall screen, so it is chosen by the screen, never by the pixels a high-density phone asks for.
|
|
446
|
+
if (own && plan.frames!.phoneBase) {
|
|
447
|
+
const { width, height } = plan.frames!.phone!;
|
|
448
|
+
return `<picture><source media="(max-width: ${FRAMES_PHONE_MAX_WIDTH}px)" srcset="${esc(frameUrl(plan.frames!, FrameSet.Phone, 0))}" width="${width}" height="${height}">${image(size)}</picture>`;
|
|
449
|
+
}
|
|
450
|
+
return image(`${srcset ? ` srcset="${esc(srcset)}" sizes="100vw"` : ''}${size}`);
|
|
445
451
|
}
|
|
446
452
|
const poster = plan.poster ? `<img class="${VideoClass.Poster}" src="${esc(plan.poster)}" alt="" loading="lazy" decoding="async">` : '';
|
|
447
453
|
const label = esc(videoPlayLabel(plan, words));
|
package/src/scene-core/scene.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { SceneRole } from '@vectoriox/iox-ui/composite-core';
|
|
2
|
-
import { VideoAttribute, VideoClass } from '@vectoriox/iox-ui/media-core';
|
|
2
|
+
import { FRAMES_PHONE_MAX_WIDTH, VideoAttribute, VideoClass } from '@vectoriox/iox-ui/media-core';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* A Scene's data and maths (premium-sites plan §5b). A Scene is a recipe of ordinary nodes — a tall Section, a pinned
|
|
@@ -83,7 +83,7 @@ export enum SceneAttribute {
|
|
|
83
83
|
}
|
|
84
84
|
|
|
85
85
|
/** At or under this stage width a layer plays its phone tracks. The `mobile` breakpoint's width. */
|
|
86
|
-
export const SCENE_PHONE_MAX_WIDTH =
|
|
86
|
+
export const SCENE_PHONE_MAX_WIDTH = FRAMES_PHONE_MAX_WIDTH;
|
|
87
87
|
|
|
88
88
|
const DECIMALS = 10000;
|
|
89
89
|
const round = (v: number): number => Math.round(v * DECIMALS) / DECIMALS;
|
|
@@ -150,8 +150,10 @@ export function revealClipPath(showing: number, from: SceneRevealFrom = SCENE_RE
|
|
|
150
150
|
}
|
|
151
151
|
}
|
|
152
152
|
|
|
153
|
-
|
|
154
|
-
|
|
153
|
+
const linear = (t: number): number => t;
|
|
154
|
+
|
|
155
|
+
/** The two keys around `p` and how far between them it has come. */
|
|
156
|
+
function span(keys: ReadonlyArray<SceneKey>, p: number, ease: (t: number) => number = smoothstep): { a: SceneKey; b: SceneKey; f: number } {
|
|
155
157
|
const at = clamp01(p);
|
|
156
158
|
if (at <= keys[0][0]) return { a: keys[0], b: keys[0], f: 0 };
|
|
157
159
|
for (let i = 1; i < keys.length; i++) {
|
|
@@ -159,7 +161,7 @@ function span(keys: ReadonlyArray<SceneKey>, p: number): { a: SceneKey; b: Scene
|
|
|
159
161
|
const a = keys[i - 1];
|
|
160
162
|
const b = keys[i];
|
|
161
163
|
const width = b[0] - a[0];
|
|
162
|
-
return { a, b, f: width > 0 ?
|
|
164
|
+
return { a, b, f: width > 0 ? ease((at - a[0]) / width) : 1 };
|
|
163
165
|
}
|
|
164
166
|
}
|
|
165
167
|
const last = keys[keys.length - 1];
|
|
@@ -167,9 +169,9 @@ function span(keys: ReadonlyArray<SceneKey>, p: number): { a: SceneKey; b: Scene
|
|
|
167
169
|
}
|
|
168
170
|
|
|
169
171
|
/** A numeric track's value at progress `p`. */
|
|
170
|
-
export function valueAt(keys: ReadonlyArray<SceneKey>, p: number): number {
|
|
172
|
+
export function valueAt(keys: ReadonlyArray<SceneKey>, p: number, ease?: (t: number) => number): number {
|
|
171
173
|
if (!keys.length) return 0;
|
|
172
|
-
const { a, b, f } = span(keys, p);
|
|
174
|
+
const { a, b, f } = span(keys, p, ease);
|
|
173
175
|
const from = Number(a[1]);
|
|
174
176
|
return round(from + (Number(b[1]) - from) * f);
|
|
175
177
|
}
|
|
@@ -229,10 +231,13 @@ export function sceneWordsLit(tracks: ReadonlyArray<SceneTrack>, p: number): num
|
|
|
229
231
|
return track ? clampPercent(valueAt(track.keys, p)) : null;
|
|
230
232
|
}
|
|
231
233
|
|
|
232
|
-
/**
|
|
234
|
+
/**
|
|
235
|
+
* How far a layer's pictures have played at `p`, in %; null when it has no such track. A film runs straight between its
|
|
236
|
+
* keys, where everything else eases: eased over a 20-screen Scene it would crawl for its first screens and rush the middle.
|
|
237
|
+
*/
|
|
233
238
|
export function scenePlayed(tracks: ReadonlyArray<SceneTrack>, p: number): number | null {
|
|
234
239
|
const track = tracks.find(t => t.prop === SceneProp.Play);
|
|
235
|
-
return track ? clampPercent(valueAt(track.keys, p)) : null;
|
|
240
|
+
return track ? clampPercent(valueAt(track.keys, p, linear)) : null;
|
|
236
241
|
}
|
|
237
242
|
|
|
238
243
|
/** Word `index` of `count`, with `lit` % lit: words light one after the other, each easing in as the last finishes. */
|
|
@@ -307,7 +312,7 @@ export const SCENE_FLOW_RULES = `.iox-role-scene{height:auto!important}`
|
|
|
307
312
|
+ `[${SceneAttribute.Tracks}]{transform:none!important;opacity:1!important;clip-path:none!important;pointer-events:auto!important}`
|
|
308
313
|
// A Video the scroll plays stands as its poster, at the poster's own height: a percent of a stage that now flows is nothing.
|
|
309
314
|
+ `[${VideoAttribute.Frames}]{height:auto!important}`
|
|
310
|
-
+ `[${VideoAttribute.Frames}]>.${VideoClass.Poster}{position:static!important;height:auto!important}`;
|
|
315
|
+
+ `[${VideoAttribute.Frames}]>.${VideoClass.Poster},[${VideoAttribute.Frames}]>picture>.${VideoClass.Poster}{position:static!important;height:auto!important}`;
|
|
311
316
|
|
|
312
317
|
/** Under reduced motion the driver never starts, so the same rules hold for the whole visit. */
|
|
313
318
|
export const SCENE_REDUCED_MOTION_CSS = `@media (prefers-reduced-motion: reduce){${SCENE_FLOW_RULES}}`;
|
package/styles/iox-design.css
CHANGED
|
@@ -353,10 +353,13 @@ dialog[data-iox-dialog]:not([open]) {
|
|
|
353
353
|
:where(.iox-video[data-iox-video-frames]) { width: 100%; }
|
|
354
354
|
.iox-video[data-iox-video-poster-empty]:not([data-iox-video-playing]) { background-color: var(--iox-surface-2); }
|
|
355
355
|
.iox-video[data-iox-video-playing] { background-color: var(--iox-inverse-bg); }
|
|
356
|
-
.iox-video >
|
|
356
|
+
.iox-video > picture { display: contents; }
|
|
357
|
+
.iox-video > :is(.iox-video__poster, .iox-video__media, .iox-video__frame, .iox-video__frames),
|
|
358
|
+
.iox-video > picture > .iox-video__poster {
|
|
357
359
|
position: absolute; inset: 0; width: 100%; height: 100%; border: 0; margin: 0; display: block; object-fit: cover;
|
|
358
360
|
}
|
|
359
|
-
.iox-video > .iox-video__poster { z-index: -1; }
|
|
361
|
+
.iox-video > .iox-video__poster, .iox-video > picture > .iox-video__poster { z-index: -1; }
|
|
362
|
+
.iox-video[data-iox-frames-showing] > .iox-video__poster, .iox-video[data-iox-frames-showing] > picture > .iox-video__poster { visibility: hidden; }
|
|
360
363
|
.iox-video > .iox-video__media { object-fit: contain; background-color: var(--iox-inverse-bg); }
|
|
361
364
|
.iox-video[data-iox-video-mode='background'] > .iox-video__media { object-fit: cover; }
|
|
362
365
|
.iox-video__play {
|