@celestia-island/hikari 0.55.77 → 0.55.78

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.
@@ -0,0 +1,177 @@
1
+ // HkStepGuide — the big-artwork illustrated step cards.
2
+ //
3
+ // Grammar (wowsp's 待命 walkthrough, user direction 2026-09-25): every
4
+ // step is a rounded SURFACE card. The artwork — stroke SVG, icon, or
5
+ // bitmap — is drawn LARGE and bare on the card face (no frame, no bed);
6
+ // the ordinal is a solid primary disc pinned to the card's outer
7
+ // top corner, half outside it; the title and one-line description sit
8
+ // left-aligned under the art. Completed steps swap the ordinal for a
9
+ // check and pick up a success edge, letting the guide double as a
10
+ // checklist while the flow progresses.
11
+ //
12
+ // Orientation follows the guide's OWN width (it is a container-query
13
+ // root): wide → cards sit side by side with a chevron bridging
14
+ // consecutive steps; narrow → they stack full-width. Fixed 520px
15
+ // breakpoint: container-query conditions cannot carry var(), so
16
+ // per-host thresholds would need a prop.
17
+
18
+ .hk-step-guide {
19
+ container-type: inline-size;
20
+ }
21
+
22
+ .hk-step-guide-title {
23
+ font-size: 0.6875rem;
24
+ font-weight: 600;
25
+ text-transform: uppercase;
26
+ letter-spacing: 0.05em;
27
+ color: var(--hi-color-text-tertiary, #8b949e);
28
+ margin: 0 0 var(--space-12, 0.75rem);
29
+ }
30
+
31
+ .hk-step-guide-list {
32
+ list-style: none;
33
+ margin: 0;
34
+ padding: 0;
35
+ // Headroom so the corner-pinned ordinals never crowd the section label.
36
+ padding-block-start: var(--space-10, 0.625rem);
37
+ display: flex;
38
+ flex-direction: column;
39
+ gap: var(--space-20, 1.25rem);
40
+ }
41
+
42
+ // ── the card ────────────────────────────────────────────────────────────────
43
+ .hk-step-guide-item {
44
+ position: relative;
45
+ display: flex;
46
+ flex-direction: column;
47
+ align-items: flex-start;
48
+ gap: var(--space-10, 0.625rem);
49
+ padding: var(--space-20, 1.25rem);
50
+ padding-block-start: calc(var(--space-20, 1.25rem) + var(--space-2, 0.125rem));
51
+ border-radius: var(--radius-lg, 1rem);
52
+ background: var(--hi-color-surface, #f0f4f8);
53
+
54
+ &[data-done] {
55
+ box-shadow: inset 0 0 0 1px rgb(from var(--hi-color-success, #0eb840) r g b / 0.55);
56
+
57
+ .hk-step-guide-ordinal {
58
+ background: var(--hi-color-success, #0eb840);
59
+ }
60
+
61
+ .hk-step-guide-art {
62
+ opacity: 0.45;
63
+ }
64
+ }
65
+ }
66
+
67
+ // ── the ordinal, pinned to the card's outer corner ──────────────────────────
68
+ .hk-step-guide-ordinal {
69
+ position: absolute;
70
+ top: calc(var(--space-10, 0.625rem) * -1);
71
+ inset-inline-start: calc(var(--space-10, 0.625rem) * -1);
72
+ width: var(--space-24, 1.5rem);
73
+ height: var(--space-24, 1.5rem);
74
+ border-radius: 50%;
75
+ display: flex;
76
+ align-items: center;
77
+ justify-content: center;
78
+ background: var(--hi-color-primary, #7aa2f7);
79
+ color: rgb(var(--color-on-solid-icon, 255 255 255));
80
+ font-size: var(--text-xs, 0.75rem);
81
+ font-weight: 700;
82
+ font-variant-numeric: tabular-nums;
83
+ box-sizing: border-box;
84
+ }
85
+
86
+ .hk-step-guide-check {
87
+ width: 62%;
88
+ height: 62%;
89
+ }
90
+
91
+ // ── the artwork, large and bare on the card face ────────────────────────────
92
+ .hk-step-guide-art {
93
+ width: 100%;
94
+ height: var(--hk-step-guide-art-height, 128px);
95
+ display: flex;
96
+ align-items: center;
97
+ justify-content: flex-start;
98
+ color: var(--hi-color-primary, #7aa2f7);
99
+ transition: opacity 200ms ease;
100
+ }
101
+
102
+ .hk-step-guide-art-svg {
103
+ width: 68%;
104
+ height: 100%;
105
+ }
106
+
107
+ .hk-step-guide-art-img {
108
+ width: 100%;
109
+ height: 100%;
110
+ object-fit: cover;
111
+ border-radius: calc(var(--radius-lg, 1rem) - var(--space-6, 0.375rem));
112
+ }
113
+
114
+ // ── the caption ─────────────────────────────────────────────────────────────
115
+ .hk-step-guide-body {
116
+ display: flex;
117
+ flex-direction: column;
118
+ gap: var(--space-2, 0.125rem);
119
+ }
120
+
121
+ .hk-step-guide-item-title {
122
+ font-size: var(--text-base, 0.9375rem);
123
+ font-weight: 600;
124
+ line-height: 1.4;
125
+ color: var(--hi-color-text-primary, #1e1e1e);
126
+ }
127
+
128
+ .hk-step-guide-item-desc {
129
+ font-size: var(--text-sm, 0.8125rem);
130
+ line-height: 1.55;
131
+ color: var(--hi-color-text-tertiary, #8b949e);
132
+ }
133
+
134
+ // ── wide host: the cards lie side by side, chevron between ──────────────────
135
+ @container (min-width: 520px) {
136
+ .hk-step-guide-list {
137
+ flex-direction: row;
138
+ align-items: stretch;
139
+ gap: var(--space-24, 1.5rem);
140
+ }
141
+
142
+ .hk-step-guide-item {
143
+ flex: 1 1 0;
144
+ min-width: 0;
145
+
146
+ // A chevron centered in the gap, pointing at the next card.
147
+ &:not(:last-child)::after {
148
+ content: "";
149
+ position: absolute;
150
+ top: 50%;
151
+ inset-inline-end: calc(var(--space-24, 1.5rem) * -1 + var(--space-2, 0.125rem));
152
+ width: 9px;
153
+ height: 9px;
154
+ border-inline-end: 2px solid var(--hi-color-text-tertiary, #8b949e);
155
+ border-block-end: 2px solid var(--hi-color-text-tertiary, #8b949e);
156
+ transform: translateY(-50%) rotate(-45deg);
157
+ }
158
+ }
159
+ }
160
+
161
+ // ── grid layout: auto-fill cards ────────────────────────────────────────────
162
+ .hk-step-guide[data-layout="grid"] {
163
+ .hk-step-guide-list {
164
+ display: grid;
165
+ grid-template-columns: repeat(
166
+ auto-fill,
167
+ minmax(min(var(--hk-step-guide-card-min, 200px), 100%), 1fr)
168
+ );
169
+ gap: var(--space-20, 1.25rem);
170
+ }
171
+ }
172
+
173
+ @media (prefers-reduced-motion: reduce) {
174
+ .hk-step-guide-art {
175
+ transition: none;
176
+ }
177
+ }
@@ -0,0 +1,81 @@
1
+ import { afterEach, describe, expect, it } from "vitest";
2
+ import { createApp, defineComponent, h } from "vue";
3
+
4
+ import HkStepGuide, { type StepGuideItem } from "./HkStepGuide";
5
+
6
+ const mounts: Array<{ app: ReturnType<typeof createApp>; container: HTMLElement }> = [];
7
+
8
+ function mount(node: ReturnType<typeof h>) {
9
+ const container = document.createElement("div");
10
+ document.body.appendChild(container);
11
+ const app = createApp({ render: () => node });
12
+ app.mount(container);
13
+ mounts.push({ app, container });
14
+ return container;
15
+ }
16
+
17
+ afterEach(() => {
18
+ for (const { app, container } of mounts.splice(0)) {
19
+ app.unmount();
20
+ container.remove();
21
+ }
22
+ });
23
+
24
+ const StubIcon = defineComponent({
25
+ name: "StubIcon",
26
+ setup() {
27
+ return () => h("i", { class: "stub-step-icon" });
28
+ },
29
+ });
30
+
31
+ const ITEMS: StepGuideItem[] = [
32
+ { id: "power", title: "插入电源", description: "接通设备供电", icon: StubIcon },
33
+ { id: "card", title: "装入 SD 卡", image: "https://example.com/card.png" },
34
+ { id: "net", title: "连接网线" },
35
+ ];
36
+
37
+ describe("HkStepGuide", () => {
38
+ it("renders every item as a list entry with its title", () => {
39
+ const c = mount(h(HkStepGuide, { items: ITEMS }));
40
+ const items = Array.from(c.querySelectorAll(".hk-step-guide-item"));
41
+ expect(items.length).toBe(3);
42
+ const titles = items.map((i) => i.querySelector(".hk-step-guide-item-title")?.textContent);
43
+ expect(titles).toEqual(["插入电源", "装入 SD 卡", "连接网线"]);
44
+ // An ordered list — a guide is sequential by contract.
45
+ expect(c.querySelector(".hk-step-guide-list")?.tagName).toBe("OL");
46
+ });
47
+
48
+ it("numbers the steps and swaps the ordinal for a check on done steps", () => {
49
+ const c = mount(h(HkStepGuide, { items: ITEMS, doneIds: ["power"] }));
50
+ const ordinals = Array.from(c.querySelectorAll(".hk-step-guide-ordinal"));
51
+ expect(ordinals[0].querySelector(".hk-step-guide-check")).not.toBeNull();
52
+ expect(ordinals[1].textContent?.trim()).toBe("2");
53
+ expect(ordinals[2].textContent?.trim()).toBe("3");
54
+ // The done step's art dims.
55
+ const arts = Array.from(c.querySelectorAll(".hk-step-guide-item"));
56
+ expect(arts[0].hasAttribute("data-done")).toBe(true);
57
+ expect(arts[1].hasAttribute("data-done")).toBe(false);
58
+ });
59
+
60
+ it("renders the icon component when given, the image otherwise, nothing last", () => {
61
+ const c = mount(h(HkStepGuide, { items: ITEMS }));
62
+ const arts = Array.from(c.querySelectorAll(".hk-step-guide-art"));
63
+ expect(arts[0].querySelector(".stub-step-icon")).not.toBeNull();
64
+ const img = arts[1].querySelector("img.hk-step-guide-art-img") as HTMLImageElement;
65
+ expect(img?.getAttribute("src")).toBe("https://example.com/card.png");
66
+ expect(img?.getAttribute("alt")).toBe("装入 SD 卡");
67
+ expect(arts[2].children.length).toBe(0);
68
+ expect(arts[2].getAttribute("aria-hidden")).toBe("true");
69
+ });
70
+
71
+ it("renders the section title and honors the grid layout hook", () => {
72
+ const c = mount(h(HkStepGuide, { items: ITEMS, title: "上架步骤", layout: "grid" }));
73
+ expect(c.querySelector(".hk-step-guide-title")?.textContent).toBe("上架步骤");
74
+ expect(c.querySelector(".hk-step-guide")?.getAttribute("data-layout")).toBe("grid");
75
+ });
76
+
77
+ it("renders nothing for an empty item list", () => {
78
+ const c = mount(h(HkStepGuide, { items: [] }));
79
+ expect(c.querySelector(".hk-step-guide")).toBeNull();
80
+ });
81
+ });
@@ -0,0 +1,129 @@
1
+ import { defineComponent, h, type Component, type PropType } from "vue";
2
+
3
+ import "./HkStepGuide.scss";
4
+
5
+ /**
6
+ * One step of an illustrated how-to guide.
7
+ *
8
+ * Each step renders as a CARD — the step's OWN artwork (an SVG component,
9
+ * a lucide icon, or a bitmap URL) drawn large on the card face, with the
10
+ * ordinal pinned to the card's outer corner and the caption beneath it.
11
+ */
12
+ export interface StepGuideItem {
13
+ /** Stable key (also the list rendering key). */
14
+ id: string;
15
+ /** Short imperative title ("插入 SD 卡"). */
16
+ title: string;
17
+ /** One-line elaboration under the title. */
18
+ description?: string;
19
+ /** SVG/lucide component rendered as the artwork. */
20
+ icon?: Component;
21
+ /** Bitmap/photo URL for the artwork (`icon` wins when both given). */
22
+ image?: string;
23
+ /** Accessible name when the artwork is decorative-only. Defaults to the title. */
24
+ alt?: string;
25
+ }
26
+
27
+ /**
28
+ * HkStepGuide — the big-artwork illustrated step cards.
29
+ *
30
+ * The "what do I physically do now?" surface for hardware-adjacent flows
31
+ * (upstreamed from the flasher's 等待上线 step and wowsp's install
32
+ * walkthrough, user direction 2026-09-25): every step is a rounded card —
33
+ * large artwork on the face, a short title and a one-line description
34
+ * under it, the ordinal badge pinned to the card's outer top corner.
35
+ *
36
+ * Orientation follows the AVAILABLE WIDTH, not the viewport: the guide is
37
+ * its own container query root, so wide hosts lay the cards side by side
38
+ * with a chevron bridging consecutive steps, and only stack them
39
+ * vertically once the width runs out. `layout="grid"` instead wraps the
40
+ * cards as an auto-fill grid.
41
+ *
42
+ * The artwork slots are deliberately permissive: pass a Vue component
43
+ * (`icon: MySvg`) for crisp theme-aware SVGs, or `image: url` for
44
+ * photos/screenshots the docs pipeline produces. Steps may be marked
45
+ * `data-done` through the `doneIds` prop (the check replaces the ordinal,
46
+ * the card picks up a success edge) so the guide doubles as a checklist
47
+ * while the flow progresses.
48
+ */
49
+ export default defineComponent({
50
+ name: "HkStepGuide",
51
+ props: {
52
+ items: { type: Array as PropType<StepGuideItem[]>, required: true },
53
+ /** Steps whose ids appear here render as completed (check, success edge). */
54
+ doneIds: { type: Array as PropType<string[]>, default: () => [] },
55
+ /** Width-responsive flow cards (default) or an auto-fill card grid. */
56
+ layout: { type: String as PropType<"list" | "grid">, default: "list" },
57
+ /** Section heading above the steps. */
58
+ title: { type: String, default: undefined },
59
+ },
60
+ setup(props) {
61
+ return () => {
62
+ if (!props.items.length) return null;
63
+ const done = new Set(props.doneIds);
64
+ return (
65
+ <div class="hk-step-guide" data-layout={props.layout}>
66
+ {props.title && <h4 class="hk-step-guide-title">{props.title}</h4>}
67
+ <ol class="hk-step-guide-list">
68
+ {props.items.map((item, idx) => {
69
+ const isDone = done.has(item.id);
70
+ const Art = item.icon;
71
+ const alt = item.alt ?? item.title;
72
+ return (
73
+ <li
74
+ key={item.id}
75
+ class="hk-step-guide-item"
76
+ data-done={isDone || undefined}
77
+ >
78
+ <span class="hk-step-guide-ordinal">
79
+ {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>
94
+ ) : (
95
+ idx + 1
96
+ )}
97
+ </span>
98
+ <span
99
+ class="hk-step-guide-art"
100
+ aria-hidden={Art || item.image ? undefined : true}
101
+ >
102
+ {Art ? (
103
+ h(Art, { class: "hk-step-guide-art-svg" })
104
+ ) : item.image ? (
105
+ <img
106
+ class="hk-step-guide-art-img"
107
+ src={item.image}
108
+ alt={alt}
109
+ draggable={false}
110
+ />
111
+ ) : null}
112
+ </span>
113
+ <span class="hk-step-guide-body">
114
+ <span class="hk-step-guide-item-title">{item.title}</span>
115
+ {item.description && (
116
+ <span class="hk-step-guide-item-desc">
117
+ {item.description}
118
+ </span>
119
+ )}
120
+ </span>
121
+ </li>
122
+ );
123
+ })}
124
+ </ol>
125
+ </div>
126
+ );
127
+ };
128
+ },
129
+ });
@@ -189,7 +189,10 @@
189
189
  "hikari::zoomToolbar.reset": "إعادة تعيين التكبير",
190
190
  "hikari::zoomToolbar.zoomIn": "تكبير",
191
191
  "hikari::zoomToolbar.zoomOut": "تصغير",
192
- "hikari::zoomToolbar.zoomSlider": "مستوى التكبير"
192
+ "hikari::zoomToolbar.zoomSlider": "مستوى التكبير",
193
+ "hikari::filterBar.trigger": "عوامل التصفية",
194
+ "hikari::filterBar.apply": "تطبيق",
195
+ "hikari::filterBar.reset": "إعادة تعيين"
193
196
  },
194
197
  "hikari::affixPicker.unset": "غير محدد",
195
198
  "hikari::affixPicker.title": "الخيارات",
@@ -189,7 +189,10 @@
189
189
  "hikari::zoomToolbar.reset": "Zoom zurücksetzen",
190
190
  "hikari::zoomToolbar.zoomIn": "Vergrößern",
191
191
  "hikari::zoomToolbar.zoomOut": "Verkleinern",
192
- "hikari::zoomToolbar.zoomSlider": "Zoom-Stufe"
192
+ "hikari::zoomToolbar.zoomSlider": "Zoom-Stufe",
193
+ "hikari::filterBar.trigger": "Filter",
194
+ "hikari::filterBar.apply": "Anwenden",
195
+ "hikari::filterBar.reset": "Zurücksetzen"
193
196
  },
194
197
  "hikari::affixPicker.unset": "Nicht festgelegt",
195
198
  "hikari::affixPicker.title": "Optionen",
@@ -189,7 +189,10 @@
189
189
  "hikari::zoomToolbar.reset": "Reset zoom",
190
190
  "hikari::zoomToolbar.zoomIn": "Zoom in",
191
191
  "hikari::zoomToolbar.zoomOut": "Zoom out",
192
- "hikari::zoomToolbar.zoomSlider": "Zoom level"
192
+ "hikari::zoomToolbar.zoomSlider": "Zoom level",
193
+ "hikari::filterBar.trigger": "Filters",
194
+ "hikari::filterBar.apply": "Apply",
195
+ "hikari::filterBar.reset": "Reset"
193
196
  },
194
197
  "hikari::affixPicker.title": "Options",
195
198
  "hikari::affixPicker.search": "Search",
@@ -189,7 +189,10 @@
189
189
  "hikari::zoomToolbar.reset": "Restablecer zoom",
190
190
  "hikari::zoomToolbar.zoomIn": "Acercar",
191
191
  "hikari::zoomToolbar.zoomOut": "Alejar",
192
- "hikari::zoomToolbar.zoomSlider": "Nivel de zoom"
192
+ "hikari::zoomToolbar.zoomSlider": "Nivel de zoom",
193
+ "hikari::filterBar.trigger": "Filtros",
194
+ "hikari::filterBar.apply": "Aplicar",
195
+ "hikari::filterBar.reset": "Restablecer"
193
196
  },
194
197
  "hikari::affixPicker.unset": "Sin establecer",
195
198
  "hikari::affixPicker.title": "Opciones",
@@ -189,7 +189,10 @@
189
189
  "hikari::zoomToolbar.reset": "Réinitialiser le zoom",
190
190
  "hikari::zoomToolbar.zoomIn": "Agrandir",
191
191
  "hikari::zoomToolbar.zoomOut": "Réduire",
192
- "hikari::zoomToolbar.zoomSlider": "Niveau de zoom"
192
+ "hikari::zoomToolbar.zoomSlider": "Niveau de zoom",
193
+ "hikari::filterBar.trigger": "Filtres",
194
+ "hikari::filterBar.apply": "Appliquer",
195
+ "hikari::filterBar.reset": "Réinitialiser"
193
196
  },
194
197
  "hikari::affixPicker.unset": "Non défini",
195
198
  "hikari::affixPicker.title": "Options",
@@ -189,7 +189,10 @@
189
189
  "hikari::zoomToolbar.reset": "ズームをリセット",
190
190
  "hikari::zoomToolbar.zoomIn": "拡大",
191
191
  "hikari::zoomToolbar.zoomOut": "縮小",
192
- "hikari::zoomToolbar.zoomSlider": "ズーム倍率"
192
+ "hikari::zoomToolbar.zoomSlider": "ズーム倍率",
193
+ "hikari::filterBar.trigger": "絞り込み",
194
+ "hikari::filterBar.apply": "適用",
195
+ "hikari::filterBar.reset": "リセット"
193
196
  },
194
197
  "hikari::affixPicker.unset": "未設定",
195
198
  "hikari::affixPicker.title": "オプション",
@@ -189,7 +189,10 @@
189
189
  "hikari::zoomToolbar.reset": "줌 초기화",
190
190
  "hikari::zoomToolbar.zoomIn": "확대",
191
191
  "hikari::zoomToolbar.zoomOut": "축소",
192
- "hikari::zoomToolbar.zoomSlider": "확대 수준"
192
+ "hikari::zoomToolbar.zoomSlider": "확대 수준",
193
+ "hikari::filterBar.trigger": "필터",
194
+ "hikari::filterBar.apply": "적용",
195
+ "hikari::filterBar.reset": "초기화"
193
196
  },
194
197
  "hikari::affixPicker.unset": "미설정",
195
198
  "hikari::affixPicker.title": "옵션",
@@ -189,7 +189,10 @@
189
189
  "hikari::zoomToolbar.reset": "Redefinir zoom",
190
190
  "hikari::zoomToolbar.zoomIn": "Ampliar",
191
191
  "hikari::zoomToolbar.zoomOut": "Reduzir",
192
- "hikari::zoomToolbar.zoomSlider": "Nível de zoom"
192
+ "hikari::zoomToolbar.zoomSlider": "Nível de zoom",
193
+ "hikari::filterBar.trigger": "Filtros",
194
+ "hikari::filterBar.apply": "Aplicar",
195
+ "hikari::filterBar.reset": "Redefinir"
193
196
  },
194
197
  "hikari::affixPicker.unset": "Não definido",
195
198
  "hikari::affixPicker.title": "Opções",
@@ -189,7 +189,10 @@
189
189
  "hikari::zoomToolbar.reset": "Сбросить масштаб",
190
190
  "hikari::zoomToolbar.zoomIn": "Увеличить",
191
191
  "hikari::zoomToolbar.zoomOut": "Уменьшить",
192
- "hikari::zoomToolbar.zoomSlider": "Уровень масштаба"
192
+ "hikari::zoomToolbar.zoomSlider": "Уровень масштаба",
193
+ "hikari::filterBar.trigger": "Фильтры",
194
+ "hikari::filterBar.apply": "Применить",
195
+ "hikari::filterBar.reset": "Сбросить"
193
196
  },
194
197
  "hikari::affixPicker.unset": "Не задано",
195
198
  "hikari::affixPicker.title": "Параметры",
@@ -193,7 +193,10 @@
193
193
  "hikari::statusBar.region.CN": "中国大陆",
194
194
  "hikari::statusBar.region.HK": "中国香港",
195
195
  "hikari::statusBar.region.MO": "中国澳门",
196
- "hikari::statusBar.region.TW": "中国台湾"
196
+ "hikari::statusBar.region.TW": "中国台湾",
197
+ "hikari::filterBar.trigger": "筛选",
198
+ "hikari::filterBar.apply": "应用",
199
+ "hikari::filterBar.reset": "重置"
197
200
  },
