@celestia-island/hikari 0.55.313 → 0.55.314

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.313",
3
+ "version": "0.55.314",
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",
@@ -143,3 +143,7 @@ registerCssAnimation("hk-screen-identify-in");
143
143
  // brushed window still reads as live. Reduced motion keeps the dashed
144
144
  // pending look and drops the loop (house pattern, see HkSkeleton).
145
145
  registerCssAnimation("hk-range-ribbon-pulse", { infinite: true });
146
+ // HkBadge's presence pulse (opacity-only, loops forever): the `pulse`
147
+ // prop's dot breathes while its state is in-flight — a settled dot sits
148
+ // static. Reduced motion drops the loop (house pattern, see HkSkeleton).
149
+ registerCssAnimation("hk-badge-dot-pulse", { infinite: true });
@@ -64,6 +64,29 @@
64
64
  flex-shrink: 0;
65
65
  }
66
66
 
67
+ /* Presence pulse (data-pulse, dot required): an in-flight / live state
68
+ breathes instead of sitting static. Opacity-only so the pill's layout
69
+ box never moves; consumers wanting a glow layer their own shadow. */
70
+ @keyframes hk-badge-dot-pulse {
71
+ 0%,
72
+ 100% {
73
+ opacity: 1;
74
+ }
75
+ 50% {
76
+ opacity: 0.4;
77
+ }
78
+ }
79
+
80
+ .hk-badge[data-pulse] [data-el="dot"] {
81
+ animation: hk-badge-dot-pulse 1.6s ease-in-out infinite;
82
+ }
83
+
84
+ @media (prefers-reduced-motion: reduce) {
85
+ .hk-badge[data-pulse] [data-el="dot"] {
86
+ animation: none;
87
+ }
88
+ }
89
+
67
90
  /* dot-only: strip the pill padding/border/background so it's just the
68
91
  indicator dot, not a dot wrapped in a bordered pill. */
