@celestia-island/hikari 0.55.310 → 0.55.312
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/package.json +1 -1
- package/src/components/HkModal.open-close-scale.test.ts +30 -111
- package/src/components/HkModal.scss +55 -146
- package/src/components/HkModal.tsx +5 -119
- package/src/components/paneSwap.contract.test.ts +0 -9
- package/src/index.ts +0 -3
- package/src/components/HkRideBox.scss +0 -110
- package/src/components/HkRideBox.test.tsx +0 -174
- package/src/components/HkRideBox.tsx +0 -165
- package/src/composables/useHeightRide.ts +0 -198
package/package.json
CHANGED
|
@@ -2,24 +2,12 @@
|
|
|
2
2
|
* Behavioral contract for the DESKTOP modal open/close choreography — run
|
|
3
3
|
* against the COMPILED stylesheet, not the source text.
|
|
4
4
|
*
|
|
5
|
-
* User direction 2026-11-07 (
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
|
|
11
|
-
* frame maps its surface phases onto `hk-ride--from` / `hk-ride--to`):
|
|
12
|
-
*
|
|
13
|
-
* frame height : chrome + content × 0.9 ⇄ chrome + content
|
|
14
|
-
* content region: scaleY(0.9) ⇄ scaleY(1) (origin top)
|
|
15
|
-
*
|
|
16
|
-
* The retired grammars, oldest first: the 2026-11-04 whole-frame
|
|
17
|
-
* `scale(1, 0.9)`, the 2026-11-05 body-only uniform `scale(0.9)`, the
|
|
18
|
-
* 2026-11-06 `scale(1.1, 1)` overshoot, its same-day `scale(0.95, 1)`
|
|
19
|
-
* pinch, and the 2026-11-06/07 frame-level `scaleY(0.9)` ride (retired
|
|
20
|
-
* because a frame scale squashed the title glyphs and the footer buttons —
|
|
21
|
-
* the user's 2026-11-07 report on 0.55.305).
|
|
22
|
-
*/
|
|
5
|
+
* User direction 2026-11-07 (fifth revision, chosen from a comparison
|
|
6
|
+
* page): the whole window zooms and rises — B3. The frame's transform is
|
|
7
|
+
* the permanent centering translate with a translateY+scale appended, a
|
|
8
|
+
* compositor-only motion; opacity rides the same clock. The body carries
|
|
9
|
+
* NO rule in this choreography (nothing inside is ever scaled on its own).
|
|
10
|
+
*/
|
|
23
11
|
import { describe, expect, it } from "vitest";
|
|
24
12
|
import { dirname, join } from "node:path";
|
|
25
13
|
import { fileURLToPath } from "node:url";
|
|
@@ -323,8 +311,8 @@ const CENTERING =
|
|
|
323
311
|
/** Frame ride states: the centering translate is a PERMANENT prefix and
|
|
324
312
|
* only the appended scaleY changes (2026-11-06: the window's height rides
|
|
325
313
|
* the open/close about its center). */
|
|
326
|
-
const FRAME_FROM = `${CENTERING}
|
|
327
|
-
const FRAME_TO = `${CENTERING}
|
|
314
|
+
const FRAME_FROM = `${CENTERING} translateY(10px) scale(0.97)`;
|
|
315
|
+
const FRAME_TO = `${CENTERING} translateY(0) scale(1)`;
|
|
328
316
|
|
|
329
317
|
/** Exact compiled transition values (winner()-normalized) — pinned whole,
|
|
330
318
|
* not by prefix: an embedded fourth time value (delay) or a smuggled extra
|
|
@@ -333,17 +321,10 @@ const FRAME_TO = `${CENTERING} scaleY(1)`;
|
|
|
333
321
|
* R2 finding 2026-11-06 (S1): prefix `toContain` let both survive. */
|
|
334
322
|
const ENTER_TRANSITION =
|
|
335
323
|
"opacity var(--hk-modal-duration, 0.42s) var(--hk-modal-ease-out, cubic-bezier(0.33, 1, 0.68, 1)), " +
|
|
336
|
-
"
|
|
324
|
+
"transform var(--hk-modal-duration, 0.42s) var(--hk-modal-ease-out, cubic-bezier(0.33, 1, 0.68, 1))";
|
|
337
325
|
const LEAVE_TRANSITION =
|
|
338
326
|
"opacity var(--hk-modal-duration, 0.32s) var(--hk-modal-ease-in, cubic-bezier(0.45, 0, 0.55, 1)), " +
|
|
339
|
-
"
|
|
340
|
-
/** The ride's own geometry lives in the shared container sheet; the modal
|
|
341
|
-
* frame maps its phases onto these two ends. */
|
|
342
|
-
const RIDE_SHEET = (() => {
|
|
343
|
-
const path = join(here, "HkRideBox.scss");
|
|
344
|
-
return compile(path, { loadPaths: [here, join(here, "..", "styles")], style: "expanded" }).css;
|
|
345
|
-
})();
|
|
346
|
-
const RIDE_RULES = parseRules(stripComments(RIDE_SHEET));
|
|
327
|
+
"transform var(--hk-modal-duration, 0.32s) var(--hk-modal-ease-in, cubic-bezier(0.45, 0, 0.55, 1))";
|
|
347
328
|
|
|
348
329
|
describe("HkModal desktop open/close contract (compiled cascade, user direction 2026-11-07)", () => {
|
|
349
330
|
it("compiles into a stylesheet with the ride rules present (known-positive)", () => {
|
|
@@ -355,17 +336,24 @@ describe("HkModal desktop open/close contract (compiled cascade, user direction
|
|
|
355
336
|
expect(selectors.some((s) => s.includes("hk-modal-content-enter-from"))).toBe(true);
|
|
356
337
|
});
|
|
357
338
|
|
|
358
|
-
it("
|
|
359
|
-
//
|
|
360
|
-
//
|
|
361
|
-
//
|
|
362
|
-
//
|
|
339
|
+
it("rides the whole window on the centering translate plus a zoom pair", () => {
|
|
340
|
+
// B3 (user direction 2026-11-07, fifth revision): the frame's transform
|
|
341
|
+
// is the PERMANENT centering translate with a translateY+scale appended
|
|
342
|
+
// — a compositor-only motion. Rest keeps the bare translate; the two
|
|
343
|
+
// ride ends are the contracted zoom pair, byte-identical across the
|
|
344
|
+
// draggable variant. Anything else (a bare scale, a lost shift token,
|
|
345
|
+
// a different function order) is the regression this pins.
|
|
363
346
|
for (const state of [...RIDE, { name: "rest", classes: [] as string[] }]) {
|
|
347
|
+
const compressed = state.classes.some((c) =>
|
|
348
|
+
c.includes("enter-from") || c.includes("leave-to"));
|
|
349
|
+
const expected = state.classes.length === 0
|
|
350
|
+
? CENTERING
|
|
351
|
+
: compressed ? FRAME_FROM : FRAME_TO;
|
|
364
352
|
for (const [label, frame] of [
|
|
365
353
|
["", frameWith(...state.classes)],
|
|
366
354
|
["draggable ", draggableFrameWith(...state.classes)],
|
|
367
355
|
] as const) {
|
|
368
|
-
expect(winner(frame, "transform", DESKTOP), `${label}${state.name}`).toBe(
|
|
356
|
+
expect(winner(frame, "transform", DESKTOP), `${label}${state.name}`).toBe(expected);
|
|
369
357
|
for (const prop of ["scale", "zoom", "translate", "rotate", "transform-origin"]) {
|
|
370
358
|
expect(winner(frame, prop, DESKTOP), `${label}${state.name} ${prop}`).toBeUndefined();
|
|
371
359
|
}
|
|
@@ -416,59 +404,16 @@ describe("HkModal desktop open/close contract (compiled cascade, user direction
|
|
|
416
404
|
"will-change",
|
|
417
405
|
DESKTOP,
|
|
418
406
|
);
|
|
419
|
-
expect(enterWill).toBe("opacity");
|
|
407
|
+
expect(enterWill).toBe("opacity, transform");
|
|
420
408
|
// The close direction promotes the same pair of layers.
|
|
421
409
|
const leaveWill = winner(
|
|
422
410
|
frameWith("hk-modal-content-leave-to", "hk-modal-content-leave-active"),
|
|
423
411
|
"will-change",
|
|
424
412
|
DESKTOP,
|
|
425
413
|
);
|
|
426
|
-
expect(leaveWill).toBe("opacity");
|
|
414
|
+
expect(leaveWill).toBe("opacity, transform");
|
|
427
415
|
});
|
|
428
416
|
|
|
429
|
-
it("rides the HEIGHT through the shared container contract, chrome untouched", () => {
|
|
430
|
-
// The two ends of the ride are expressed as `height: calc(chrome +
|
|
431
|
-
// content × factor)` on the frame (`!important` beats the size morph's
|
|
432
|
-
// inline pin for the duration of the flight) and as a top-origin
|
|
433
|
-
// `scaleY` on the CONTENT REGION only — so the title bar and the footer
|
|
434
|
-
// keep their natural size, which is the 2026-11-07 user requirement.
|
|
435
|
-
// A selector may be declared by more than one rule (the ride's geometry
|
|
436
|
-
// pairs: the compressed height, the content's natural-height lock and the
|
|
437
|
-
// content's scale). Look the declaration up by property, not by "the
|
|
438
|
-
// first rule with this selector".
|
|
439
|
-
const rideRule = (selector: string, prop = "height"): CssRule | undefined =>
|
|
440
|
-
RIDE_RULES.find((r) => r.selector === selector && r.declarations.has(prop));
|
|
441
|
-
|
|
442
|
-
const from = rideRule(".hk-ride[data-hk-ride-ready].hk-ride--from");
|
|
443
|
-
expect(from, "compressed end rule").toBeTruthy();
|
|
444
|
-
expect(from!.declarations.get("height")).toBe(
|
|
445
|
-
"calc(var(--hk-ride-chrome, 0px) + var(--hk-ride-content, 0px) * var(--hk-ride-factor, 0.9))",
|
|
446
|
-
);
|
|
447
|
-
expect(from!.important.has("height"), "the ride outranks the morph's inline pin").toBe(true);
|
|
448
|
-
|
|
449
|
-
const to = rideRule(".hk-ride[data-hk-ride-ready].hk-ride--to");
|
|
450
|
-
expect(to, "natural end rule").toBeTruthy();
|
|
451
|
-
expect(to!.declarations.get("height")).toBe(
|
|
452
|
-
"calc(var(--hk-ride-chrome, 0px) + var(--hk-ride-content, 0px))",
|
|
453
|
-
);
|
|
454
|
-
expect(to!.important.has("height")).toBe(true);
|
|
455
|
-
|
|
456
|
-
// Only the content region is ever scaled — top origin, so the compressed
|
|
457
|
-
// paint lands exactly in the shorter slot the frame leaves for it.
|
|
458
|
-
const scaled = rideRule(".hk-ride[data-hk-ride-ready].hk-ride--from > .hk-ride-content", "transform");
|
|
459
|
-
expect(scaled, "content compress rule").toBeTruthy();
|
|
460
|
-
expect(scaled!.declarations.get("transform")).toBe(
|
|
461
|
-
"scaleY(var(--hk-ride-factor, 0.9))",
|
|
462
|
-
);
|
|
463
|
-
const origin = rideRule(".hk-ride-content", "transform-origin");
|
|
464
|
-
expect(origin!.declarations.get("transform-origin")).toBe("50% 0");
|
|
465
|
-
|
|
466
|
-
// …and the CHROME is never named by any ride rule: the modal's header,
|
|
467
|
-
// sub-header and footer must not be reachable from the ride sheet.
|
|
468
|
-
const CHROME = /hk-modal-(header|subheader|footer|title|close)\b/;
|
|
469
|
-
const offenders = RIDE_RULES.filter((r) => CHROME.test(r.selector)).map((r) => r.selector);
|
|
470
|
-
expect(offenders, "the ride container must not target modal chrome").toEqual([]);
|
|
471
|
-
});
|
|
472
417
|
|
|
473
418
|
it("never transforms the chrome: title bar, sub-header, footer, close button, title glyph", () => {
|
|
474
419
|
for (const state of RIDE) {
|
|
@@ -550,15 +495,6 @@ describe("HkModal desktop open/close contract (compiled cascade, user direction
|
|
|
550
495
|
).toBeUndefined();
|
|
551
496
|
});
|
|
552
497
|
|
|
553
|
-
it("maps the surface states onto the ride's two ends", () => {
|
|
554
|
-
// The modal's TSX adds `hk-ride--from` for openingFrom/closingTo and
|
|
555
|
-
// `hk-ride--to` for openingTo/closingFrom; the container sheet keys off
|
|
556
|
-
// exactly those class names. A rename on either side has to fail here.
|
|
557
|
-
const names = RIDE_RULES.map((r) => r.selector).join("\n");
|
|
558
|
-
expect(names).toContain("hk-ride--from");
|
|
559
|
-
expect(names).toContain("hk-ride--to");
|
|
560
|
-
expect(names).toContain("hk-ride-content");
|
|
561
|
-
});
|
|
562
498
|
|
|
563
499
|
it("never lets ANY rule put a ride transform on the content region", () => {
|
|
564
500
|
// Fail-closed sweep, selector-level and media-independent (R2 finding
|
|
@@ -580,8 +516,6 @@ describe("HkModal desktop open/close contract (compiled cascade, user direction
|
|
|
580
516
|
// width never does). Everything else — scaleX, a 2-arg scale, a
|
|
581
517
|
// translate, a matrix — is a contract break.
|
|
582
518
|
const LEGAL_BODY_TRANSFORM = new Set([
|
|
583
|
-
"scaleY(var(--hk-ride-factor, 0.9))",
|
|
584
|
-
"scaleY(1)",
|
|
585
519
|
"none",
|
|
586
520
|
]);
|
|
587
521
|
const offenders: string[] = [];
|
|
@@ -626,17 +560,6 @@ describe("HkModal desktop open/close contract (compiled cascade, user direction
|
|
|
626
560
|
"transition",
|
|
627
561
|
"will-change",
|
|
628
562
|
"opacity",
|
|
629
|
-
// The from-pair carries the ride's height end, and the -active pair
|
|
630
|
-
// locks the content region out of the flex shrink for the flight
|
|
631
|
-
// (the 0.81-compounding guard, 2026-11-07 R3 finding).
|
|
632
|
-
"height",
|
|
633
|
-
"flex",
|
|
634
|
-
// …and the -active pair pins the footer band to the frame's bottom
|
|
635
|
-
// edge for the flight (it would otherwise be pushed out by the
|
|
636
|
-
// content region's locked natural box).
|
|
637
|
-
"position",
|
|
638
|
-
"inset-inline",
|
|
639
|
-
"bottom",
|
|
640
563
|
]);
|
|
641
564
|
// Chrome ride states now also carry `transform` (the 2026-11-06 frame
|
|
642
565
|
// height ride) — but nothing else beyond opacity/transition/will-change.
|
|
@@ -660,8 +583,6 @@ describe("HkModal desktop open/close contract (compiled cascade, user direction
|
|
|
660
583
|
// The body carries no transform contract any more (2026-11-07): any
|
|
661
584
|
// ride rule targeting it is an offender outright.
|
|
662
585
|
const LEGAL_BODY = new Set<string>([
|
|
663
|
-
"scaleY(var(--hk-ride-factor, 0.9))",
|
|
664
|
-
"scaleY(1)",
|
|
665
586
|
"none",
|
|
666
587
|
]);
|
|
667
588
|
const offenders: string[] = [];
|
|
@@ -718,12 +639,7 @@ describe("HkModal desktop open/close contract (compiled cascade, user direction
|
|
|
718
639
|
const bodyTransform = winner(body, "transform", DESKTOP) ?? "";
|
|
719
640
|
// The contracted body grammar: nothing but the vertical compression
|
|
720
641
|
// at the ride's two ends (and no transform at rest).
|
|
721
|
-
|
|
722
|
-
c.includes("enter-from") || c.includes("leave-to"));
|
|
723
|
-
const expected = expectCompressed
|
|
724
|
-
? "scaleY(var(--hk-ride-factor, 0.9))"
|
|
725
|
-
: "scaleY(1)";
|
|
726
|
-
expect(bodyTransform, `body ${state.name}`).toBe(expected);
|
|
642
|
+
expect(bodyTransform, `body ${state.name}`).toBe("");
|
|
727
643
|
expect(winner(body, "scale", DESKTOP), `body ${state.name}`).toBeUndefined();
|
|
728
644
|
expect(winner(body, "translate", DESKTOP), `body ${state.name}`).toBeUndefined();
|
|
729
645
|
expect(winner(body, "zoom", DESKTOP), `body ${state.name}`).toBeUndefined();
|
|
@@ -732,7 +648,10 @@ describe("HkModal desktop open/close contract (compiled cascade, user direction
|
|
|
732
648
|
// the only `scaleY` in the choreography lives on the content region's
|
|
733
649
|
// shared contract (pinned by the ride test above).
|
|
734
650
|
const frameTransform = winner(frame, "transform", DESKTOP) ?? "";
|
|
735
|
-
|
|
651
|
+
const compressed = state.classes.some((c) =>
|
|
652
|
+
c.includes("enter-from") || c.includes("leave-to"));
|
|
653
|
+
const frameExpected = compressed ? FRAME_FROM : FRAME_TO;
|
|
654
|
+
expect(frameTransform, `frame ${state.name}`).toBe(frameExpected);
|
|
736
655
|
expect(frameTransform, `frame ${state.name}`).not.toMatch(FRAME_ESCAPES);
|
|
737
656
|
expect(winner(frame, "scale", DESKTOP), `frame ${state.name}`).toBeUndefined();
|
|
738
657
|
expect(winner(frame, "zoom", DESKTOP), `frame ${state.name}`).toBeUndefined();
|
|
@@ -57,11 +57,11 @@
|
|
|
57
57
|
z-index: 1;
|
|
58
58
|
top: 50%;
|
|
59
59
|
left: 50%;
|
|
60
|
-
// The centering translate is the frame's
|
|
61
|
-
// (rest, enter-from/to, leave-from/to)
|
|
62
|
-
//
|
|
63
|
-
//
|
|
64
|
-
//
|
|
60
|
+
// The centering translate is the frame's PERMANENT transform prefix:
|
|
61
|
+
// every state (rest, enter-from/to, leave-from/to) carries this exact
|
|
62
|
+
// translate, and the open/close choreography only ever APPENDS a
|
|
63
|
+
// translateY + scale to it (see the desktop block) — so the ride can
|
|
64
|
+
// never re-seat the frame and dragged windows keep their shift.
|
|
65
65
|
transform: translate(
|
|
66
66
|
calc(-50% + var(--hk-modal-shift-x, 0px)),
|
|
67
67
|
calc(-50% + var(--hk-modal-shift-y, 0px))
|
|
@@ -99,164 +99,76 @@
|
|
|
99
99
|
// ------
|
|
100
100
|
// Content enter/leave transitions (desktop ≥768px)
|
|
101
101
|
//
|
|
102
|
-
// Default window open/close (user direction 2026-11-07,
|
|
103
|
-
//
|
|
104
|
-
//
|
|
105
|
-
//
|
|
106
|
-
//
|
|
107
|
-
//
|
|
108
|
-
//
|
|
109
|
-
//
|
|
110
|
-
//
|
|
111
|
-
//
|
|
112
|
-
//
|
|
113
|
-
//
|
|
114
|
-
//
|
|
115
|
-
// sheet maps the surface states onto the ride's two ends. Chrome lives
|
|
116
|
-
// outside the transform, so its type renders at natural size for the whole
|
|
117
|
-
// flight, and the content region keeps its natural LAYOUT box (only its
|
|
118
|
-
// paint is compressed about its own top edge), so the compressed paint
|
|
119
|
-
// lands exactly in the shorter slot the frame leaves for it.
|
|
120
|
-
//
|
|
121
|
-
// The centering translate stays the frame's whole transform in every state
|
|
122
|
-
// (byte-identical between rest and both ride ends, `--hk-modal-shift-*`
|
|
123
|
-
// included), so the ride never re-seats the frame, dragged windows keep
|
|
124
|
-
// their shift, and a box that grows by Δ about its center moves its top
|
|
125
|
-
// edge up by Δ/2 — the symmetric growth the user asked for.
|
|
126
|
-
//
|
|
127
|
-
// Scroll containers are untouched by construction: `HkScrollContainer`,
|
|
128
|
-
// `HkScrollPin` and the overlay scrollbar measure layout boxes, which the
|
|
129
|
-
// content region's transform never changes, and nothing inside the content
|
|
130
|
-
// reflows per frame. Surfaces may read `data-hk-riding` to skip work while
|
|
131
|
-
// a ride is in flight.
|
|
132
|
-
//
|
|
133
|
-
// The `!important` on the two height ends is load-bearing: the size morph
|
|
134
|
-
// pins the frame's height as an inline px value, and the ride — not the pin
|
|
135
|
-
// — owns the box for the duration of the flight. Reduced motion flattens
|
|
136
|
-
// both the height transition and the content scale.
|
|
102
|
+
// Default window open/close (user direction 2026-11-07, fifth revision —
|
|
103
|
+
// chosen from a side-by-side comparison page): the WHOLE WINDOW zooms and
|
|
104
|
+
// rises while it fades. A compositor animation end to end — no layout
|
|
105
|
+
// property moves, nothing reflows per frame, the backdrop-filtered fixed
|
|
106
|
+
// layer never re-rasters. The 3% scale is imperceptible as glyph
|
|
107
|
+
// distortion; the 10px rise gives the motion direction. Chrome, content,
|
|
108
|
+
// sticky bars — everything inside keeps its exact shape at every t.
|
|
109
|
+
// (Retired grammars, oldest first: the 2026-11-04 whole-frame scale(1,0.9);
|
|
110
|
+
// the 2026-11-05 body-only uniform scale; the 0.95 pinch; the frame-level
|
|
111
|
+
// scaleY(0.9); and the 2026-11-07 layout-height ride of 0.55.307–310, whose
|
|
112
|
+
// family container — HkRideBox/useHeightRide — was retired with it: it
|
|
113
|
+
// fought the size morph for the same property and animated layout on a
|
|
114
|
+
// backdrop-filtered fixed layer.)
|
|
137
115
|
// ------
|
|
138
116
|
|
|
139
117
|
@media (min-width: 768px) {
|
|
140
|
-
//
|
|
141
|
-
//
|
|
142
|
-
//
|
|
143
|
-
//
|
|
144
|
-
//
|
|
145
|
-
//
|
|
146
|
-
//
|
|
147
|
-
//
|
|
148
|
-
//
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
transition:
|
|
156
|
-
opacity var(--hk-modal-duration, 0.42s)
|
|
157
|
-
var(--hk-modal-ease-out, cubic-bezier(0.33, 1, 0.68, 1)),
|
|
158
|
-
height var(--hk-modal-duration, 0.42s)
|
|
159
|
-
var(--hk-modal-ease-out, cubic-bezier(0.33, 1, 0.68, 1)),
|
|
160
|
-
max-height var(--duration-fast, 0.15s) var(--ease-standard, cubic-bezier(0.4, 0, 0.2, 1)),
|
|
161
|
-
clip-path var(--duration-fast, 0.15s) var(--ease-standard, cubic-bezier(0.4, 0, 0.2, 1));
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
// The content's compression leg is resident for the same reason — and it
|
|
165
|
-
// is what makes the ENTER start at exactly scaleY(0.9): a freshly
|
|
166
|
-
// inserted element's first style never transitions, so the from-pair
|
|
167
|
-
// paints at 0.9 and only ever animates from there.
|
|
168
|
-
.hk-modal-body {
|
|
169
|
-
transition: transform var(--hk-modal-duration, 0.42s)
|
|
170
|
-
var(--hk-modal-ease-out, cubic-bezier(0.33, 1, 0.68, 1));
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
// Open: the window's height rides while the chrome keeps its size.
|
|
118
|
+
// Whole-window zoom + gentle rise + fade (user direction 2026-11-07,
|
|
119
|
+
// fifth revision, chosen from a side-by-side comparison page):
|
|
120
|
+
//
|
|
121
|
+
// transform: translateY(10px) scale(0.97) -> translateY(0) scale(1)
|
|
122
|
+
// opacity : 0 -> 1
|
|
123
|
+
//
|
|
124
|
+
// A COMPOSITOR animation end to end: no layout property moves, so nothing
|
|
125
|
+
// reflows per frame and the backdrop-filtered fixed layer never re-rasters
|
|
126
|
+
// — the previous height-ride choreography (0.55.307–0.55.310) animated the
|
|
127
|
+
// frame's layout height and read as jank on content-heavy windows. The 3%
|
|
128
|
+
// scale is imperceptible as glyph distortion while still giving the window
|
|
129
|
+
// a sense of arriving; the 10px rise adds direction. The centering
|
|
130
|
+
// translate stays the frame's PERMANENT transform prefix (byte-identical
|
|
131
|
+
// in every state, `--hk-modal-shift-*` included) so the ride can never
|
|
132
|
+
// re-seat the frame and dragged windows keep their shift.
|
|
174
133
|
.hk-modal-content-enter-active {
|
|
175
134
|
transition:
|
|
176
135
|
opacity var(--hk-modal-duration, 0.42s)
|
|
177
136
|
var(--hk-modal-ease-out, cubic-bezier(0.33, 1, 0.68, 1)),
|
|
178
|
-
|
|
137
|
+
transform var(--hk-modal-duration, 0.42s)
|
|
179
138
|
var(--hk-modal-ease-out, cubic-bezier(0.33, 1, 0.68, 1));
|
|
180
|
-
will-change: opacity;
|
|
139
|
+
will-change: opacity, transform;
|
|
181
140
|
}
|
|
182
141
|
|
|
183
|
-
// Close: the
|
|
184
|
-
//
|
|
142
|
+
// Close: the true reverse — the window settles back to 0.97 and dips
|
|
143
|
+
// 10px while it fades. Width, height and chrome never move in either
|
|
144
|
+
// direction.
|
|
185
145
|
.hk-modal-content-leave-active {
|
|
186
146
|
transition:
|
|
187
147
|
opacity var(--hk-modal-duration, 0.32s)
|
|
188
148
|
var(--hk-modal-ease-in, cubic-bezier(0.45, 0, 0.55, 1)),
|
|
189
|
-
|
|
149
|
+
transform var(--hk-modal-duration, 0.32s)
|
|
190
150
|
var(--hk-modal-ease-in, cubic-bezier(0.45, 0, 0.55, 1));
|
|
191
|
-
will-change: opacity;
|
|
151
|
+
will-change: opacity, transform;
|
|
192
152
|
}
|
|
193
153
|
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
// so the compressed height and the 0.9 content scale are both real at
|
|
197
|
-
// frame one — the phase-mapped classes that shipped one tick late are
|
|
198
|
-
// exactly what made the open look like clipping (2026-11-07 reports).
|
|
199
|
-
// `!important` outranks the size morph's inline pin for the flight.
|
|
200
|
-
.hk-modal-content[data-hk-ride-ready].hk-modal-content-enter-from,
|
|
201
|
-
.hk-modal-content[data-hk-ride-ready].hk-modal-content-leave-to {
|
|
154
|
+
.hk-modal-content-enter-from,
|
|
155
|
+
.hk-modal-content-leave-to {
|
|
202
156
|
opacity: 0;
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
157
|
+
transform: translate(
|
|
158
|
+
calc(-50% + var(--hk-modal-shift-x, 0px)),
|
|
159
|
+
calc(-50% + var(--hk-modal-shift-y, 0px))
|
|
160
|
+
)
|
|
161
|
+
translateY(10px) scale(0.97);
|
|
206
162
|
}
|
|
207
163
|
|
|
208
|
-
// The natural end is an EXPLICIT calc too: the frame's base height is
|
|
209
|
-
// `auto`, and a transition cannot interpolate px -> auto. Without this
|
|
210
|
-
// leg the -active body lock made `auto` resolve to (almost) the
|
|
211
|
-
// compressed height, so the frame never grew while the content's scale
|
|
212
|
-
// unfolded underneath it — the "clipped open" the user reported. When the
|
|
213
|
-
// classes come off at rest, the morph's pin (the same px) catches the
|
|
214
|
-
// handoff with no visible change.
|
|
215
164
|
.hk-modal-content-enter-to,
|
|
216
165
|
.hk-modal-content-leave-from {
|
|
217
166
|
opacity: 1;
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
.hk-modal-content-enter-from > .hk-modal-body,
|
|
224
|
-
.hk-modal-content-leave-to > .hk-modal-body {
|
|
225
|
-
transform: scaleY(var(--hk-ride-factor, 0.9));
|
|
226
|
-
transform-origin: 50% 0;
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
.hk-modal-content-enter-to > .hk-modal-body,
|
|
230
|
-
.hk-modal-content-leave-from > .hk-modal-body {
|
|
231
|
-
transform: scaleY(1);
|
|
232
|
-
transform-origin: 50% 0;
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
// The content region keeps its NATURAL layout height for the whole
|
|
236
|
-
// flight (or the slot shrink and the scale would compound to 0.81 and
|
|
237
|
-
// leave a gap above the footer), and the footer pins to the frame's
|
|
238
|
-
// bottom edge so it stays visible while the content's natural box
|
|
239
|
-
// overflows underneath it. The -active pair spans the flight window in
|
|
240
|
-
// both directions.
|
|
241
|
-
.hk-modal-content-enter-active > .hk-modal-body,
|
|
242
|
-
.hk-modal-content-leave-active > .hk-modal-body {
|
|
243
|
-
flex: 0 0 auto;
|
|
244
|
-
height: var(--hk-ride-content, auto);
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
.hk-modal-content-enter-active > .hk-modal-footer,
|
|
248
|
-
.hk-modal-content-leave-active > .hk-modal-footer {
|
|
249
|
-
position: absolute;
|
|
250
|
-
inset-inline: 0;
|
|
251
|
-
bottom: 0;
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
// Close-direction compression timing for the content (the frame's height
|
|
255
|
-
// leg above is 0.32s ease-in; a 0.42s content leg would trail it and open
|
|
256
|
-
// a gap above the pinned footer).
|
|
257
|
-
.hk-modal-content-leave-active > .hk-modal-body {
|
|
258
|
-
transition: transform var(--hk-modal-duration, 0.32s)
|
|
259
|
-
var(--hk-modal-ease-in, cubic-bezier(0.45, 0, 0.55, 1));
|
|
167
|
+
transform: translate(
|
|
168
|
+
calc(-50% + var(--hk-modal-shift-x, 0px)),
|
|
169
|
+
calc(-50% + var(--hk-modal-shift-y, 0px))
|
|
170
|
+
)
|
|
171
|
+
translateY(0) scale(1);
|
|
260
172
|
}
|
|
261
173
|
}
|
|
262
174
|
|
|
@@ -735,7 +647,7 @@
|
|
|
735
647
|
.hk-modal-content {
|
|
736
648
|
transition: none;
|
|
737
649
|
}
|
|
738
|
-
// The
|
|
650
|
+
// The zoom choreography flattens with it: the ride states fall back to
|
|
739
651
|
// the bare centering translate (same specificity as the desktop state
|
|
740
652
|
// rules above, later in the sheet, so the flattened state wins by source
|
|
741
653
|
// order — one painted frame at opacity 0 hides the flip).
|
|
@@ -746,11 +658,8 @@
|
|
|
746
658
|
calc(-50% + var(--hk-modal-shift-y, 0px))
|
|
747
659
|
);
|
|
748
660
|
}
|
|
749
|
-
// The content region carries no transform of its own
|
|
750
|
-
//
|
|
751
|
-
// at (0,2,0) they would NOT outrank a future ride rule at (0,4,0), so the
|
|
752
|
-
// family sheet's own reduced-motion block (which repeats the full
|
|
753
|
-
// selector) is the one that actually flattens the content scale.
|
|
661
|
+
// The content region carries no transform of its own in this choreography
|
|
662
|
+
// (the whole window zooms; the body is never scaled). Belt-and-braces.
|
|
754
663
|
.hk-modal-content .hk-modal-body {
|
|
755
664
|
transform: none;
|
|
756
665
|
transition: none;
|