198
201
  "hikari::affixPicker.title": "选项",
199
202
  "hikari::affixPicker.search": "搜索",
@@ -193,7 +193,10 @@
193
193
  "hikari::statusBar.region.CN": "中國大陸",
194
194
  "hikari::statusBar.region.HK": "中國香港",
195
195
  "hikari::statusBar.region.MO": "中國澳門",
196
- "hikari::statusBar.region.TW": "中國台灣"
196
+ "hikari::statusBar.region.TW": "中國台灣",
197
+ "hikari::filterBar.trigger": "篩選",
198
+ "hikari::filterBar.apply": "套用",
199
+ "hikari::filterBar.reset": "重設"
197
200
  },
198
201
  "hikari::affixPicker.title": "選項",
199
202
  "hikari::affixPicker.search": "搜尋",
package/src/index.ts CHANGED
@@ -25,6 +25,8 @@ export {
25
25
  } from "./components/HkDockBar";
26
26
  export { default as HDrawer } from "./components/HkDrawer";
27
27
  export { default as HEmptyState } from "./components/HkEmptyState";
28
+ export { default as HStepGuide } from "./components/HkStepGuide";
29
+ export type { StepGuideItem } from "./components/HkStepGuide";
28
30
  export { default as HExpansionPanel } from "./components/HkExpansionPanel";