69
92
  .hk-badge[data-dot-only] {
@@ -0,0 +1,71 @@
1
+ import { afterEach, describe, expect, it } from "vitest";
2
+ import { createApp, h } from "vue";
3
+ import { readFileSync } from "node:fs";
4
+ import { compile } from "sass";
5
+ import { dirname, resolve } from "node:path";
6
+ import { fileURLToPath } from "node:url";
7
+
8
+ import HkBadge from "./HkBadge";
9
+
10
+ const mounts: Array<{ app: ReturnType<typeof createApp>; container: HTMLElement }> = [];
11
+
12
+ function mount(renderNode: () => ReturnType<typeof h>) {
13
+ const container = document.createElement("div");
14
+ document.body.appendChild(container);
15
+ const app = createApp({ render: renderNode });
16
+ app.mount(container);
17
+ mounts.push({ app, container });
18
+ return container;
19
+ }
20
+
21
+ afterEach(() => {
22
+ for (const { app, container } of mounts.splice(0)) {
23
+ app.unmount();
24
+ container.remove();
25
+ }
26
+ });
27
+
28
+ // Vitest stubs CSS, so the runtime suite can never catch a lost rule —
29
+ // the pulse animation contract is additionally pinned by the compiled
30
+ // stylesheet guard at the bottom of this file (HkCornerBadge pattern).
31
+ describe("HkBadge pulse", () => {
32
+ it("renders the dot and data-pulse only when both dot and pulse are set", () => {
33
+ const container = mount(() => h(HkBadge, { dot: true, pulse: true }, () => "Creating"));
34
+ expect(container.querySelector(".hk-badge [data-el='dot']")).toBeTruthy();
35
+ expect(container.querySelector(".hk-badge[data-pulse]")).toBeTruthy();
36
+ });
37
+
38
+ it("never sets data-pulse without a dot — a pulsing pill with no dot is meaningless", () => {
39
+ const container = mount(() => h(HkBadge, { pulse: true }, () => "Creating"));
40
+ expect(container.querySelector(".hk-badge")).toBeTruthy();
41
+ expect(container.querySelector("[data-pulse]")).toBeNull();
42
+ expect(container.querySelector("[data-el='dot']")).toBeNull();
43
+ });
44
+
45
+ it("keeps the static dot the default — pulse is opt-in per call site", () => {
46
+ const container = mount(() => h(HkBadge, { dot: true }, () => "Idle"));
47
+ expect(container.querySelector(".hk-badge [data-el='dot']")).toBeTruthy();
48
+ expect(container.querySelector("[data-pulse]")).toBeNull();
49
+ });
50
+ });
51
+
52
+ describe("HkBadge pulse stylesheet contract", () => {
53
+ const here = dirname(fileURLToPath(import.meta.url));
54
+ const css = compile(resolve(here, "HkBadge.scss"), { style: "expanded" }).css;
55
+
56
+ it("drives the pulse through the data-pulse selector on the dot only", () => {
57
+ // Sass compiles quoted attribute selectors unquoted ([data-el=dot]).
58
+ expect(css).toContain(".hk-badge[data-pulse] [data-el=dot]");
59
+ // The animation must never ride the pill itself — layout stays frozen.
60
+ expect(css.match(/\.hk-badge\s*\{[^}]*animation/)).toBeNull();
61
+ });
62
+
63
+ it("names the keyframes and runs them infinitely", () => {
64
+ expect(css).toContain("@keyframes hk-badge-dot-pulse");
65
+ expect(/\.hk-badge\[data-pulse\] \[data-el=dot\]\s*\{[^}]*animation:\s*hk-badge-dot-pulse[^;]*infinite/.test(css)).toBe(true);
66
+ });
67
+
68
+ it("switches the pulse off under prefers-reduced-motion", () => {
69
+ expect(css).toMatch(/@media \(prefers-reduced-motion: reduce\)\s*\{[^@]*\.hk-badge\[data-pulse\] \[data-el=dot\]\s*\{[^}]*animation:\s*none/);
70
+ });
71
+ });
@@ -15,6 +15,9 @@ export default defineComponent({
15
15
  props: {
16
16
  variant: { type: String as PropType<BadgeVariant>, default: "default" },
17
17
  dot: { type: Boolean, default: false },
18
+ /** Animate the dot with a presence pulse (in-flight / live state).
19
+ * Meaningful only with `dot`; honors prefers-reduced-motion. */
20
+ pulse: { type: Boolean, default: false },
18
21
  size: { type: String as PropType<"sm" | "md">, default: "md" },
19
22
  mono: { type: Boolean, default: false },
20
23
  uppercase: { type: Boolean, default: false },
@@ -38,6 +41,7 @@ export default defineComponent({
38
41
  data-size={props.size}
39
42
  data-pill={props.pill ? undefined : "false"}
40
43
  data-mono={props.mono ? "" : undefined}
44
+ data-pulse={props.pulse && props.dot ? "" : undefined}
41
45
  data-uppercase={props.uppercase ? "" : undefined}
42
46
  data-dot-only={(props.dot && !slots.default) || undefined}
43
47
  style={Object.keys(style.value).length ? style.value : undefined}
@@ -6,6 +6,7 @@
6
6
  // host themes (dark mode etc.) when the vars exist.
7
7
 
8
8
  @use "../styles/polyfill" as *;
9
+ @use "./hikari-vars" as vars;
9
10
 
10
11
  .hk-error-landing {
11
12
  --hel-background: var(--color-background, 246 247 249);
@@ -34,7 +35,7 @@
34
35
  padding: var(--space-20, 1.25rem);
35
36
  background: rgb(var(--hel-background));
36
37
  color: rgb(var(--hel-text));
37
- font-family: var(--font-sans, system-ui, -apple-system, "Segoe UI", sans-serif);
38
+ font-family: var(--font-sans, vars.$hikari-font-family-sans);
38
39
  }
39
40
 
40
41
  .hk-error-landing__card {
@@ -1,3 +1,5 @@
1
+ @use "./hikari-vars" as vars;
2
+
1
3
  .hk-splash {
2
4
  display: flex;
3
5
  flex-direction: column;
@@ -6,7 +8,7 @@
6
8
  min-height: 100vh;
7
9
  background: var(--hi-color-bg-canvas, #0d1117);
8
10
  color: var(--hi-color-text-primary, #1e1e1e);
9
- font-family: var(--font-sans, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "Noto Sans CJK SC", sans-serif);
11
+ font-family: var(--font-sans, vars.$hikari-font-family-sans);
10
12
  }
11
13
 
12
14
  .hk-splash-main {
@@ -6,6 +6,7 @@
6
6
  // Text selection is chrome-disabled across the whole bar.
7
7
 
8
8
  @use "../styles/polyfill" as *;
9
+ @use "./hikari-vars" as vars;
9
10
 
10
11
  .hk-titlebar {
11
12
  --hk-tb-height: 32px;
@@ -42,7 +43,10 @@
42
43
  background: var(--hk-tb-bg);
43
44
  @include backdrop-filter(blur(var(--blur-md, 16px)));
44
45
  border-bottom: 1px solid var(--hk-tb-border);
45
- font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
46
+ // The token, not a literal (docs/en/guides/fonts.md): this sheet used
47
+ // to hand-roll a CJK-less sans, dropping CJK titlebar text onto a
48
+ // platform fallback on Linux.
49
+ font-family: var(--font-sans, vars.$hikari-font-family-sans);
46
50
  -webkit-app-region: drag;
47
51
  app-region: drag;
48
52
  }
@@ -11,4 +11,4 @@
11
11
  // HkErrorLanding, so it ships the component's real stylesheet instead of a
12
12
  // hand-copied palette. bootLandingCss.test.ts recompiles the entry and fails
13
13
  // on drift.
14
- export const BOOT_LANDING_CSS = ":root{--c-primary-faint: rgb(var(--color-primary, 122 162 247) / 5%);--c-primary-subtle: rgb(var(--color-primary, 122 162 247) / 8%);--c-primary-dim: rgb(var(--color-primary, 122 162 247) / 10%);--c-primary-light: rgb(var(--color-primary, 122 162 247) / 15%);--c-primary-overlay: rgb(var(--color-primary, 122 162 247) / 15%);--c-primary-medium: rgb(var(--color-primary, 122 162 247) / 25%);--c-primary-strong: rgb(var(--color-primary, 122 162 247) / 40%);--c-primary-intense: rgb(var(--color-primary, 122 162 247) / 40%);--c-error-dim: rgb(var(--color-error, 247 118 142) / 10%);--c-error-overlay: rgb(var(--color-error, 247 118 142) / 20%);--c-error-strong: rgb(var(--color-error, 247 118 142) / 50%);--shadow-elevated: 0 8px 32px rgb(0 0 0 / 15%);--shadow-dropdown: 0 4px 24px rgb(0 0 0 / 12%), 0 0 0 1px rgb(255 255 255 / 4%);--shadow-button: 0 4px 14px rgb(var(--color-primary, 122 162 247) / 35%);--shadow-button-danger: 0 4px 14px rgb(var(--color-error, 247 118 142) / 35%);--shadow-focus: 0 0 0 3px rgb(var(--color-primary, 122 162 247) / 12%);--shadow-focus-error: 0 0 0 3px rgb(var(--color-error, 247 118 142) / 12%);--border-input: rgb(var(--color-border, 100 100 100) / 15%);--border-faint: rgb(var(--color-border, 100 100 100) / 10%);--border-subtle: rgb(var(--color-border, 100 100 100) / 12%);--hi-icon-color: rgb(var(--color-text, 30 30 30));--hi-icon-size-xs: 0.75rem;--hi-icon-size-sm: 1rem;--hi-icon-size-md: 1.25rem;--hi-icon-size-lg: 1.5rem;--hi-duration-instant: var(--duration-instant, 0.1s);--hi-border-color-focus: rgb(var(--color-primary, 122 162 247));--hi-glow-color: transparent;--hi-bg-surface-dark: rgb(var(--color-muted, 108 108 108) / 12%);--hi-color-primary-dark: color-mix(in srgb, rgb(var(--color-primary, 122 162 247)) 82%, #000);--hi-color-secondary-dark: color-mix(in srgb, rgb(var(--color-secondary, 81 154 115)) 82%, #000);--hi-color-danger-dark: color-mix(in srgb, rgb(var(--color-error, 247 118 142)) 82%, #000);--hi-color-success-dark: color-mix(in srgb, rgb(var(--color-success, 14 184 64)) 82%, #000);--hi-color-text-on-secondary: rgb(var(--color-on-solid, 255 255 255));--hi-color-text-on-danger: rgb(var(--color-on-solid, 255 255 255));--hi-color-text-on-success: rgb(var(--color-on-solid, 255 255 255))}:root{--color-primary: 122 162 247;--color-on-primary: 255 255 255;--color-on-solid: 255 255 255;--color-on-solid-text: 255 255 255;--color-on-solid-icon: 255 255 255;--color-secondary: 81 154 115;--color-accent: 247 181 0;--color-text: 30 30 30;--color-text-secondary: 102 102 102;--color-text-tertiary: 140 140 140;--color-muted: 108 108 108;--color-border: 100 100 100;--color-focused-border: 122 162 247;--color-background: 255 255 255;--color-surface: 255 255 255;--color-selected-bg: 191 218 242;--color-selected-text: 30 30 30;--color-status-bar-bg: 240 244 248;--color-success: 14 184 64;--color-error: 247 118 142;--color-warning: 238 214 119;--color-info: 106 190 214;--hi-color-primary: rgb(var(--color-primary));--hi-color-secondary: rgb(var(--color-secondary));--hi-color-surface: rgb(var(--color-surface));--hi-color-background: rgb(var(--color-background));--hi-color-border: rgb(var(--color-border) / 15%);--hi-color-text-primary: rgb(var(--color-text));--hi-color-text-secondary: rgb(var(--color-text-secondary));--hi-color-muted: rgb(var(--color-muted));--hi-color-on-primary: rgb(var(--color-on-primary));--hi-color-text-on-primary: rgb(var(--color-on-primary));--hi-color-text-on-solid: rgb(var(--color-on-solid-text, 255 255 255));--hi-color-icon-on-solid: rgb(var(--color-on-solid-icon, 255 255 255));--hi-color-success: rgb(var(--color-success));--hi-color-error: rgb(var(--color-error));--hi-color-warning: rgb(var(--color-warning));--hi-color-info: rgb(var(--color-info));--hi-color-bg-subtle: rgb(var(--color-surface) / 0.5);--hi-color-bg-elevated: rgb(var(--color-surface));--hi-color-bg-canvas: rgb(var(--color-background));--hi-secondary-bg: rgb(var(--color-surface) / 0.5);--hi-color-accent: rgb(var(--color-accent));--hi-primary: var(--hi-color-primary);--hi-surface: var(--hi-color-surface);--hi-card-bg: var(--hi-color-surface)}:root{--space-1: 0.0625rem;--space-2: 0.125rem;--space-4: 0.25rem;--space-6: 0.375rem;--space-8: 0.5rem;--space-10: 0.625rem;--space-12: 0.75rem;--space-14: 0.875rem;--space-16: 1rem;--space-20: 1.25rem;--space-24: 1.5rem;--space-28: 1.75rem;--space-32: 2rem;--space-40: 2.5rem;--text-3xs: 0.5rem;--text-2xs: 0.625rem;--text-xs: 0.75rem;--text-sm: 0.8125rem;--text-base: 0.875rem;--text-md: 1rem;--text-lg: 1.125rem;--text-xl: 1.25rem;--text-2xl: 1.5rem;--radius-sm: 4px;--radius-md: 8px;--radius-lg: 12px;--radius-xl: 16px;--radius-full: 9999px;--blur-xs: 4px;--blur-sm: 8px;--blur-md: 16px;--blur-lg: 24px;--blur-xl: 24px;--opacity-half: 0.92;--duration-instant: 0.1s;--duration-short: 0.15s;--duration-fast: 0.15s;--duration-normal: 0.3s;--ease-standard: cubic-bezier(0.4, 0, 0.2, 1);--ease-out-expo: cubic-bezier(0.19, 1, 0.22, 1);--ease-in-expo: cubic-bezier(0.95, 0.05, 0.795, 0.035);--ease-in: cubic-bezier(0.4, 0, 1, 1);--ease-elastic: cubic-bezier(0.16, 1, 0.3, 1);--hi-duration-fast: 0.15s;--hi-ease-default: cubic-bezier(0.4, 0, 0.2, 1);--hk-z-base: 0;--hk-z-above: 1;--hk-z-scrollbar: 10;--hk-z-fab: 40;--hk-z-toolbar: 50;--hk-z-header: 100;--hk-z-sidebar: 900;--hk-z-drag: 950;--hk-z-breadcrumb: 2500;--hk-z-float: 3000;--hk-z-toast: 4000;--hk-z-veil: 4100;--hk-z-modal: 5000;--hk-z-tooltip: 6000;--hk-z-overlay-root: 90000;--hk-z-hud: 90050;--hk-z-error: 95000;--hk-z-titlebar: 100001;--viewport-gutter: 16px;--font-sans: -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, \"Helvetica Neue\", Arial, \"PingFang SC\", \"Hiragino Sans GB\", \"Microsoft YaHei\", \"Noto Sans CJK SC\", sans-serif;--font-reading: -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, \"Helvetica Neue\", Arial, \"PingFang SC\", \"Hiragino Sans GB\", \"Microsoft YaHei\", \"Noto Sans CJK SC\", sans-serif;--font-mono: ui-monospace, \"SF Mono\", Menlo, Consolas, \"DejaVu Sans Mono\", \"Liberation Mono\", \"PingFang SC\", \"Microsoft YaHei\", monospace}@media(max-width: 767px){:root{--viewport-gutter: 8px}}.hk-error-landing{--hel-background: var(--color-background, 246 247 249);--hel-surface: var(--color-surface, 255 255 255);--hel-text: var(--color-text, 30 32 36);--hel-muted: var(--color-muted, 118 122 128);--hel-border: var(--color-border, 222 224 228);--hel-primary: var(--color-primary, 58 118 236);--hel-error: var(--color-error, 226 84 84);--hel-warning: var(--color-warning, 222 168 62);width:100%;min-height:100vh;min-height:100dvh;display:flex;align-items:center;justify-content:center;padding:var(--space-20, 1.25rem);background:rgb(var(--hel-background));color:rgb(var(--hel-text));font-family:var(--font-sans, system-ui, -apple-system, \"Segoe UI\", sans-serif)}.hk-error-landing__card{position:relative;overflow:hidden;width:min(440px,100%);background:rgb(var(--hel-surface));border:1px solid rgb(var(--hel-border)/70%);border-radius:var(--radius-xl, 20px);box-shadow:var(--shadow-lg, 0 18px 48px rgba(0, 0, 0, 0.08));padding:2.25rem var(--space-28, 1.75rem) var(--space-28, 1.75rem);text-align:center}.hk-error-landing__card::before{content:\"\";position:absolute;inset:0 0 auto;height:150px;background:radial-gradient(58% 100% at 50% 0%, rgb(var(--hel-error)/8%), transparent 72%);pointer-events:none}.hk-error-landing.is-warning .hk-error-landing__card::before{background:radial-gradient(58% 100% at 50% 0%, rgb(var(--hel-warning)/10%), transparent 72%)}.hk-error-landing.is-info .hk-error-landing__card::before{background:radial-gradient(58% 100% at 50% 0%, rgb(var(--hel-primary)/7%), transparent 72%)}.hk-error-landing__brand{position:relative;display:flex;justify-content:center;margin-bottom:var(--space-16, 1rem)}.hk-error-landing__brand:empty{display:none}.hk-error-landing__icon{position:relative;width:64px;height:64px;margin:0 auto var(--space-14, 0.875rem);display:flex;align-items:center;justify-content:center;border-radius:50%}.hk-error-landing__icon,.hk-error-landing__icon svg{color:rgb(var(--hel-error))}.hk-error-landing__icon{background:linear-gradient(145deg, rgb(var(--hel-error)/18%), rgb(var(--hel-error)/6%));border:1px solid rgb(var(--hel-error)/26%);box-shadow:0 0 0 8px rgb(var(--hel-error)/5%),0 10px 28px -12px rgb(var(--hel-error)/45%)}.hk-error-landing.is-warning .hk-error-landing__icon,.hk-error-landing.is-warning .hk-error-landing__icon svg{color:rgb(var(--hel-warning))}.hk-error-landing.is-warning .hk-error-landing__icon{background:linear-gradient(145deg, rgb(var(--hel-warning)/20%), rgb(var(--hel-warning)/7%));border-color:rgb(var(--hel-warning)/28%);box-shadow:0 0 0 8px rgb(var(--hel-warning)/6%),0 10px 28px -12px rgb(var(--hel-warning)/45%)}.hk-error-landing.is-info .hk-error-landing__icon,.hk-error-landing.is-info .hk-error-landing__icon svg{color:rgb(var(--hel-primary))}.hk-error-landing.is-info .hk-error-landing__icon{background:linear-gradient(145deg, rgb(var(--hel-primary)/16%), rgb(var(--hel-primary)/5%));border-color:rgb(var(--hel-primary)/24%);box-shadow:0 0 0 8px rgb(var(--hel-primary)/5%),0 10px 28px -12px rgb(var(--hel-primary)/40%)}.hk-error-landing__meta{position:relative;display:flex;align-items:center;justify-content:center;flex-wrap:wrap;gap:var(--space-6, 0.375rem);margin-bottom:var(--space-8, 0.5rem)}.hk-error-landing__meta .hk-badge:not(.hk-badge-muted){--hk-badge-text: rgb(var(--hel-error));--hk-badge-bg: rgb(var(--hel-error) / 10%);--hk-badge-border: rgb(var(--hel-error) / 22%)}.hk-error-landing.is-warning .hk-error-landing__meta .hk-badge:not(.hk-badge-muted){--hk-badge-text: rgb(var(--hel-warning));--hk-badge-bg: rgb(var(--hel-warning) / 12%);--hk-badge-border: rgb(var(--hel-warning) / 24%)}.hk-error-landing.is-info .hk-error-landing__meta .hk-badge:not(.hk-badge-muted){--hk-badge-text: rgb(var(--hel-primary));--hk-badge-bg: rgb(var(--hel-primary) / 10%);--hk-badge-border: rgb(var(--hel-primary) / 20%)}.hk-error-landing__meta .hk-badge-muted{--hk-badge-text: rgb(var(--hel-muted));--hk-badge-bg: rgb(var(--hel-muted) / 8%);--hk-badge-border: rgb(var(--hel-border) / 80%)}.hk-error-landing__title{position:relative;margin:0;font-size:var(--text-xl, 1.25rem);font-weight:650;letter-spacing:-0.01em;line-height:1.35;color:rgb(var(--hel-text));white-space:pre-line;overflow-wrap:anywhere}.hk-error-landing__desc{position:relative;margin:var(--space-8, 0.5rem) 0 0;font-size:var(--text-sm, 0.8125rem);line-height:1.6;color:rgb(var(--hel-muted));white-space:pre-line;overflow-wrap:anywhere}.hk-error-landing__details{position:relative;margin-top:var(--space-16, 1rem);text-align:start}.hk-error-landing__details-label{display:flex;align-items:center;gap:var(--space-4, 4px);padding:0 var(--space-2, 2px);font-size:var(--text-xs, 0.75rem);font-weight:500;letter-spacing:.02em;color:rgb(var(--hel-muted));-webkit-user-select:none;user-select:none}.hk-error-landing__details-pane{position:relative;margin-top:var(--space-6, 0.375rem);height:max(9rem,20vh);height:max(9rem,20dvh);border:1px solid rgb(var(--hel-border)/70%);border-radius:var(--radius-md, 10px);background:rgb(var(--hel-muted)/4%)}.hk-error-landing__details-body{height:100%;overflow:auto;border-radius:inherit;padding:var(--space-4, 0.25rem);scrollbar-width:none}.hk-error-landing__details-body::-webkit-scrollbar{display:none}.hk-error-landing .hk-error-landing__details-body .s-tool-json-tree{max-height:none;height:auto;overflow:visible;margin:0;padding:0;border:none;border-radius:0;background:rgba(0,0,0,0)}.hk-error-landing__actions{position:relative;display:flex;align-items:center;justify-content:center;flex-wrap:wrap;gap:var(--space-8, 0.5rem);margin-top:var(--space-16, 1rem)}.hk-error-landing.is-inline{min-height:100%;padding:0 var(--space-12, 0.75rem);align-items:flex-start;background:rgba(0,0,0,0)}.hk-error-landing.is-inline.is-fill{flex:1 1 auto;min-height:0}.hk-error-landing.is-inline .hk-error-landing__card{margin:auto;padding:var(--space-16, 1rem) var(--space-28, 1.75rem) var(--space-24, 1.5rem);display:flex;flex-direction:column;max-height:100%;min-height:min-content}.hk-error-landing.is-inline .hk-error-landing__details{display:flex;flex-direction:column;min-height:0}.hk-error-landing.is-inline .hk-error-landing__details-pane{height:auto;flex-grow:0;flex-shrink:1;flex-basis:max(9rem,20vh);flex-basis:max(9rem,20dvh);min-height:6rem}@media(max-width: 480px){.hk-error-landing__card{padding:var(--space-28, 1.75rem) 1.125rem var(--space-24, 1.5rem)}}:root{--c-primary-faint: rgb(var(--color-primary, 122 162 247) / 5%);--c-primary-subtle: rgb(var(--color-primary, 122 162 247) / 8%);--c-primary-dim: rgb(var(--color-primary, 122 162 247) / 10%);--c-primary-light: rgb(var(--color-primary, 122 162 247) / 15%);--c-primary-overlay: rgb(var(--color-primary, 122 162 247) / 15%);--c-primary-medium: rgb(var(--color-primary, 122 162 247) / 25%);--c-primary-strong: rgb(var(--color-primary, 122 162 247) / 40%);--c-primary-intense: rgb(var(--color-primary, 122 162 247) / 40%);--c-error-dim: rgb(var(--color-error, 247 118 142) / 10%);--c-error-overlay: rgb(var(--color-error, 247 118 142) / 20%);--c-error-strong: rgb(var(--color-error, 247 118 142) / 50%);--shadow-elevated: 0 8px 32px rgb(0 0 0 / 15%);--shadow-dropdown: 0 4px 24px rgb(0 0 0 / 12%), 0 0 0 1px rgb(255 255 255 / 4%);--shadow-button: 0 4px 14px rgb(var(--color-primary, 122 162 247) / 35%);--shadow-button-danger: 0 4px 14px rgb(var(--color-error, 247 118 142) / 35%);--shadow-focus: 0 0 0 3px rgb(var(--color-primary, 122 162 247) / 12%);--shadow-focus-error: 0 0 0 3px rgb(var(--color-error, 247 118 142) / 12%);--border-input: rgb(var(--color-border, 100 100 100) / 15%);--border-faint: rgb(var(--color-border, 100 100 100) / 10%);--border-subtle: rgb(var(--color-border, 100 100 100) / 12%);--hi-icon-color: rgb(var(--color-text, 30 30 30));--hi-icon-size-xs: 0.75rem;--hi-icon-size-sm: 1rem;--hi-icon-size-md: 1.25rem;--hi-icon-size-lg: 1.5rem;--hi-duration-instant: var(--duration-instant, 0.1s);--hi-border-color-focus: rgb(var(--color-primary, 122 162 247));--hi-glow-color: transparent;--hi-bg-surface-dark: rgb(var(--color-muted, 108 108 108) / 12%);--hi-color-primary-dark: color-mix(in srgb, rgb(var(--color-primary, 122 162 247)) 82%, #000);--hi-color-secondary-dark: color-mix(in srgb, rgb(var(--color-secondary, 81 154 115)) 82%, #000);--hi-color-danger-dark: color-mix(in srgb, rgb(var(--color-error, 247 118 142)) 82%, #000);--hi-color-success-dark: color-mix(in srgb, rgb(var(--color-success, 14 184 64)) 82%, #000);--hi-color-text-on-secondary: rgb(var(--color-on-solid, 255 255 255));--hi-color-text-on-danger: rgb(var(--color-on-solid, 255 255 255));--hi-color-text-on-success: rgb(var(--color-on-solid, 255 255 255))}:root{--color-primary: 122 162 247;--color-on-primary: 255 255 255;--color-on-solid: 255 255 255;--color-on-solid-text: 255 255 255;--color-on-solid-icon: 255 255 255;--color-secondary: 81 154 115;--color-accent: 247 181 0;--color-text: 30 30 30;--color-text-secondary: 102 102 102;--color-text-tertiary: 140 140 140;--color-muted: 108 108 108;--color-border: 100 100 100;--color-focused-border: 122 162 247;--color-background: 255 255 255;--color-surface: 255 255 255;--color-selected-bg: 191 218 242;--color-selected-text: 30 30 30;--color-status-bar-bg: 240 244 248;--color-success: 14 184 64;--color-error: 247 118 142;--color-warning: 238 214 119;--color-info: 106 190 214;--hi-color-primary: rgb(var(--color-primary));--hi-color-secondary: rgb(var(--color-secondary));--hi-color-surface: rgb(var(--color-surface));--hi-color-background: rgb(var(--color-background));--hi-color-border: rgb(var(--color-border) / 15%);--hi-color-text-primary: rgb(var(--color-text));--hi-color-text-secondary: rgb(var(--color-text-secondary));--hi-color-muted: rgb(var(--color-muted));--hi-color-on-primary: rgb(var(--color-on-primary));--hi-color-text-on-primary: rgb(var(--color-on-primary));--hi-color-text-on-solid: rgb(var(--color-on-solid-text, 255 255 255));--hi-color-icon-on-solid: rgb(var(--color-on-solid-icon, 255 255 255));--hi-color-success: rgb(var(--color-success));--hi-color-error: rgb(var(--color-error));--hi-color-warning: rgb(var(--color-warning));--hi-color-info: rgb(var(--color-info));--hi-color-bg-subtle: rgb(var(--color-surface) / 0.5);--hi-color-bg-elevated: rgb(var(--color-surface));--hi-color-bg-canvas: rgb(var(--color-background));--hi-secondary-bg: rgb(var(--color-surface) / 0.5);--hi-color-accent: rgb(var(--color-accent));--hi-primary: var(--hi-color-primary);--hi-surface: var(--hi-color-surface);--hi-card-bg: var(--hi-color-surface)}.hk-btn{display:inline-flex;align-items:center;justify-content:center;gap:var(--space-6);font-family:inherit;font-weight:600;line-height:1;border:1px solid rgba(0,0,0,0);border-radius:var(--radius-sm);cursor:pointer;transition-property:background-color,border-color,color,box-shadow,opacity,transform,filter;transition-duration:var(--duration-normal);transition-timing-function:var(--ease-standard);white-space:nowrap;-webkit-user-select:none;user-select:none;outline:none;position:relative}.hk-btn:focus-visible{box-shadow:0 0 0 2px rgb(var(--color-surface)),0 0 0 4px rgb(var(--color-primary))}.hk-btn:disabled{opacity:.5;cursor:not-allowed}.hk-btn-xs{padding:var(--space-4) var(--space-8);font-size:var(--text-xs);min-height:1.375rem}.hk-btn-sm{padding:var(--space-6) var(--space-12);font-size:var(--text-xs);min-height:1.75rem}.hk-btn-md{padding:var(--space-8) var(--space-16);font-size:var(--text-base);min-height:2.5rem}.hk-btn-lg{padding:var(--space-10) var(--space-24);font-size:var(--text-base);min-height:2.75rem}.hk-btn-xs .hk-btn-icon,.hk-btn-xs .hk-btn-suffix{inline-size:.8125rem;block-size:.8125rem}.hk-btn-xs .hk-btn-icon .hk-icon,.hk-btn-xs .hk-btn-suffix .hk-icon{inline-size:100%;block-size:100%}.hk-btn-icon,.hk-btn-suffix{display:inline-flex;align-items:center;justify-content:center;flex:none;inline-size:1rem;block-size:1rem;line-height:0}.hk-btn-icon svg,.hk-btn-suffix svg{display:block}.hk-btn>svg{flex:none}.hk-btn-icon-only{padding:0;box-sizing:border-box}.hk-btn-xs.hk-btn-icon-only{width:1.375rem}.hk-btn-sm.hk-btn-icon-only{width:1.75rem}.hk-btn-md.hk-btn-icon-only{width:2.5rem}.hk-btn-lg.hk-btn-icon-only{width:2.75rem}.hk-btn-block.hk-btn-icon-only{width:100%}.hk-btn-label{display:inline-flex;align-items:center;min-width:0}@media(max-width: 639.98px){.hk-btn-label-collapse-sm.hk-btn-xs .hk-btn-label{display:none}.hk-btn-label-collapse-sm.hk-btn-xs{padding:0;box-sizing:border-box;width:1.375rem}}@media(max-width: 639.98px){.hk-btn-label-collapse-sm.hk-btn-sm .hk-btn-label{display:none}.hk-btn-label-collapse-sm.hk-btn-sm{padding:0;box-sizing:border-box;width:1.75rem}}@media(max-width: 639.98px){.hk-btn-label-collapse-sm.hk-btn-md .hk-btn-label{display:none}.hk-btn-label-collapse-sm.hk-btn-md{padding:0;box-sizing:border-box;width:2.5rem}}@media(max-width: 639.98px){.hk-btn-label-collapse-sm.hk-btn-lg .hk-btn-label{display:none}.hk-btn-label-collapse-sm.hk-btn-lg{padding:0;box-sizing:border-box;width:2.75rem}}@media(max-width: 767.98px){.hk-btn-label-collapse-md.hk-btn-xs .hk-btn-label{display:none}.hk-btn-label-collapse-md.hk-btn-xs{padding:0;box-sizing:border-box;width:1.375rem}}@media(max-width: 767.98px){.hk-btn-label-collapse-md.hk-btn-sm .hk-btn-label{display:none}.hk-btn-label-collapse-md.hk-btn-sm{padding:0;box-sizing:border-box;width:1.75rem}}@media(max-width: 767.98px){.hk-btn-label-collapse-md.hk-btn-md .hk-btn-label{display:none}.hk-btn-label-collapse-md.hk-btn-md{padding:0;box-sizing:border-box;width:2.5rem}}@media(max-width: 767.98px){.hk-btn-label-collapse-md.hk-btn-lg .hk-btn-label{display:none}.hk-btn-label-collapse-md.hk-btn-lg{padding:0;box-sizing:border-box;width:2.75rem}}@media(max-width: 1023.98px){.hk-btn-label-collapse-lg.hk-btn-xs .hk-btn-label{display:none}.hk-btn-label-collapse-lg.hk-btn-xs{padding:0;box-sizing:border-box;width:1.375rem}}@media(max-width: 1023.98px){.hk-btn-label-collapse-lg.hk-btn-sm .hk-btn-label{display:none}.hk-btn-label-collapse-lg.hk-btn-sm{padding:0;box-sizing:border-box;width:1.75rem}}@media(max-width: 1023.98px){.hk-btn-label-collapse-lg.hk-btn-md .hk-btn-label{display:none}.hk-btn-label-collapse-lg.hk-btn-md{padding:0;box-sizing:border-box;width:2.5rem}}@media(max-width: 1023.98px){.hk-btn-label-collapse-lg.hk-btn-lg .hk-btn-label{display:none}.hk-btn-label-collapse-lg.hk-btn-lg{padding:0;box-sizing:border-box;width:2.75rem}}@media(max-width: 1279.98px){.hk-btn-label-collapse-xl.hk-btn-xs .hk-btn-label{display:none}.hk-btn-label-collapse-xl.hk-btn-xs{padding:0;box-sizing:border-box;width:1.375rem}}@media(max-width: 1279.98px){.hk-btn-label-collapse-xl.hk-btn-sm .hk-btn-label{display:none}.hk-btn-label-collapse-xl.hk-btn-sm{padding:0;box-sizing:border-box;width:1.75rem}}@media(max-width: 1279.98px){.hk-btn-label-collapse-xl.hk-btn-md .hk-btn-label{display:none}.hk-btn-label-collapse-xl.hk-btn-md{padding:0;box-sizing:border-box;width:2.5rem}}@media(max-width: 1279.98px){.hk-btn-label-collapse-xl.hk-btn-lg .hk-btn-label{display:none}.hk-btn-label-collapse-xl.hk-btn-lg{padding:0;box-sizing:border-box;width:2.75rem}}.hk-btn-primary{background:rgb(var(--color-primary));color:rgb(var(--color-on-solid))}.hk-btn-primary svg{color:rgb(var(--color-on-solid-icon, 255 255 255))}.hk-btn-primary:hover:not(:disabled):not(:focus-visible){filter:brightness(1.1);box-shadow:var(--shadow-button)}.hk-btn-primary:active:not(:disabled){filter:brightness(0.95)}.hk-btn-secondary{background:rgb(var(--color-surface));color:rgb(var(--color-text));border-color:rgb(var(--color-border)/20%);-webkit-backdrop-filter:blur(var(--blur-sm));backdrop-filter:blur(var(--blur-sm))}.hk-btn-secondary:hover:not(:disabled){border-color:var(--c-primary-strong)}.hk-btn-secondary:active:not(:disabled){filter:brightness(0.95)}.hk-btn-outline{background:rgba(0,0,0,0);color:rgb(var(--color-primary));border-color:var(--c-primary-intense)}.hk-btn-outline:hover:not(:disabled){background:var(--c-primary-light);border-color:rgb(var(--color-primary))}.hk-btn-outline:active:not(:disabled){filter:brightness(0.95)}.hk-btn-ghost{background:rgba(0,0,0,0);color:rgb(var(--color-text));border-color:rgba(0,0,0,0)}.hk-btn-ghost:hover:not(:disabled){background:var(--c-primary-light)}.hk-btn-ghost:active:not(:disabled){filter:brightness(0.95)}.hk-btn-danger{background:rgb(var(--color-error));color:rgb(var(--color-on-solid))}.hk-btn-danger:hover:not(:disabled):not(:focus-visible){filter:brightness(1.1);box-shadow:var(--shadow-button-danger)}.hk-btn-danger:active:not(:disabled){filter:brightness(0.95)}.hk-btn-block{width:100%}.hk-btn-loading{pointer-events:none}@media(prefers-color-scheme: dark){:root{--color-primary: 136 192 208;--color-on-solid: 46 52 64;--color-on-solid-text: 46 52 64;--color-on-solid-icon: 46 52 64;--color-text: 247 249 252;--color-muted: 180 180 180;--color-border: 128 128 128;--color-background: 22 27 38;--color-surface: 34 40 54;--color-error: 191 97 106}}";
14
+ export const BOOT_LANDING_CSS = ":root{--c-primary-faint: rgb(var(--color-primary, 122 162 247) / 5%);--c-primary-subtle: rgb(var(--color-primary, 122 162 247) / 8%);--c-primary-dim: rgb(var(--color-primary, 122 162 247) / 10%);--c-primary-light: rgb(var(--color-primary, 122 162 247) / 15%);--c-primary-overlay: rgb(var(--color-primary, 122 162 247) / 15%);--c-primary-medium: rgb(var(--color-primary, 122 162 247) / 25%);--c-primary-strong: rgb(var(--color-primary, 122 162 247) / 40%);--c-primary-intense: rgb(var(--color-primary, 122 162 247) / 40%);--c-error-dim: rgb(var(--color-error, 247 118 142) / 10%);--c-error-overlay: rgb(var(--color-error, 247 118 142) / 20%);--c-error-strong: rgb(var(--color-error, 247 118 142) / 50%);--shadow-elevated: 0 8px 32px rgb(0 0 0 / 15%);--shadow-dropdown: 0 4px 24px rgb(0 0 0 / 12%), 0 0 0 1px rgb(255 255 255 / 4%);--shadow-button: 0 4px 14px rgb(var(--color-primary, 122 162 247) / 35%);--shadow-button-danger: 0 4px 14px rgb(var(--color-error, 247 118 142) / 35%);--shadow-focus: 0 0 0 3px rgb(var(--color-primary, 122 162 247) / 12%);--shadow-focus-error: 0 0 0 3px rgb(var(--color-error, 247 118 142) / 12%);--border-input: rgb(var(--color-border, 100 100 100) / 15%);--border-faint: rgb(var(--color-border, 100 100 100) / 10%);--border-subtle: rgb(var(--color-border, 100 100 100) / 12%);--hi-icon-color: rgb(var(--color-text, 30 30 30));--hi-icon-size-xs: 0.75rem;--hi-icon-size-sm: 1rem;--hi-icon-size-md: 1.25rem;--hi-icon-size-lg: 1.5rem;--hi-duration-instant: var(--duration-instant, 0.1s);--hi-border-color-focus: rgb(var(--color-primary, 122 162 247));--hi-glow-color: transparent;--hi-bg-surface-dark: rgb(var(--color-muted, 108 108 108) / 12%);--hi-color-primary-dark: color-mix(in srgb, rgb(var(--color-primary, 122 162 247)) 82%, #000);--hi-color-secondary-dark: color-mix(in srgb, rgb(var(--color-secondary, 81 154 115)) 82%, #000);--hi-color-danger-dark: color-mix(in srgb, rgb(var(--color-error, 247 118 142)) 82%, #000);--hi-color-success-dark: color-mix(in srgb, rgb(var(--color-success, 14 184 64)) 82%, #000);--hi-color-text-on-secondary: rgb(var(--color-on-solid, 255 255 255));--hi-color-text-on-danger: rgb(var(--color-on-solid, 255 255 255));--hi-color-text-on-success: rgb(var(--color-on-solid, 255 255 255))}:root{--color-primary: 122 162 247;--color-on-primary: 255 255 255;--color-on-solid: 255 255 255;--color-on-solid-text: 255 255 255;--color-on-solid-icon: 255 255 255;--color-secondary: 81 154 115;--color-accent: 247 181 0;--color-text: 30 30 30;--color-text-secondary: 102 102 102;--color-text-tertiary: 140 140 140;--color-muted: 108 108 108;--color-border: 100 100 100;--color-focused-border: 122 162 247;--color-background: 255 255 255;--color-surface: 255 255 255;--color-selected-bg: 191 218 242;--color-selected-text: 30 30 30;--color-status-bar-bg: 240 244 248;--color-success: 14 184 64;--color-error: 247 118 142;--color-warning: 238 214 119;--color-info: 106 190 214;--hi-color-primary: rgb(var(--color-primary));--hi-color-secondary: rgb(var(--color-secondary));--hi-color-surface: rgb(var(--color-surface));--hi-color-background: rgb(var(--color-background));--hi-color-border: rgb(var(--color-border) / 15%);--hi-color-text-primary: rgb(var(--color-text));--hi-color-text-secondary: rgb(var(--color-text-secondary));--hi-color-muted: rgb(var(--color-muted));--hi-color-on-primary: rgb(var(--color-on-primary));--hi-color-text-on-primary: rgb(var(--color-on-primary));--hi-color-text-on-solid: rgb(var(--color-on-solid-text, 255 255 255));--hi-color-icon-on-solid: rgb(var(--color-on-solid-icon, 255 255 255));--hi-color-success: rgb(var(--color-success));--hi-color-error: rgb(var(--color-error));--hi-color-warning: rgb(var(--color-warning));--hi-color-info: rgb(var(--color-info));--hi-color-bg-subtle: rgb(var(--color-surface) / 0.5);--hi-color-bg-elevated: rgb(var(--color-surface));--hi-color-bg-canvas: rgb(var(--color-background));--hi-secondary-bg: rgb(var(--color-surface) / 0.5);--hi-color-accent: rgb(var(--color-accent));--hi-primary: var(--hi-color-primary);--hi-surface: var(--hi-color-surface);--hi-card-bg: var(--hi-color-surface)}:root{--space-1: 0.0625rem;--space-2: 0.125rem;--space-4: 0.25rem;--space-6: 0.375rem;--space-8: 0.5rem;--space-10: 0.625rem;--space-12: 0.75rem;--space-14: 0.875rem;--space-16: 1rem;--space-20: 1.25rem;--space-24: 1.5rem;--space-28: 1.75rem;--space-32: 2rem;--space-40: 2.5rem;--text-3xs: 0.5rem;--text-2xs: 0.625rem;--text-xs: 0.75rem;--text-sm: 0.8125rem;--text-base: 0.875rem;--text-md: 1rem;--text-lg: 1.125rem;--text-xl: 1.25rem;--text-2xl: 1.5rem;--radius-sm: 4px;--radius-md: 8px;--radius-lg: 12px;--radius-xl: 16px;--radius-full: 9999px;--blur-xs: 4px;--blur-sm: 8px;--blur-md: 16px;--blur-lg: 24px;--blur-xl: 24px;--opacity-half: 0.92;--duration-instant: 0.1s;--duration-short: 0.15s;--duration-fast: 0.15s;--duration-normal: 0.3s;--ease-standard: cubic-bezier(0.4, 0, 0.2, 1);--ease-out-expo: cubic-bezier(0.19, 1, 0.22, 1);--ease-in-expo: cubic-bezier(0.95, 0.05, 0.795, 0.035);--ease-in: cubic-bezier(0.4, 0, 1, 1);--ease-elastic: cubic-bezier(0.16, 1, 0.3, 1);--hi-duration-fast: 0.15s;--hi-ease-default: cubic-bezier(0.4, 0, 0.2, 1);--hk-z-base: 0;--hk-z-above: 1;--hk-z-scrollbar: 10;--hk-z-fab: 40;--hk-z-toolbar: 50;--hk-z-header: 100;--hk-z-sidebar: 900;--hk-z-drag: 950;--hk-z-breadcrumb: 2500;--hk-z-float: 3000;--hk-z-toast: 4000;--hk-z-veil: 4100;--hk-z-modal: 5000;--hk-z-tooltip: 6000;--hk-z-overlay-root: 90000;--hk-z-hud: 90050;--hk-z-error: 95000;--hk-z-titlebar: 100001;--viewport-gutter: 16px;--font-sans: -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, \"Helvetica Neue\", Arial, \"PingFang SC\", \"Hiragino Sans GB\", \"Microsoft YaHei\", \"Noto Sans CJK SC\", sans-serif;--font-reading: -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, \"Helvetica Neue\", Arial, \"PingFang SC\", \"Hiragino Sans GB\", \"Microsoft YaHei\", \"Noto Sans CJK SC\", sans-serif;--font-mono: ui-monospace, \"SF Mono\", Menlo, Consolas, \"DejaVu Sans Mono\", \"Liberation Mono\", \"PingFang SC\", \"Microsoft YaHei\", \"Noto Sans CJK SC\", monospace}@media(max-width: 767px){:root{--viewport-gutter: 8px}}.hk-error-landing{--hel-background: var(--color-background, 246 247 249);--hel-surface: var(--color-surface, 255 255 255);--hel-text: var(--color-text, 30 32 36);--hel-muted: var(--color-muted, 118 122 128);--hel-border: var(--color-border, 222 224 228);--hel-primary: var(--color-primary, 58 118 236);--hel-error: var(--color-error, 226 84 84);--hel-warning: var(--color-warning, 222 168 62);width:100%;min-height:100vh;min-height:100dvh;display:flex;align-items:center;justify-content:center;padding:var(--space-20, 1.25rem);background:rgb(var(--hel-background));color:rgb(var(--hel-text));font-family:var(--font-sans, -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, \"Helvetica Neue\", Arial, \"PingFang SC\", \"Hiragino Sans GB\", \"Microsoft YaHei\", \"Noto Sans CJK SC\", sans-serif)}.hk-error-landing__card{position:relative;overflow:hidden;width:min(440px,100%);background:rgb(var(--hel-surface));border:1px solid rgb(var(--hel-border)/70%);border-radius:var(--radius-xl, 20px);box-shadow:var(--shadow-lg, 0 18px 48px rgba(0, 0, 0, 0.08));padding:2.25rem var(--space-28, 1.75rem) var(--space-28, 1.75rem);text-align:center}.hk-error-landing__card::before{content:\"\";position:absolute;inset:0 0 auto;height:150px;background:radial-gradient(58% 100% at 50% 0%, rgb(var(--hel-error)/8%), transparent 72%);pointer-events:none}.hk-error-landing.is-warning .hk-error-landing__card::before{background:radial-gradient(58% 100% at 50% 0%, rgb(var(--hel-warning)/10%), transparent 72%)}.hk-error-landing.is-info .hk-error-landing__card::before{background:radial-gradient(58% 100% at 50% 0%, rgb(var(--hel-primary)/7%), transparent 72%)}.hk-error-landing__brand{position:relative;display:flex;justify-content:center;margin-bottom:var(--space-16, 1rem)}.hk-error-landing__brand:empty{display:none}.hk-error-landing__icon{position:relative;width:64px;height:64px;margin:0 auto var(--space-14, 0.875rem);display:flex;align-items:center;justify-content:center;border-radius:50%}.hk-error-landing__icon,.hk-error-landing__icon svg{color:rgb(var(--hel-error))}.hk-error-landing__icon{background:linear-gradient(145deg, rgb(var(--hel-error)/18%), rgb(var(--hel-error)/6%));border:1px solid rgb(var(--hel-error)/26%);box-shadow:0 0 0 8px rgb(var(--hel-error)/5%),0 10px 28px -12px rgb(var(--hel-error)/45%)}.hk-error-landing.is-warning .hk-error-landing__icon,.hk-error-landing.is-warning .hk-error-landing__icon svg{color:rgb(var(--hel-warning))}.hk-error-landing.is-warning .hk-error-landing__icon{background:linear-gradient(145deg, rgb(var(--hel-warning)/20%), rgb(var(--hel-warning)/7%));border-color:rgb(var(--hel-warning)/28%);box-shadow:0 0 0 8px rgb(var(--hel-warning)/6%),0 10px 28px -12px rgb(var(--hel-warning)/45%)}.hk-error-landing.is-info .hk-error-landing__icon,.hk-error-landing.is-info .hk-error-landing__icon svg{color:rgb(var(--hel-primary))}.hk-error-landing.is-info .hk-error-landing__icon{background:linear-gradient(145deg, rgb(var(--hel-primary)/16%), rgb(var(--hel-primary)/5%));border-color:rgb(var(--hel-primary)/24%);box-shadow:0 0 0 8px rgb(var(--hel-primary)/5%),0 10px 28px -12px rgb(var(--hel-primary)/40%)}.hk-error-landing__meta{position:relative;display:flex;align-items:center;justify-content:center;flex-wrap:wrap;gap:var(--space-6, 0.375rem);margin-bottom:var(--space-8, 0.5rem)}.hk-error-landing__meta .hk-badge:not(.hk-badge-muted){--hk-badge-text: rgb(var(--hel-error));--hk-badge-bg: rgb(var(--hel-error) / 10%);--hk-badge-border: rgb(var(--hel-error) / 22%)}.hk-error-landing.is-warning .hk-error-landing__meta .hk-badge:not(.hk-badge-muted){--hk-badge-text: rgb(var(--hel-warning));--hk-badge-bg: rgb(var(--hel-warning) / 12%);--hk-badge-border: rgb(var(--hel-warning) / 24%)}.hk-error-landing.is-info .hk-error-landing__meta .hk-badge:not(.hk-badge-muted){--hk-badge-text: rgb(var(--hel-primary));--hk-badge-bg: rgb(var(--hel-primary) / 10%);--hk-badge-border: rgb(var(--hel-primary) / 20%)}.hk-error-landing__meta .hk-badge-muted{--hk-badge-text: rgb(var(--hel-muted));--hk-badge-bg: rgb(var(--hel-muted) / 8%);--hk-badge-border: rgb(var(--hel-border) / 80%)}.hk-error-landing__title{position:relative;margin:0;font-size:var(--text-xl, 1.25rem);font-weight:650;letter-spacing:-0.01em;line-height:1.35;color:rgb(var(--hel-text));white-space:pre-line;overflow-wrap:anywhere}.hk-error-landing__desc{position:relative;margin:var(--space-8, 0.5rem) 0 0;font-size:var(--text-sm, 0.8125rem);line-height:1.6;color:rgb(var(--hel-muted));white-space:pre-line;overflow-wrap:anywhere}.hk-error-landing__details{position:relative;margin-top:var(--space-16, 1rem);text-align:start}.hk-error-landing__details-label{display:flex;align-items:center;gap:var(--space-4, 4px);padding:0 var(--space-2, 2px);font-size:var(--text-xs, 0.75rem);font-weight:500;letter-spacing:.02em;color:rgb(var(--hel-muted));-webkit-user-select:none;user-select:none}.hk-error-landing__details-pane{position:relative;margin-top:var(--space-6, 0.375rem);height:max(9rem,20vh);height:max(9rem,20dvh);border:1px solid rgb(var(--hel-border)/70%);border-radius:var(--radius-md, 10px);background:rgb(var(--hel-muted)/4%)}.hk-error-landing__details-body{height:100%;overflow:auto;border-radius:inherit;padding:var(--space-4, 0.25rem);scrollbar-width:none}.hk-error-landing__details-body::-webkit-scrollbar{display:none}.hk-error-landing .hk-error-landing__details-body .s-tool-json-tree{max-height:none;height:auto;overflow:visible;margin:0;padding:0;border:none;border-radius:0;background:rgba(0,0,0,0)}.hk-error-landing__actions{position:relative;display:flex;align-items:center;justify-content:center;flex-wrap:wrap;gap:var(--space-8, 0.5rem);margin-top:var(--space-16, 1rem)}.hk-error-landing.is-inline{min-height:100%;padding:0 var(--space-12, 0.75rem);align-items:flex-start;background:rgba(0,0,0,0)}.hk-error-landing.is-inline.is-fill{flex:1 1 auto;min-height:0}.hk-error-landing.is-inline .hk-error-landing__card{margin:auto;padding:var(--space-16, 1rem) var(--space-28, 1.75rem) var(--space-24, 1.5rem);display:flex;flex-direction:column;max-height:100%;min-height:min-content}.hk-error-landing.is-inline .hk-error-landing__details{display:flex;flex-direction:column;min-height:0}.hk-error-landing.is-inline .hk-error-landing__details-pane{height:auto;flex-grow:0;flex-shrink:1;flex-basis:max(9rem,20vh);flex-basis:max(9rem,20dvh);min-height:6rem}@media(max-width: 480px){.hk-error-landing__card{padding:var(--space-28, 1.75rem) 1.125rem var(--space-24, 1.5rem)}}:root{--c-primary-faint: rgb(var(--color-primary, 122 162 247) / 5%);--c-primary-subtle: rgb(var(--color-primary, 122 162 247) / 8%);--c-primary-dim: rgb(var(--color-primary, 122 162 247) / 10%);--c-primary-light: rgb(var(--color-primary, 122 162 247) / 15%);--c-primary-overlay: rgb(var(--color-primary, 122 162 247) / 15%);--c-primary-medium: rgb(var(--color-primary, 122 162 247) / 25%);--c-primary-strong: rgb(var(--color-primary, 122 162 247) / 40%);--c-primary-intense: rgb(var(--color-primary, 122 162 247) / 40%);--c-error-dim: rgb(var(--color-error, 247 118 142) / 10%);--c-error-overlay: rgb(var(--color-error, 247 118 142) / 20%);--c-error-strong: rgb(var(--color-error, 247 118 142) / 50%);--shadow-elevated: 0 8px 32px rgb(0 0 0 / 15%);--shadow-dropdown: 0 4px 24px rgb(0 0 0 / 12%), 0 0 0 1px rgb(255 255 255 / 4%);--shadow-button: 0 4px 14px rgb(var(--color-primary, 122 162 247) / 35%);--shadow-button-danger: 0 4px 14px rgb(var(--color-error, 247 118 142) / 35%);--shadow-focus: 0 0 0 3px rgb(var(--color-primary, 122 162 247) / 12%);--shadow-focus-error: 0 0 0 3px rgb(var(--color-error, 247 118 142) / 12%);--border-input: rgb(var(--color-border, 100 100 100) / 15%);--border-faint: rgb(var(--color-border, 100 100 100) / 10%);--border-subtle: rgb(var(--color-border, 100 100 100) / 12%);--hi-icon-color: rgb(var(--color-text, 30 30 30));--hi-icon-size-xs: 0.75rem;--hi-icon-size-sm: 1rem;--hi-icon-size-md: 1.25rem;--hi-icon-size-lg: 1.5rem;--hi-duration-instant: var(--duration-instant, 0.1s);--hi-border-color-focus: rgb(var(--color-primary, 122 162 247));--hi-glow-color: transparent;--hi-bg-surface-dark: rgb(var(--color-muted, 108 108 108) / 12%);--hi-color-primary-dark: color-mix(in srgb, rgb(var(--color-primary, 122 162 247)) 82%, #000);--hi-color-secondary-dark: color-mix(in srgb, rgb(var(--color-secondary, 81 154 115)) 82%, #000);--hi-color-danger-dark: color-mix(in srgb, rgb(var(--color-error, 247 118 142)) 82%, #000);--hi-color-success-dark: color-mix(in srgb, rgb(var(--color-success, 14 184 64)) 82%, #000);--hi-color-text-on-secondary: rgb(var(--color-on-solid, 255 255 255));--hi-color-text-on-danger: rgb(var(--color-on-solid, 255 255 255));--hi-color-text-on-success: rgb(var(--color-on-solid, 255 255 255))}:root{--color-primary: 122 162 247;--color-on-primary: 255 255 255;--color-on-solid: 255 255 255;--color-on-solid-text: 255 255 255;--color-on-solid-icon: 255 255 255;--color-secondary: 81 154 115;--color-accent: 247 181 0;--color-text: 30 30 30;--color-text-secondary: 102 102 102;--color-text-tertiary: 140 140 140;--color-muted: 108 108 108;--color-border: 100 100 100;--color-focused-border: 122 162 247;--color-background: 255 255 255;--color-surface: 255 255 255;--color-selected-bg: 191 218 242;--color-selected-text: 30 30 30;--color-status-bar-bg: 240 244 248;--color-success: 14 184 64;--color-error: 247 118 142;--color-warning: 238 214 119;--color-info: 106 190 214;--hi-color-primary: rgb(var(--color-primary));--hi-color-secondary: rgb(var(--color-secondary));--hi-color-surface: rgb(var(--color-surface));--hi-color-background: rgb(var(--color-background));--hi-color-border: rgb(var(--color-border) / 15%);--hi-color-text-primary: rgb(var(--color-text));--hi-color-text-secondary: rgb(var(--color-text-secondary));--hi-color-muted: rgb(var(--color-muted));--hi-color-on-primary: rgb(var(--color-on-primary));--hi-color-text-on-primary: rgb(var(--color-on-primary));--hi-color-text-on-solid: rgb(var(--color-on-solid-text, 255 255 255));--hi-color-icon-on-solid: rgb(var(--color-on-solid-icon, 255 255 255));--hi-color-success: rgb(var(--color-success));--hi-color-error: rgb(var(--color-error));--hi-color-warning: rgb(var(--color-warning));--hi-color-info: rgb(var(--color-info));--hi-color-bg-subtle: rgb(var(--color-surface) / 0.5);--hi-color-bg-elevated: rgb(var(--color-surface));--hi-color-bg-canvas: rgb(var(--color-background));--hi-secondary-bg: rgb(var(--color-surface) / 0.5);--hi-color-accent: rgb(var(--color-accent));--hi-primary: var(--hi-color-primary);--hi-surface: var(--hi-color-surface);--hi-card-bg: var(--hi-color-surface)}.hk-btn{display:inline-flex;align-items:center;justify-content:center;gap:var(--space-6);font-family:inherit;font-weight:600;line-height:1;border:1px solid rgba(0,0,0,0);border-radius:var(--radius-sm);cursor:pointer;transition-property:background-color,border-color,color,box-shadow,opacity,transform,filter;transition-duration:var(--duration-normal);transition-timing-function:var(--ease-standard);white-space:nowrap;-webkit-user-select:none;user-select:none;outline:none;position:relative}.hk-btn:focus-visible{box-shadow:0 0 0 2px rgb(var(--color-surface)),0 0 0 4px rgb(var(--color-primary))}.hk-btn:disabled{opacity:.5;cursor:not-allowed}.hk-btn-xs{padding:var(--space-4) var(--space-8);font-size:var(--text-xs);min-height:1.375rem}.hk-btn-sm{padding:var(--space-6) var(--space-12);font-size:var(--text-xs);min-height:1.75rem}.hk-btn-md{padding:var(--space-8) var(--space-16);font-size:var(--text-base);min-height:2.5rem}.hk-btn-lg{padding:var(--space-10) var(--space-24);font-size:var(--text-base);min-height:2.75rem}.hk-btn-xs .hk-btn-icon,.hk-btn-xs .hk-btn-suffix{inline-size:.8125rem;block-size:.8125rem}.hk-btn-xs .hk-btn-icon .hk-icon,.hk-btn-xs .hk-btn-suffix .hk-icon{inline-size:100%;block-size:100%}.hk-btn-icon,.hk-btn-suffix{display:inline-flex;align-items:center;justify-content:center;flex:none;inline-size:1rem;block-size:1rem;line-height:0}.hk-btn-icon svg,.hk-btn-suffix svg{display:block}.hk-btn>svg{flex:none}.hk-btn-icon-only{padding:0;box-sizing:border-box}.hk-btn-xs.hk-btn-icon-only{width:1.375rem}.hk-btn-sm.hk-btn-icon-only{width:1.75rem}.hk-btn-md.hk-btn-icon-only{width:2.5rem}.hk-btn-lg.hk-btn-icon-only{width:2.75rem}.hk-btn-block.hk-btn-icon-only{width:100%}.hk-btn-label{display:inline-flex;align-items:center;min-width:0}@media(max-width: 639.98px){.hk-btn-label-collapse-sm.hk-btn-xs .hk-btn-label{display:none}.hk-btn-label-collapse-sm.hk-btn-xs{padding:0;box-sizing:border-box;width:1.375rem}}@media(max-width: 639.98px){.hk-btn-label-collapse-sm.hk-btn-sm .hk-btn-label{display:none}.hk-btn-label-collapse-sm.hk-btn-sm{padding:0;box-sizing:border-box;width:1.75rem}}@media(max-width: 639.98px){.hk-btn-label-collapse-sm.hk-btn-md .hk-btn-label{display:none}.hk-btn-label-collapse-sm.hk-btn-md{padding:0;box-sizing:border-box;width:2.5rem}}@media(max-width: 639.98px){.hk-btn-label-collapse-sm.hk-btn-lg .hk-btn-label{display:none}.hk-btn-label-collapse-sm.hk-btn-lg{padding:0;box-sizing:border-box;width:2.75rem}}@media(max-width: 767.98px){.hk-btn-label-collapse-md.hk-btn-xs .hk-btn-label{display:none}.hk-btn-label-collapse-md.hk-btn-xs{padding:0;box-sizing:border-box;width:1.375rem}}@media(max-width: 767.98px){.hk-btn-label-collapse-md.hk-btn-sm .hk-btn-label{display:none}.hk-btn-label-collapse-md.hk-btn-sm{padding:0;box-sizing:border-box;width:1.75rem}}@media(max-width: 767.98px){.hk-btn-label-collapse-md.hk-btn-md .hk-btn-label{display:none}.hk-btn-label-collapse-md.hk-btn-md{padding:0;box-sizing:border-box;width:2.5rem}}@media(max-width: 767.98px){.hk-btn-label-collapse-md.hk-btn-lg .hk-btn-label{display:none}.hk-btn-label-collapse-md.hk-btn-lg{padding:0;box-sizing:border-box;width:2.75rem}}@media(max-width: 1023.98px){.hk-btn-label-collapse-lg.hk-btn-xs .hk-btn-label{display:none}.hk-btn-label-collapse-lg.hk-btn-xs{padding:0;box-sizing:border-box;width:1.375rem}}@media(max-width: 1023.98px){.hk-btn-label-collapse-lg.hk-btn-sm .hk-btn-label{display:none}.hk-btn-label-collapse-lg.hk-btn-sm{padding:0;box-sizing:border-box;width:1.75rem}}@media(max-width: 1023.98px){.hk-btn-label-collapse-lg.hk-btn-md .hk-btn-label{display:none}.hk-btn-label-collapse-lg.hk-btn-md{padding:0;box-sizing:border-box;width:2.5rem}}@media(max-width: 1023.98px){.hk-btn-label-collapse-lg.hk-btn-lg .hk-btn-label{display:none}.hk-btn-label-collapse-lg.hk-btn-lg{padding:0;box-sizing:border-box;width:2.75rem}}@media(max-width: 1279.98px){.hk-btn-label-collapse-xl.hk-btn-xs .hk-btn-label{display:none}.hk-btn-label-collapse-xl.hk-btn-xs{padding:0;box-sizing:border-box;width:1.375rem}}@media(max-width: 1279.98px){.hk-btn-label-collapse-xl.hk-btn-sm .hk-btn-label{display:none}.hk-btn-label-collapse-xl.hk-btn-sm{padding:0;box-sizing:border-box;width:1.75rem}}@media(max-width: 1279.98px){.hk-btn-label-collapse-xl.hk-btn-md .hk-btn-label{display:none}.hk-btn-label-collapse-xl.hk-btn-md{padding:0;box-sizing:border-box;width:2.5rem}}@media(max-width: 1279.98px){.hk-btn-label-collapse-xl.hk-btn-lg .hk-btn-label{display:none}.hk-btn-label-collapse-xl.hk-btn-lg{padding:0;box-sizing:border-box;width:2.75rem}}.hk-btn-primary{background:rgb(var(--color-primary));color:rgb(var(--color-on-solid))}.hk-btn-primary svg{color:rgb(var(--color-on-solid-icon, 255 255 255))}.hk-btn-primary:hover:not(:disabled):not(:focus-visible){filter:brightness(1.1);box-shadow:var(--shadow-button)}.hk-btn-primary:active:not(:disabled){filter:brightness(0.95)}.hk-btn-secondary{background:rgb(var(--color-surface));color:rgb(var(--color-text));border-color:rgb(var(--color-border)/20%);-webkit-backdrop-filter:blur(var(--blur-sm));backdrop-filter:blur(var(--blur-sm))}.hk-btn-secondary:hover:not(:disabled){border-color:var(--c-primary-strong)}.hk-btn-secondary:active:not(:disabled){filter:brightness(0.95)}.hk-btn-outline{background:rgba(0,0,0,0);color:rgb(var(--color-primary));border-color:var(--c-primary-intense)}.hk-btn-outline:hover:not(:disabled){background:var(--c-primary-light);border-color:rgb(var(--color-primary))}.hk-btn-outline:active:not(:disabled){filter:brightness(0.95)}.hk-btn-ghost{background:rgba(0,0,0,0);color:rgb(var(--color-text));border-color:rgba(0,0,0,0)}.hk-btn-ghost:hover:not(:disabled){background:var(--c-primary-light)}.hk-btn-ghost:active:not(:disabled){filter:brightness(0.95)}.hk-btn-danger{background:rgb(var(--color-error));color:rgb(var(--color-on-solid))}.hk-btn-danger:hover:not(:disabled):not(:focus-visible){filter:brightness(1.1);box-shadow:var(--shadow-button-danger)}.hk-btn-danger:active:not(:disabled){filter:brightness(0.95)}.hk-btn-block{width:100%}.hk-btn-loading{pointer-events:none}@media(prefers-color-scheme: dark){:root{--color-primary: 136 192 208;--color-on-solid: 46 52 64;--color-on-solid-text: 46 52 64;--color-on-solid-icon: 46 52 64;--color-text: 247 249 252;--color-muted: 180 180 180;--color-border: 128 128 128;--color-background: 22 27 38;--color-surface: 34 40 54;--color-error: 191 97 106}}";
@@ -27,6 +27,13 @@ import { BOOT_LANDING_CSS } from "./bootLandingCss.mjs";
27
27
  * "reuse the component" a mechanical property instead of a promise (edit the
28
28
  * component sheet and the panel follows through one regeneration).
29
29
  *
30
+ * Scope note (2026-11-07 mutation round): the entry @uses the VENDORED
31
+ * `styles/theme/scale.scss`, not the canonical `src/scale.scss` — drifting
32
+ * the canonical copy alone turns fontStacks.contract.test.ts red (three
33
+ * arms) but is invisible here. Token edits: change src/scale.scss, re-copy
34
+ * to the vendored path, regenerate this artifact — all three guards then
35
+ * see the same bytes.
36
+ *
30
37
  * Regenerate with (from packages/vue): pnpm run gen:boot-landing-css
31
38
  */
32
39
 
package/src/scale.scss CHANGED
@@ -117,7 +117,7 @@
117
117
  /* Fonts — keep in sync with packages/vue/src/theme/fontContext.ts */
118
118
  --font-sans: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "Noto Sans CJK SC", sans-serif;
119
119
  --font-reading: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "Noto Sans CJK SC", sans-serif;
120
- --font-mono: ui-monospace, "SF Mono", Menlo, Consolas, "DejaVu Sans Mono", "Liberation Mono", "PingFang SC", "Microsoft YaHei", monospace;
120
+ --font-mono: ui-monospace, "SF Mono", Menlo, Consolas, "DejaVu Sans Mono", "Liberation Mono", "PingFang SC", "Microsoft YaHei", "Noto Sans CJK SC", monospace;
121
121
  }
122
122
 
123
123
  /* Mobile viewports halve the floating-layer gutter. The breakpoint mirrors
@@ -119,7 +119,7 @@
119
119
  /* Fonts — keep in sync with packages/vue/src/theme/fontContext.ts */
120
120
  --font-sans: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "Noto Sans CJK SC", sans-serif;
121
121
  --font-reading: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "Noto Sans CJK SC", sans-serif;
122
- --font-mono: ui-monospace, "SF Mono", Menlo, Consolas, "DejaVu Sans Mono", "Liberation Mono", "PingFang SC", "Microsoft YaHei", monospace;
122
+ --font-mono: ui-monospace, "SF Mono", Menlo, Consolas, "DejaVu Sans Mono", "Liberation Mono", "PingFang SC", "Microsoft YaHei", "Noto Sans CJK SC", monospace;
123
123
  }
124
124
 
125
125
  /* Mobile viewports halve the floating-layer gutter. The breakpoint mirrors
@@ -0,0 +1,98 @@
1
+ import { readFileSync } from "node:fs";
2
+ import { dirname, resolve } from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+
5
+ import { describe, expect, it } from "vitest";
6
+
7
+ import { HIKARI_FONT_MONO, HIKARI_FONT_READING, HIKARI_FONT_SANS } from "./fontContext";
8
+
9
+ /**
10
+ * Font-stack sync contract (2026-11-07): the static SCSS tokens and the
11
+ * runtime fontContext constants are copies of one truth, and they
12
+ * drifted once — `--font-sans` carried the Linux CJK face
13
+ * ("Noto Sans CJK SC") in both sheet copies while `--font-mono` carried
14
+ * it only in fontContext.ts, so any surface rendering the STATIC sheet
15
+ * (pre-boot paint, or a downstream utility emitting its own literal
16
+ * stack) dropped CJK text into a random platform mono on Linux. All
17
+ * FOUR copies are pinned here — both scale.scss sheets, the fontContext
18
+ * constants, and the Sass-variable fallbacks in
19
+ * `components/_hikari-vars.scss` — so the next face can only be added
20
+ * in one place by failing here.
21
+ *
22
+ * Known-positive anchor: the mono stack must keep a CJK UI face AFTER the
23
+ * Latin mono faces and BEFORE the generic `monospace` catcher — that
24
+ * ordering IS the CJK-safety contract (Latin glyphs mono, CJK glyphs
25
+ * sans), not an accident of copy order.
26
+ */
27
+
28
+ const here = dirname(fileURLToPath(import.meta.url));
29
+
30
+ const SCSS_SOURCES = [
31
+ resolve(here, "../scale.scss"),
32
+ // Vendored byte-identical copy for npm installs (see its header).
33
+ resolve(here, "../styles/theme/scale.scss"),
34
+ ];
35
+
36
+ function tokenFromScss(path: string, token: string): string {
37
+ const css = readFileSync(path, "utf-8");
38
+ const match = css.match(new RegExp(`--${token}:\\s*([^;]+);`));
39
+ expect(match, `${path} must declare --${token}`).toBeTruthy();
40
+ return match![1]!.trim();
41
+ }
42
+
43
+ describe("font stack sync (scale.scss ↔ fontContext.ts)", () => {
44
+ it("keeps every vendored scale.scss copy byte-identical on the font block", () => {
45
+ const blocks = SCSS_SOURCES.map((path) => {
46
+ const css = readFileSync(path, "utf-8");
47
+ const match = css.match(/--font-sans:[^;]+;\s*--font-reading:[^;]+;\s*--font-mono:[^;]+;/);
48
+ expect(match, `${path} must declare the font token block`).toBeTruthy();
49
+ return match![0];
50
+ });
51
+ for (const block of blocks.slice(1)) {
52
+ expect(block).toBe(blocks[0]);
53
+ }
54
+ });
55
+
56
+ it("keeps --font-sans/--font-reading/--font-mono identical to the fontContext constants", () => {
57
+ for (const path of SCSS_SOURCES) {
58
+ expect(tokenFromScss(path, "font-sans")).toBe(HIKARI_FONT_SANS);
59
+ expect(tokenFromScss(path, "font-reading")).toBe(HIKARI_FONT_READING);
60
+ expect(tokenFromScss(path, "font-mono")).toBe(HIKARI_FONT_MONO);
61
+ }
62
+ });
63
+
64
+ it("keeps the Sass-variable fallback copies identical to the constants too", () => {
65
+ // _hikari-vars.scss mirrors both stacks as Sass variables for the
66
+ // `var(--font-*, …)` fallbacks component sheets use; normalize its
67
+ // single-quoted faces before comparing.
68
+ const normalize = (stack: string) => stack.replace(/'/g, '"').trim();
69
+ const vars = readFileSync(resolve(here, "../components/_hikari-vars.scss"), "utf-8");
70
+ const sassSans = vars.match(/\$hikari-font-family-sans:\s*([^;]+);/);
71
+ const sassMono = vars.match(/\$hikari-font-family-mono:\s*([^;]+);/);
72
+ expect(sassSans, "_hikari-vars.scss must declare the sans fallback").toBeTruthy();
73
+ expect(sassMono, "_hikari-vars.scss must declare the mono fallback").toBeTruthy();
74
+ expect(normalize(sassSans![1]!)).toBe(HIKARI_FONT_SANS);
75
+ expect(normalize(sassMono![1]!)).toBe(HIKARI_FONT_MONO);
76
+ });
77
+
78
+ it("orders every mono stack CJK-safe: the Linux CJK face lands before the generic monospace catcher", () => {
79
+ // The generic catcher is where Linux fontconfig roulette lives — the
80
+ // Linux-reachable CJK face must always be resolved before the stack
81
+ // reaches it. Known positive: this exact predicate FAILS on the
82
+ // pre-2026-11-07 stack (`…"PingFang SC", "Microsoft YaHei",
83
+ // monospace`) — "PingFang SC" / "Microsoft YaHei" don't exist on
84
+ // Linux builds, so a predicate accepting ANY CJK face there would
85
+ // have passed the stack that caused the incident. Only the
86
+ // Linux-coverable face satisfies it.
87
+ for (const stack of [HIKARI_FONT_MONO, ...SCSS_SOURCES.map((p) => tokenFromScss(p, "font-mono")),]) {
88
+ const faces = stack.split(",").map((f) => f.trim().replace(/^"|"$/g, ""));
89
+ const catcher = faces.indexOf("monospace");
90
+ expect(catcher, "stack must end in the generic monospace catcher").toBe(faces.length - 1);
91
+ const beforeCatcher = faces.slice(0, catcher);
92
+ expect(
93
+ beforeCatcher.some((f) => /Noto Sans CJK/.test(f)),
94
+ `Linux CJK face must precede the monospace catcher: ${stack}`,
95
+ ).toBe(true);
96
+ }
97
+ });
98
+ });