@vectoriox/iox-ui 5.84.0 → 5.86.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/actions-core/index.d.ts +47 -3
- package/composite-core/index.d.ts +93 -15
- package/design-core/index.d.ts +2013 -187
- package/fesm2022/vectoriox-iox-ui-actions-core.mjs +153 -70
- package/fesm2022/vectoriox-iox-ui-actions-core.mjs.map +1 -1
- package/fesm2022/vectoriox-iox-ui-composite-core.mjs +307 -39
- package/fesm2022/vectoriox-iox-ui-composite-core.mjs.map +1 -1
- package/fesm2022/vectoriox-iox-ui-design-core.mjs +2222 -85
- package/fesm2022/vectoriox-iox-ui-design-core.mjs.map +1 -1
- package/fesm2022/vectoriox-iox-ui-forms-core.mjs +27 -4
- package/fesm2022/vectoriox-iox-ui-forms-core.mjs.map +1 -1
- package/fesm2022/vectoriox-iox-ui-generation-core.mjs +250 -12
- package/fesm2022/vectoriox-iox-ui-generation-core.mjs.map +1 -1
- package/fesm2022/vectoriox-iox-ui-scene-core.mjs +101 -5
- package/fesm2022/vectoriox-iox-ui-scene-core.mjs.map +1 -1
- package/fesm2022/vectoriox-iox-ui.mjs +1545 -78
- package/fesm2022/vectoriox-iox-ui.mjs.map +1 -1
- package/forms-core/index.d.ts +23 -4
- package/generation-core/index.d.ts +13 -0
- package/index.d.ts +563 -62
- package/package.json +1 -1
- package/scene-core/index.d.ts +32 -4
- package/src/actions-core/action.model.ts +1 -1
- package/src/actions-core/dialog-motion.ts +25 -0
- package/src/actions-core/dialog-opener.ts +9 -3
- package/src/actions-core/element-actions.ts +25 -1
- package/src/actions-core/popover-opener.ts +50 -0
- package/src/actions-core/public-api.ts +1 -0
- package/src/composite-core/header.ts +25 -0
- package/src/composite-core/public-api.ts +1 -0
- package/src/composite-core/slider-runtime.ts +82 -0
- package/src/composite-core/slider.ts +194 -27
- package/src/composite-core/structure.ts +14 -9
- package/src/design-core/flavor-spec.generated.ts +2145 -85
- package/src/design-core/header-behaviour.ts +76 -0
- package/src/design-core/public-api.ts +1 -0
- package/src/design-core/theme-color.ts +21 -5
- package/src/forms-core/submit-lifecycle.ts +30 -3
- package/src/generation-core/baseline-roles.ts +1 -0
- package/src/generation-core/compose-site.ts +8 -0
- package/src/generation-core/node-baselines.generated.ts +231 -11
- package/src/generation-core/patterns.ts +2 -0
- package/src/generation-core/site-spec.generated.ts +8 -0
- package/src/scene-core/scene.ts +18 -0
- package/src/scene-core/scroll-driver.ts +83 -6
- package/styles/iox-design.css +431 -1
- package/styles/iox-forms.css +29 -4
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { SceneRole } from '@vectoriox/iox-ui/composite-core';
|
|
2
2
|
import { FramePlayer, normalizeFrameSequence, VideoAttribute } from '@vectoriox/iox-ui/media-core';
|
|
3
3
|
|
|
4
|
-
import { normalizeTracks, SCENE_PHONE_MAX_WIDTH, SceneAttribute, sceneFrame, SceneFrame, scenePlayed, sceneWordOpacity, sceneWordsLit, SceneTrack } from './scene';
|
|
4
|
+
import { heldProgress, normalizeTracks, SCENE_PHONE_MAX_WIDTH, SceneAttribute, sceneFrame, SceneFrame, scenePlayed, sceneWordOpacity, sceneWordsLit, SceneTrack } from './scene';
|
|
5
5
|
import { LIGHT_UP_PIECE_CAP, SplitText, splitText } from './split-text';
|
|
6
6
|
|
|
7
7
|
/**
|
|
@@ -24,9 +24,32 @@ const SETTLED_PX = 0.25;
|
|
|
24
24
|
const MAX_FRAME_SECONDS = 0.1;
|
|
25
25
|
/** The first frame after a sleep has no previous one to measure from: it counts as one frame, or a scroll would lose it. */
|
|
26
26
|
const WAKING_FRAME_SECONDS = 1 / 60;
|
|
27
|
-
const STAGE_SELECTOR = `.iox-role-${SceneRole.Stage.replace(/\./g, '-')}`;
|
|
27
|
+
const STAGE_SELECTOR = `.iox-role-${SceneRole.Stage.replace(/\./g, '-')}, [${SceneAttribute.Stage}]`;
|
|
28
28
|
const TRANSFORM_WILL_CHANGE = 'transform, opacity';
|
|
29
29
|
|
|
30
|
+
/**
|
|
31
|
+
* Dispatched (bubbling) by something the driver plays that arrived after the driver last looked — a Swiper booted on a
|
|
32
|
+
* slider's stage. Never a DOM `scroll`: nothing that follows the driver may dispatch one (SC2).
|
|
33
|
+
*/
|
|
34
|
+
export const SCENE_WAKE_EVENT = 'iox-scene-wake';
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* ONE driver per root (premium M4 — §0 "To verify first"). The live site attaches a driver per runtime instance (the
|
|
38
|
+
* page's and the globals'); an instance that scanned a container holding the other's Scenes would drive them a second
|
|
39
|
+
* time, two damped values fighting over one layer. A root is held by the first driver that adds it until that driver
|
|
40
|
+
* lets it go.
|
|
41
|
+
*/
|
|
42
|
+
const HELD = new WeakMap<HTMLElement, ScrollDriver>();
|
|
43
|
+
|
|
44
|
+
/** The slider a stage carries when the scroll plays it: a booted Swiper (it sets `el.swiper`), read by shape. */
|
|
45
|
+
interface PlayedSlider { slides?: ArrayLike<unknown>; setProgress?: (progress: number, speed?: number) => void; destroyed?: boolean }
|
|
46
|
+
function sliderOf(stage: HTMLElement | null): PlayedSlider | null {
|
|
47
|
+
// The stage is the runway's child: the `.swiper` itself (both engine paths), or the canvas's shell around it.
|
|
48
|
+
const holder = (stage?.classList.contains('swiper') ? stage : stage?.querySelector('.swiper')) as (HTMLElement & { swiper?: PlayedSlider }) | null | undefined;
|
|
49
|
+
const swiper = holder?.swiper;
|
|
50
|
+
return swiper && typeof swiper.setProgress === 'function' && !swiper.destroyed ? swiper : null;
|
|
51
|
+
}
|
|
52
|
+
|
|
30
53
|
/** One frame of the damping: frame-rate independent, so 60 Hz and 120 Hz settle in the same time. */
|
|
31
54
|
export function dampToward(current: number, target: number, seconds: number, rate: number = SCENE_DAMPING): number {
|
|
32
55
|
return current + (target - current) * (1 - Math.exp(-seconds * rate));
|
|
@@ -86,6 +109,8 @@ interface Scene {
|
|
|
86
109
|
/** Page px and the stage's own width, measured when something resizes and never per frame. */
|
|
87
110
|
height: number;
|
|
88
111
|
travel: number;
|
|
112
|
+
/** Page px from the top of the screen at which the stage holds: 0 for a Scene, mid-screen for a played slider. */
|
|
113
|
+
stick: number;
|
|
89
114
|
phone: boolean;
|
|
90
115
|
inlineSign: 1 | -1;
|
|
91
116
|
/** No frame written yet: the first one lands exactly where the scroll is, with no glide. */
|
|
@@ -105,20 +130,28 @@ export class ScrollDriver {
|
|
|
105
130
|
private frame = 0;
|
|
106
131
|
private lastTime = 0;
|
|
107
132
|
private readonly wakeUp = (): void => this.wake();
|
|
133
|
+
private readonly onWakeEvent = (e: Event): void => {
|
|
134
|
+
const scene = [...this.scenes.values()].find(s => s.root.contains(e.target as Node));
|
|
135
|
+
if (scene) { scene.fresh = true; this.wake(); }
|
|
136
|
+
};
|
|
108
137
|
|
|
109
138
|
constructor(private readonly options: ScrollDriverOptions = {}) {
|
|
110
139
|
this.host = options.host ?? window;
|
|
111
140
|
// In capture: a scroll does not bubble, and the canvas's Lenis scrolls an element, not the window.
|
|
112
141
|
this.host.addEventListener('scroll', this.wakeUp, { capture: true, passive: true });
|
|
113
142
|
this.host.addEventListener('resize', this.wakeUp, { passive: true });
|
|
143
|
+
this.host.addEventListener(SCENE_WAKE_EVENT, this.onWakeEvent, { capture: true });
|
|
114
144
|
const Observer = (this.host as Window & { ResizeObserver?: typeof ResizeObserver }).ResizeObserver;
|
|
115
145
|
this.resize = Observer ? new Observer(() => this.remeasureAll()) : null;
|
|
116
146
|
}
|
|
117
147
|
|
|
118
|
-
/** Drive a Scene's root. Idempotent: adding a root again re-reads it. */
|
|
119
|
-
add(root: HTMLElement):
|
|
148
|
+
/** Drive a Scene's root. Idempotent: adding a root again re-reads it. False when another driver holds it. */
|
|
149
|
+
add(root: HTMLElement): boolean {
|
|
150
|
+
const holder = HELD.get(root);
|
|
151
|
+
if (holder && holder !== this) return false;
|
|
120
152
|
this.remove(root);
|
|
121
|
-
|
|
153
|
+
HELD.set(root, this);
|
|
154
|
+
const scene: Scene = { root, stage: null, layers: [], progress: 0, target: 0, height: 0, travel: 0, stick: 0, phone: false, inlineSign: 1, fresh: true, mutations: null };
|
|
122
155
|
this.scenes.set(root, scene);
|
|
123
156
|
this.resize?.observe(root);
|
|
124
157
|
if (this.options.observe) {
|
|
@@ -129,11 +162,18 @@ export class ScrollDriver {
|
|
|
129
162
|
}
|
|
130
163
|
}
|
|
131
164
|
this.readScene(scene);
|
|
165
|
+
return true;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/** True when this driver holds `root`. */
|
|
169
|
+
holds(root: HTMLElement): boolean {
|
|
170
|
+
return this.scenes.has(root);
|
|
132
171
|
}
|
|
133
172
|
|
|
134
173
|
remove(root: HTMLElement): void {
|
|
135
174
|
const scene = this.scenes.get(root);
|
|
136
175
|
if (!scene) return;
|
|
176
|
+
if (HELD.get(root) === this) HELD.delete(root);
|
|
137
177
|
scene.mutations?.disconnect();
|
|
138
178
|
this.resize?.unobserve(root);
|
|
139
179
|
if (scene.stage) scene.stage.style.removeProperty('translate');
|
|
@@ -198,6 +238,7 @@ export class ScrollDriver {
|
|
|
198
238
|
this.frame = 0;
|
|
199
239
|
this.host.removeEventListener('scroll', this.wakeUp, { capture: true });
|
|
200
240
|
this.host.removeEventListener('resize', this.wakeUp);
|
|
241
|
+
this.host.removeEventListener(SCENE_WAKE_EVENT, this.onWakeEvent, { capture: true });
|
|
201
242
|
for (const root of [...this.scenes.keys()]) this.remove(root);
|
|
202
243
|
this.resize?.disconnect();
|
|
203
244
|
}
|
|
@@ -250,6 +291,9 @@ export class ScrollDriver {
|
|
|
250
291
|
private measure(scene: Scene): void {
|
|
251
292
|
scene.height = scene.root.offsetHeight;
|
|
252
293
|
scene.travel = Math.max(0, scene.height - (scene.stage?.offsetHeight ?? scene.height));
|
|
294
|
+
// Where the stage holds: its sticky top (a played slider holds mid-screen; a Scene's stage at 0). Read when
|
|
295
|
+
// something resizes, never per frame.
|
|
296
|
+
scene.stick = scene.stage ? (parseFloat(this.host.getComputedStyle(scene.stage).top) || 0) : 0;
|
|
253
297
|
scene.phone = (scene.stage?.offsetWidth ?? Infinity) <= SCENE_PHONE_MAX_WIDTH;
|
|
254
298
|
scene.inlineSign = scene.stage && this.host.getComputedStyle(scene.stage).direction === 'rtl' ? -1 : 1;
|
|
255
299
|
for (const layer of scene.layers) layer.stale = true;
|
|
@@ -281,11 +325,14 @@ export class ScrollDriver {
|
|
|
281
325
|
const rect = scene.root.getBoundingClientRect();
|
|
282
326
|
const origin = this.options.originOf?.(scene.root)?.getBoundingClientRect().top ?? 0;
|
|
283
327
|
const zoom = scene.height ? rect.height / scene.height : 1;
|
|
284
|
-
const { local, progress } = sceneScrollState(rect.top, origin, zoom, scene.travel);
|
|
328
|
+
const { local, progress } = sceneScrollState(rect.top, origin + scene.stick * zoom, zoom, scene.travel);
|
|
285
329
|
return { scene, local, target: progress };
|
|
286
330
|
}
|
|
287
331
|
|
|
288
332
|
private write(scene: Scene): void {
|
|
333
|
+
// A slider the scroll plays (M4): the damped moment, held on each picture.
|
|
334
|
+
const slider = sliderOf(scene.stage);
|
|
335
|
+
if (slider) slider.setProgress!(heldProgress(scene.progress, slider.slides?.length ?? 0), 0);
|
|
289
336
|
for (const layer of scene.layers) {
|
|
290
337
|
const tracks = scene.phone && layer.phoneTracks.length ? layer.phoneTracks : layer.tracks;
|
|
291
338
|
this.writeWords(layer, sceneWordsLit(tracks, scene.progress));
|
|
@@ -359,3 +406,33 @@ export function attachScenes(container: HTMLElement, options: ScrollDriverOption
|
|
|
359
406
|
roots.forEach(root => driver.add(root));
|
|
360
407
|
return driver;
|
|
361
408
|
}
|
|
409
|
+
|
|
410
|
+
/**
|
|
411
|
+
* The Scene roots that belong to `elements` (each, and what it holds) — what one runtime instance owns: the page's nodes,
|
|
412
|
+
* or the globals'. Never a whole container that also holds another instance's Scenes (premium M4: the globals' instance
|
|
413
|
+
* scanned the app root and drove the page's Scenes a second time).
|
|
414
|
+
*/
|
|
415
|
+
export function sceneRootsIn(elements: Iterable<Element>): HTMLElement[] {
|
|
416
|
+
const out = new Set<HTMLElement>();
|
|
417
|
+
for (const el of elements) {
|
|
418
|
+
if ((el as HTMLElement).hasAttribute?.(SceneAttribute.Root)) out.add(el as HTMLElement);
|
|
419
|
+
// A wrapper the node renders around itself (a played slider's runway around its `.swiper`) is the node's own.
|
|
420
|
+
const parent = el.parentElement;
|
|
421
|
+
if (parent?.hasAttribute(SceneAttribute.Root) && parent.children.length === 1) out.add(parent);
|
|
422
|
+
el.querySelectorAll?.<HTMLElement>(`[${SceneAttribute.Root}]`).forEach(root => out.add(root));
|
|
423
|
+
}
|
|
424
|
+
return [...out];
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
/** The live site's door for a runtime that owns some nodes: their Scenes on one driver (null when none, or under reduced
|
|
428
|
+
* motion — the page's own CSS then lets each Scene flow). A root another driver holds stays with it. */
|
|
429
|
+
export function attachSceneRoots(roots: ReadonlyArray<HTMLElement>, options: ScrollDriverOptions = {}): ScrollDriver | null {
|
|
430
|
+
if (!roots.length) return null;
|
|
431
|
+
const host = options.host ?? window;
|
|
432
|
+
if (host.matchMedia?.('(prefers-reduced-motion: reduce)').matches) return null;
|
|
433
|
+
const driver = new ScrollDriver(options);
|
|
434
|
+
const held = roots.filter(root => driver.add(root));
|
|
435
|
+
if (held.length) return driver;
|
|
436
|
+
driver.destroy();
|
|
437
|
+
return null;
|
|
438
|
+
}
|
package/styles/iox-design.css
CHANGED
|
@@ -142,13 +142,40 @@ iox-gallery.iox-es-media[data-iox-empty] > .iox-gallery {
|
|
|
142
142
|
--iox-image-hover-scale: 1;
|
|
143
143
|
}
|
|
144
144
|
.iox-es-card .iox-es-media {
|
|
145
|
-
transition: transform var(--iox-dur-
|
|
145
|
+
transition: transform var(--iox-dur-ui) var(--iox-ease-standard), scale var(--iox-dur-ui) var(--iox-ease-standard);
|
|
146
146
|
}
|
|
147
147
|
@media (hover: hover) and (pointer: fine) {
|
|
148
148
|
:is(a.iox-es-card, .iox-es-card:has(> a.linked-container-root), .iox-es-card:has(.iox-card-link)):hover .iox-es-media {
|
|
149
149
|
scale: var(--card-media-zoom);
|
|
150
150
|
}
|
|
151
151
|
}
|
|
152
|
+
/* M4 (the D3 follow-ups — Emil 144, 28, 74): a LINKED card's lift, its raised shadow and its picture's zoom are ONE
|
|
153
|
+
timing under 300ms (dur-ui; the card style's dur-base and the media's dur-slow ran past it in most looks). Its shadow
|
|
154
|
+
rises by fading a pseudo-element that wears card-shadow-raise — the card's own box-shadow no longer changes on hover
|
|
155
|
+
(every card's hover shadow is its rest shadow now; a non-linked card never rose). `translate: 0px` is identity: it
|
|
156
|
+
makes the card the pseudo's containing block and stacking context whatever position the card holds (the canvas
|
|
157
|
+
forces its own), so the pseudo sits under the card's content and its shadow outside the card's edge. */
|
|
158
|
+
:is(a.iox-es-card, .iox-es-card:has(> a.linked-container-root), .iox-es-card:has(.iox-card-link)) {
|
|
159
|
+
--iox-card-shadow-hover: var(--iox-card-shadow);
|
|
160
|
+
--iox-dur-base: var(--iox-dur-ui);
|
|
161
|
+
translate: 0px;
|
|
162
|
+
}
|
|
163
|
+
:is(a.iox-es-card, .iox-es-card:has(> a.linked-container-root), .iox-es-card:has(.iox-card-link))::before {
|
|
164
|
+
content: '';
|
|
165
|
+
position: absolute;
|
|
166
|
+
inset: calc(var(--iox-card-border-width, 0px) * -1);
|
|
167
|
+
border-radius: inherit;
|
|
168
|
+
box-shadow: var(--iox-card-shadow-raise);
|
|
169
|
+
opacity: 0;
|
|
170
|
+
pointer-events: none;
|
|
171
|
+
z-index: -1;
|
|
172
|
+
transition: opacity var(--iox-dur-ui) var(--iox-ease-standard);
|
|
173
|
+
}
|
|
174
|
+
@media (hover: hover) and (pointer: fine) {
|
|
175
|
+
:is(a.iox-es-card, .iox-es-card:has(> a.linked-container-root), .iox-es-card:has(.iox-card-link)):hover::before {
|
|
176
|
+
opacity: 1;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
152
179
|
|
|
153
180
|
/* ── CC1: mobile navigation (complex-components plan §7a, R1; boards 1–2) ─────────────────────── */
|
|
154
181
|
|
|
@@ -226,6 +253,16 @@ dialog[data-iox-dialog].iox-es:is([data-iox-closing], :not([open]))::backdrop {
|
|
|
226
253
|
@starting-style {
|
|
227
254
|
dialog[data-iox-dialog].iox-es[open]::backdrop { opacity: 0; }
|
|
228
255
|
}
|
|
256
|
+
/* M4 (M1.1's carry-over — Emil 101): a styled dialog separates from the page behind it by a hairline edge in the look's
|
|
257
|
+
`dialog-edge` (its line colour) — on a DARK page under a dark scrim its shadow is swallowed and the phone drawer
|
|
258
|
+
vanished into the page. An outline, so the dialog's own box-shadow (the look's, or the author's) is untouched; drawn
|
|
259
|
+
inside its edge, following its radius. The sheet itself takes focus when it opens (the engine), so it never rings:
|
|
260
|
+
the first Tab moves to its Close, with the ring. */
|
|
261
|
+
dialog[data-iox-dialog].iox-es {
|
|
262
|
+
outline: var(--iox-border-width, 1px) solid var(--iox-dialog-edge, transparent);
|
|
263
|
+
outline-offset: calc(var(--iox-border-width, 1px) * -1);
|
|
264
|
+
}
|
|
265
|
+
|
|
229
266
|
/* The drawer's pages arrive WITH it (decision D1 — Emil 64/65/110: one entrance per container, no menu-row stagger):
|
|
230
267
|
the sheet slides in carrying its links, and they leave with it. */
|
|
231
268
|
|
|
@@ -240,6 +277,7 @@ dialog[data-iox-dialog] {
|
|
|
240
277
|
.iox-role-nav-links,
|
|
241
278
|
.iox-role-nav-drawer-links {
|
|
242
279
|
--iox-link-decoration: none;
|
|
280
|
+
--iox-link-underline-rest: 0;
|
|
243
281
|
}
|
|
244
282
|
|
|
245
283
|
/* The BOTTOM BAR (board 2, "The CSS, exactly"): fixed to the bottom of the viewport (on the canvas, of the
|
|
@@ -474,3 +512,395 @@ span.iox-video__play { pointer-events: none; }
|
|
|
474
512
|
background: var(--iox-scrim);
|
|
475
513
|
}
|
|
476
514
|
}
|
|
515
|
+
|
|
516
|
+
/* ── Premium components M4 (T2): how a header behaves as the page scrolls ─────────────────────────────────────────
|
|
517
|
+
Keyed on the attributes every renderer writes from the header's two traits (design-core headerAttributes). A header
|
|
518
|
+
that STAYS or HIDES keeps to the top of the screen while the page scrolls under it; one that hides steps aside on the
|
|
519
|
+
way down, moved by the header runtime (`translate`, from the live value — HeaderRuntime). On the live site the
|
|
520
|
+
globals' wrapper is `display: contents`, so the header sticks to the whole page; on the builder canvas the header lives
|
|
521
|
+
in the frame-pinned overlay (global-elements.md) and the canvas keeps its own `position`. */
|
|
522
|
+
:root [data-iox-header] {
|
|
523
|
+
position: sticky;
|
|
524
|
+
inset-block-start: 0;
|
|
525
|
+
z-index: 30;
|
|
526
|
+
}
|
|
527
|
+
/* Faded under reduced motion (the runtime): not clickable while it is away. */
|
|
528
|
+
[data-iox-header][data-iox-header-hidden] {
|
|
529
|
+
pointer-events: none;
|
|
530
|
+
}
|
|
531
|
+
/* The EDGE (Emil 99): once the page has moved under it, a header shows a soft fade of its own ground — or a hairline —
|
|
532
|
+
instead of a divider drawn at all times. Its look's own bottom line gives way; the edge is a pseudo-element that only
|
|
533
|
+
fades in (no layout, no box-shadow). An eased gradient (Emil 174): no visible band where the fade ends. Only on a
|
|
534
|
+
header that stays at the top (one that scrolls away is gone by the time the page has moved); the header is its
|
|
535
|
+
containing block — sticky here, and `relative` as stored (the Header section writes it; the canvas forces a node's
|
|
536
|
+
own position, and a transform on the header would trap its drawer's stage). */
|
|
537
|
+
[data-iox-header][data-iox-header-edge] {
|
|
538
|
+
--iox-nav-border-width: 0px;
|
|
539
|
+
}
|
|
540
|
+
[data-iox-header][data-iox-header-edge]::after {
|
|
541
|
+
content: '';
|
|
542
|
+
position: absolute;
|
|
543
|
+
inset-inline: 0;
|
|
544
|
+
inset-block-start: 100%;
|
|
545
|
+
block-size: 16px;
|
|
546
|
+
pointer-events: none;
|
|
547
|
+
opacity: 0;
|
|
548
|
+
transition: opacity var(--iox-dur-ui) var(--iox-ease-standard);
|
|
549
|
+
}
|
|
550
|
+
[data-iox-header][data-iox-header-edge='fade']::after {
|
|
551
|
+
background: linear-gradient(to bottom,
|
|
552
|
+
color-mix(in srgb, var(--iox-nav-bg, transparent) 100%, transparent) 0%,
|
|
553
|
+
color-mix(in srgb, var(--iox-nav-bg, transparent) 74%, transparent) 19%,
|
|
554
|
+
color-mix(in srgb, var(--iox-nav-bg, transparent) 54%, transparent) 34%,
|
|
555
|
+
color-mix(in srgb, var(--iox-nav-bg, transparent) 34%, transparent) 50%,
|
|
556
|
+
color-mix(in srgb, var(--iox-nav-bg, transparent) 17%, transparent) 67%,
|
|
557
|
+
color-mix(in srgb, var(--iox-nav-bg, transparent) 6%, transparent) 83%,
|
|
558
|
+
transparent 100%);
|
|
559
|
+
}
|
|
560
|
+
[data-iox-header][data-iox-header-edge='line']::after {
|
|
561
|
+
block-size: var(--iox-border-width, 1px);
|
|
562
|
+
background: var(--iox-nav-border, currentColor);
|
|
563
|
+
}
|
|
564
|
+
[data-iox-scrolled] [data-iox-header][data-iox-header-edge]:not([data-iox-header-edge='none'])::after {
|
|
565
|
+
opacity: 1;
|
|
566
|
+
}
|
|
567
|
+
/* Emil 194/195: reduced transparency makes the bar solid (the token sheet clears its blur and its ground); more
|
|
568
|
+
contrast makes it near-solid with a defined, contrasting edge. A Brand-scheme bar keeps its own solid ground. */
|
|
569
|
+
@media (prefers-contrast: more) {
|
|
570
|
+
.iox-es-nav:not([class*='iox-scheme-']) {
|
|
571
|
+
--iox-nav-bg: var(--iox-bg);
|
|
572
|
+
}
|
|
573
|
+
.iox-es-nav,
|
|
574
|
+
[data-iox-header][data-iox-header-edge] {
|
|
575
|
+
--iox-nav-backdrop: none;
|
|
576
|
+
--iox-nav-border: var(--iox-text);
|
|
577
|
+
--iox-nav-border-width: 0px 0px var(--iox-border-width, 1px) 0px;
|
|
578
|
+
}
|
|
579
|
+
.iox-es-bottom-bar {
|
|
580
|
+
--iox-tabbar-bg: var(--iox-bg);
|
|
581
|
+
--iox-tabbar-backdrop: none;
|
|
582
|
+
--iox-tabbar-border: var(--iox-text);
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
/* ── Premium components M4 (T3): a link's underline, drawn ──────────────────────────────────────────────────────────
|
|
587
|
+
An element-styled link draws its underline as a line in its own ink under its words, scaled along them from the
|
|
588
|
+
READING START (mirrored in RTL) — so it can grow on hover. `link-underline-rest` is how much shows at rest (1 in
|
|
589
|
+
running content; 0 in a navigation's lists, by their role): it grows on hover (hover devices only — G1), for keyboard
|
|
590
|
+
focus, and the current page's link holds its 2px accent line. The node's own text-decoration reads none here (the
|
|
591
|
+
two decoration tokens are re-declared), so a line is never drawn twice; a legacy link keeps its text-decoration.
|
|
592
|
+
A background line, so it follows the words onto a second line and needs no positioning (the canvas forces a node's
|
|
593
|
+
position); its growth is a background-size the shared compiler adds to the link's transition (withStateTransitions).
|
|
594
|
+
The colour and the line move on the hover timing — ≤150ms on `ease` (Emil 92). */
|
|
595
|
+
.iox-es-link {
|
|
596
|
+
--iox-link-decoration: none;
|
|
597
|
+
--iox-link-current-decoration: none;
|
|
598
|
+
--iox-dur-fast: var(--iox-dur-hover);
|
|
599
|
+
--iox-ease-standard: var(--iox-ease-hover);
|
|
600
|
+
background-image: linear-gradient(currentColor, currentColor);
|
|
601
|
+
background-repeat: no-repeat;
|
|
602
|
+
background-position: 0% calc(100% - var(--iox-link-underline-offset, 0px));
|
|
603
|
+
background-size: calc(var(--iox-link-underline-rest, 1) * 100%) var(--iox-link-underline-thickness, 1px);
|
|
604
|
+
-webkit-box-decoration-break: clone;
|
|
605
|
+
box-decoration-break: clone;
|
|
606
|
+
}
|
|
607
|
+
.iox-es-link:dir(rtl) {
|
|
608
|
+
background-position-x: 100%;
|
|
609
|
+
}
|
|
610
|
+
@media (hover: hover) and (pointer: fine) {
|
|
611
|
+
.iox-es-link:hover {
|
|
612
|
+
background-size: 100% var(--iox-link-underline-thickness, 1px);
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
.iox-es-link:focus-visible {
|
|
616
|
+
background-size: 100% var(--iox-link-underline-thickness, 1px);
|
|
617
|
+
}
|
|
618
|
+
.iox-es-link[aria-current='page'] {
|
|
619
|
+
background-image: linear-gradient(var(--iox-link-current-underline, currentColor), var(--iox-link-current-underline, currentColor));
|
|
620
|
+
background-size: 100% var(--iox-link-current-underline-thickness, 2px);
|
|
621
|
+
}
|
|
622
|
+
/* Reduced motion: the line appears and goes in place (it never travels); the colour still eases (Emil 192). */
|
|
623
|
+
@media (prefers-reduced-motion: reduce) {
|
|
624
|
+
.iox-es.iox-es-link {
|
|
625
|
+
transition-property: color;
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
/* A chip's colour moves on the hover timing too (Emil 92 — M3's review). */
|
|
629
|
+
.iox-es-chip {
|
|
630
|
+
--iox-dur-fast: var(--iox-dur-hover);
|
|
631
|
+
--iox-ease-standard: var(--iox-ease-hover);
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
/* ── Premium components M4 (Emil 55/85): a press lands faster than its release ──────────────────────────────────────
|
|
635
|
+
While pressed, every pressable's transition runs on dur-press; it releases on its own (slower) dur-fast. */
|
|
636
|
+
:is(.iox-es-button-primary, .iox-es-button-secondary, .iox-es-button-ghost, .iox-es-bottom-bar-item):active {
|
|
637
|
+
transition-duration: var(--iox-dur-press);
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
/* ── Premium components M4 (T3): the Slider's feel, its bar and the slider the scroll plays ─────────────────────────
|
|
641
|
+
One curve for the slides: the look's (Emil 18/21 — never Swiper's built-in `ease`). A horizontal carousel leaves
|
|
642
|
+
vertical panning to the page (M9). */
|
|
643
|
+
:where(.swiper) {
|
|
644
|
+
--swiper-wrapper-transition-timing-function: var(--iox-slider-ease, ease);
|
|
645
|
+
}
|
|
646
|
+
:where(.swiper-horizontal) {
|
|
647
|
+
touch-action: pan-y;
|
|
648
|
+
}
|
|
649
|
+
/* The BAR under the picture (M1.3): the caption at the reading start, then the count and the arrows — in flow, never on
|
|
650
|
+
the picture (the bar node lays them out; these keep its words from pushing the arrows). The count reads "1 / 3" in
|
|
651
|
+
every language. */
|
|
652
|
+
:where(.iox-role-slider-bar) .iox-role-slider-caption {
|
|
653
|
+
min-width: 0;
|
|
654
|
+
}
|
|
655
|
+
/* Their words are the slide in view's, written by the runtime as an attribute (composite-core attachSliderBar). */
|
|
656
|
+
:is(.iox-role-slider-caption, .iox-role-slider-count)::before {
|
|
657
|
+
content: attr(data-iox-text);
|
|
658
|
+
}
|
|
659
|
+
.iox-role-slider-count {
|
|
660
|
+
direction: ltr;
|
|
661
|
+
unicode-bidi: isolate;
|
|
662
|
+
font-variant-numeric: tabular-nums;
|
|
663
|
+
white-space: nowrap;
|
|
664
|
+
}
|
|
665
|
+
/* The caption follows the slide in view: it changes behind a short fade (the runtime holds it at 0 for a frame). */
|
|
666
|
+
.iox-role-slider-caption {
|
|
667
|
+
transition: opacity var(--iox-dur-hover) var(--iox-ease-hover);
|
|
668
|
+
}
|
|
669
|
+
.iox-role-slider-caption[data-iox-swapping] {
|
|
670
|
+
opacity: 0;
|
|
671
|
+
transition-duration: 0ms;
|
|
672
|
+
}
|
|
673
|
+
/* On a phone a "cards" stack stays a centred card with its neighbours peeking out (one picture the full width is
|
|
674
|
+
planSwiperOptions' default for every other effect). */
|
|
675
|
+
@media (max-width: 767px) {
|
|
676
|
+
.swiper.swiper-cards {
|
|
677
|
+
width: 86%;
|
|
678
|
+
margin-inline: auto;
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
/* A Slider the SCROLL plays: its runway holds the slider mid-screen while the page scrolls past half a screen per
|
|
682
|
+
picture; the ONE scene driver moves it, resting on each picture (scene-core `heldProgress`). `--slider-steps` is
|
|
683
|
+
written by the renderers (no layout shift), `--slider-h` by the slider's boot. On the canvas the driver pins it
|
|
684
|
+
(its page never scrolls itself) and `--preview-vh` stands for the frame's height. Under reduced motion it is an
|
|
685
|
+
ordinary slider: no runway, swiped and pressed. */
|
|
686
|
+
.iox-slider-runway {
|
|
687
|
+
position: relative;
|
|
688
|
+
}
|
|
689
|
+
.iox-slider-runway > :is(.swiper, .slider-edit-shell) {
|
|
690
|
+
position: sticky;
|
|
691
|
+
inset-block-start: max(0px, calc((var(--preview-vh, 1svh) * 100 - var(--slider-h, 0px)) / 2));
|
|
692
|
+
}
|
|
693
|
+
.iox-slider-runway::after {
|
|
694
|
+
content: '';
|
|
695
|
+
display: block;
|
|
696
|
+
block-size: calc(var(--slider-steps, 0) * var(--preview-vh, 1svh) * 55);
|
|
697
|
+
}
|
|
698
|
+
@media (prefers-reduced-motion: reduce) {
|
|
699
|
+
.iox-slider-runway::after {
|
|
700
|
+
display: none;
|
|
701
|
+
}
|
|
702
|
+
.iox-slider-runway > :is(.swiper, .slider-edit-shell) {
|
|
703
|
+
position: relative;
|
|
704
|
+
inset-block-start: 0;
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
/* ── The floating family (premium components M5 — N3 toast · N4 tooltip and popover) ──────────────────────────────
|
|
709
|
+
ONE motion grammar for all three: they come FROM where they belong — a tooltip and a popover grow from their trigger
|
|
710
|
+
(the transform-origin placeFloating writes, Emil 47), a toast slides from its edge and back to it (Emil 129–130) —
|
|
711
|
+
and leave faster than they came (Emil 30). A neighbour tooltip, a keyboard open and Esc have no motion at all
|
|
712
|
+
(`data-iox-instant`, Emil 2/125). On a page that wears the design system the look's tokens time them (a look whose
|
|
713
|
+
motion is Off: 0ms); under reduced motion they keep the fade and drop the movement (Emil 192). The element styles'
|
|
714
|
+
colours and type are the contract's floating baseline, verbatim (`.iox-es-tooltip` / `.iox-es-toast`, pinned by the
|
|
715
|
+
token-names spec); a legacy page keeps the neutral literals of the unmarked rules. */
|
|
716
|
+
|
|
717
|
+
/* The layer they are placed in: the viewport on the site (a top-layer popover, so a fixed header, a slider's clip or a
|
|
718
|
+
nav's backdrop-filter cannot hold them), the page frame on the canvas (fixed inside the zoomed frame). */
|
|
719
|
+
.iox-float-layer {
|
|
720
|
+
position: fixed;
|
|
721
|
+
inset: 0;
|
|
722
|
+
width: auto;
|
|
723
|
+
height: auto;
|
|
724
|
+
max-width: none;
|
|
725
|
+
max-height: none;
|
|
726
|
+
margin: 0;
|
|
727
|
+
padding: 0;
|
|
728
|
+
border: 0;
|
|
729
|
+
background: transparent;
|
|
730
|
+
color: inherit;
|
|
731
|
+
overflow: visible;
|
|
732
|
+
pointer-events: none;
|
|
733
|
+
z-index: 60;
|
|
734
|
+
}
|
|
735
|
+
.iox-float-layer[popover]:not(:popover-open) { display: none; }
|
|
736
|
+
|
|
737
|
+
/* The tooltip. */
|
|
738
|
+
.iox-tooltip {
|
|
739
|
+
--tip-scale: 0.97;
|
|
740
|
+
position: absolute;
|
|
741
|
+
top: 0;
|
|
742
|
+
left: 0;
|
|
743
|
+
box-sizing: border-box;
|
|
744
|
+
max-width: min(280px, calc(100% - 24px));
|
|
745
|
+
padding: 6px 10px;
|
|
746
|
+
font: 500 13px/1.35 system-ui, -apple-system, 'Segoe UI', sans-serif;
|
|
747
|
+
background-color: #1f2328;
|
|
748
|
+
color: #ffffff;
|
|
749
|
+
border-radius: 6px;
|
|
750
|
+
box-shadow: 0 4px 14px rgba(0, 0, 0, 0.18);
|
|
751
|
+
overflow-wrap: anywhere;
|
|
752
|
+
text-align: start;
|
|
753
|
+
pointer-events: auto;
|
|
754
|
+
transition: opacity 125ms cubic-bezier(0.23, 1, 0.32, 1), scale 125ms cubic-bezier(0.23, 1, 0.32, 1);
|
|
755
|
+
}
|
|
756
|
+
.iox-tooltip[hidden] { display: none; }
|
|
757
|
+
@starting-style { .iox-tooltip:not([hidden]) { opacity: 0; scale: var(--tip-scale); } }
|
|
758
|
+
.iox-tooltip[data-iox-closing] { opacity: 0; scale: var(--tip-scale); transition-duration: 80ms; }
|
|
759
|
+
.iox-es-tooltip {
|
|
760
|
+
background-color: var(--iox-tooltip-bg);
|
|
761
|
+
color: var(--iox-tooltip-ink);
|
|
762
|
+
border-radius: var(--iox-tooltip-radius);
|
|
763
|
+
box-shadow: var(--iox-tooltip-shadow);
|
|
764
|
+
font-family: var(--iox-font-body);
|
|
765
|
+
font-size: var(--iox-tooltip-size);
|
|
766
|
+
font-weight: var(--iox-weight-strong);
|
|
767
|
+
}
|
|
768
|
+
.iox-tooltip.iox-es-tooltip {
|
|
769
|
+
--tip-scale: var(--iox-tooltip-scale);
|
|
770
|
+
transition: opacity var(--iox-tooltip-dur-in) var(--iox-tooltip-ease), scale var(--iox-tooltip-dur-in) var(--iox-tooltip-ease);
|
|
771
|
+
}
|
|
772
|
+
.iox-tooltip.iox-es-tooltip[data-iox-closing] { transition-duration: var(--iox-tooltip-dur-out); }
|
|
773
|
+
.iox-tooltip[data-iox-instant],
|
|
774
|
+
.iox-tooltip.iox-es-tooltip[data-iox-instant] { transition: none; }
|
|
775
|
+
|
|
776
|
+
/* The toast stack, at its edge — above the home indicator (M7) and above a bottom tab bar (CC1's reserve). */
|
|
777
|
+
.iox-toasts {
|
|
778
|
+
position: absolute;
|
|
779
|
+
inset-inline: 0;
|
|
780
|
+
display: flex;
|
|
781
|
+
flex-direction: column;
|
|
782
|
+
align-items: center;
|
|
783
|
+
gap: 8px;
|
|
784
|
+
padding-inline: 16px;
|
|
785
|
+
box-sizing: border-box;
|
|
786
|
+
pointer-events: none;
|
|
787
|
+
}
|
|
788
|
+
.iox-toasts[data-iox-edge='bottom'] { bottom: calc(16px + env(safe-area-inset-bottom, 0px)); }
|
|
789
|
+
.iox-toasts[data-iox-edge='top'] { top: calc(16px + env(safe-area-inset-top, 0px)); flex-direction: column-reverse; }
|
|
790
|
+
html:has(.iox-bar-reserve) .iox-toasts[data-iox-edge='bottom'] { bottom: calc(16px + var(--iox-tabbar-height, 64px) + env(safe-area-inset-bottom, 0px)); }
|
|
791
|
+
.iox-toast {
|
|
792
|
+
--toast-from: translateY(100%);
|
|
793
|
+
pointer-events: auto;
|
|
794
|
+
box-sizing: border-box;
|
|
795
|
+
display: flex;
|
|
796
|
+
align-items: center;
|
|
797
|
+
gap: 8px;
|
|
798
|
+
max-width: min(440px, 100%);
|
|
799
|
+
padding-block: 10px;
|
|
800
|
+
padding-inline: 16px 6px;
|
|
801
|
+
font: 500 15px/1.4 system-ui, -apple-system, 'Segoe UI', sans-serif;
|
|
802
|
+
background-color: #1f2328;
|
|
803
|
+
color: #ffffff;
|
|
804
|
+
border-radius: 10px;
|
|
805
|
+
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.25);
|
|
806
|
+
transition: transform 400ms ease, opacity 400ms ease;
|
|
807
|
+
}
|
|
808
|
+
.iox-toasts[data-iox-edge='top'] .iox-toast { --toast-from: translateY(-100%); }
|
|
809
|
+
@starting-style { .iox-toast { opacity: 0; transform: var(--toast-from); } }
|
|
810
|
+
.iox-toast[data-iox-closing] { opacity: 0; transform: var(--toast-from); transition-duration: 220ms; }
|
|
811
|
+
.iox-es-toast {
|
|
812
|
+
background-color: var(--iox-toast-bg);
|
|
813
|
+
color: var(--iox-toast-ink);
|
|
814
|
+
border-radius: var(--iox-toast-radius);
|
|
815
|
+
box-shadow: var(--iox-toast-shadow);
|
|
816
|
+
font-family: var(--iox-font-body);
|
|
817
|
+
font-size: var(--iox-size-body);
|
|
818
|
+
font-weight: var(--iox-weight-strong);
|
|
819
|
+
}
|
|
820
|
+
.iox-toast.iox-es-toast { transition: transform var(--iox-toast-dur-in) var(--iox-toast-ease), opacity var(--iox-toast-dur-in) var(--iox-toast-ease); }
|
|
821
|
+
.iox-toast.iox-es-toast[data-iox-closing] { transition-duration: var(--iox-toast-dur-out); }
|
|
822
|
+
.iox-toast__text { margin: 0; flex: 1 1 auto; min-width: 0; overflow-wrap: anywhere; }
|
|
823
|
+
/* The close control: a 44px target (M2) drawn in the toast's own ink, whatever the look — its ring too (currentColor
|
|
824
|
+
on the toast's fill is the pair the toast already clears). */
|
|
825
|
+
.iox-toast__close {
|
|
826
|
+
flex: none;
|
|
827
|
+
display: inline-grid;
|
|
828
|
+
place-items: center;
|
|
829
|
+
inline-size: 44px;
|
|
830
|
+
block-size: 44px;
|
|
831
|
+
margin-block: -8px;
|
|
832
|
+
padding: 0;
|
|
833
|
+
border: 0;
|
|
834
|
+
border-radius: 999px;
|
|
835
|
+
background: transparent;
|
|
836
|
+
color: inherit;
|
|
837
|
+
cursor: pointer;
|
|
838
|
+
opacity: 0.8;
|
|
839
|
+
touch-action: manipulation;
|
|
840
|
+
transition: opacity 150ms ease-out, background-color 150ms ease-out;
|
|
841
|
+
}
|
|
842
|
+
.iox-toast__close:focus-visible { outline: 2px solid currentColor; outline-offset: -4px; opacity: 1; }
|
|
843
|
+
@media (hover: hover) and (pointer: fine) {
|
|
844
|
+
.iox-toast__close:hover { opacity: 1; background-color: color-mix(in srgb, currentColor 14%, transparent); }
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
/* The popover — the node itself, in the top layer beside its opener (placeFloating writes top / left / the origin).
|
|
848
|
+
Closed it is the browser's hidden popover; the panel takes focus on open and draws no ring (its controls do). */
|
|
849
|
+
[data-iox-popover] { box-sizing: border-box; }
|
|
850
|
+
[data-iox-popover][popover] {
|
|
851
|
+
--pop-scale: 0.95;
|
|
852
|
+
position: fixed;
|
|
853
|
+
inset: auto;
|
|
854
|
+
margin: 0;
|
|
855
|
+
max-width: calc(100vw - 24px);
|
|
856
|
+
max-height: calc(100dvh - 24px);
|
|
857
|
+
overflow: auto;
|
|
858
|
+
overscroll-behavior: contain;
|
|
859
|
+
outline: none;
|
|
860
|
+
transition: opacity 200ms cubic-bezier(0.23, 1, 0.32, 1), scale 200ms cubic-bezier(0.23, 1, 0.32, 1), overlay 200ms allow-discrete, display 200ms allow-discrete;
|
|
861
|
+
}
|
|
862
|
+
@starting-style { [data-iox-popover][popover]:popover-open { opacity: 0; scale: var(--pop-scale); } }
|
|
863
|
+
[data-iox-popover][popover][data-iox-closing] { opacity: 0; scale: var(--pop-scale); transition-duration: 140ms; }
|
|
864
|
+
.iox-es-popover[data-iox-popover][popover] {
|
|
865
|
+
--pop-scale: var(--iox-popover-scale);
|
|
866
|
+
transition: opacity var(--iox-popover-dur-in) var(--iox-popover-ease), scale var(--iox-popover-dur-in) var(--iox-popover-ease),
|
|
867
|
+
overlay var(--iox-popover-dur-in) allow-discrete, display var(--iox-popover-dur-in) allow-discrete;
|
|
868
|
+
}
|
|
869
|
+
.iox-es-popover[data-iox-popover][popover][data-iox-closing] { transition-duration: var(--iox-popover-dur-out); }
|
|
870
|
+
/* On a phone: a bottom sheet over the scrim, the safe area kept (the date picker's decision, FLOATING_SHEET_QUERY). */
|
|
871
|
+
[data-iox-popover][popover][data-iox-sheet] {
|
|
872
|
+
inset: auto 0 0 0;
|
|
873
|
+
width: 100%;
|
|
874
|
+
max-width: none;
|
|
875
|
+
max-height: 85dvh;
|
|
876
|
+
border-end-start-radius: 0;
|
|
877
|
+
border-end-end-radius: 0;
|
|
878
|
+
padding-bottom: calc(16px + env(safe-area-inset-bottom, 0px));
|
|
879
|
+
transition: translate 500ms cubic-bezier(0.32, 0.72, 0, 1), overlay 500ms allow-discrete, display 500ms allow-discrete;
|
|
880
|
+
}
|
|
881
|
+
@starting-style { [data-iox-popover][popover][data-iox-sheet]:popover-open { opacity: 1; scale: 1; translate: 0 100%; } }
|
|
882
|
+
[data-iox-popover][popover][data-iox-sheet][data-iox-closing] { opacity: 1; scale: 1; translate: 0 100%; transition-duration: 280ms; }
|
|
883
|
+
[data-iox-popover][popover][data-iox-sheet]::backdrop { background: rgba(0, 0, 0, 0.45); transition: opacity 500ms cubic-bezier(0.32, 0.72, 0, 1); }
|
|
884
|
+
@starting-style { [data-iox-popover][popover][data-iox-sheet]:popover-open::backdrop { opacity: 0; } }
|
|
885
|
+
[data-iox-popover][popover][data-iox-sheet][data-iox-closing]::backdrop { opacity: 0; transition-duration: 280ms; }
|
|
886
|
+
.iox-es-popover[data-iox-popover][popover][data-iox-sheet] {
|
|
887
|
+
padding-bottom: calc(var(--iox-popover-pad) + env(safe-area-inset-bottom, 0px));
|
|
888
|
+
transition: translate var(--iox-sheet-dur-in) var(--iox-sheet-ease), overlay var(--iox-sheet-dur-in) allow-discrete, display var(--iox-sheet-dur-in) allow-discrete;
|
|
889
|
+
}
|
|
890
|
+
.iox-es-popover[data-iox-popover][popover][data-iox-sheet][data-iox-closing] { transition-duration: var(--iox-sheet-dur-out); }
|
|
891
|
+
.iox-es-popover[data-iox-popover][popover][data-iox-sheet]::backdrop { background: var(--iox-scrim); transition: opacity var(--iox-sheet-dur-in) var(--iox-sheet-ease); }
|
|
892
|
+
.iox-es-popover[data-iox-popover][popover][data-iox-sheet][data-iox-closing]::backdrop { transition-duration: var(--iox-sheet-dur-out); }
|
|
893
|
+
.iox-es-popover[data-iox-popover][popover][data-iox-instant],
|
|
894
|
+
.iox-es-popover[data-iox-popover][popover][data-iox-instant]::backdrop,
|
|
895
|
+
[data-iox-popover][popover][data-iox-instant],
|
|
896
|
+
[data-iox-popover][popover][data-iox-instant]::backdrop { transition: none; }
|
|
897
|
+
|
|
898
|
+
/* Reduced motion: each of them fades — gentler, not zero; nothing scales or slides. */
|
|
899
|
+
@media (prefers-reduced-motion: reduce) {
|
|
900
|
+
.iox-tooltip.iox-tooltip,
|
|
901
|
+
[data-iox-popover][popover][popover] { --tip-scale: 1; --pop-scale: 1; }
|
|
902
|
+
.iox-toasts[data-iox-edge] .iox-toast.iox-toast { --toast-from: none; }
|
|
903
|
+
[data-iox-popover][popover][data-iox-sheet][data-iox-sheet] { transition-property: opacity, overlay, display; }
|
|
904
|
+
@starting-style { [data-iox-popover][popover][data-iox-sheet][data-iox-sheet]:popover-open { opacity: 0; translate: none; } }
|
|
905
|
+
[data-iox-popover][popover][data-iox-sheet][data-iox-sheet][data-iox-closing] { opacity: 0; translate: none; }
|
|
906
|
+
}
|