29
31
  export { default as HFab, type HFabAction } from "./components/HkFab";
30
32
  export { default as HIcon } from "./components/HkIcon";
@@ -492,7 +494,7 @@ export type { ChallengeDescriptor } from "./utils/powNonce";
492
494
  export { HkAdminTablePage as HAdminTablePage } from "./components/HkAdminTablePage";
493
495
  export type { HTableColumn } from "./components/HkAdminTablePage";
494
496
  export { HkPageHeader as HPageHeader } from "./components/HkPageHeader";
495
- export { HkStatCard as HStatCard, type StatTone } from "./components/HkStatCard";
497
+ export { HkStatCard as HStatCard } from "./components/HkStatCard";
496
498
  export { HkStatusPill as HStatusPill, type PillState } from "./components/HkStatusPill";
497
499
  export { HkShareBar as HShareBar } from "./components/HkShareBar";
498
500
  export { HkSecretRevealModal as HSecretRevealModal } from "./components/HkSecretRevealModal";
@@ -749,7 +751,10 @@ export { HkLocalePicker as HkLocalePicker } from "./components/HkLocalePicker";
749
751
  export { HkAdminTablePage as HkAdminTablePage } from "./components/HkAdminTablePage";
750
752
  export { HkPageHeader as HkPageHeader } from "./components/HkPageHeader";
