@celestia-island/hikari 0.45.5 → 0.45.6

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.45.5",
3
+ "version": "0.45.6",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "description": "Hikari Vue 3 component library — production-grade UI components based on shittim-chest design system",
@@ -30,25 +30,39 @@
30
30
  gap: var(--space-16, 1rem);
31
31
  }
32
32
 
33
+ /* ── Identity hero: haloed logo → name → version · tagline ─────────────── */
34
+
33
35
  .s-about-modal-header {
34
36
  display: flex;
35
37
  flex-direction: column;
36
38
  align-items: center;
37
- gap: var(--space-8, 0.5rem);
39
+ gap: var(--space-10, 0.625rem);
38
40
  text-align: center;
39
41
  }
40
42
 
41
- .s-about-modal-header > div {
42
- display: flex;
43
- flex-direction: column;
44
- align-items: center;
43
+ // Framed mark. The app logo is a light-on-light illustration (a white tablet
44
+ // render with its own baked soft shadow), so it needs a tinted, bounded bed:
45
+ // floating on the modal surface it read as a stray smudge, and the same bed
46
+ // gives it contrast on dark themes without hard-coding a mode.
47
+ .s-about-modal-logo-frame {
48
+ display: grid;
49
+ place-items: center;
50
+ width: 5rem;
51
+ height: 5rem;
52
+ flex-shrink: 0;
53
+ border: 1px solid rgb(var(--color-primary) / 14%);
54
+ border-radius: var(--radius-xl, 16px);
55
+ background: linear-gradient(
56
+ 155deg,
57
+ rgb(var(--color-primary) / 13%),
58
+ rgb(var(--color-primary) / 4%)
59
+ );
60
+ box-shadow: 0 6px 18px rgb(var(--color-primary) / 12%);
45
61
  }
46
62
 
47
63
  .s-about-modal-logo-img {
48
- width: 3rem;
49
- height: 3rem;
50
- flex-shrink: 0;
51
- border-radius: var(--radius-lg, 12px);
64
+ width: 3.75rem;
65
+ height: 3.75rem;
52
66
  object-fit: contain;
53
67
  user-select: none;
54
68
  }
@@ -57,35 +71,69 @@
57
71
  display: flex;
58
72
  align-items: center;
59
73
  justify-content: center;
60
- width: 3rem;
61
- height: 3rem;
62
- flex-shrink: 0;
74
+ width: 3.25rem;
75
+ height: 3.25rem;
63
76
  border-radius: var(--radius-lg, 12px);
64
- background: rgb(var(--color-primary));
77
+ background: linear-gradient(
78
+ 150deg,
79
+ rgb(var(--color-primary)),
80
+ rgb(var(--color-secondary, var(--color-primary)))
81
+ );
65
82
  color: rgb(var(--color-on-solid-text, 255 255 255));
66
- font-size: var(--text-lg, 1.125rem);
83
+ font-size: var(--text-xl, 1.25rem);
67
84
  font-weight: 700;
68
85
  }
69
86
 
87
+ .s-about-modal-identity {
88
+ display: flex;
89
+ flex-direction: column;
90
+ align-items: center;
91
+ gap: var(--space-2, 0.125rem);
92
+ }
93
+
70
94
  .s-about-modal-name {
71
95
  margin: 0;
72
- font-size: var(--text-lg, 1.125rem);
96
+ font-size: var(--text-xl, 1.25rem);
73
97
  font-weight: 700;
98
+ line-height: 1.3;
99
+ letter-spacing: 0.01em;
74
100
  color: rgb(var(--color-text));
75
101
  }
76
102
 
