@misoto22/kioku-ui-theme-kioku 0.0.0 → 1.0.0

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/README.md CHANGED
@@ -1,7 +1,7 @@
1
1
  # Kioku theme pack
2
2
 
3
- `@misoto22/kioku-ui-theme-kioku` provides the Washi, Muji, and Sumi visual
4
- identities as an external theme collection for `@misoto22/kioku-ui`.
3
+ `@misoto22/kioku-ui-theme-kioku` provides the Washi, Muji, Sumi, and Kasumi
4
+ visual identities as an external theme collection for `@misoto22/kioku-ui`.
5
5
 
6
6
  ```tsx
7
7
  import {ThemeProvider} from '@misoto22/kioku-ui/theme';
@@ -22,12 +22,17 @@ CSS `light-dark()`, so they follow the host's inherited `color-scheme`. The
22
22
  themes share the package's semantic spacing, radius, and control-size scales.
23
23
  The stylesheet never targets `:root`, `html`, or `body`.
24
24
 
25
- The three skins carry Kioku Console's own palettes, geometry, and type scale:
25
+ Washi, Muji, and Sumi carry Kioku Console's own palettes, geometry, and type
26
+ scale:
26
27
  warm paper rather than white, one 3px corner for every element including
27
28
  controls, a hairline rule instead of a drop shadow, and the console's compact
28
29
  spacing. Where a role that this package holds to WCAG AA missed the threshold on
29
30
  one of its backgrounds, it keeps the console's hue and loses only lightness.
30
31
 
32
+ Kasumi is the fourth skin and the one exception — see **Kasumi and the frost
33
+ lever** below. It shares the same geometry and type scale, and differs only in
34
+ that its field surfaces are translucent.
35
+
31
36
  Each skin fulfills the spacing roles at both densities, so a reader selecting
32
37
  `standard` widens the same rhythm rather than switching visual identity:
33
38
 
@@ -48,3 +53,190 @@ sets it transparent, which is how a skin declines the texture.
48
53
 
49
54
  The initial theme release supports core `>=0.0.0 <0.2.0`. A later pre-1 core
50
55
  minor must be compatibility-reviewed before this peer range is widened.
56
+
57
+ ## Kasumi and the frost lever
58
+
59
+ 霞 is mist, and the only skin here that expects something behind it. Its field
60
+ surfaces are translucent and it blurs whatever the host drew, so a backdrop
61
+ reads through the interface while type keeps its contrast.
62
+
63
+ The model is ported from [Hermes Desktop][hermes]'s glass mode. Worth knowing
64
+ before you reach for it: in Hermes the blur is **native macOS vibrancy
65
+ composited below the web contents**, not CSS — in a transparent window
66
+ `backdrop-filter` reaches nothing, because the backdrop root is the document and
67
+ the desktop was never in it. On the web the blur is ours to draw, and three
68
+ things separate glass from a coloured film.
69
+
70
+ ### 1. The tint goes to an extreme
71
+
72
+ Near-white in light, near-black in dark — never the mid-tone a skin would
73
+ otherwise use. A mid-tone film greys out whatever is behind it. Solving [Radix
74
+ Themes][radix]' alpha equation for a panel that must composite to `#f1f4f8` over
75
+ this canvas returns `#fefeff` at 50%, and Apple's navigation glass is
76
+ `rgba(0,0,0,0.8)` rather than a dark grey — the same answer from two directions.
77
+ So the field and the surfaces carry a near-neutral tint, and the skin spends its
78
+ whole chromatic budget on the accent.
79
+
80
+ ### 2. The blur lives on a pseudo-element
81
+
82
+ `backdrop-filter` on the theme root would make it the containing block for every
83
+ `position: fixed` descendant — Tooltip, Toast, ContextMenu, AppShell, Popover,
84
+ Overlay and MobileNav all position against the viewport. A pseudo-element has no
85
+ descendants, so it can never be a containing block for anything. The root gets
86
+ `isolation: isolate`, which opens a stacking context rather than a containing
87
+ block, and `[data-theme='kasumi']::before` carries the filter at `z-index: -1`.
88
+
89
+ One consequence to know about: that stacking context contains the app. If a host
90
+ renders its own UI as a **sibling** of the ThemeProvider, a Dialog or Toast
91
+ inside the provider can no longer paint above it whatever its z-index. Wrapping
92
+ the whole application, which is the normal arrangement, is unaffected.
93
+
94
+ ### 3. Saturation comes back
95
+
96
+ Glass amplifies the colour behind it; macOS vibrancy and Apple's navigation bar
97
+ both lift saturation to 180%, and `--kioku-theme-kasumi-frost-saturate` does the
98
+ same here. Mixing a tint toward `transparent` does the opposite.
99
+
100
+ ### The host draws the backdrop — sharp
101
+
102
+ Put something behind the provider and leave it unfiltered. The theme blurs it:
103
+
104
+ ```tsx
105
+ <div className="frost">
106
+ <div className="frost__backdrop" aria-hidden />
107
+ <ThemeProvider defaultThemeId={kasumiTheme.id} themes={kiokuThemes}>
108
+ {children}
109
+ </ThemeProvider>
110
+ </div>
111
+ ```
112
+
113
+ ```css
114
+ .frost {
115
+ /* So the backdrop's negative z-index stays inside it. */
116
+ isolation: isolate;
117
+ }
118
+
119
+ .frost__backdrop {
120
+ position: fixed;
121
+ inset: 0;
122
+ z-index: -1;
123
+ background: url('/wallpaper.avif') center / cover no-repeat;
124
+ }
125
+ ```
126
+
127
+ With nothing back there the filter is a no-op and Kasumi is simply a clean
128
+ near-white — or near-black — theme.
129
+
130
+ ### One lever
131
+
132
+ `--kioku-theme-kasumi-frost-keep` is the percent of its own tint the skin keeps:
133
+ `100%` is an ordinary opaque skin, `0%` is bare backdrop. It ships at `64%`. Set
134
+ it at the same specificity as the theme's own default and later in the cascade,
135
+ and yours wins:
136
+
137
+ ```css
138
+ [data-theme='kasumi'] {
139
+ --kioku-theme-kasumi-frost-keep: 52%;
140
+ --kioku-theme-kasumi-frost-blur: 26px;
141
+ --kioku-theme-kasumi-frost-saturate: 180%;
142
+ }
143
+
144
+ /* Reduced transparency is a host policy, not a theme default. This package's
145
+ stylesheet may only ever write a theme root, so it ships no media query —
146
+ a host that honours the preference pins the lever back itself. */
147
+ @media (prefers-reduced-transparency: reduce) {
148
+ [data-theme='kasumi'] {
149
+ --kioku-theme-kasumi-frost-keep: 100%;
150
+ }
151
+ }
152
+ ```
153
+
154
+ ### What thins and what does not
155
+
156
+ **Only the field thins.** `color.canvas` and `color.surfaceMuted` follow the
157
+ lever. Everything wearing `color.surface` or `color.surfaceRaised` — 47
158
+ components, every card, input, badge, tooltip and menu — clamps to
159
+ `max(84%, keep)` and is never thinner than the field beneath it. This is the
160
+ rule Hermes paid for: thinning nested surfaces stacks the tint once per layer,
161
+ so a deeply nested pane lands near-opaque while a shallow route reads clear, on
162
+ the same lever setting.
163
+
164
+ **Borders never thin.** The rim is drawn with alpha — dark in light mode, light
165
+ in dark mode — because an opaque line makes a thinned card read as a flat box
166
+ rather than a pane. But it does not follow the lever: whatever the host drew,
167
+ two regions still have to separate. `border.interactive` is the exception and
168
+ stays an exact colour, being the accent that carries focus and selected state.
169
+
170
+ **The grain stays.** It is what keeps a thinned fill reading as matte rather
171
+ than as a polished sheet, and it is the reason the mode is called matte
172
+ transparency rather than glass.
173
+
174
+ One thing to pass yourself: `Card` defaults to `elevation="none"`, which draws
175
+ the border and no shadow. Kasumi puts its specular edge — the bright inset
176
+ hairline that reads as a pane rather than a panel — in the elevation tokens, so
177
+ `<Card elevation="low">` is what shows it.
178
+
179
+ ### Contrast, honestly
180
+
181
+ Under frost the real background is whatever the host drew, which no theme can
182
+ know. So the guarantee is stated against the extremes — a backdrop of pure black
183
+ and one of pure white bracket anything you can put back there — and the two
184
+ load-bearing numbers are asserted by the test suite rather than claimed.
185
+
186
+ **On a raised surface, muted text is AA against any backdrop at all, at every
187
+ setting of the lever.** That is what the `max(84%, keep)` clamp buys: no more
188
+ than 16% of the backdrop ever reaches a card, and the worst case across both
189
+ modes and both extremes is 5.02:1. Lower the clamp, or lighten muted text, and
190
+ the test notices.
191
+
192
+ **On the bare field the guarantee has an edge**, the field being the one thing
193
+ that is not clamped. Measured against those same extremes:
194
+
195
+ | Text sitting on the bare field | AA against any backdrop down to |
196
+ | ------------------------------ | ------------------------------- |
197
+ | `color.text` | keep 52% light, 60% dark |
198
+ | `color.textSecondary` | keep 64% light, 70% dark |
199
+ | `color.textMuted` | keep 74% light, 79% dark |
200
+
201
+ At the shipping 64% body text is covered and muted text is not. Two things
202
+ follow, and they are the whole of the guidance:
203
+
204
+ 1. **Under frost, muted text belongs on a raised surface.** A caption or a
205
+ timestamp sitting directly on the canvas is the one combination the lever can
206
+ take below AA.
207
+ 2. **Match the backdrop's luminance to the mode.** The binding case in that
208
+ table is always the mismatched one — a light skin over a near-black backdrop.
209
+ Pair light with a light image and dark with a dark one and the real ratios
210
+ sit well above the floor.
211
+
212
+ [hermes]: https://github.com/NousResearch/hermes-agent
213
+ [radix]: https://www.radix-ui.com/themes/docs/theme/color
214
+
215
+ ## Fonts
216
+
217
+ These themes name font families in their typography tokens but ship no
218
+ `@font-face` of their own — delivering font files is a host application's
219
+ decision, not a theme pack's. **A host that does not load them silently gets
220
+ its system UI font instead**, which is not what any of these themes describe.
221
+
222
+ | Role | Family | Used by |
223
+ | ---------------------------------------- | ------------------------------- | ------------------- |
224
+ | Body, heading | `Zen Kaku Gothic New` | all four themes |
225
+ | Display | `Shippori Mincho` | Washi, Sumi, Kasumi |
226
+ | Display | `Zen Kaku Gothic New` | Muji |
227
+ | Mono | `IBM Plex Mono` | all four themes |
228
+ | Body, heading, display under `:lang(zh)` | `Noto Sans SC`, `Noto Serif SC` | all four themes |
229
+
230
+ Every stack falls back to a system family, so nothing breaks without them —
231
+ it simply stops looking like the theme. Load them however your application
232
+ already loads fonts; self-hosting is preferable to a third-party CDN if you
233
+ care about the request leaving your origin.
234
+
235
+ Request the weights `400`, `500`, and `700`. The themes ask for `600` as their
236
+ strong weight, but `Zen Kaku Gothic New` ships no 600 face, so the browser
237
+ resolves it to 700 — request 700 and it uses a real bold, omit it and it
238
+ synthesises one, which is visibly heavier and looser.
239
+
240
+ Chinese content resolves through `:lang(zh)`, so set the language on the
241
+ document or the subtree (`<html lang="zh">`) or the Latin families will be
242
+ asked to render glyphs they do not have.
package/dist/index.d.ts CHANGED
@@ -1 +1 @@
1
- export { kiokuThemes, mujiTheme, sumiTheme, washiTheme } from './themes.js';
1
+ export { kasumiTheme, kiokuThemes, mujiTheme, sumiTheme, washiTheme, } from './themes.js';
package/dist/index.js CHANGED
@@ -1 +1 @@
1
- export { kiokuThemes, mujiTheme, sumiTheme, washiTheme } from './themes.js';
1
+ export { kasumiTheme, kiokuThemes, mujiTheme, sumiTheme, washiTheme, } from './themes.js';
package/dist/theme.css CHANGED
@@ -1,7 +1,20 @@
1
1
  /* Ready-to-use theme CSS. Every selector targets a ThemeProvider data-theme root. */
