@celestia-island/hikari 0.55.304 → 0.55.305

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.304",
3
+ "version": "0.55.305",
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",
@@ -1,3 +1,5 @@
1
+ import { ChevronRight } from "lucide-vue-next";
2
+
1
3
  import { computed, defineComponent, type PropType } from "vue";
2
4
  import { safeHref } from "../utils/safeHref";
3
5
  import { useI18n } from "../i18n/context";
@@ -26,9 +28,7 @@ export default defineComponent({
26
28
  )}
27
29
  {index < props.items.length - 1 && (
28
30
  <span class="hk-breadcrumb-separator">
29
- <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round">
30
- <polyline points="9 18 15 12 9 6" />
31
- </svg>
31
+ <ChevronRight />
32
32
  </span>
33
33
  )}
34
34
  </li>
@@ -1,3 +1,5 @@
1
+ import { GripVertical } from "lucide-vue-next";
2
+
1
3
  import { defineComponent, onBeforeUnmount, ref, shallowRef, watch, type PropType } from "vue";
2
4
  import { onceFrame } from "../runtime/animationBus";
3
5
  import { useReportedTransition } from "../composables/useReportedTransition";
@@ -271,14 +273,7 @@ export default defineComponent({
271
273
  }}
272
274
  >
273
275
  {slots.handle ? slots.handle() : (
274
- <svg width="14" height="14" viewBox="0 0 14 14" fill="currentColor">
275
- <circle cx="4" cy="3" r="1.2" />
276
- <circle cx="4" cy="7" r="1.2" />
277
- <circle cx="4" cy="11" r="1.2" />
278
- <circle cx="10" cy="3" r="1.2" />
279
- <circle cx="10" cy="7" r="1.2" />
280
- <circle cx="10" cy="11" r="1.2" />
281
- </svg>
276
+ <GripVertical size={14} />
282
277
  )}
283
278
  </span>
284
279
  <div class="hk-draggable-grid-content">
@@ -1,3 +1,5 @@
1
+ import { GripVertical } from "lucide-vue-next";
2
+
1
3
  import { defineComponent, onBeforeUnmount, onMounted, ref, shallowRef, type PropType } from "vue";
2
4
  import { onceFrame } from "../runtime/animationBus";
3
5
  import { useReportedTransition } from "../composables/useReportedTransition";
@@ -349,14 +351,7 @@ export default defineComponent({
349
351
  }}
350
352
  >
351
353
  {slots.handle ? slots.handle() : (
352
- <svg width="14" height="14" viewBox="0 0 14 14" fill="currentColor">
353
- <circle cx="4" cy="3" r="1.2" />
354
- <circle cx="4" cy="7" r="1.2" />
355
- <circle cx="4" cy="11" r="1.2" />
356
- <circle cx="10" cy="3" r="1.2" />
357
- <circle cx="10" cy="7" r="1.2" />
358
- <circle cx="10" cy="11" r="1.2" />
359
- </svg>
354
+ <GripVertical size={14} />
360
355
  )}
361
356
  </span>
362
357
  <div class="hk-draggable-list-content">
@@ -1,3 +1,5 @@
1
+ import { Package } from "lucide-vue-next";
2
+
1
3
  import { defineComponent, h, type Component, type PropType } from "vue";
2
4
 
3
5
  import { useI18n } from "../i18n/context";
@@ -56,19 +58,7 @@ export default defineComponent({
56
58
  ) : props.icon ? (
57
59
  h(props.icon, { size: 32, "aria-hidden": true })
58
60
  ) : (
59
- <svg
60
- viewBox="0 0 24 24"
61
- fill="none"
62
- stroke="currentColor"
63
- stroke-width="1.5"
64
- stroke-linecap="round"
65
- stroke-linejoin="round"
66
- width="48"
67
- height="48"
68
- >
69
- <polyline points="22 12 16 12 14 15 10 15 8 12 2 12" />
70
- <path d="M5.45 5.11L2 12v6a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2v-6l-3.45-6.89A2 2 0 0 0 16.76 4H7.24a2 2 0 0 0-1.79 1.11z" />
71
- </svg>
61
+ <Package size={48} stroke-width={1.5} />
72
62
  )}
73
63
  </div>
74
64
  {props.title ? <p class="hk-empty-title">{props.title}</p> : null}
