@celestia-island/hikari 0.55.310 → 0.55.311

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@celestia-island/hikari",
3
- "version": "0.55.310",
3
+ "version": "0.55.311",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "description": "Hikari UI library, Vue 3 runtime — one design system for Vue 3 and Rust/WASM applications",
@@ -11,7 +11,8 @@
11
11
  * frame maps its surface phases onto `hk-ride--from` / `hk-ride--to`):
12
12
  *
13
13
  * frame height : chrome + content × 0.9 ⇄ chrome + content
14
- * content region: scaleY(0.9) ⇄ scaleY(1) (origin top)
14
+ * frame : translateY(10px) scale(0.97) ⇄ translateY(0) scale(1)
15
+ * content/body : never transformed (the whole window zooms)
15
16
  *
16
17
  * The retired grammars, oldest first: the 2026-11-04 whole-frame
17
18
  * `scale(1, 0.9)`, the 2026-11-05 body-only uniform `scale(0.9)`, the
@@ -323,8 +324,8 @@ const CENTERING =
323
324
  /** Frame ride states: the centering translate is a PERMANENT prefix and
324
325
  * only the appended scaleY changes (2026-11-06: the window's height rides
325
326
  * the open/close about its center). */
326
- const FRAME_FROM = `${CENTERING} scaleY(0.9)`;
327
- const FRAME_TO = `${CENTERING} scaleY(1)`;
327
+ const FRAME_FROM = `${CENTERING} translateY(10px) scale(0.97)`;
328
+ const FRAME_TO = `${CENTERING} translateY(0) scale(1)`;
328
329
 
329
330
  /** Exact compiled transition values (winner()-normalized) — pinned whole,
330
331
  * not by prefix: an embedded fourth time value (delay) or a smuggled extra
@@ -333,10 +334,10 @@ const FRAME_TO = `${CENTERING} scaleY(1)`;
333
334
  * R2 finding 2026-11-06 (S1): prefix `toContain` let both survive. */
334
335
  const ENTER_TRANSITION =
335
336
  "opacity var(--hk-modal-duration, 0.42s) var(--hk-modal-ease-out, cubic-bezier(0.33, 1, 0.68, 1)), " +
336
- "height var(--hk-modal-duration, 0.42s) var(--hk-modal-ease-out, cubic-bezier(0.33, 1, 0.68, 1))";
337
+ "transform var(--hk-modal-duration, 0.42s) var(--hk-modal-ease-out, cubic-bezier(0.33, 1, 0.68, 1))";
337
338
  const LEAVE_TRANSITION =
338
339
  "opacity var(--hk-modal-duration, 0.32s) var(--hk-modal-ease-in, cubic-bezier(0.45, 0, 0.55, 1)), " +
339
- "height var(--hk-modal-duration, 0.32s) var(--hk-modal-ease-in, cubic-bezier(0.45, 0, 0.55, 1))";
340
+ "transform var(--hk-modal-duration, 0.32s) var(--hk-modal-ease-in, cubic-bezier(0.45, 0, 0.55, 1))";
340
341
  /** The ride's own geometry lives in the shared container sheet; the modal
341
342
  * frame maps its phases onto these two ends. */