2
2
  [data-theme='washi'],
3
3
  [data-theme='muji'],
4
- [data-theme='sumi'] {
4
+ [data-theme='sumi'],
5
+ [data-theme='kasumi'] {
6
+ /*
7
+ * Declared here, in CSS, rather than left to the host to set inline. Two
8
+ * reasons, and the second is the one that bites. `light-dark()` resolves
9
+ * against the element's used color-scheme, so without this a theme root
10
+ * follows whatever an ancestor happened to declare. And a bundler that
11
+ * lowers `light-dark()` for older targets — lightningcss does — only emits
12
+ * the toggle custom properties when it can see a `color-scheme` declaration
13
+ * in the stylesheet. An inline style is invisible to it, so it rewrote every
14
+ * colour to `var(--lightningcss-light, …)` and emitted no definition: 104
15
+ * dead declarations in a built site that rendered correctly in dev.
16
+ */
17
+ color-scheme: light dark;
5
18
  --kioku-ui-color-canvas: var(--kioku-theme-color-canvas);
6
19
  --kioku-ui-color-surface: var(--kioku-theme-color-surface);
7
20
  --kioku-ui-color-surface-raised: var(--kioku-theme-color-surface-raised);
@@ -18,6 +31,7 @@
18
31
  --kioku-ui-color-disabled-surface: var(--kioku-theme-color-disabled-surface);
19
32
  --kioku-ui-color-disabled-text: var(--kioku-theme-color-disabled-text);
20
33
  --kioku-ui-color-focus: var(--kioku-theme-color-focus);
34
+ --kioku-ui-color-scrim: var(--kioku-theme-color-scrim);
21
35
  --kioku-ui-border-default: var(--kioku-theme-border-default);
22
36
  --kioku-ui-border-strong: var(--kioku-theme-border-strong);
23
37
  --kioku-ui-border-interactive: var(--kioku-theme-border-interactive);
@@ -64,6 +78,24 @@
64
78
  --kioku-ui-typography-line-height-heading: var(
65
79
  --kioku-theme-line-height-heading
66
80
  );
81
+ --kioku-ui-typography-letter-spacing-title: var(
82
+ --kioku-theme-letter-spacing-title
83
+ );
84
+ --kioku-ui-typography-letter-spacing-heading: var(
85
+ --kioku-theme-letter-spacing-heading
86
+ );
87
+ --kioku-ui-typography-letter-spacing-body: var(
88
+ --kioku-theme-letter-spacing-body
89
+ );
90
+ --kioku-ui-typography-letter-spacing-label: var(
91
+ --kioku-theme-letter-spacing-label
92
+ );
93
+ --kioku-ui-typography-letter-spacing-eyebrow: var(
94
+ --kioku-theme-letter-spacing-eyebrow
95
+ );
96
+ --kioku-ui-typography-letter-spacing-mono: var(
97
+ --kioku-theme-letter-spacing-mono
98
+ );
67
99
  --kioku-ui-spacing-xs: var(--kioku-theme-spacing-xs);
68
100
  --kioku-ui-spacing-sm: var(--kioku-theme-spacing-sm);
69
101
  --kioku-ui-spacing-md: var(--kioku-theme-spacing-md);
@@ -117,6 +149,16 @@
117
149
  --kioku-theme-font-weight-strong: 600;
118
150
  --kioku-theme-line-height-body: 1.62;
119
151
  --kioku-theme-line-height-heading: 1.25;
152
+
153
+ /* Tracking runs inverse to size: the smaller the type, the further it is
154
+ opened. Mincho at 11px closes up into a smudge without it, and mono is
155
+ the one role that tightens because its figures are already too wide. */
156
+ --kioku-theme-letter-spacing-title: 0.01em;
157
+ --kioku-theme-letter-spacing-heading: 0.02em;
158
+ --kioku-theme-letter-spacing-body: 0;
159
+ --kioku-theme-letter-spacing-label: 0.04em;
160
+ --kioku-theme-letter-spacing-eyebrow: 0.1em;
161
+ --kioku-theme-letter-spacing-mono: -0.01em;
120
162
  --kioku-theme-radius-inner: 3px;
121
163
  --kioku-theme-radius-element: 3px;
122
164
  --kioku-theme-radius-container: 3px;
@@ -144,7 +186,8 @@
144
186
  /* Density is a matter of eyesight and screen size, so it stays a knob. */
145
187
  [data-theme='washi'][data-density='standard'],
146
188
  [data-theme='muji'][data-density='standard'],
147
- [data-theme='sumi'][data-density='standard'] {
189
+ [data-theme='sumi'][data-density='standard'],
190
+ [data-theme='kasumi'][data-density='standard'] {
148
191
  --kioku-theme-spacing-xs: 4px;
149
192
  --kioku-theme-spacing-sm: 8px;
150
193
  --kioku-theme-spacing-md: 13px;
@@ -157,7 +200,7 @@
157
200
  [data-theme='washi'] {
158
201
  --kioku-theme-color-canvas: light-dark(#efebe0, #191713);
159
202
  --kioku-theme-color-surface: light-dark(#f6f3e9, #211e19);
160
- --kioku-theme-color-surface-raised: light-dark(#f6f3e9, #282419);
203
+ --kioku-theme-color-surface-raised: light-dark(#f6f3e9, #38342e);
161
204
  --kioku-theme-color-surface-muted: light-dark(#e7e2d3, #2a2620);
162
205
  --kioku-theme-color-text: light-dark(#26221c, #ece6d9);
163
206
  --kioku-theme-color-text-secondary: light-dark(#5d574c, #a49c8c);
@@ -177,6 +220,7 @@
177
220
  --kioku-theme-color-disabled-surface: light-dark(#e2dccb, #151310);
178
221
  --kioku-theme-color-disabled-text: light-dark(#8a8375, #7d766a);
179
222
  --kioku-theme-color-focus: light-dark(#2f5d8a, #7ba6cf);
223
+ --kioku-theme-color-scrim: light-dark(rgb(38 34 28 / 34%), rgb(0 0 0 / 62%));
180
224
  --kioku-theme-border-default: light-dark(#ddd7c5, #302b24);
181
225
  --kioku-theme-border-strong: light-dark(#c8c0aa, #423b31);
182
226
  --kioku-theme-border-interactive: light-dark(#2f5d8a, #7ba6cf);
@@ -199,8 +243,13 @@
199
243
  --kioku-theme-elevation-medium:
200
244
  0 1px 0 light-dark(rgb(38 34 28 / 4%), rgb(0 0 0 / 22%)),
201
245
  0 0 0 1px light-dark(#ddd7c5, #302b24);
246
+ /* `high` is still the top of the ladder, but it says so with a heavier
247
+ hard line rather than a softer one: nothing in this system blurs. Its
248
+ five consumers are all modal surfaces, and a modal is separated from
249
+ the page by the scrim — the shadow only has to say how far off the
250
+ desk the sheet sits. 霞 is the documented exception; see its block. */
202
251
  --kioku-theme-elevation-high:
203
- 0 2px 5px light-dark(rgb(38 34 28 / 7%), rgb(0 0 0 / 34%)),
252
+ 0 2px 0 light-dark(rgb(38 34 28 / 7%), rgb(0 0 0 / 34%)),
204
253
  0 0 0 1px light-dark(#ddd7c5, #302b24);
205
254
  }
206
255
 
@@ -208,7 +257,7 @@
208
257
  [data-theme='muji'] {
209
258
  --kioku-theme-color-canvas: light-dark(#f2f2ef, #1a1c19);
210
259
  --kioku-theme-color-surface: light-dark(#f9f9f7, #222420);
211
- --kioku-theme-color-surface-raised: light-dark(#f9f9f7, #292c27);
260
+ --kioku-theme-color-surface-raised: light-dark(#f9f9f7, #393b35);
212
261
  --kioku-theme-color-surface-muted: light-dark(#eae4d8, #2b2d27);
213
262
  --kioku-theme-color-text: light-dark(#2f322c, #e7e8e2);
214
263
  --kioku-theme-color-text-secondary: light-dark(#64675d, #9b9e95);
@@ -228,6 +277,7 @@
228
277
  --kioku-theme-color-disabled-surface: light-dark(#e0d9ca, #141613);
229
278
  --kioku-theme-color-disabled-text: light-dark(#93968b, #75786f);
230
279
  --kioku-theme-color-focus: light-dark(#566b52, #9cb597);
280
+ --kioku-theme-color-scrim: light-dark(rgb(47 50 44 / 34%), rgb(0 0 0 / 62%));
231
281
  --kioku-theme-border-default: light-dark(#dcdcd4, #31342d);
232
282
  --kioku-theme-border-strong: light-dark(#c2c3b8, #43463d);
233
283
  --kioku-theme-border-interactive: light-dark(#566b52, #9cb597);
@@ -252,7 +302,7 @@
252
302
  0 1px 0 light-dark(rgb(47 50 44 / 4%), rgb(0 0 0 / 24%)),
253
303
  0 0 0 1px light-dark(#dcdcd4, #31342d);
254
304
  --kioku-theme-elevation-high:
255
- 0 2px 5px light-dark(rgb(47 50 44 / 7%), rgb(0 0 0 / 36%)),
305
+ 0 2px 0 light-dark(rgb(47 50 44 / 7%), rgb(0 0 0 / 36%)),
256
306
  0 0 0 1px light-dark(#dcdcd4, #31342d);
257
307
  }
258
308
 
@@ -260,7 +310,7 @@
260
310
  [data-theme='sumi'] {
261
311
  --kioku-theme-color-canvas: light-dark(#f5f5f3, #0f0f0e);
262
312
  --kioku-theme-color-surface: light-dark(#ffffff, #191918);
263
- --kioku-theme-color-surface-raised: light-dark(#ffffff, #212120);
313
+ --kioku-theme-color-surface-raised: light-dark(#ffffff, #30302e);
264
314
  --kioku-theme-color-surface-muted: light-dark(#edede9, #222220);
265
315
  --kioku-theme-color-text: light-dark(#131311, #f2f2ee);
266
316
  --kioku-theme-color-text-secondary: light-dark(#4a4a46, #a8a8a2);
@@ -280,6 +330,7 @@
280
330
  --kioku-theme-color-disabled-surface: light-dark(#e4e4e0, #090908);
281
331
  --kioku-theme-color-disabled-text: light-dark(#7c7c76, #7b7b75);
282
332
  --kioku-theme-color-focus: light-dark(#1d4d7c, #79aade);
333
+ --kioku-theme-color-scrim: light-dark(rgb(19 19 17 / 36%), rgb(0 0 0 / 66%));
283
334
  --kioku-theme-border-default: light-dark(#dcdcd8, #2a2a27);
284
335
  --kioku-theme-border-strong: light-dark(#b4b4ae, #3d3d39);
285
336
  --kioku-theme-border-interactive: light-dark(#1d4d7c, #79aade);
@@ -300,11 +351,197 @@
300
351
  0 1px 0 light-dark(rgb(19 19 17 / 6%), rgb(0 0 0 / 40%)),
301
352
  0 0 0 1px light-dark(#dcdcd8, #2a2a27);
302
353
  --kioku-theme-elevation-high:
303
- 0 2px 5px light-dark(rgb(19 19 17 / 10%), rgb(0 0 0 / 52%)),
354
+ 0 2px 0 light-dark(rgb(19 19 17 / 10%), rgb(0 0 0 / 52%)),
304
355
  0 0 0 1px light-dark(#dcdcd8, #2a2a27);
305
356
  }
306
357
 
307
358
  /* ThemeDefinition values resolve through a dictionary owned by each skin. */
359
+ /* 霞: mist, and the only skin that expects something behind it.
360
+
361
+ Ported from Hermes Desktop's glass mode by way of Apple's own recipe, and
362
+ the three things that separate glass from a coloured film are all here.
363
+
364
+ 1. THE TINT GOES TO AN EXTREME. Near-white in light, near-black in dark —
365
+ never the mid-tone a skin would otherwise use. A mid-tone film greys out
366
+ whatever is behind it; an extreme reads as light passing through. Solving
367
+ Radix's alpha equation for a panel that must composite to #f1f4f8 over
368
+ this canvas returns #fefeff at 50%, which is the same answer, and Apple's
369
+ navigation glass is rgba(0,0,0,0.8) rather than a dark grey. So the field
370
+ and the surfaces carry a near-neutral tint and the skin spends its whole
371
+ chromatic budget on the accent.
372
+
373
+ 2. THE BLUR IS REAL, and it lives on this root's ::before. A backdrop-filter
374
+ on the root ITSELF would make it the containing block for every
375
+ `position: fixed` descendant — Tooltip, Toast, ContextMenu, AppShell,
376
+ Popover, Overlay and MobileNav all position against the viewport. A
377
+ pseudo-element has no descendants, so it cannot be a containing block for
378
+ anything, and `isolation: isolate` keeps its negative z-index inside this
379
+ root instead of letting it fall behind the host's page. Isolation makes a
380
+ stacking context, which is not a containing block, so fixed positioning
381
+ is untouched.
382
+
383
+ 3. SATURATION COMES BACK. Glass amplifies the colour behind it; macOS
384
+ vibrancy and Apple's navigation bar both lift saturation to 180%. Mixing
385
+ a tint toward `transparent` does the opposite, which is what made an
386
+ earlier pass of this skin read as plastic.
387
+
388
+ ONE PAINTER, which is the rule Hermes paid for. Thinning nested field
389
+ surfaces stacks the tint once per layer, so a deep pane lands near-opaque
390
+ while a shallow route reads clear on the same setting. The field here is
391
+ `color.canvas` — three painters in the whole library (Layout, Box's canvas
392
+ variant, the global sheet) — plus `color.surfaceMuted` for the recessed
393
+ band. The 47 components wearing `color.surface` are raised content and clamp
394
+ above it.
395
+
396
+ THE LEVER is `--kioku-theme-kasumi-frost-keep`: the percent of its own tint
397
+ the skin keeps. 100% is an ordinary near-white skin, 0% is bare backdrop.
398
+ Raised surfaces clamp to max(84%, keep), which is where muted text still
399
+ holds AA over a backdrop of pure black or pure white.
400
+
401
+ Reduced transparency is a host policy: this sheet may only write a theme
402
+ root, so it ships no media query and a host that honours the preference pins
403
+ the lever back to 100% itself. */
404
+ [data-theme='kasumi'] {
405
+ /* A stacking context so ::before's negative z-index stays in this root.
406
+ Deliberately not a filter or a transform — those would capture fixed
407
+ descendants; isolation does not. */
408
+ isolation: isolate;
409
+ /* And a containing block, so the frost's `inset: 0` resolves against this
410
+ root rather than against whatever positioned ancestor happens to be above
411
+ it. Isolation governs paint order, not geometry: without this the frost
412
+ escaped the skin and blurred everything up to the nearest positioned
413
+ ancestor, and a page showing several skins at once stacked one 180%
414
+ saturation per swatch until the whole document went yellow. `relative`
415
+ is safe where `transform` and `filter` are not — it does not become the
416
+ containing block for a fixed descendant. */
417
+ position: relative;
418
+
419
+ --kioku-theme-kasumi-frost-keep: 64%;
420
+ --kioku-theme-kasumi-frost-raised: max(
421
+ 84%,
422
+ var(--kioku-theme-kasumi-frost-keep, 100%)
423
+ );
424
+ --kioku-theme-kasumi-frost-blur: 26px;
425
+ --kioku-theme-kasumi-frost-saturate: 180%;
426
+
427
+ --kioku-theme-color-canvas: color-mix(
428
+ in srgb,
429
+ light-dark(#fbfcfe, #0b0d12) var(--kioku-theme-kasumi-frost-keep, 100%),
430
+ transparent
431
+ );
432
+ --kioku-theme-color-surface: color-mix(
433
+ in srgb,
434
+ light-dark(#ffffff, #12151b) var(--kioku-theme-kasumi-frost-raised, 100%),
435
+ transparent
436
+ );
437
+ --kioku-theme-color-surface-raised: color-mix(
438
+ in srgb,
439
+ light-dark(#ffffff, #171a21) var(--kioku-theme-kasumi-frost-raised, 100%),
440
+ transparent
441
+ );
442
+ --kioku-theme-color-surface-muted: color-mix(
443
+ in srgb,
444
+ light-dark(#e9edf3, #06080b) var(--kioku-theme-kasumi-frost-keep, 100%),
445
+ transparent
446
+ );
447
+ --kioku-theme-color-text: light-dark(#161b22, #eef1f6);
448
+ --kioku-theme-color-text-secondary: light-dark(#2f3846, #c3cbd8);
449
+ --kioku-theme-color-text-muted: light-dark(#414b59, #a3adbc);
450
+ --kioku-theme-color-text-on-accent: light-dark(#ffffff, #10131a);
451
+ --kioku-theme-color-accent: light-dark(#4c4a86, #a99bdc);
452
+ --kioku-theme-color-accent-hover: light-dark(#3b3a6b, #c2b7e9);
453
+ --kioku-theme-color-accent-active: light-dark(#2e2d54, #d6cef3);
454
+ --kioku-theme-color-overlay-hover: light-dark(
455
+ rgb(22 27 34 / 6%),
456
+ rgb(238 241 246 / 8%)
457
+ );
458
+ --kioku-theme-color-overlay-active: light-dark(
459
+ rgb(22 27 34 / 11%),
460
+ rgb(238 241 246 / 13%)
461
+ );
462
+ --kioku-theme-color-disabled-surface: light-dark(#e4e8ee, #0d1014);
463
+ --kioku-theme-color-disabled-text: light-dark(#7c8695, #6e7887);
464
+ --kioku-theme-color-focus: light-dark(#4c4a86, #a99bdc);
465
+ /* Frosted glass already dims what is behind it, so the scrim only has to
466
+ separate the surface from the page rather than hide it: lighter than the
467
+ opaque skins in both modes. */
468
+ --kioku-theme-color-scrim: light-dark(rgb(22 27 34 / 28%), rgb(0 0 0 / 56%));
469
+ /* Structural borders are the rim, and on glass the rim is ALPHA — dark in
470
+ light mode, light in dark mode, both at low strength. An opaque line is
471
+ what makes a thinned card read as a flat box rather than a pane, which is
472
+ what an earlier pass of this skin got wrong; every glass UI worth copying
473
+ draws this edge with alpha instead. It stays legible over the field and
474
+ over the card alike because it straddles the two, and it never thins with
475
+ the lever, so a region always has an edge no matter what the host drew.
476
+
477
+ `interactive` is the exception and stays opaque: it is the accent, it
478
+ carries focus and selected state, and those must be the exact colour
479
+ rather than an approximation of it over an unknown backdrop. */
480
+ --kioku-theme-border-default: light-dark(
481
+ rgb(22 27 34 / 14%),
482
+ rgb(255 255 255 / 14%)
483
+ );
484
+ --kioku-theme-border-strong: light-dark(
485
+ rgb(22 27 34 / 28%),
486
+ rgb(255 255 255 / 26%)
487
+ );
488
+ --kioku-theme-border-interactive: light-dark(#4c4a86, #a99bdc);
489
+ --kioku-theme-border-disabled: light-dark(
490
+ rgb(22 27 34 / 7%),
491
+ rgb(255 255 255 / 7%)
492
+ );
493
+ --kioku-theme-status-info-surface: light-dark(#e2eaf6, #7ba6cf26);
494
+ --kioku-theme-status-info-text: light-dark(#28496e, #a3c4e2);
495
+ --kioku-theme-status-success-surface: light-dark(#e0ece5, #6fae9022);
496
+ --kioku-theme-status-success-text: light-dark(#39654c, #7fbf9d);
497
+ --kioku-theme-status-warning-surface: light-dark(#f4ecd9, #c9a45c22);
498
+ --kioku-theme-status-warning-text: light-dark(#775d13, #d8b46a);
499
+ --kioku-theme-status-danger-surface: light-dark(#f5e2e3, #cf737d22);
500
+ --kioku-theme-status-danger-text: light-dark(#973d45, #e08b93);
501
+ /* Matte, not gloss. The speckle is what stops a thinned fill reading as a
502
+ polished sheet, and it is the reason the mode is called matte
503
+ transparency rather than glass. */
504
+ --kioku-theme-texture-grain: light-dark(
505
+ rgb(22 27 34 / 3%),
506
+ rgb(238 241 246 / 5%)
507
+ );
508
+ --kioku-theme-font-family-display:
509
+ 'Shippori Mincho', 'Hiragino Mincho ProN', serif;
510
+ /* The specular edge: a bright inset hairline along the top, which is the
511
+ single strongest cue that a surface is a pane rather than a panel. The
512
+ outer ring stays a neutral so a card still has an edge on a host that
513
+ drew nothing behind the app. `high` is the one place this skin spends a
514
+ drop shadow — glass needs the lift, and the pack's hairline rule is
515
+ already carried by the ring in front of it. */
516
+ --kioku-theme-elevation-low:
517
+ inset 0 1px 0 light-dark(rgb(255 255 255 / 85%), rgb(255 255 255 / 12%)),
518
+ 0 0 0 1px light-dark(rgb(22 27 34 / 10%), rgb(255 255 255 / 10%));
519
+ --kioku-theme-elevation-medium:
520
+ inset 0 1px 0 light-dark(rgb(255 255 255 / 88%), rgb(255 255 255 / 14%)),
521
+ 0 0 0 1px light-dark(rgb(22 27 34 / 11%), rgb(255 255 255 / 11%)),
522
+ 0 1px 2px light-dark(rgb(22 27 34 / 6%), rgb(0 0 0 / 30%));
523
+ --kioku-theme-elevation-high:
524
+ inset 0 1px 0 light-dark(rgb(255 255 255 / 90%), rgb(255 255 255 / 16%)),
525
+ 0 0 0 1px light-dark(rgb(22 27 34 / 12%), rgb(255 255 255 / 12%)),
526
+ 0 8px 24px light-dark(rgb(22 27 34 / 14%), rgb(0 0 0 / 45%));
527
+ }
528
+
529
+ /* The frost itself. On the pseudo-element, never on the root — see (2) above.
530
+ The host draws whatever goes behind the app; this blurs it. With nothing
531
+ back there the filter is a no-op and the skin is simply a clean near-white
532
+ (or near-black) theme. */
533
+ [data-theme='kasumi']::before {
534
+ backdrop-filter: blur(var(--kioku-theme-kasumi-frost-blur, 26px))
535
+ saturate(var(--kioku-theme-kasumi-frost-saturate, 180%));
536
+ -webkit-backdrop-filter: blur(var(--kioku-theme-kasumi-frost-blur, 26px))
537
+ saturate(var(--kioku-theme-kasumi-frost-saturate, 180%));
538
+ content: '';
539
+ inset: 0;
540
+ pointer-events: none;
541
+ position: absolute;
542
+ z-index: -1;
543
+ }
544
+
308
545
  [data-theme='washi'] {
309
546
  --kioku-theme-washi-color-canvas: var(--kioku-theme-color-canvas);
310
547
  --kioku-theme-washi-color-surface: var(--kioku-theme-color-surface);
@@ -340,6 +577,7 @@
340
577
  --kioku-theme-color-disabled-text
341
578
  );
342
579
  --kioku-theme-washi-color-focus: var(--kioku-theme-color-focus);
580
+ --kioku-theme-washi-color-scrim: var(--kioku-theme-color-scrim);
343
581
  --kioku-theme-washi-border-default: var(--kioku-theme-border-default);
344
582
  --kioku-theme-washi-border-strong: var(--kioku-theme-border-strong);
345
583
  --kioku-theme-washi-border-interactive: var(--kioku-theme-border-interactive);
@@ -394,6 +632,24 @@
394
632
  --kioku-theme-washi-line-height-heading: var(
395
633
  --kioku-theme-line-height-heading
396
634
  );
635
+ --kioku-theme-washi-letter-spacing-title: var(
636
+ --kioku-theme-letter-spacing-title
637
+ );
638
+ --kioku-theme-washi-letter-spacing-heading: var(
639
+ --kioku-theme-letter-spacing-heading
640
+ );
641
+ --kioku-theme-washi-letter-spacing-body: var(
642
+ --kioku-theme-letter-spacing-body
643
+ );
644
+ --kioku-theme-washi-letter-spacing-label: var(
645
+ --kioku-theme-letter-spacing-label
646
+ );
647
+ --kioku-theme-washi-letter-spacing-eyebrow: var(
648
+ --kioku-theme-letter-spacing-eyebrow
649
+ );
650
+ --kioku-theme-washi-letter-spacing-mono: var(
651
+ --kioku-theme-letter-spacing-mono
652
+ );
397
653
  --kioku-theme-washi-spacing-xs: var(--kioku-theme-spacing-xs);
398
654
  --kioku-theme-washi-spacing-sm: var(--kioku-theme-spacing-sm);
399
655
  --kioku-theme-washi-spacing-md: var(--kioku-theme-spacing-md);
@@ -465,6 +721,7 @@
465
721
  --kioku-theme-color-disabled-text
466
722
  );
467
723
  --kioku-theme-muji-color-focus: var(--kioku-theme-color-focus);
724
+ --kioku-theme-muji-color-scrim: var(--kioku-theme-color-scrim);
468
725
  --kioku-theme-muji-border-default: var(--kioku-theme-border-default);
469
726
  --kioku-theme-muji-border-strong: var(--kioku-theme-border-strong);
470
727
  --kioku-theme-muji-border-interactive: var(--kioku-theme-border-interactive);
@@ -519,6 +776,24 @@
519
776
  --kioku-theme-muji-line-height-heading: var(
520
777
  --kioku-theme-line-height-heading
521
778
  );
779
+ --kioku-theme-muji-letter-spacing-title: var(
780
+ --kioku-theme-letter-spacing-title
781
+ );
782
+ --kioku-theme-muji-letter-spacing-heading: var(
783
+ --kioku-theme-letter-spacing-heading
784
+ );
785
+ --kioku-theme-muji-letter-spacing-body: var(
786
+ --kioku-theme-letter-spacing-body
787
+ );
788
+ --kioku-theme-muji-letter-spacing-label: var(
789
+ --kioku-theme-letter-spacing-label
790
+ );
791
+ --kioku-theme-muji-letter-spacing-eyebrow: var(
792
+ --kioku-theme-letter-spacing-eyebrow
793
+ );
794
+ --kioku-theme-muji-letter-spacing-mono: var(
795
+ --kioku-theme-letter-spacing-mono
796
+ );
522
797
  --kioku-theme-muji-spacing-xs: var(--kioku-theme-spacing-xs);
523
798
  --kioku-theme-muji-spacing-sm: var(--kioku-theme-spacing-sm);
524
799
  --kioku-theme-muji-spacing-md: var(--kioku-theme-spacing-md);
@@ -590,6 +865,7 @@
590
865
  --kioku-theme-color-disabled-text
591
866
  );
592
867
  --kioku-theme-sumi-color-focus: var(--kioku-theme-color-focus);
868
+ --kioku-theme-sumi-color-scrim: var(--kioku-theme-color-scrim);
593
869
  --kioku-theme-sumi-border-default: var(--kioku-theme-border-default);
594
870
  --kioku-theme-sumi-border-strong: var(--kioku-theme-border-strong);
595
871
  --kioku-theme-sumi-border-interactive: var(--kioku-theme-border-interactive);
@@ -644,6 +920,24 @@
644
920
  --kioku-theme-sumi-line-height-heading: var(
645
921
  --kioku-theme-line-height-heading
646
922
  );
923
+ --kioku-theme-sumi-letter-spacing-title: var(
924
+ --kioku-theme-letter-spacing-title
925
+ );
926
+ --kioku-theme-sumi-letter-spacing-heading: var(
927
+ --kioku-theme-letter-spacing-heading
928
+ );
929
+ --kioku-theme-sumi-letter-spacing-body: var(
930
+ --kioku-theme-letter-spacing-body
931
+ );
932
+ --kioku-theme-sumi-letter-spacing-label: var(
933
+ --kioku-theme-letter-spacing-label
934
+ );
935
+ --kioku-theme-sumi-letter-spacing-eyebrow: var(
936
+ --kioku-theme-letter-spacing-eyebrow
937
+ );
938
+ --kioku-theme-sumi-letter-spacing-mono: var(
939
+ --kioku-theme-letter-spacing-mono
940
+ );
647
941
  --kioku-theme-sumi-spacing-xs: var(--kioku-theme-spacing-xs);
648
942
  --kioku-theme-sumi-spacing-sm: var(--kioku-theme-spacing-sm);
649
943
  --kioku-theme-sumi-spacing-md: var(--kioku-theme-spacing-md);
@@ -680,9 +974,164 @@
680
974
  );
681
975
  }
682
976
 
977
+ [data-theme='kasumi'] {
978
+ --kioku-theme-kasumi-color-canvas: var(--kioku-theme-color-canvas);
979
+ --kioku-theme-kasumi-color-surface: var(--kioku-theme-color-surface);
980
+ --kioku-theme-kasumi-color-surface-raised: var(
981
+ --kioku-theme-color-surface-raised
982
+ );
983
+ --kioku-theme-kasumi-color-surface-muted: var(
984
+ --kioku-theme-color-surface-muted
985
+ );
986
+ --kioku-theme-kasumi-color-text: var(--kioku-theme-color-text);
987
+ --kioku-theme-kasumi-color-text-secondary: var(
988
+ --kioku-theme-color-text-secondary
989
+ );
990
+ --kioku-theme-kasumi-color-text-muted: var(--kioku-theme-color-text-muted);
991
+ --kioku-theme-kasumi-color-text-on-accent: var(
992
+ --kioku-theme-color-text-on-accent
993
+ );
994
+ --kioku-theme-kasumi-color-accent: var(--kioku-theme-color-accent);
995
+ --kioku-theme-kasumi-color-accent-hover: var(
996
+ --kioku-theme-color-accent-hover
997
+ );
998
+ --kioku-theme-kasumi-color-accent-active: var(
999
+ --kioku-theme-color-accent-active
1000
+ );
1001
+ --kioku-theme-kasumi-color-overlay-hover: var(
1002
+ --kioku-theme-color-overlay-hover
1003
+ );
1004
+ --kioku-theme-kasumi-color-overlay-active: var(
1005
+ --kioku-theme-color-overlay-active
1006
+ );
1007
+ --kioku-theme-kasumi-color-disabled-surface: var(
1008
+ --kioku-theme-color-disabled-surface
1009
+ );
1010
+ --kioku-theme-kasumi-color-disabled-text: var(
1011
+ --kioku-theme-color-disabled-text
1012
+ );
1013
+ --kioku-theme-kasumi-color-focus: var(--kioku-theme-color-focus);
1014
+ --kioku-theme-kasumi-color-scrim: var(--kioku-theme-color-scrim);
1015
+ --kioku-theme-kasumi-border-default: var(--kioku-theme-border-default);
1016
+ --kioku-theme-kasumi-border-strong: var(--kioku-theme-border-strong);
1017
+ --kioku-theme-kasumi-border-interactive: var(
1018
+ --kioku-theme-border-interactive
1019
+ );
1020
+ --kioku-theme-kasumi-border-disabled: var(--kioku-theme-border-disabled);
1021
+ --kioku-theme-kasumi-border-width: var(--kioku-theme-border-width);
1022
+ --kioku-theme-kasumi-border-style: var(--kioku-theme-border-style);
1023
+ --kioku-theme-kasumi-status-info-surface: var(
1024
+ --kioku-theme-status-info-surface
1025
+ );
1026
+ --kioku-theme-kasumi-status-info-text: var(--kioku-theme-status-info-text);
1027
+ --kioku-theme-kasumi-status-success-surface: var(
1028
+ --kioku-theme-status-success-surface
1029
+ );
1030
+ --kioku-theme-kasumi-status-success-text: var(
1031
+ --kioku-theme-status-success-text
1032
+ );
1033
+ --kioku-theme-kasumi-status-warning-surface: var(
1034
+ --kioku-theme-status-warning-surface
1035
+ );
1036
+ --kioku-theme-kasumi-status-warning-text: var(
1037
+ --kioku-theme-status-warning-text
1038
+ );
1039
+ --kioku-theme-kasumi-status-danger-surface: var(
1040
+ --kioku-theme-status-danger-surface
1041
+ );
1042
+ --kioku-theme-kasumi-status-danger-text: var(
1043
+ --kioku-theme-status-danger-text
1044
+ );
1045
+ --kioku-theme-kasumi-focus-width: var(--kioku-theme-focus-width);
1046
+ --kioku-theme-kasumi-focus-offset: var(--kioku-theme-focus-offset);
1047
+ --kioku-theme-kasumi-font-family-body: var(--kioku-theme-font-family-body);
1048
+ --kioku-theme-kasumi-font-family-heading: var(
1049
+ --kioku-theme-font-family-heading
1050
+ );
1051
+ --kioku-theme-kasumi-font-family-display: var(
1052
+ --kioku-theme-font-family-display
1053
+ );
1054
+ --kioku-theme-kasumi-font-family-mono: var(--kioku-theme-font-family-mono);
1055
+ --kioku-theme-kasumi-font-feature-settings: var(
1056
+ --kioku-theme-font-feature-settings
1057
+ );
1058
+ --kioku-theme-kasumi-font-size-xs: var(--kioku-theme-font-size-xs);
1059
+ --kioku-theme-kasumi-font-size-sm: var(--kioku-theme-font-size-sm);
1060
+ --kioku-theme-kasumi-font-size-md: var(--kioku-theme-font-size-md);
1061
+ --kioku-theme-kasumi-font-size-lg: var(--kioku-theme-font-size-lg);
1062
+ --kioku-theme-kasumi-font-size-xl: var(--kioku-theme-font-size-xl);
1063
+ --kioku-theme-kasumi-font-size-2xl: var(--kioku-theme-font-size-2xl);
1064
+ --kioku-theme-kasumi-font-weight-regular: var(
1065
+ --kioku-theme-font-weight-regular
1066
+ );
1067
+ --kioku-theme-kasumi-font-weight-medium: var(
1068
+ --kioku-theme-font-weight-medium
1069
+ );
1070
+ --kioku-theme-kasumi-font-weight-strong: var(
1071
+ --kioku-theme-font-weight-strong
1072
+ );
1073
+ --kioku-theme-kasumi-line-height-body: var(--kioku-theme-line-height-body);
1074
+ --kioku-theme-kasumi-line-height-heading: var(
1075
+ --kioku-theme-line-height-heading
1076
+ );
1077
+ --kioku-theme-kasumi-letter-spacing-title: var(
1078
+ --kioku-theme-letter-spacing-title
1079
+ );
1080
+ --kioku-theme-kasumi-letter-spacing-heading: var(
1081
+ --kioku-theme-letter-spacing-heading
1082
+ );
1083
+ --kioku-theme-kasumi-letter-spacing-body: var(
1084
+ --kioku-theme-letter-spacing-body
1085
+ );
1086
+ --kioku-theme-kasumi-letter-spacing-label: var(
1087
+ --kioku-theme-letter-spacing-label
1088
+ );
1089
+ --kioku-theme-kasumi-letter-spacing-eyebrow: var(
1090
+ --kioku-theme-letter-spacing-eyebrow
1091
+ );
1092
+ --kioku-theme-kasumi-letter-spacing-mono: var(
1093
+ --kioku-theme-letter-spacing-mono
1094
+ );
1095
+ --kioku-theme-kasumi-spacing-xs: var(--kioku-theme-spacing-xs);
1096
+ --kioku-theme-kasumi-spacing-sm: var(--kioku-theme-spacing-sm);
1097
+ --kioku-theme-kasumi-spacing-md: var(--kioku-theme-spacing-md);
1098
+ --kioku-theme-kasumi-spacing-lg: var(--kioku-theme-spacing-lg);
1099
+ --kioku-theme-kasumi-spacing-xl: var(--kioku-theme-spacing-xl);
1100
+ --kioku-theme-kasumi-spacing-2xl: var(--kioku-theme-spacing-2xl);
1101
+ --kioku-theme-kasumi-radius-inner: var(--kioku-theme-radius-inner);
1102
+ --kioku-theme-kasumi-radius-element: var(--kioku-theme-radius-element);
1103
+ --kioku-theme-kasumi-radius-container: var(--kioku-theme-radius-container);
1104
+ --kioku-theme-kasumi-radius-page: var(--kioku-theme-radius-page);
1105
+ --kioku-theme-kasumi-radius-full: var(--kioku-theme-radius-full);
1106
+ --kioku-theme-kasumi-size-control-sm: var(--kioku-theme-size-control-sm);
1107
+ --kioku-theme-kasumi-size-control-md: var(--kioku-theme-size-control-md);
1108
+ --kioku-theme-kasumi-size-control-lg: var(--kioku-theme-size-control-lg);
1109
+ --kioku-theme-kasumi-size-hit-target: var(--kioku-theme-size-hit-target);
1110
+ --kioku-theme-kasumi-elevation-low: var(--kioku-theme-elevation-low);
1111
+ --kioku-theme-kasumi-elevation-medium: var(--kioku-theme-elevation-medium);
1112
+ --kioku-theme-kasumi-elevation-high: var(--kioku-theme-elevation-high);
1113
+ --kioku-theme-kasumi-texture-grain: var(--kioku-theme-texture-grain);
1114
+ --kioku-theme-kasumi-motion-duration-fast: var(
1115
+ --kioku-theme-motion-duration-fast
1116
+ );
1117
+ --kioku-theme-kasumi-motion-duration-moderate: var(
1118
+ --kioku-theme-motion-duration-moderate
1119
+ );
1120
+ --kioku-theme-kasumi-motion-duration-slow: var(
1121
+ --kioku-theme-motion-duration-slow
1122
+ );
1123
+ --kioku-theme-kasumi-motion-easing-standard: var(
1124
+ --kioku-theme-motion-easing-standard
1125
+ );
1126
+ --kioku-theme-kasumi-motion-easing-emphasized: var(
1127
+ --kioku-theme-motion-easing-emphasized
1128
+ );
1129
+ }
1130
+
683
1131
  /* Chinese uses complete SC families inside each selected theme. */
684
1132
  [data-theme='washi']:lang(zh),
685
- [data-theme='sumi']:lang(zh) {
1133
+ [data-theme='sumi']:lang(zh),
1134
+ [data-theme='kasumi']:lang(zh) {
686
1135
  --kioku-theme-font-family-body:
687
1136
  'Noto Sans SC', 'PingFang SC', 'Hiragino Sans GB', sans-serif;
688
1137
  --kioku-theme-font-family-heading:
@@ -699,3 +1148,28 @@
699
1148
  --kioku-theme-font-family-display:
700
1149
  'Noto Sans SC', 'PingFang SC', 'Hiragino Sans GB', sans-serif;
701
1150
  }
1151
+
1152
+ /*
1153
+ * Chinese also needs more air between lines than Latin does. A Latin line is
1154
+ * opened by its own ascenders and descenders; hanzi are square, dense and full
1155
+ * height, so consecutive lines close up at the leading that suits English.
1156
+ * Leading is a property of the writing system rather than of the skin, so it
1157
+ * sits with the families rather than in each theme — and it reaches every page
1158
+ * through `lineHeightBody`, which is what `Text` already sets.
1159
+ */
1160
+ [data-theme='washi']:lang(zh),
1161
+ [data-theme='muji']:lang(zh),
1162
+ [data-theme='sumi']:lang(zh),
1163
+ [data-theme='kasumi']:lang(zh) {
1164
+ --kioku-theme-line-height-body: 1.8;
1165
+
1166
+ /* And more air between the characters, at both ranks. A han character is a
1167
+ square that already fills its em; set solid, a run of them closes into a
1168
+ grey block with no word boundaries for the eye to catch, because there are
1169
+ no spaces and no ascenders to break the line. Latin body text is set at 0
1170
+ for the same reason it does not need it — its own letterforms do the
1171
+ spacing. An 11px eyebrow needs the most of all: at that size the strokes
1172
+ of a dense character merge before the characters do. */
1173
+ --kioku-theme-letter-spacing-body: 0.03em;
1174
+ --kioku-theme-letter-spacing-eyebrow: 0.14em;
1175
+ }
package/dist/themes.d.ts CHANGED
@@ -2,4 +2,5 @@ import type { ThemeDefinition } from '@misoto22/kioku-ui/theme';
2
2
  export declare const washiTheme: ThemeDefinition;
3
3
  export declare const mujiTheme: ThemeDefinition;
4
4
  export declare const sumiTheme: ThemeDefinition;
5
+ export declare const kasumiTheme: ThemeDefinition;
5
6
  export declare const kiokuThemes: readonly ThemeDefinition[];
package/dist/themes.js CHANGED
@@ -17,6 +17,7 @@ function themeTokenReferences(id) {
17
17
  'color.disabledSurface': variable('color-disabled-surface'),
18
18
  'color.disabledText': variable('color-disabled-text'),
19
19
  'color.focus': variable('color-focus'),
20
+ 'color.scrim': variable('color-scrim'),
20
21
  'border.default': variable('border-default'),
21
22
  'border.strong': variable('border-strong'),
22
23
  'border.interactive': variable('border-interactive'),
@@ -49,6 +50,12 @@ function themeTokenReferences(id) {
49
50
  'typography.fontWeightStrong': variable('font-weight-strong'),
50
51
  'typography.lineHeightBody': variable('line-height-body'),
51
52
  'typography.lineHeightHeading': variable('line-height-heading'),
53
+ 'typography.letterSpacingTitle': variable('letter-spacing-title'),
54
+ 'typography.letterSpacingHeading': variable('letter-spacing-heading'),
55
+ 'typography.letterSpacingBody': variable('letter-spacing-body'),
56
+ 'typography.letterSpacingLabel': variable('letter-spacing-label'),
57
+ 'typography.letterSpacingEyebrow': variable('letter-spacing-eyebrow'),
58
+ 'typography.letterSpacingMono': variable('letter-spacing-mono'),
52
59
  'spacing.xs': variable('spacing-xs'),
53
60
  'spacing.sm': variable('spacing-sm'),
54
61
  'spacing.md': variable('spacing-md'),
@@ -85,8 +92,10 @@ function defineTheme(id, label) {
85
92
  export const washiTheme = defineTheme('washi', 'Washi');
86
93
  export const mujiTheme = defineTheme('muji', 'Muji');
87
94
  export const sumiTheme = defineTheme('sumi', 'Sumi');
95
+ export const kasumiTheme = defineTheme('kasumi', 'Kasumi');
88
96
  export const kiokuThemes = Object.freeze([
89
97
  washiTheme,
90
98
  mujiTheme,
91
99
  sumiTheme,
100
+ kasumiTheme,
92
101
  ]);
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@misoto22/kioku-ui-theme-kioku",
3
- "version": "0.0.0",
4
- "description": "Kioku Washi, Muji, and Sumi themes for Kioku UI.",
3
+ "version": "1.0.0",
4
+ "description": "Kioku Washi, Muji, Sumi, and Kasumi themes for Kioku UI.",
5
5
  "license": "MIT",
6
6
  "repository": {
7
7
  "type": "git",
@@ -33,12 +33,12 @@
33
33
  "./theme.css": "./dist/theme.css"
34
34
  },
35
35
  "peerDependencies": {
36
- "@misoto22/kioku-ui": ">=0.0.0 <0.2.0",
36
+ "@misoto22/kioku-ui": ">=0.2.0",
37
37
  "react": "^19.0.0",
38
38
  "react-dom": "^19.0.0"
39
39
  },
40
40
  "devDependencies": {
41
- "@misoto22/kioku-ui": "0.0.0"
41
+ "@misoto22/kioku-ui": "0.2.0"
42
42
  },
43
43
  "scripts": {
44
44
  "build": "tsc -p tsconfig.build.json && node build.mjs",