@celestia-island/hikari 0.55.235 → 0.55.236

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.235",
3
+ "version": "0.55.236",
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",
@@ -216,16 +216,53 @@
216
216
  color: rgb(var(--color-muted));
217
217
  }
218
218
 
219
+ // Tone-pill ink, WCAG AA (2026-10-30 record-level leftover; PR #920 R2 S3-⑤).
220
+ //
221
+ // The pill text sits at `--text-2xs` (10px) — WCAG small text — so it owes
222
+ // 4.5:1, and the raw channel colour does not clear that on every palette:
223
+ // hosts swap the preset table wholesale (chest clears every stock key at
224
+ // boot and registers only its brand line), so no palette's value may be
225
+ // hardcoded here. The ink is therefore DERIVED from the live channel in
226
+ // OKLCH, whose perceptual uniformness lets the derivation touch lightness
227
+ // only — hue and chroma pass through, so each tone keeps its identity:
228
+ //
229
+ // light: L capped at 0.5 — darkens only colours too light for the white
230
+ // modal surface (stock amber would sit at 1.9:1);
231
+ // dark: L floored at 0.72 — lifts only colours too dark for the navy one
232
+ // (chest red would sit at 3.3:1).
233
+ //
234
+ // On the two shipped palettes the light cap grips EVERY coloured tone (all
235
+ // six entries sit above L 0.5 — chest light red 0.541 clamps to 0.501); the
236
+ // band's only pass-through is the stock dark positive green (L 0.768,
237
+ // already above the floor). Hue and chroma always survive. Both bounds were
238
+ // solved against the pill's real backdrop —
239
+ // the 10% channel tint composited over the 3%-primary card on
240
+ // `--color-surface` — for the WORST entry of the stock AND chest palettes:
241
+ // every tone lands ≥4.6:1. `light-dark()` resolves through the same
242
+ // color-scheme the theme engine already drives (`[data-mode] body` in
243
+ // theme.scss sets it alongside the channel swap), and an engine without
244
+ // relative-colour support drops the declaration, falling back to the
245
+ // neutral muted ink above — still AA everywhere: ≥4.87:1 even under the
246
+ // coloured pills' own tinted backdrop (4.87–7.20 across both palettes and
247
+ // modes; 5.98–8.06 on the plain neutral pill) — rather than shipping an
248
+ // unreadable tone colour. The border and background keep the raw channel:
249
+ // only the glyphs owe contrast, the tint is decorative.
219
250
  .s-about-modal-row-meta[data-tone="positive"] {
220
251
  border-color: rgb(var(--color-success) / 32%);
221
252
  background: rgb(var(--color-success) / 10%);
222
- color: rgb(var(--color-success));
253
+ color: light-dark(
254
+ oklch(from rgb(var(--color-success)) min(l, 0.5) c h),
255
+ oklch(from rgb(var(--color-success)) max(l, 0.72) c h)
256
+ );
223
257
  }
224
258
 
225
259
  .s-about-modal-row-meta[data-tone="caution"] {
226
260
  border-color: rgb(var(--color-warning) / 32%);
227
261
  background: rgb(var(--color-warning) / 10%);
228
- color: rgb(var(--color-warning));
262
+ color: light-dark(
263
+ oklch(from rgb(var(--color-warning)) min(l, 0.5) c h),
264
+ oklch(from rgb(var(--color-warning)) max(l, 0.72) c h)
265
+ );
229
266
  }
230
267
 
231
268
  // Red state: the component is not wired up / unreachable. The row then
@@ -233,7 +270,10 @@
233
270
  .s-about-modal-row-meta[data-tone="danger"] {
234
271
  border-color: rgb(var(--color-error) / 32%);
235
272
  background: rgb(var(--color-error) / 10%);
236
- color: rgb(var(--color-error));
273
+ color: light-dark(
274
+ oklch(from rgb(var(--color-error)) min(l, 0.5) c h),
275
+ oklch(from rgb(var(--color-error)) max(l, 0.72) c h)
276
+ );
237
277
  }
238
278
 
239
279
  /* ── Links: licenses, external links, credits names and filings ───────── */
@@ -239,22 +239,44 @@ describe("HkAboutModal stylesheet contract", () => {
239
239
  describe("row meta tones", () => {
240
240
  const TONE = (tone: string) => `.s-about-modal-row-meta[data-tone=${tone}]`;
241
241
 
242
- it("paints the danger tone in the theme's error colour", () => {
243
- const danger = ruleBody(css, TONE("danger"));
244
- expect(danger, "the danger-tone rule must survive compilation").not.toBe("");
245
- expect({
246
- borderColor: declaration(danger, "border-color"),
247
- background: declaration(danger, "background"),
248
- color: declaration(danger, "color"),
249
- }).toEqual({
250
- // Same geometry as the positive / caution tones, only the channel
251
- // differs — `--color-error` is the theme's existing danger token.
252
- // (Dart Sass compiles the seed-relative form without spaces around
253
- // the slash.)
254
- borderColor: "rgb(var(--color-error)/32%)",
255
- background: "rgb(var(--color-error)/10%)",
256
- color: "rgb(var(--color-error))",
257
- });
242
+ // Channel behind each coloured tone, and the WCAG-AA ink derived from it.
243
+ // The pill text is `--text-2xs` (10px) — small text, 4.5:1 — and the raw
244
+ // channel misses that on both shipped palettes (stock light warning sits
245
+ // at ~1.9:1, chest dark red at ~3.3:1). The ink is therefore an OKLCH
246
+ // derivation from the live channel: hue and chroma pass through, only
247
+ // lightness moves — capped at 0.5 in light mode, floored at 0.72 in dark
248
+ // (bounds solved so the worst stock AND chest entry clears 4.5:1 against
249
+ // the pill's real backdrop: the 10% channel tint over the 3%-primary
250
+ // card on `--color-surface`). Reverting any arm to the raw channel — or
251
+ // dropping one `light-dark()` side — must turn this red.
252
+ const AA_INK = (channel: string) =>
253
+ `light-dark(` +
254
+ `oklch(from rgb(var(--color-${channel})) min(l, 0.5) c h), ` +
255
+ `oklch(from rgb(var(--color-${channel})) max(l, 0.72) c h))`;
256
+ const TONES: Record<string, string> = {
257
+ positive: "success",
258
+ caution: "warning",
259
+ danger: "error",
260
+ };
261
+
262
+ it("paints every tone in the theme channel with its AA-derived ink", () => {
263
+ for (const [tone, channel] of Object.entries(TONES)) {
264
+ const rule = ruleBody(css, TONE(tone));
265
+ expect(rule, `the ${tone}-tone rule must survive compilation`).not.toBe("");
266
+ expect({
267
+ borderColor: declaration(rule, "border-color"),
268
+ background: declaration(rule, "background"),
269
+ color: declaration(rule, "color"),
270
+ }).toEqual({
271
+ // Same geometry across the tones, only the channel differs; the
272
+ // border and tint keep the RAW channel (decorative identity),
273
+ // while the glyphs get the derived ink. (Dart Sass compiles the
274
+ // seed-relative form without spaces around the slash.)
275
+ borderColor: `rgb(var(--color-${channel})/32%)`,
276
+ background: `rgb(var(--color-${channel})/10%)`,
277
+ color: AA_INK(channel),
278
+ });
279
+ }
258
280
  });
259
281
  });
260
282
  });