103
+ // Version and tagline share one compact muted line — two stacked lines of
104
+ // equal weight made the hero read as a form.
105
+ .s-about-modal-subtitle {
106
+ display: flex;
107
+ flex-wrap: wrap;
108
+ align-items: center;
109
+ justify-content: center;
110
+ gap: var(--space-6, 0.375rem);
111
+ margin: 0;
112
+ font-size: var(--text-xs, 0.75rem);
113
+ line-height: 1.5;
114
+ color: rgb(var(--color-muted));
115
+ }
116
+
77
117
  .s-about-modal-version {
78
- margin: var(--space-4, 0.25rem) 0 0;
79
- font-size: var(--text-sm, 0.8125rem);
118
+ font-variant-numeric: tabular-nums;
119
+ }
120
+
121
+ .s-about-modal-subtitle-sep {
122
+ color: rgb(var(--color-border));
123
+ }
124
+
125
+ .s-about-modal-tagline {
80
126
  color: rgb(var(--color-muted));
81
127
  }
82
128
 
83
- .s-about-modal-slogan {
84
- margin: 0;
85
- font-size: var(--text-base, 1rem);
86
- font-weight: 600;
87
- text-align: center;
88
- color: rgb(var(--color-text));
129
+ /* ── Credits: made-by sentence + organization blurb ────────────────────── */
130
+
131
+ .s-about-modal-credits {
132
+ display: flex;
133
+ flex-direction: column;
134
+ align-items: center;
135
+ gap: var(--space-4, 0.25rem);
136
+ margin-top: calc(-1 * var(--space-4, 0.25rem));
89
137
  }
90
138
 
91
139
  .s-about-modal-made-by {
@@ -95,10 +143,17 @@
95
143
  color: rgb(var(--color-text));
96
144
  }
97
145
 
98
- .s-about-modal-tagline {
99
- margin: var(--space-2, 0.125rem) 0 0;
100
- font-size: var(--text-xs, 0.75rem);
101
- color: rgb(var(--color-muted));
146
+ .s-about-modal-made-by .s-about-modal-row-link {
147
+ margin: 0 0.15em;
148
+ }
149
+
150
+ .s-about-modal-row-link {
151
+ color: rgb(var(--color-primary));
152
+ text-decoration: none;
153
+ }
154
+
155
+ .s-about-modal-row-link:hover {
156
+ text-decoration: underline;
102
157
  }
103
158
 
104
159
  .s-about-modal-description {
@@ -106,45 +161,84 @@
106
161
  font-size: var(--text-xs, 0.75rem);
107
162
  line-height: 1.6;
108
163
  text-align: center;
164
+ text-wrap: pretty;
109
165
  color: rgb(var(--color-muted));
110
166
  }
111
167
 
168
+ /* ── Spec card: component version rows ────────────────────────────────── */
169
+
112
170
  .s-about-modal-rows {
113
171
  display: flex;
114
172
  flex-direction: column;
115
- gap: var(--space-8, 0.5rem);
173
+ padding: var(--space-2, 0.125rem) var(--space-14, 0.875rem);
174
+ border: 1px solid var(--border-subtle, rgb(100 100 100 / 12%));
175
+ border-radius: var(--radius-lg, 12px);
176
+ background: rgb(var(--color-primary) / 3%);
116
177
  }
117
178
 
118
179
  .s-about-modal-row {
119
180
  display: flex;
120
181
  align-items: center;
121
182
  justify-content: space-between;
122
- gap: var(--space-8, 0.5rem);
183
+ gap: var(--space-12, 0.75rem);
184
+ min-height: 2.125rem;
185
+ }
186
+
187
+ .s-about-modal-row + .s-about-modal-row {
188
+ border-top: 1px solid var(--border-faint, rgb(100 100 100 / 10%));
123
189
  }
124
190
 
125
191
  .s-about-modal-row-label {
126
- font-size: var(--text-sm, 0.8125rem);
192
+ flex: 0 1 auto;
193
+ font-size: var(--text-xs, 0.75rem);
127
194
  color: rgb(var(--color-muted));
128
195
  }
129
196
 
130
197
  .s-about-modal-row-value {
131
- font-size: var(--text-sm, 0.8125rem);
132
- color: rgb(var(--color-text));
198
+ display: inline-flex;
199
+ align-items: center;
200
+ gap: var(--space-8, 0.5rem);
201
+ flex: 0 0 auto;
202
+ }
203
+
204
+ .s-about-modal-row-number {
133
205
  font-family: var(--font-mono, vars.$hikari-font-family-mono);
206
+ font-size: var(--text-xs, 0.75rem);
207
+ font-variant-numeric: tabular-nums;
208
+ letter-spacing: -0.01em;
209
+ color: rgb(var(--color-text));
134
210
  }
135
211
 
136
- .s-about-modal-row-link {
137
- color: rgb(var(--color-primary));
138
- text-decoration: none;
212
+ // Environment word (生产 / 测试) lives in its own pill so it never melts
213
+ // into the version string.
214
+ .s-about-modal-row-meta {
215
+ padding: 0 var(--space-6, 0.375rem);
216
+ border: 1px solid var(--border-subtle, rgb(100 100 100 / 12%));
217
+ border-radius: var(--radius-full, 9999px);
218
+ font-size: var(--text-2xs, 0.625rem);
219
+ line-height: 1.6;
220
+ white-space: nowrap;
221
+ color: rgb(var(--color-muted));
139
222
  }
140
223
 
141
- .s-about-modal-row-link:hover {
142
- text-decoration: underline;
224
+ .s-about-modal-row-meta[data-tone="positive"] {
225
+ border-color: rgb(var(--color-success) / 32%);
226
+ background: rgb(var(--color-success) / 10%);
227
+ color: rgb(var(--color-success));
228
+ }
229
+
230
+ .s-about-modal-row-meta[data-tone="caution"] {
231
+ border-color: rgb(var(--color-warning) / 32%);
232
+ background: rgb(var(--color-warning) / 10%);
233
+ color: rgb(var(--color-warning));
143
234
  }
144
235
 
145
- .s-about-modal-links {
236
+ /* ── Chips: licenses and external links ───────────────────────────────── */
237
+
238
+ .s-about-modal-chips {
146
239
  display: flex;
147
240
  flex-direction: column;
241
+ align-items: center;
148
242
  gap: var(--space-8, 0.5rem);
149
243
  }
150
244
 
@@ -152,38 +246,50 @@
152
246
  display: flex;
153
247
  flex-wrap: wrap;
154
248
  justify-content: center;
155
- gap: var(--space-8, 0.5rem);
249
+ gap: var(--space-6, 0.375rem);
156
250
  }
157
251
 
252
+ // Ghost pills: the chips are secondary navigation, so they carry a hairline
253
+ // instead of a filled brand block.
158
254
  .s-about-modal-link {
159
- padding: var(--space-4, 0.25rem) var(--space-8, 0.5rem);
160
- border-radius: var(--radius-sm, 4px);
161
- background: rgb(var(--color-primary) / 12%);
162
- color: rgb(var(--color-primary));
255
+ padding: var(--space-2, 0.125rem) var(--space-10, 0.625rem);
256
+ border: 1px solid var(--border-subtle, rgb(100 100 100 / 12%));
257
+ border-radius: var(--radius-full, 9999px);
258
+ color: rgb(var(--color-muted));
163
259
  font-size: var(--text-xs, 0.75rem);
260
+ line-height: 1.7;
164
261
  text-decoration: none;
165
- transition: background 200ms ease;
262
+ transition:
263
+ color var(--duration-short, 0.15s) ease,
264
+ border-color var(--duration-short, 0.15s) ease,
265
+ background var(--duration-short, 0.15s) ease;
166
266
  }
167
267
 
168
268
  .s-about-modal-link:hover {
169
- background: rgb(var(--color-primary) / 20%);
269
+ border-color: var(--c-primary-strong, rgb(var(--color-primary) / 40%));
270
+ background: var(--c-primary-faint, rgb(var(--color-primary) / 5%));
271
+ color: rgb(var(--color-primary));
170
272
  }
171
273
 
172
274
  .s-about-modal-link:active {
173
- background: rgb(var(--color-primary) / 26%);
275
+ background: var(--c-primary-subtle, rgb(var(--color-primary) / 8%));
174
276
  }
175
277
 
176
278
  .s-about-modal-link:focus-visible {
279
+ outline: none;
177
280
  box-shadow:
178
281
  0 0 0 2px rgb(var(--color-surface)),
179
282
  0 0 0 4px rgb(var(--color-primary));
180
283
  }
181
284
 
285
+ /* ── Legal footer: filing links + copyright ───────────────────────────── */
286
+
182
287
  .s-about-modal-footer-links {
183
288
  display: flex;
184
289
  flex-wrap: wrap;
185
290
  justify-content: center;
186
- gap: var(--space-12, 0.75rem);
291
+ gap: var(--space-16, 1rem);
292
+ margin-top: calc(-1 * var(--space-4, 0.25rem));
187
293
  }
188
294
 
189
295
  .s-about-modal-footer-link {
@@ -200,4 +306,5 @@
200
306
  .s-about-modal-footer {
201
307
  display: flex;
202
308
  justify-content: center;
309
+ margin-top: calc(-1 * var(--space-6, 0.375rem));
203
310
  }
@@ -5,14 +5,16 @@ import { HkAboutModal } from "./HkAboutModal";
5
5
  import { setLocale } from "../i18n/context";
6
6
 
7
7
  /**
8
- * HkAboutModal contract tests for the branding wave:
8
+ * HkAboutModal contract tests for the branding + redesign waves:
9
9
  * - the first-letter tile is the default identity and `logoSrc` swaps it
10
- * for an image
11
- * - tagline / description / author / license / copyright render the given
12
- * strings, authorHref turns the author value into an external link
10
+ * for an image inside the identity frame
11
+ * - version and tagline share one subtitle line
12
+ * - description / made-by / license / copyright render the given strings,
13
+ * madeByNameHref turns the author name into an external link
14
+ * - component rows split the machine value from an optional meta pill
13
15
  * - the backdrop factory renders inside the clipped backdrop layer
14
- * - links render as external chips
15
- * - author / license labels resolve through the i18n bundles
16
+ * - licenses and links render as chips under one centered block
17
+ * - footer (filing) links render as external links
16
18
  *
17
19
  * (Repo test convention: raw createApp + document queries, no
18
20
  * @vue/test-utils dependency.)
@@ -117,10 +119,10 @@ describe("HkAboutModal branding", () => {
117
119
  expect(document.body.querySelector(".s-about-modal-row-link")).toBeNull();
118
120
  });
119
121
 
120
- it("renders software-component version rows", async () => {
122
+ it("renders software-component version rows with a meta pill and tone", async () => {
121
123
  mountAbout({
122
124
  componentVersions: [
123
- { label: "WebUI 版本", value: "0.1.138 REW2HF 生产" },
125
+ { label: "WebUI 版本", value: "0.1.138 REW2HF", meta: "生产", metaTone: "positive" },
124
126
  { label: "计算引擎版本", value: "0.1.0" },
125
127
  ],
126
128
  });
@@ -128,8 +130,25 @@ describe("HkAboutModal branding", () => {
128
130
  const rows = [...document.body.querySelectorAll<HTMLElement>(".s-about-modal-row")];
129
131
  expect(rows.length).toBe(2);
130
132
  expect(rows[0]!.textContent).toContain("WebUI 版本");
131
- expect(rows[0]!.textContent).toContain("0.1.138 REW2HF 生产");
132
- expect(rows[1]!.textContent).toContain("计算引擎版本");
133
+ // The number and the environment word are separate nodes: the value
134
+ // stays machine-readable, the tag carries the typography.
135
+ const number = query<HTMLElement>(".s-about-modal-row-number");
136
+ expect(number.textContent).toBe("0.1.138 REW2HF");
137
+ const meta = query<HTMLElement>(".s-about-modal-row-meta");
138
+ expect(meta.textContent).toBe("生产");
139
+ expect(meta.getAttribute("data-tone")).toBe("positive");
140
+ // The second row has no meta, so it renders no pill.
141
+ expect(rows[1]!.querySelector(".s-about-modal-row-meta")).toBeNull();
142
+ });
143
+
144
+ it("defaults the meta pill to the neutral tone", async () => {
145
+ mountAbout({
146
+ componentVersions: [{ label: "计算引擎版本", value: "0.1.0", meta: "测试" }],
147
+ });
148
+ await flushModal();
149
+ expect(query<HTMLElement>(".s-about-modal-row-meta").getAttribute("data-tone")).toBe(
150
+ "neutral",
151
+ );
133
152
  });
134
153
 
135
154
  it("renders centered license chips", async () => {
@@ -205,5 +224,43 @@ describe("HkAboutModal branding", () => {
205
224
  expect(document.body.querySelectorAll(".s-about-modal-row").length).toBe(0);
206
225
  expect(document.body.querySelector(".s-about-modal-made-by")).toBeNull();
207
226
  expect(document.body.querySelector(".s-about-modal-links")).toBeNull();
227
+ expect(document.body.querySelector(".s-about-modal-chips")).toBeNull();
228
+ expect(document.body.querySelector(".s-about-modal-credits")).toBeNull();
229
+ });
230
+
231
+ it("groups license and link chips under one centered chips block", async () => {
232
+ mountAbout({
233
+ licenses: [{ label: "SySL-1.0", href: "https://example.test/sysl" }],
234
+ links: [{ label: "celestia.world", href: "https://celestia.world" }],
235
+ });
236
+ await flushModal();
237
+ const chips = query<HTMLElement>(".s-about-modal-chips");
238
+ const slots = [...chips.querySelectorAll<HTMLElement>(".s-about-modal-links")].map((el) =>
239
+ el.getAttribute("data-slot"),
240
+ );
241
+ expect(slots).toEqual(["licenses", "links"]);
242
+ expect(
243
+ [...chips.querySelectorAll<HTMLAnchorElement>(".s-about-modal-link")].map(
244
+ (chip) => chip.textContent,
245
+ ),
246
+ ).toEqual(["SySL-1.0", "celestia.world"]);
247
+ });
248
+
249
+ it("frames the logo image inside the identity frame", async () => {
250
+ mountAbout({ logoSrc: "data:image/webp;base64,AAA" });
251
+ await flushModal();
252
+ const frame = query<HTMLElement>(".s-about-modal-logo-frame");
253
+ expect(frame.querySelector("img.s-about-modal-logo-img")).toBeTruthy();
254
+ });
255
+
256
+ it("keeps version and tagline on one subtitle line", async () => {
257
+ mountAbout({ version: "9.9.9", tagline: "Chat with your agents" });
258
+ await flushModal();
259
+ const subtitle = query<HTMLElement>(".s-about-modal-subtitle");
260
+ expect(subtitle.querySelector(".s-about-modal-version")?.textContent).toContain("9.9.9");
261
+ expect(subtitle.querySelector(".s-about-modal-tagline")?.textContent).toBe(
262
+ "Chat with your agents",
263
+ );
264
+ expect(subtitle.querySelector(".s-about-modal-subtitle-sep")).toBeTruthy();
208
265
  });
209
266
  });
@@ -10,23 +10,34 @@ export interface HAboutLink {
10
10
  href: string;
11
11
  }
12
12
 
13
- /** One software-component version row (label left, composed value right). */
13
+ /**
14
+ * One software-component version row.
15
+ *
16
+ * `value` is the machine-readable half (version, optionally plus a build
17
+ * hash) and renders in the mono/tabular face; `meta` is the optional
18
+ * human-facing trailing tag (e.g. the environment word 生产 / 测试),
19
+ * rendered as a separate muted pill so it never melts into the version
20
+ * string. `metaTone` tints that pill when the tag carries a status
21
+ * meaning (e.g. a production environment).
22
+ */
14
23
  export interface HAboutComponentVersion {
15
24
  label: string;
16
25
  value: string;
26
+ meta?: string;
27
+ metaTone?: "neutral" | "positive" | "caution";
17
28
  }
18
29
 
19
30
  /**
20
31
  * HkAboutModal — version / about dialog.
21
32
  * (Upstreamed from shittim-chest's plana-legacy layer.)
22
33
  *
23
- * Shows a centered identity block (logo above the name above the tagline)
24
- * plus optional branding: a made-by sentence with a linked author name, a
25
- * centered organization line, software-component version rows, license and
26
- * external link chips, a decorative backdrop layer (canvas or anything
27
- * else, rendered behind the content and pointer-inert) and centered legal
28
- * footer links (e.g. ICP filings). Every branding prop is optional — the
29
- * modal degrades to the plain identity card when none are given.
34
+ * Layout (2026-09 redesign): a centered identity hero (haloed logo, name,
35
+ * then version + tagline on one compact line), an optional credits block
36
+ * (made-by sentence with a linked author name + organization blurb), a
37
+ * bordered spec card holding the software-component versions, ghost chip
38
+ * rows for licenses / external links, and a muted legal footer (filing
39
+ * links + copyright). Every branding prop is optional — the modal degrades
40
+ * to the plain identity card when none are given.
30
41
  */
31
42
  export const HkAboutModal = defineComponent({
32
43
  name: "HkAboutModal",
@@ -38,9 +49,9 @@ export const HkAboutModal = defineComponent({
38
49
  version: { type: String, required: true },
39
50
  /** Optional logo image URL — replaces the first-letter tile. */
40
51
  logoSrc: { type: String, default: undefined },
41
- /** Optional one-liner under the version (e.g. the app tagline). */
52
+ /** Optional one-liner shown beside the version (e.g. the app tagline). */
42
53
  tagline: { type: String, default: undefined },
43
- /** Optional centered small line under the header (organization blurb). */
54
+ /** Optional centered small line under the credits (organization blurb). */
44
55
  description: { type: String, default: undefined },
45
56
  /** Made-by sentence: text before the linked author name (e.g. "由"). */
46
57
  madeByPrefix: { type: String, default: undefined },
@@ -100,6 +111,27 @@ export const HkAboutModal = defineComponent({
100
111
  );
101
112
  };
102
113
 
114
+ const renderChips = (items: HAboutLink[], slot: string) => {
115
+ if (items.length === 0) return null;
116
+ return (
117
+ <div class="s-about-modal-links" data-slot={slot}>
118
+ <div class="s-about-modal-links-list">
119
+ {items.map((item) => (
120
+ <a
121
+ key={item.href}
122
+ href={item.href}
123
+ target="_blank"
124
+ rel="noopener noreferrer"
125
+ class="s-about-modal-link"
126
+ >
127
+ {item.label}
128
+ </a>
129
+ ))}
130
+ </div>
131
+ </div>
132
+ );
133
+ };
134
+
103
135
  return () => (
104
136
  <HModal
105
137
  modelValue={props.modelValue}
@@ -115,67 +147,70 @@ export const HkAboutModal = defineComponent({
115
147
  )}
116
148
  <div class="s-about-modal-body">
117
149
  <header class="s-about-modal-header">
118
- {props.logoSrc ? (
119
- <img class="s-about-modal-logo-img" src={props.logoSrc} alt="" draggable={false} />
120
- ) : (
121
- <div class="s-about-modal-logo">{props.appName.slice(0, 1).toUpperCase()}</div>
122
- )}
123
- <div>
150
+ <div class="s-about-modal-logo-frame">
151
+ {props.logoSrc ? (
152
+ <img
153
+ class="s-about-modal-logo-img"
154
+ src={props.logoSrc}
155
+ alt=""
156
+ draggable={false}
157
+ />
158
+ ) : (
159
+ <div class="s-about-modal-logo">{props.appName.slice(0, 1).toUpperCase()}</div>
160
+ )}
161
+ </div>
162
+ <div class="s-about-modal-identity">
124
163
  <h2 class="s-about-modal-name">{props.appName}</h2>
125
- <p class="s-about-modal-version">
126
- {t("hikari::about.version", "Version")} {props.version}
164
+ <p class="s-about-modal-subtitle">
165
+ <span class="s-about-modal-version">
166
+ {t("hikari::about.version", "Version")} {props.version}
167
+ </span>
168
+ {props.tagline && (
169
+ <>
170
+ <span class="s-about-modal-subtitle-sep" aria-hidden="true">
171
+ ·
172
+ </span>
173
+ <span class="s-about-modal-tagline">{props.tagline}</span>
174
+ </>
175
+ )}
127
176
  </p>
128
- {props.tagline && <p class="s-about-modal-tagline">{props.tagline}</p>}
129
177
  </div>
130
178
  </header>
131
179
 
132
- {renderMadeBy()}
133
- {props.description && <p class="s-about-modal-description">{props.description}</p>}
180
+ {(props.madeByName || props.description) && (
181
+ <div class="s-about-modal-credits">
182
+ {renderMadeBy()}
183
+ {props.description && (
184
+ <p class="s-about-modal-description">{props.description}</p>
185
+ )}
186
+ </div>
187
+ )}
134
188
 
135
189
  {props.componentVersions.length > 0 && (
136
190
  <div class="s-about-modal-rows">
137
191
  {props.componentVersions.map((component) => (
138
192
  <div class="s-about-modal-row" key={component.label}>
139
193
  <span class="s-about-modal-row-label">{component.label}</span>
140
- <span class="s-about-modal-row-value">{component.value}</span>
194
+ <span class="s-about-modal-row-value">
195
+ <span class="s-about-modal-row-number">{component.value}</span>
196
+ {component.meta && (
197
+ <span
198
+ class="s-about-modal-row-meta"
199
+ data-tone={component.metaTone ?? "neutral"}
200
+ >
201
+ {component.meta}
202
+ </span>
203
+ )}
204
+ </span>
141
205
  </div>
142
206
  ))}
143
207
  </div>
144
208
  )}
145
209
 
146
- {props.licenses.length > 0 && (
147
- <div class="s-about-modal-links">
148
- <div class="s-about-modal-links-list">
149
- {props.licenses.map((license) => (
150
- <a
151
- key={license.href}
152
- href={license.href}
153
- target="_blank"
154
- rel="noopener noreferrer"
155
- class="s-about-modal-link"
156
- >
157
- {license.label}
158
- </a>
159
- ))}
160
- </div>
161
- </div>
162
- )}
163
-
164
- {props.links.length > 0 && (
165
- <div class="s-about-modal-links">
166
- <div class="s-about-modal-links-list">
167
- {props.links.map((link) => (
168
- <a
169
- key={link.href}
170
- href={link.href}
171
- target="_blank"
172
- rel="noopener noreferrer"
173
- class="s-about-modal-link"
174
- >
175
- {link.label}
176
- </a>
177
- ))}
178
- </div>
210
+ {(props.licenses.length > 0 || props.links.length > 0) && (
211
+ <div class="s-about-modal-chips">
212
+ {renderChips(props.licenses, "licenses")}
213
+ {renderChips(props.links, "links")}
179
214
  </div>
180
215
  )}
181
216
 
package/src/index.ts CHANGED
@@ -353,7 +353,11 @@ export { HkSecretRevealModal as HSecretRevealModal } from "./components/HkSecret
353
353
  export { HkCaptchaWidget as HCaptchaWidget, type HkCaptchaProvider } from "./components/HkCaptchaWidget";
354
354
  export { HkCaptchaModal as HCaptchaModal } from "./components/HkCaptchaModal";
355
355
  export { HkProtocolModal as HProtocolModal } from "./components/HkProtocolModal";
356
- export { HkAboutModal as HAboutModal, type HAboutLink } from "./components/HkAboutModal";
356
+ export {
357
+ HkAboutModal as HAboutModal,
358
+ type HAboutComponentVersion,
359
+ type HAboutLink,
360
+ } from "./components/HkAboutModal";
357
361
  export { HkLogWindow as HLogWindow, type HLogTab } from "./components/HkLogWindow";
358
362
  export { HkCookieConsent as HCookieConsent } from "./components/HkCookieConsent";
359
363
  export {