@@ -1,3 +1,5 @@
1
+ import { ChevronDown } from "lucide-vue-next";
2
+
1
3
  import { computed, defineComponent, ref, watch } from "vue";
2
4
  import "./HkExpansionPanel.scss";
3
5
 
@@ -87,9 +89,7 @@ export default defineComponent({
87
89
  </div>
88
90
  )}
89
91
  <span class="hk-expansion-panel-chevron" aria-hidden="true">
90
- <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
91
- <polyline points="6 9 12 15 18 9" />
92
- </svg>
92
+ <ChevronDown />
93
93
  </span>
94
94
  </button>
95
95
  <div class="hk-expansion-panel-body">
@@ -64,11 +64,12 @@ afterEach(() => {
64
64
  });
65
65
 
66
66
  describe("HkFab shell", () => {
67
- it("renders a round button with the aria label and the default chevron svg", () => {
67
+ it("renders a round button with the aria label and the default chevron glyph", () => {
68
68
  const m = mountFab({ ariaLabel: "Jump to latest" });
69
69
  expect(m.button.getAttribute("aria-label")).toBe("Jump to latest");
70
- // No icon prop -> inline chevron-down fallback svg.
71
- expect(m.button.querySelector("svg polyline")).not.toBeNull();
70
+ // No icon prop -> the standard lucide chevron-down (wave B4: the
71
+ // hand-drawn inline fallback retired).
72
+ expect(m.button.querySelector("svg.lucide-chevron-down")).not.toBeNull();
72
73
  });
73
74
 
74
75
  it("mirrors positioning/corner/size/variant props onto data attributes", () => {
@@ -1,3 +1,5 @@
1
+ import { ChevronDown } from "lucide-vue-next";
2
+
1
3
  import {
2
4
  defineComponent,
3
5
  h,
@@ -141,19 +143,9 @@ export default defineComponent({
141
143
  : RegistryIcon
142
144
  ? h(RegistryIcon)
143
145
  : (
144
- // Inline chevron-down fallback (kept local to avoid
145
- // pulling the lucide symbol through a non-registry path).
146
- <svg
147
- viewBox="0 0 24 24"
148
- fill="none"
149
- stroke="currentColor"
150
- stroke-width="2"
151
- stroke-linecap="round"
152
- stroke-linejoin="round"
153
- aria-hidden="true"
154
- >
155
- <polyline points="6 9 12 15 18 9" />
156
- </svg>
146
+ // Wave B4: the hand-drawn fallback retired — the standard
147
+ // lucide component, same channel as every other control.
148
+ <ChevronDown aria-hidden="true" />
157
149
  )}
158
150
  </span>
159
151
  );
@@ -1,3 +1,5 @@
1
+ import { ChevronDown } from "lucide-vue-next";
2
+
1
3
  import { defineComponent, ref, type PropType } from "vue";
2
4
 
3
5
  import HkLocalePickerPopup from "./HkLocalePickerPopup";
@@ -50,14 +52,7 @@ export const HkLocalePicker = defineComponent({
50
52
  onClick={() => (open.value = !open.value)}
51
53
  >
52
54
  {props.currentLocale.toUpperCase()}
53
- <svg width="10" height="10" viewBox="0 0 10 10" aria-hidden="true">
54
- <path
55
- d="M2 3.5l3 3 3-3"
56
- fill="none"
57
- stroke="currentColor"
58
- stroke-width="1.2"
59
- />
60
- </svg>
55
+ <ChevronDown size={10} aria-hidden="true" />
61
56
  </button>
62
57
  <HkLocalePickerPopup
63
58
  open={open.value}
@@ -2,21 +2,19 @@
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-06 (supersedes the 2026-11-05 content-only ride):
6
- * the window's own HEIGHT rides the open/close — the frame settles
7
- * `translate(…) scaleY(0.9) → translate(…) scaleY(1)` about its center, so
8
- * the box grows symmetrically and anything centered in it (the content
9
- * region above all) stays horizontally AND vertically centered at every t;
10
- * the translate prefix is byte-identical in every state so the ride can
11
- * never re-seat the frame. `.hk-modal-body` keeps a ride of its own:
12
- * `scale(0.95, 1) → scale(1, 1)` — the horizontal factor starts slightly
13
- * NARROW and spreads out to fill the window (user direction 2026-11-06,
14
- * revising the first cut: a 1.1 overshoot read as uglier than a slight
15
- * pinch), while its vertical factor stays 1 because the body's Y already
16
- * rides the frame's scaleY (own 0.9 would compound to 0.81). The retired
17
- * grammars: the 2026-11-04 whole-frame `scale(1, 0.9)`, the 2026-11-05
18
- * body-only uniform `scale(0.9)`, and the 2026-11-06 `scale(1.1, 1)`
19
- * overshoot.
5
+ * User direction 2026-11-07 (supersedes the 2026-11-05 content-only ride
6
+ * and the 2026-11-06 body scales): ONLY THE HEIGHT COMPRESSES — width is
7
+ * never scaled. The frame settles `translate(…) scaleY(0.9) →
8
+ * translate(…) scaleY(1)` about its center, so the box grows symmetrically
9
+ * and anything centered in it (the content region above all) stays
10
+ * horizontally AND vertically centered at every t; the translate prefix is
11
+ * byte-identical in every state so the ride can never re-seat the frame.
12
+ * `.hk-modal-body` carries NO transform rule of its own: its vertical ride
13
+ * is entirely the frame's scaleY, so its width stays exactly 100% while its
14
+ * height follows the 0.9 → 1 compression. The retired grammars, oldest
15
+ * first: the 2026-11-04 whole-frame `scale(1, 0.9)`, the 2026-11-05
16
+ * body-only uniform `scale(0.9)`, the 2026-11-06 `scale(1.1, 1)` overshoot
17
+ * and its same-day `scale(0.95, 1)` pinch.
20
18
  *
21
19
  * Why compile: a source-text scanner can be dodged by a later duplicate
22
20
  * rule, a block comment, SCSS interpolation, `@extend` or an `@use`d
@@ -343,12 +341,8 @@ const ENTER_TRANSITION =
343
341
  const LEAVE_TRANSITION =
344
342
  "opacity var(--hk-modal-duration, 0.32s) var(--hk-modal-ease-in, cubic-bezier(0.45, 0, 0.55, 1)), " +
345
343
  "transform var(--hk-modal-duration, 0.32s) var(--hk-modal-ease-in, cubic-bezier(0.45, 0, 0.55, 1))";
346
- const BODY_ENTER_TRANSITION =
347
- "transform var(--hk-modal-duration, 0.42s) var(--hk-modal-ease-out, cubic-bezier(0.33, 1, 0.68, 1))";
348
- const BODY_LEAVE_TRANSITION =
349
- "transform var(--hk-modal-duration, 0.32s) var(--hk-modal-ease-in, cubic-bezier(0.45, 0, 0.55, 1))";
350
344
 
351
- describe("HkModal desktop open/close contract (compiled cascade, user direction 2026-11-06)", () => {
345
+ describe("HkModal desktop open/close contract (compiled cascade, user direction 2026-11-07)", () => {
352
346
  it("compiles into a stylesheet with the ride rules present (known-positive)", () => {
353
347
  // Guard against a vacuous suite: the compiled sheet must really contain
354
348
  // the frame rule, the body rule and the reduced-motion flatten.
@@ -437,49 +431,27 @@ describe("HkModal desktop open/close contract (compiled cascade, user direction
437
431
  expect(leaveWill).toBe("opacity, transform");
438
432
  });
439
433
 
440
- it("rides the content region X 0.95→1 with Y pinned at 1, about its center", () => {
441
- // 2026-11-06 (revised): the content starts slightly NARROW (95% of the
442
- // window width) and spreads to 100%; the vertical factor stays 1 — the
443
- // body's Y already rides the frame's scaleY, so its own 1 keeps the
444
- // compound at exactly 0.9→1.
445
- const expect_ = (state: string, value: string) => {
446
- const frame = frameWith(...RIDE.find((r) => r.name === state)!.classes);
447
- const body = child(frame, "hk-modal-body");
448
- expect(winner(body, "transform", DESKTOP), state).toBe(value);
449
- // `center center` / `center` / `50%` all compute to the same origin.
450
- const origin = (winner(body, "transform-origin", DESKTOP) ?? "")
451
- .replace(/\bcenter\b/g, "50%")
452
- .split(/\s+/)
453
- .filter(Boolean);
454
- const [x = "50%", y = "50%"] = origin;
455
- expect([x, y], state).toEqual(["50%", "50%"]);
456
- };
457
- expect_("enter-from", "scale(0.95, 1)");
458
- expect_("leave-to", "scale(0.95, 1)");
459
- expect_("enter-to", "scale(1, 1)");
460
- expect_("leave-from", "scale(1, 1)");
461
- // …and the same with the opt-in draggable flag on, where an
462
- // attribute-qualified rule could otherwise slip past the mocks.
463
- for (const state of RIDE) {
464
- const frame = draggableFrameWith(...state.classes);
465
- const body = child(frame, "hk-modal-body");
466
- const expected =
467
- state.name === "enter-from" || state.name === "leave-to" ? "scale(0.95, 1)" : "scale(1, 1)";
468
- expect(winner(body, "transform", DESKTOP), `draggable ${state.name}`).toBe(expected);
434
+ it("never scales the content region: width stays 100% at every t", () => {
435
+ // 2026-11-07: only the height compresses. The body must carry no
436
+ // transform of its own in ANY ride state — its vertical ride is the
437
+ // frame's scaleY inherited down the tree, so a body transform here
438
+ // would either re-scale the width or double the Y compression.
439
+ const REST_OR_RIDE = [...RIDE, { name: "rest", classes: [] as string[] }];
440
+ // The whole transform family, on BOTH branches (R2 finding 2026-11-07
441
+ // (S2): the draggable branch used to pin `transform` only, so a
442
+ // `[data-draggable] … { scale: … }` longhand stayed green).
443
+ const PROPS = ["transform", "transform-origin", "scale", "translate", "rotate", "zoom", "perspective"];
444
+ for (const state of REST_OR_RIDE) {
445
+ for (const [label, frame] of [
446
+ ["", frameWith(...state.classes)],
447
+ ["draggable ", draggableFrameWith(...state.classes)],
448
+ ] as const) {
449
+ const body = child(frame, "hk-modal-body");
450
+ for (const prop of PROPS) {
451
+ expect(winner(body, prop, DESKTOP), `${label}${state.name} body ${prop}`).toBeUndefined();
452
+ }
453
+ }
469
454
  }
470
- // At rest the body carries no transform at all.
471
- expect(winner(child(frameWith(), "hk-modal-body"), "transform", DESKTOP)).toBeUndefined();
472
- });
473
-
474
- it("rides the content scale on the family timing tokens", () => {
475
- const body = (state: string, prop: string) => {
476
- const frame = frameWith(...RIDE.find((r) => r.name === state)!.classes);
477
- return winner(child(frame, "hk-modal-body"), prop, DESKTOP);
478
- };
479
- // Whole-value pins, same rationale as the frame transitions above.
480
- expect(body("enter-from", "transition")).toBe(BODY_ENTER_TRANSITION);
481
- expect(body("leave-to", "transition")).toBe(BODY_LEAVE_TRANSITION);
482
- expect(body("enter-from", "will-change")).toBe("transform");
483
455
  });
484
456
 
485
457
  it("never transforms the chrome: title bar, sub-header, footer, close button, title glyph", () => {
@@ -509,7 +481,7 @@ describe("HkModal desktop open/close contract (compiled cascade, user direction
509
481
  }
510
482
  });
511
483
 
512
- it("flattens the content scale under prefers-reduced-motion", () => {
484
+ it("flattens the frame ride (and any stray body transform) under prefers-reduced-motion", () => {
513
485
  const frame = frameWith("hk-modal-content-enter-from", "hk-modal-content-enter-active");
514
486
  const body = child(frame, "hk-modal-body");
515
487
  expect(winner(body, "transform", DESKTOP_REDUCED)).toBe("none");
@@ -562,6 +534,35 @@ describe("HkModal desktop open/close contract (compiled cascade, user direction
562
534
  ).toBeUndefined();
563
535
  });
564
536
 
537
+ it("never lets ANY rule put a ride transform on the content region", () => {
538
+ // Fail-closed sweep, selector-level and media-independent (R2 finding
539
+ // 2026-11-07 (S2): the cascade mock cannot match `:not()`, a type
540
+ // qualifier, an extra media gate or a pseudo-element, so twelve such
541
+ // mutations stayed green). Every rule that targets the body — in any
542
+ // media query, at any specificity — must never SCALE it. Allowed
543
+ // anywhere: an inert `transform: none` (the reduced-motion guard) and
544
+ // any `transform-origin`, which does nothing without a transform.
545
+ //
546
+ // Class-TOKEN based, not class-dot based: `div.hk-modal-body` (the same
547
+ // compound, type-qualified) puts a letter before the dot and slipped
548
+ // through the first cut, as did `[class~="hk-modal-body"]` (R3 finding
549
+ // 2026-11-07 (S2)). The trailing guard keeps -scroll / -inner out.
550
+ const BODY_TARGET = /(^|[^\w-])hk-modal-body(?![\w-])/;
551
+ const TRANSFORM_FAMILY = /^(transform|transform-origin|translate|scale|rotate|zoom|perspective)$/;
552
+ const INERT = new Set(["none", "auto"]);
553
+ const offenders: string[] = [];
554
+ for (const rule of RULES) {
555
+ if (!BODY_TARGET.test(rule.selector)) continue;
556
+ for (const prop of rule.declarations.keys()) {
557
+ if (!TRANSFORM_FAMILY.test(prop)) continue;
558
+ if (prop === "transform-origin") continue;
559
+ const value = (rule.declarations.get(prop) ?? "").replace(/\s+/g, " ").trim();
560
+ if (!INERT.has(value)) offenders.push(`${rule.selector} { ${prop}: ${value} }`);
561
+ }
562
+ }
563
+ expect(offenders, "the content region must never be scaled").toEqual([]);
564
+ });
565
+
565
566
  it("never lets ANY rule put a transform on a chrome surface", () => {
566
567
  // Fail-closed sweep: selector-level and media-independent, so a chrome
567
568
  // transform is caught whatever the combinator, grouping, attribute
@@ -598,9 +599,11 @@ describe("HkModal desktop open/close contract (compiled cascade, user direction
598
599
  const CHROME_ALLOWED = new Set(["opacity", "transition", "will-change", "transform"]);
599
600
  // The contracted transform values a ride rule may carry. Frame: the
600
601
  // three translate+scaleY states (reduced-motion flatten included);
601
- // body: the two two-arg scales.
602
+ // body: nothing (its width is never scaled — 2026-11-07).
602
603
  const LEGAL_FRAME = new Set([CENTERING, FRAME_FROM, FRAME_TO]);
603
- const LEGAL_BODY = new Set(["scale(0.95, 1)", "scale(1, 1)"]);
604
+ // The body carries no transform contract any more (2026-11-07): any
605
+ // ride rule targeting it is an offender outright.
606
+ const LEGAL_BODY = new Set<string>([]);
604
607
  const offenders: string[] = [];
605
608
  // Media-independent on purpose: a ride selector declared at top level
606
609
  // (no media query) applies at every width, so it must obey the same
@@ -641,19 +644,19 @@ describe("HkModal desktop open/close contract (compiled cascade, user direction
641
644
  });
642
645
 
643
646
  it("keeps the contracted per-axis grammar (no alternate syntax escapes)", () => {
644
- // 2026-11-06 grammar: the frame rides `translate(…) scaleY(s)`; the
645
- // body rides two-arg `scale(x, 1)`. Any other expression of the same
646
- // idea — single-arg scale on the frame list, per-axis longhand
647
- // functions, scale3d, a computed matrix, or the independent transform
648
- // properties — would break the contracted interpolation shape and must
649
- // be impossible.
647
+ // 2026-11-07 grammar: the FRAME rides `translate(…) scaleY(s)` and the
648
+ // body rides nothing. Any other expression of the same idea — a
649
+ // single-arg scale or a scaleX on the frame list, a two-arg scale, any
650
+ // body transform, scale3d, a computed matrix, or the independent
651
+ // transform properties — would break the contracted shape (or the
652
+ // width contract) and must be impossible.
650
653
  const BODY_ESCAPES = /scale[XYZ]\(|scale3d\(|matrix[23]d?\(/;
651
654
  const FRAME_ESCAPES = /scale\([^)]*,|scaleX\(|scale3d\(|matrix[23]d?\(|\brotate\(/;
652
655
  for (const state of RIDE) {
653
656
  const frame = frameWith(...state.classes);
654
657
  const body = child(frame, "hk-modal-body");
655
658
  const bodyTransform = winner(body, "transform", DESKTOP) ?? "";
656
- expect(bodyTransform, `body ${state.name}`).toMatch(/^scale\(([\d.]+), 1\)$/);
659
+ expect(bodyTransform, `body ${state.name}`).toBe("");
657
660
  expect(bodyTransform, `body ${state.name}`).not.toMatch(BODY_ESCAPES);
658
661
  expect(winner(body, "scale", DESKTOP), `body ${state.name}`).toBeUndefined();
659
662
  expect(winner(body, "translate", DESKTOP), `body ${state.name}`).toBeUndefined();
@@ -113,23 +113,21 @@
113
113
  // transform rule of its own; it inherits this single frame scale, which
114
114
  // IS the window-height motion the user asked for.
115
115
  //
116
- // The content region keeps a ride of its own: `.hk-modal-body` settles
117
- // `scale(0.95, 1) → scale(1, 1)` about its center. The horizontal factor
118
- // starts slightly NARROW (user direction 2026-11-06, after reviewing the
119
- // first cut: a 1.1 overshoot read as uglier than a slight pinch, so the
120
- // content starts at 95% width and spreads out to fill the window). The
121
- // vertical factor stays exactly 1: the body's Y dimension already rides
122
- // the frame's `scaleY`, so its own 1 makes the compound 0.9 → 1 — the
123
- // same vertical ride as before, never the doubled 0.81 a second Y factor
124
- // would produce.
116
+ // The content region does NOT ride on its own (user direction 2026-11-07,
117
+ // superseding the body scale that shipped in 0.55.303 / 0.55.304): width
118
+ // is never scaled, only height is. `.hk-modal-body` therefore carries NO
119
+ // transform rule in any ride state — its vertical ride is entirely the
120
+ // frame's `scaleY` above, so during the open/close its width stays exactly
121
+ // 100% of the window while its height follows the 0.9 → 1 compression. Two
122
+ // retired grammars before this one: the two-arg `scale(0.95, 1)` pinch and
123
+ // the `scale(1.1, 1)` overshoot.
125
124
  //
126
125
  // Hosts retune the ride through the `--hk-modal-*` tokens (duration /
127
126
  // easing) or the body padding vars — NOT by setting `transform` on
128
- // `.hk-modal-body` or the frame: the state rules pair matched function
129
- // lists (`translate+scaleY` on the frame, two-arg `scale` on the body)
130
- // so both ends of every interpolation ride the same shape, and a host's
131
- // single-class transform would be overridden mid-ride and again under
132
- // reduced motion.
127
+ // `.hk-modal-body` or the frame: the frame's state rules pair a matched
128
+ // function list (`translate + scaleY`) so both ends of the interpolation
129
+ // ride the same shape, and a host's single-class transform would be
130
+ // overridden mid-ride and again under reduced motion.
133
131
  //
134
132
  // No clip-path and no Y offset ride the open/close: the visible box IS the
135
133
  // frame at every t, so nothing is ever sliced off (the 2026-09-14 clip
@@ -153,7 +151,7 @@
153
151
  }
154
152
 
155
153
  // Close: the reveal's TRUE reverse — same chrome fade, while the window
156
- // settles back toward 90% height and the content pinches back in.
154
+ // settles back toward 90% height, width untouched in both directions.
157
155
  .hk-modal-content-leave-active {
158
156
  transition:
159
157
  opacity var(--hk-modal-duration, 0.32s)
@@ -185,37 +183,6 @@
185
183
  )
186
184
  scaleY(1);
187
185
  }
188
-
189
- // Content region: the one surface with its own transform. The timing
190
- // lives on the per-state transition so the enter and the leave can
191
- // differ (0.42s open / 0.32s close) exactly like the chrome fade does.
192
- .hk-modal-content-enter-active .hk-modal-body {
193
- transform-origin: 50% 50%;
194
- transition:
195
- transform var(--hk-modal-duration, 0.42s)
196
- var(--hk-modal-ease-out, cubic-bezier(0.33, 1, 0.68, 1));
197
- will-change: transform;
198
- }
199
-
200
- .hk-modal-content-leave-active .hk-modal-body {
201
- transform-origin: 50% 50%;
202
- transition:
203
- transform var(--hk-modal-duration, 0.32s)
204
- var(--hk-modal-ease-in, cubic-bezier(0.45, 0, 0.55, 1));
205
- will-change: transform;
206
- }
207
-
208
- // Both ends two-arg `scale`: X starts at 95% and spreads to 1, Y stays 1
209
- // (the body's vertical ride comes from the frame's scaleY, not itself).
210
- .hk-modal-content-enter-from .hk-modal-body,
211
- .hk-modal-content-leave-to .hk-modal-body {
212
- transform: scale(0.95, 1);
213
- }
214
-
215
- .hk-modal-content-enter-to .hk-modal-body,
216
- .hk-modal-content-leave-from .hk-modal-body {
217
- transform: scale(1, 1);
218
- }
219
186
  }
220
187
 
221
188
  // ------
@@ -704,14 +671,13 @@
704
671
  calc(-50% + var(--hk-modal-shift-y, 0px))
705
672
  );
706
673
  }
707
- // The content-scale reveal flattens with it. Both selectors sit at the
708
- // same specificity as the desktop pairs above and this block comes
709
- // later in the sheet, so the flattened state wins by source order.
674
+ // The content region carries no transform of its own any more (width is
675
+ // never scaled — 2026-11-07), so only the frame needs flattening above.
676
+ // The body lines stay as a fail-closed guard: if a future state rule ever
677
+ // reintroduces a body transform, reduced motion still flattens it.
710
678
  .hk-modal-content .hk-modal-body {
711
679
  transform: none;
712
680
  transition: none;
713
- // …and drop the compositor hint the ride would otherwise leave on
714
- // the (tall) content block for the flattened frame.
715
681
  will-change: auto;
716
682
  }
717
683
  }
@@ -1,3 +1,5 @@
1
+ import { ChevronRight } from "lucide-vue-next";
2
+
1
3
  import {
2
4
  computed,
3
5
  defineComponent,
@@ -626,18 +628,7 @@ export default defineComponent({
626
628
  .join(" ");
627
629
 
628
630
  const separator = () => (
629
- <svg
630
- class="hk-modal-breadcrumb-sep"
631
- width="12"
632
- height="12"
633
- viewBox="0 0 24 24"
634
- fill="none"
635
- stroke="currentColor"
636
- stroke-width="2"
637
- stroke-linecap="round"
638
- >
639
- <polyline points="9 18 15 12 9 6" />
640
- </svg>
631
+ <ChevronRight class="hk-modal-breadcrumb-sep" size={12} />
641
632
  );
642
633
 
643
634
  return () =>
@@ -1,3 +1,5 @@
1
+ import { User } from "lucide-vue-next";
2
+
1
3
  import { credentialAutocomplete } from "../runtime/credentialAutofill";
2
4
  import { defineComponent, ref } from "vue";
3
5
  import { useI18n } from "../i18n/context";
@@ -131,20 +133,7 @@ export const HkSignInCard = defineComponent({
131
133
  slots.usernameIcon ? (
132
134
  slots.usernameIcon()
133
135
  ) : (
134
- <svg
135
- xmlns="http://www.w3.org/2000/svg"
136
- viewBox="0 0 24 24"
137
- fill="none"
138
- stroke="currentColor"
139
- stroke-width="2"
140
- stroke-linecap="round"
141
- stroke-linejoin="round"
142
- class="s-auth-card-field-icon"
143
- aria-hidden="true"
144
- >
145
- <path d="M19 21v-2a4 4 0 0 0-4-4H9a4 4 0 0 0-4 4v2"></path>
146
- <circle cx="12" cy="7" r="4"></circle>
147
- </svg>
136
+ <User class="s-auth-card-field-icon" aria-hidden="true" />
148
137
  ),
149
138
  }}
150
139
  </HkInput>
@@ -1,3 +1,5 @@
1
+ import { Check } from "lucide-vue-next";
2
+
1
3
  import { defineComponent, h, type Component, type PropType } from "vue";
2
4
 
3
5
  import "./HkStepGuide.scss";
@@ -77,20 +79,7 @@ export default defineComponent({
77
79
  >
78
80
  <span class="hk-step-guide-ordinal">
79
81
  {isDone ? (
80
- <svg
81
- class="hk-step-guide-check"
82
- viewBox="0 0 24 24"
83
- aria-hidden="true"
84
- >
85
- <path
86
- d="M20 6L9 17l-5-5"
87
- fill="none"
88
- stroke="currentColor"
89
- stroke-width="3"
90
- stroke-linecap="round"
91
- stroke-linejoin="round"
92
- />
93
- </svg>
82
+ <Check class="hk-step-guide-check" stroke-width={3} aria-hidden="true" />
94
83
  ) : (
95
84
  idx + 1
96
85
  )}
@@ -1,3 +1,5 @@
1
+ import { Check, ChevronUp, GripVertical } from "lucide-vue-next";
2
+
1
3
  import { computed, defineComponent, inject, onBeforeUnmount, onMounted, ref, watch, type PropType } from "vue";
2
4
 
3
5
  import { useI18n } from "../i18n/context";
@@ -770,17 +772,7 @@ export default defineComponent({
770
772
  ]}
771
773
  >
772
774
  {allChecked.value && (
773
- <svg
774
- class="hk-table-checkbox-icon"
775
- viewBox="0 0 24 24"
776
- fill="none"
777
- stroke="currentColor"
778
- stroke-width="3"
779
- stroke-linecap="round"
780
- stroke-linejoin="round"
781
- >
782
- <polyline points="20 6 9 17 4 12" />
783
- </svg>
775
+ <Check class="hk-table-checkbox-icon" stroke-width={3} />
784
776
  )}
785
777
  </span>
786
778
  </label>
@@ -802,20 +794,10 @@ export default defineComponent({
802
794
  >
803
795
  <span class="hk-table-header-label">{col.title}</span>
804
796
  {canSort && sortKey.value === col.key && (
805
- <svg
806
- class={[
797
+ <ChevronUp class={[
807
798
  "hk-table-sort-icon",
808
799
  sortDirection.value === "desc" ? "hk-table-sort-icon-desc" : "",
809
- ]}
810
- viewBox="0 0 24 24"
811
- fill="none"
812
- stroke="currentColor"
813
- stroke-width="2"
814
- stroke-linecap="round"
815
- stroke-linejoin="round"
816
- >
817
- <polyline points="18 15 12 9 6 15" />
818
- </svg>
800
+ ]} />
819
801
  )}
820
802
  </th>
821
803
  );
@@ -874,14 +856,7 @@ export default defineComponent({
874
856
  onPointerdown={(e: PointerEvent) => onHandlePointerdown(e, index)}
875
857
  onKeydown={(e: KeyboardEvent) => onHandleKeydown(e, index)}
876
858
  >
877
- <svg width="12" height="14" viewBox="0 0 12 14" fill="currentColor" aria-hidden="true">
878
- <circle cx="3.5" cy="3" r="1.2" />
879
- <circle cx="8.5" cy="3" r="1.2" />
880
- <circle cx="3.5" cy="7" r="1.2" />
881
- <circle cx="8.5" cy="7" r="1.2" />
882
- <circle cx="3.5" cy="11" r="1.2" />
883
- <circle cx="8.5" cy="11" r="1.2" />
884
- </svg>
859
+ <GripVertical size={14} aria-hidden="true" />
885
860
  </span>
886
861
  </td>
887
862
  )}
@@ -902,17 +877,7 @@ export default defineComponent({
902
877
  ]}
903
878
  >
904
879
  {selectedRowKeys.value.has(rowKey) && (
905
- <svg
906
- class="hk-table-checkbox-icon"
907
- viewBox="0 0 24 24"
908
- fill="none"
909
- stroke="currentColor"
910
- stroke-width="3"
911
- stroke-linecap="round"
912
- stroke-linejoin="round"
913
- >
914
- <polyline points="20 6 9 17 4 12" />
915
- </svg>
880
+ <Check class="hk-table-checkbox-icon" stroke-width={3} />
916
881
  )}
917
882
  </span>
918
883
  </label>
@@ -1,3 +1,5 @@
1
+ import { ChevronRight } from "lucide-vue-next";
2
+
1
3
  import { defineComponent, ref, watch, h, type Component, type PropType, type VNodeChild } from "vue";
2
4
 
3
5
  import "./HkTree.scss";
@@ -158,9 +160,7 @@ const TreeNodeRow = ({
158
160
  onClick={(e: MouseEvent) => { e.stopPropagation(); if (hasChildren) onToggle(node.id); }}
159
161
  >
160
162
  {hasChildren ? (
161
- <svg viewBox="0 0 16 16" width="12" height="12" class="hk-tree-chevron" data-open={isExpanded || undefined}>
162
- <path d="M6 4l4 4-4 4" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" />
163
- </svg>
163
+ <ChevronRight class="hk-tree-chevron" data-open={isExpanded || undefined} size={12} stroke-width={1.5} />
164
164
  ) : (
165
165
  <span class="hk-tree-leaf" />
166
166
  )}