751
753
  export { HkStatCard as HkStatCard } from "./components/HkStatCard";
754
+ export { statToneColor, type StatTone as StatTone, type StatVariant as StatVariant } from "./components/HkStatCard";
752
755
  export { HkStatusPill as HkStatusPill } from "./components/HkStatusPill";
756
+ export { default as HkFilterBar } from "./components/HkFilterBar";
757
+ export { default as HFilterBar } from "./components/HkFilterBar";
753
758
  export { HkShareBar as HkShareBar } from "./components/HkShareBar";
754
759
  export { HkSecretRevealModal as HkSecretRevealModal } from "./components/HkSecretRevealModal";
755
760
  export { HkCaptchaWidget as HkCaptchaWidget } from "./components/HkCaptchaWidget";
@@ -1040,3 +1040,24 @@
1040
1040
  .a-auth-page {
1041
1041
  min-height: calc(100vh - var(--s-footer-height, 3rem));
1042
1042
  }
1043
+
1044
+ // ── adjacent-button spacing (the button-row grammar) ──────────────────────
1045
+ // EVERY horizontal run of buttons keeps a visible gap between neighbours —
1046
+ // two buttons touching reads as one split control (user direction
1047
+ // 2026-09-25). Hosts wrap their button clusters in .hk-button-row and get
1048
+ // the house rhythm; modal footers and step docks already carry their own
1049
+ // gap on the same token, so everything agrees.
1050
+ .hk-button-row {
1051
+ display: flex;
1052
+ flex-direction: row;
1053
+ align-items: center;
1054
+ gap: var(--space-12, 0.75rem);
1055
+
1056
+ &[data-compact] {
1057
+ gap: var(--space-8, 0.5rem);
1058
+ }
1059
+
1060
+ &[data-end] {
1061
+ justify-content: flex-end;
1062
+ }
1063
+ }