342
343
  const RIDE_SHEET = (() => {
@@ -355,17 +356,24 @@ describe("HkModal desktop open/close contract (compiled cascade, user direction
355
356
  expect(selectors.some((s) => s.includes("hk-modal-content-enter-from"))).toBe(true);
356
357
  });
357
358
 
358
- it("keeps the frame's transform at the plain centering translate in every state", () => {
359
- // The ride is layout height, not a frame transform — that is the whole
360
- // point (a frame scale squashed the chrome). So the transform must be
361
- // the SAME `translate(...)` in rest and at both ride ends, and adding a
362
- // scale to it in any state is the regression this pins.
359
+ it("rides the whole window on the centering translate plus a zoom pair", () => {
360
+ // B3 (user direction 2026-11-07, fifth revision): the frame's transform
361
+ // is the PERMANENT centering translate with a translateY+scale appended
362
+ // — a compositor-only motion. Rest keeps the bare translate; the two
363
+ // ride ends are the contracted zoom pair, byte-identical across the
364
+ // draggable variant. Anything else (a bare scale, a lost shift token,
365
+ // a different function order) is the regression this pins.
363
366
  for (const state of [...RIDE, { name: "rest", classes: [] as string[] }]) {
367
+ const compressed = state.classes.some((c) =>
368
+ c.includes("enter-from") || c.includes("leave-to"));
369
+ const expected = state.classes.length === 0
370
+ ? CENTERING
371
+ : compressed ? FRAME_FROM : FRAME_TO;
364
372
  for (const [label, frame] of [
365
373
  ["", frameWith(...state.classes)],
366
374
  ["draggable ", draggableFrameWith(...state.classes)],
367
375
  ] as const) {
368
- expect(winner(frame, "transform", DESKTOP), `${label}${state.name}`).toBe(CENTERING);
376
+ expect(winner(frame, "transform", DESKTOP), `${label}${state.name}`).toBe(expected);
369
377
  for (const prop of ["scale", "zoom", "translate", "rotate", "transform-origin"]) {
370
378
  expect(winner(frame, prop, DESKTOP), `${label}${state.name} ${prop}`).toBeUndefined();
371
379
  }
@@ -416,14 +424,14 @@ describe("HkModal desktop open/close contract (compiled cascade, user direction
416
424
  "will-change",
417
425
  DESKTOP,
418
426
  );
419
- expect(enterWill).toBe("opacity");
427
+ expect(enterWill).toBe("opacity, transform");
420
428
  // The close direction promotes the same pair of layers.
421
429
  const leaveWill = winner(
422
430
  frameWith("hk-modal-content-leave-to", "hk-modal-content-leave-active"),
423
431
  "will-change",
424
432
  DESKTOP,
425
433
  );
426
- expect(leaveWill).toBe("opacity");
434
+ expect(leaveWill).toBe("opacity, transform");
427
435
  });
428
436
 
429
437
  it("rides the HEIGHT through the shared container contract, chrome untouched", () => {
@@ -626,17 +634,6 @@ describe("HkModal desktop open/close contract (compiled cascade, user direction
626
634
  "transition",
627
635
  "will-change",
628
636
  "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
637
  ]);
641
638
  // Chrome ride states now also carry `transform` (the 2026-11-06 frame
642
639
  // height ride) — but nothing else beyond opacity/transition/will-change.
@@ -718,12 +715,7 @@ describe("HkModal desktop open/close contract (compiled cascade, user direction
718
715
  const bodyTransform = winner(body, "transform", DESKTOP) ?? "";
719
716
  // The contracted body grammar: nothing but the vertical compression
720
717
  // at the ride's two ends (and no transform at rest).
721
- const expectCompressed = state.classes.some((c) =>
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);
718
+ expect(bodyTransform, `body ${state.name}`).toBe("");
727
719
  expect(winner(body, "scale", DESKTOP), `body ${state.name}`).toBeUndefined();
728
720
  expect(winner(body, "translate", DESKTOP), `body ${state.name}`).toBeUndefined();
729
721
  expect(winner(body, "zoom", DESKTOP), `body ${state.name}`).toBeUndefined();
@@ -732,7 +724,10 @@ describe("HkModal desktop open/close contract (compiled cascade, user direction
732
724
  // the only `scaleY` in the choreography lives on the content region's
733
725
  // shared contract (pinned by the ride test above).
734
726
  const frameTransform = winner(frame, "transform", DESKTOP) ?? "";
735
- expect(frameTransform, `frame ${state.name}`).toBe(CENTERING);
727
+ const compressed = state.classes.some((c) =>
728
+ c.includes("enter-from") || c.includes("leave-to"));
729
+ const frameExpected = compressed ? FRAME_FROM : FRAME_TO;
730
+ expect(frameTransform, `frame ${state.name}`).toBe(frameExpected);
736
731
  expect(frameTransform, `frame ${state.name}`).not.toMatch(FRAME_ESCAPES);
737
732
  expect(winner(frame, "scale", DESKTOP), `frame ${state.name}`).toBeUndefined();
738
733
  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 WHOLE transform, in every state
61
- // (rest, enter-from/to, leave-from/to). The open/close ride is carried by
62
- // the height instead (see the choreography block below), so the frame can
63
- // never re-seat mid-flight, dragged windows keep their shift, and the
64
- // chrome is never scaled.
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))
@@ -137,126 +137,60 @@
137
137
  // ------
138
138
 
139
139
  @media (min-width: 768px) {
140
- // RESIDENT legs — the ride's continuity never depends on the transition
141
- // classes' lifetime. The surface machine's deadline is advisory: it can
142
- // flip the phase (and drop the classes) before the CSS lands. With the
143
- // legs resident, that class drop only changes the TARGET (calc -> auto,
144
- // scale -> none) and the browser finishes the motion on these legs
145
- // instead of snapping (2026-11-07 user report: the open "replayed" — a
146
- // partial ride, a snap, then a second animation). The height/max-height/
147
- // clip-path legs stay at the base tokens for the size morph (its own
148
- // heightMorph overrides the duration inline when it runs).
149
- .hk-modal-content {
150
- // Let the height leg interpolate to/from `auto` (Chromium 129+; older
151
- // engines just snap that one handoff): when the transition classes come
152
- // off mid-ride the calc end becomes the frame's own `auto` layout, and
153
- // without this the motion would jump instead of finishing.
154
- interpolate-size: allow-keywords;
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.
140
+ // Whole-window zoom + gentle rise + fade (user direction 2026-11-07,
141
+ // fifth revision, chosen from a side-by-side comparison page):
142
+ //
143
+ // transform: translateY(10px) scale(0.97) -> translateY(0) scale(1)
144
+ // opacity : 0 -> 1
145
+ //
146
+ // A COMPOSITOR animation end to end: no layout property moves, so nothing
147
+ // reflows per frame and the backdrop-filtered fixed layer never re-rasters
148
+ // — the previous height-ride choreography (0.55.307–0.55.310) animated the
149
+ // frame's layout height and read as jank on content-heavy windows. The 3%
150
+ // scale is imperceptible as glyph distortion while still giving the window
151
+ // a sense of arriving; the 10px rise adds direction. The centering
152
+ // translate stays the frame's PERMANENT transform prefix (byte-identical
153
+ // in every state, `--hk-modal-shift-*` included) so the ride can never
154
+ // re-seat the frame and dragged windows keep their shift.
174
155
  .hk-modal-content-enter-active {
175
156
  transition:
176
157
  opacity var(--hk-modal-duration, 0.42s)
177
158
  var(--hk-modal-ease-out, cubic-bezier(0.33, 1, 0.68, 1)),
178
- height var(--hk-modal-duration, 0.42s)
159
+ transform var(--hk-modal-duration, 0.42s)
179
160
  var(--hk-modal-ease-out, cubic-bezier(0.33, 1, 0.68, 1));
180
- will-change: opacity;
161
+ will-change: opacity, transform;
181
162
  }
182
163
 
183
- // Close: the reveal's TRUE reverse — same ride, the window settles back
184
- // toward 90% height, width untouched in both directions.
164
+ // Close: the true reverse — the window settles back to 0.97 and dips
165
+ // 10px while it fades. Width, height and chrome never move in either
166
+ // direction.
185
167
  .hk-modal-content-leave-active {
186
168
  transition:
187
169
  opacity var(--hk-modal-duration, 0.32s)
188
170
  var(--hk-modal-ease-in, cubic-bezier(0.45, 0, 0.55, 1)),
189
- height var(--hk-modal-duration, 0.32s)
171
+ transform var(--hk-modal-duration, 0.32s)
190
172
  var(--hk-modal-ease-in, cubic-bezier(0.45, 0, 0.55, 1));
191
- will-change: opacity;
173
+ will-change: opacity, transform;
192
174
  }
193
175
 
194
- // The ride's two ends. The from-pair ships with the FIRST paint (a
195
- // freshly inserted element does not transition into its initial style),
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 {
176
+ .hk-modal-content-enter-from,
177
+ .hk-modal-content-leave-to {
202
178
  opacity: 0;
203
- height: calc(
204
- var(--hk-ride-chrome, 0px) + var(--hk-ride-content, 0px) * var(--hk-ride-factor, 0.9)
205
- ) !important;
179
+ transform: translate(
180
+ calc(-50% + var(--hk-modal-shift-x, 0px)),
181
+ calc(-50% + var(--hk-modal-shift-y, 0px))
182
+ )
183
+ translateY(10px) scale(0.97);
206
184
  }
207
185
 
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
186
  .hk-modal-content-enter-to,
216
187
  .hk-modal-content-leave-from {
217
188
  opacity: 1;
218
- height: calc(var(--hk-ride-chrome, 0px) + var(--hk-ride-content, 0px));
219
- }
220
-
221
- // Only the content region is ever scaled — top origin, so the compressed
222
- // paint lands exactly in the shorter slot the frame leaves for it.
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));
189
+ transform: translate(
190
+ calc(-50% + var(--hk-modal-shift-x, 0px)),
191
+ calc(-50% + var(--hk-modal-shift-y, 0px))
192
+ )
193
+ translateY(0) scale(1);
260
194
  }
261
195
  }
262
196
 
@@ -735,7 +669,7 @@
735
669
  .hk-modal-content {
736
670
  transition: none;
737
671
  }
738
- // The frame's height ride flattens with it: the ride states fall back to
672
+ // The zoom choreography flattens with it: the ride states fall back to
739
673
  // the bare centering translate (same specificity as the desktop state
740
674
  // rules above, later in the sheet, so the flattened state wins by source
741
675
  // order — one painted frame at opacity 0 hides the flip).
@@ -746,11 +680,8 @@
746
680
  calc(-50% + var(--hk-modal-shift-y, 0px))
747
681
  );
748
682
  }
749
- // The content region carries no transform of its own any more (the ride is
750
- // height-only — 2026-11-07). These lines are a belt-and-braces guard only:
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.
683
+ // The content region carries no transform of its own in this choreography
684
+ // (the whole window zooms; the body is never scaled). Belt-and-braces.
754
685
  .hk-modal-content .hk-modal-body {
755
686
  transform: none;
756
687
  transition: none;
@@ -18,7 +18,6 @@ import { useSurfaceTransition } from "../composables/useSurfaceTransition";
18
18
  import { useSurfaceMachine } from "../composables/useSurfaceMachine";
19
19
  import { useSurfaceContentHold } from "../composables/useSurfaceContentHold";
20
20
  import { useSizeMorph, type RideEntry } from "../composables/useSizeMorph";
21
- import { useHeightRide } from "../composables/useHeightRide";
22
21
 
23
22
  import { STEPFLOW_SWAP_EVENT } from "./HkStepFlow";
24
23
 
@@ -332,14 +331,8 @@ export default defineComponent({
332
331
  // finding). Riding the block keeps the scrollport's content
333
332
  // untransformed; the block's own poke below the frame lands
334
333
  // off-screen on a bottom-docked sheet.
335
- // While the desktop height ride owns the body's paint (its scaleY
336
- // IS the choreography), the morph must not write an inline transform
337
- // on the same element: an inline style outranks the ride's class
338
- // rule, and the close would stop compressing (2026-11-07 user
339
- // report: the window folded "together with the chrome"). The body
340
- // ride matters for the ≤767px sheet sweep, where the ride geometry
341
- // is disabled — so the two never legitimately overlap.
342
- if (bodyRef.value && !desktopRide()) out.push({ el: bodyRef.value });
334
+ if (bodyRef.value) out.push({ el: bodyRef.value });
335
+ if (bodyRef.value) out.push({ el: bodyRef.value });
343
336
  return out;
344
337
  },
345
338
  // Republish the morph's real sweep span and its landing on the body
@@ -361,47 +354,11 @@ export default defineComponent({
361
354
  },
362
355
  });
363
356
 
364
- // Desktop open/close: the WINDOW's own height rides, the chrome
365
- // (title bar, sub-header, footer) keeps its natural size, and only the
366
- // content region is visually compressed. The measurement half lives in
367
- // the family primitive (useHeightRide); this sheet maps its surface
368
- // states onto the ride's two ends (see the choreography block below).
369
- const ride = useHeightRide(contentRef, bodyRef, { factor: 0.9 });
370
-
371
357
  /** The window ride's own state, translated from the surface phases:
372
358
  * the open/close endpoints are the compressed end (`--from`) and the
373
359
  * natural end (`--to`). Between and after them no ride class is
374
360
  * present, so the frame keeps its laid-out height (or the morph's
375
361
  * px pin) and the geometry contract never leaks into the rest state. */
376
- // The body element mounts with the frame on every open, and the inline
377
- // variables die with the previous frame. Republish the split cached from
378
- // the last rest measurement FIRST (so the first painted frame is already
379
- // correct), then — if this is the very first open — measure as soon as
380
- // the element lands (the sheet keeps the frame at `auto` until then, so
381
- // that measurement sees honest natural geometry).
382
- watch(
383
- bodyRef,
384
- (el) => {
385
- if (!el) return;
386
- // ALWAYS measure: the component instance survives a close, so a
387
- // cached split may belong to different content (this modal is reused
388
- // across tabs/entries). Publishing the cache first made the second
389
- // and later opens ride the PREVIOUS geometry, cropping new content
390
- // and then snapping when the cache expired (R5 finding).
391
- void nextTick(() => ride.refresh());
392
- },
393
- { flush: "post" },
394
- );
395
-
396
- // The ride's geometry lives behind `@media (min-width: 768px)`, so the
397
- // phase->class mapping alone cannot tell whether the ride actually owns
398
- // the frame (on a phone the classes land but nothing happens). This
399
- // predicate gates every "is the ride flying" decision below.
400
- const desktopRide = (): boolean =>
401
- typeof window !== "undefined" &&
402
- window.matchMedia("(min-width: 768px)").matches;
403
-
404
-
405
362
  let previouslyFocused: HTMLElement | null = null;
406
363
  let unmounted = false;
407
364
 
@@ -620,10 +577,6 @@ export default defineComponent({
620
577
  ],
621
578
  onPhase: (from, to, event) => {
622
579
  if (to === "openingFrom") {
623
- // Measure BEFORE the enter classes land: the ride's `height:
624
- // calc()` needs the rest chrome/content split, and a frame that is
625
- // already compressed reports the compressed box.
626
- ride.refresh();
627
580
  // Open-request bookkeeping (was the modelValue watcher's open
628
581
  // arm): registration, overlay registry, back-guard, focus
629
582
  // capture.
@@ -653,71 +606,13 @@ export default defineComponent({
653
606
  void nextTick(() => {
654
607
  const p = machine.phase.value;
655
608
  if (p !== "openingFrom" && p !== "openingTo") return;
656
- // DESKTOP RIDE FLIGHT = MORPH BLACKOUT. While the ride owns the
657
- // frame, the morph must not write anything to it: arming installs
658
- // a ResizeObserver on the body, and the ride's own class change
659
- // (locking the body to its measured height) fires it — the
660
- // remeasure then lands `transition: none` mid-flight, which kills
661
- // the ride's running height/scaleY transitions and pins the
662
- // COMPRESSED used height as if it were natural (2026-11-07 user
663
- // report: the open "replays" — ride animates part-way, snaps to
664
- // the bad pin, then re-animates to full height). The morph arms at
665
- // the open edge instead, where the ride classes are already off
666
- // and the pin equals the used height (no visible change). The
667
- // phone keeps the enter-edge arm: its sheet choreography rides
668
- // the morph's clip sweep, and the ride geometry is gated off.
669
- if (!desktopRide()) morph.start();
609
+ if (p === "openingFrom" || p === "openingTo") morph.start();
670
610
  });
671
611
  } else if (to === "open") {
672
612
  surfTrack.cancel();
673
- // Re-measure at the landed rest geometry so the NEXT ride starts
674
- // from this box (content may have changed while the surface opened).
675
- void nextTick(() => ride.refresh());
676
613
  onAfterEnter();
677
- // Idempotent re-arm for the fast paths (see openingFrom) — but on
678
- // desktop the ride may still be RUNNING when the machine flips to
679
- // `open` (its deadline is advisory): arming the morph right now
680
- // would land `transition: none` + a px pin on a frame whose height
681
- // is mid-transition, snapping it to the end value (2026-11-07
682
- // user report: the window "jumps" the last third of its growth).
683
- // Wait for the height leg to actually land (watchdog: the budget
684
- // plus slack), then arm — the pin then equals the used height and
685
- // the handoff is invisible. The phone has no ride; arm at once.
686
- if (desktopRide()) {
687
- const f = contentRef.value;
688
- const armNow = (): void => {
689
- if (machine.phase.value !== "open" || unmounted) return;
690
- morph.start();
691
- };
692
- // No live CSS duration (test DOMs, reduced motion, the global
693
- // animation switch): nothing is animating, arm at once — the
694
- // same zero-duration semantics the surface machine uses.
695
- const rawDuration = f ? getComputedStyle(f).transitionDuration : "";
696
- const durMs = rawDuration
697
- .split(",")
698
- .map((part) => parseFloat(part))
699
- .filter((v) => Number.isFinite(v))
700
- .reduce((a, b) => Math.max(a, b), 0);
701
- if (!f || durMs === 0) {
702
- armNow();
703
- } else {
704
- let done = false;
705
- const finish = (): void => {
706
- if (done) return;
707
- done = true;
708
- f.removeEventListener("transitionend", onHeightEnd);
709
- clearTimeout(guard);
710
- armNow();
711
- };
712
- const onHeightEnd = (e: TransitionEvent): void => {
713
- if (e.target === f && e.propertyName === "height") finish();
714
- };
715
- const guard = setTimeout(finish, 600);
716
- f.addEventListener("transitionend", onHeightEnd);
717
- }
718
- } else {
719
- morph.start();
720
- }
614
+ // Idempotent re-arm for the fast paths (see openingFrom).
615
+ morph.start();
721
616
  // Flush the growth deferred through the unfold. One tick later
722
617
  // the transition classes are off the frame, so the base height
723
618
  // transition animates the pin change instead of the enter's
@@ -726,9 +621,6 @@ export default defineComponent({
726
621
  if (machine.phase.value === "open") morph.remeasure();
727
622
  });
728
623
  } else if (to === "closingFrom") {
729
- // Same rule on the way out: the frame is at its open (natural)
730
- // height right now, so this is the last honest measurement.
731
- ride.refresh();
732
624
  // Close-request bookkeeping (was the watcher's close arm): the
733
625
  // registries forget the surface at request time; the finalize
734
626
  // edge below completes the teardown at leave end.
@@ -1185,7 +1077,6 @@ export default defineComponent({
1185
1077
  <div
1186
1078
  ref={contentRef}
1187
1079
  class={["hk-modal-content", props.contentClass, ...machine.classesFor("hk-modal-content")]}
1188
- data-hk-ride-ready={ride.hasCache() ? "" : undefined}
1189
1080
  role="dialog"
1190
1081
  aria-modal="true"
1191
1082
  aria-label={resolvedSurfaceName.value}
@@ -1193,7 +1084,6 @@ export default defineComponent({
1193
1084
  style={{
1194
1085
  maxWidth: resolvedWidth.value,
1195
1086
  zIndex: contentZ.value,
1196
- ...ride.styleVars(),
1197
1087
 
1198
1088
  }}
1199
1089
  onClick={onContentClick}