@iyulab/house-style 0.7.0 → 0.8.1

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/CHANGELOG.md CHANGED
@@ -1,5 +1,69 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.8.1] - 2026-10-17
4
+
5
+ ### Fixed
6
+
7
+ - A KPI figure (`u-info-field size="xl"`) is the house hero step (`--hs-text-hero-size`, 28px at the 24px display) —
8
+ 0.8.0 defined the step but nothing read it. Needs `@iyulab/modern-app` 0.56 or later (the optional peer floor moves
9
+ from 0.54 to 0.56).
10
+
11
+ ### Documentation
12
+
13
+ - The guide's KPI section shows the tile grid (`appearance="tile"`, `size="xl"`, a toned tile with a status icon, a
14
+ blank figure with its reason) next to the strip, and when to use which.
15
+
16
+ ## [0.8.0] - 2026-10-17
17
+
18
+ The palette moves to the application and the house gains presets; controls, rows and the type ladder grow a step.
19
+ The default look changes — see *Migrating*.
20
+
21
+ ### Added
22
+
23
+ - **Seed colours.** An application sets `--hs-accent` (the main action, the current item, links, focus) and
24
+ `--hs-secondary` (a second emphasis) on `:root`. Every step the components need is derived from the seed, in light
25
+ and dark: `--hs-accent-fill` (the seed, darkened only as far as white text needs), `--hs-accent-contrast`,
26
+ `--hs-accent-soft`, `--hs-accent-ink`, and the same for `--hs-secondary`. Each derived step is its own token, so an
27
+ application overrides only the step its seed defeats. The derivations use relative colour syntax and `color-mix()`.
28
+ - **Presets** — `<html hs-palette="…">`, each with a light and a dark side:
29
+ - `cool` (no attribute, the new default): cool-grey neutrals, the accent as the filled main action, a light sidebar
30
+ whose current item is the accent tint, the current tab in the accent.
31
+ - `warm`: the look before this release — warm-grey neutrals, an ink main action, a neutral current item, an ink tab.
32
+ - `contrast`: the `cool` screen with a dark sidebar; the sidebar re-scopes the neutral roles, so its links, groups,
33
+ section titles and any menu it opens read as one dark surface.
34
+ - `--u-secondary-*` role tokens (for `color="secondary"` — requires an `@iyulab/components` release with the
35
+ `secondary` role) and chart series slot two following the secondary seed.
36
+ - Tailwind entry: `accent` · `accent-soft` · `accent-ink` · `on-accent` (and `secondary-*`) colours, `rounded-pill`.
37
+
38
+ ### Changed
39
+
40
+ - Controls are 32 / 36 / 40 (were 28 / 32 / 40) — buttons and fields alike, still a function of `--u-density`; buttons
41
+ are semibold, md at the density size. Labelled fields stay 40.
42
+ - Control, field and menu-item corners are 8px (were 6px); cards stay 10px. Tags are pills.
43
+ - Table rows are 44px, compact 36px (were 40 / 32). Cells are at the body size (14px) with a 13px semibold header;
44
+ `.hs-table` keeps a 20px edge on its first and last cells.
45
+ - Type: display 24px (was 22px) with a 32px line, overline tracking 0.04em; the hero figure stays 28px (display + 4).
46
+ - Tabs (`u-tab-nav`, `u-tab-panel`): the current tab takes the preset's tab colour, 14px, 10px × 12px.
47
+ - Shell: a 240px sidebar of 38px rows (8px × 10px, 18px icons, 10px gap, 8px corners); main padding 20px 32px 48px;
48
+ 20px between page sections (`.hs-page`, `.hs-detail__cols`, `.hs-grid-cards`).
49
+ - Cool neutrals: canvas `#F5F6F8`, lines `#E0E0E0`, body text `#333232`, secondary text `#666666`; field edges stay
50
+ at 3:1 (`#858C96`).
51
+
52
+ ### Removed
53
+
54
+ - `--hs-brand`, `--hs-brand-soft`, `--hs-brand-ink` and the Tailwind `brand*` colours — use the seeds.
55
+
56
+ ### Migrating
57
+
58
+ - Brand tokens: `--hs-brand` → `--hs-accent` (one seed; drop `-soft`/`-ink` unless the derived step does not suit you,
59
+ then set `--hs-accent-soft` / `--hs-accent-ink`). Tailwind `bg-brand` → `bg-accent`, `bg-brand-soft` →
60
+ `bg-accent-soft`, `text-brand-ink` → `text-accent-ink`.
61
+ - To keep the previous colours: `<html hs-palette="warm">`. The size changes are not part of a preset; to keep the
62
+ previous sizes, set the tokens (`--u-radius-lg: 6px`, `--hs-row: 40px`, `--u-text-display-size: 22px`, …) and the
63
+ control padding on your own `:root` / `u-button`.
64
+ - Requires `@iyulab/components` 2.29.0 or later (peer range `^2.29.0`, was `^2.26.0`) — the house sets the
65
+ `secondary` role tokens, which components reads from 2.29.0.
66
+
3
67
  ## [0.7.0] - 2026-10-16
4
68
 
5
69
  ### Removed
package/README.md CHANGED
@@ -36,7 +36,7 @@ so its preflight would beat the theme; `tailwind.css` imports the theme into Tai
36
36
  |---|---|
37
37
  | `styles/index.css` (package root) | everything below plus Pretendard — for apps without Tailwind |
38
38
  | `styles/theme.css` | the theme without fonts (bring your own face) |
39
- | `styles/tokens.css` | house values for the `--u-*` token axes, light and dark; brand tokens `--hs-brand*` |
39
+ | `styles/tokens.css` | the presets (`cool` · `warm` · `contrast`, light and dark) for the `--u-*` token axes, the seeds `--hs-accent` · `--hs-secondary` and their derived steps, and the house scale |
40
40
  | `styles/components.css` | the document flow (body text, `h1`–`h6`, block gaps, code spans, native control and scrollbar colours) and sizes and finish for `u-button`, `u-card`, `u-drawer`, `flex-table`, the app shell |
41
41
  | `styles/patterns.css` | `hs-*` layout patterns — page head, saved views, filter bar, stat strip, table card, callout, form sections, side-sheet footer |
42
42
  | `styles/tailwind.css` | the Tailwind v4 entry: fonts + theme (in Tailwind's `components` layer) + role utilities — `bg-canvas`, `text-ink-weak`, `border-line`, `bg-warning-soft`, `rounded-card` … |
@@ -56,30 +56,46 @@ Requires `@iyulab/components` 2.0 or later (the release that put its defaults in
56
56
  Because the application is unlayered, a plain `:root { … }` rule overrides the theme — no
57
57
  specificity tricks, no load-order rules. Scope a mode-specific value with `:root[theme="dark"]`.
58
58
 
59
- ### Brand it
59
+ ### Palette — seeds from the app, balance from a preset
60
60
 
61
- The house is neutral on purpose. An application sets its identity colour with three tokens and
62
- keeps it out of buttons and warnings (a red brand next to a red "danger" reads as one colour):
61
+ The application decides its colours; the house decides how the screen is balanced around them.
62
+
63
+ **Seeds.** Two colours on `:root`. Every step the components need is derived from them — a fill that white text
64
+ can sit on, a soft tint (the current navigation item, a selected row), a readable ink (links, the current tab),
65
+ and the dark-mode steps:
63
66
 
64
67
  ```css
65
68
  :root {
66
- --hs-brand: #c8161d; /* logo mark */
67
- --hs-brand-soft: #fcebec; /* current navigation item */
68
- --hs-brand-ink: #a3121a; /* its text */
69
- }
70
- :root[theme="dark"] {
71
- --hs-brand-soft: #3a1a1c;
72
- --hs-brand-ink: #ff9a9e;
69
+ --hs-accent: #c8161d; /* identity: the main action, the current item, links, focus */
70
+ --hs-secondary: #0f766e; /* a second emphasis: color="secondary", the second chart series */
73
71
  }
74
72
  ```
75
73
 
76
- Everything else — warm-grey neutrals, the ink primary action, blue for links and focus, status
77
- tints, the 22/18/15/14/13/12/11 type ladder, 28/32/40 controls, 40px table rows — comes from the
78
- theme and can be overridden token by token.
74
+ A derivation cannot promise contrast for every seed (a pale yellow cannot carry white text). Each derived step is
75
+ its own token — `--hs-accent-fill` · `--hs-accent-contrast` · `--hs-accent-soft` · `--hs-accent-ink` (and the same
76
+ for `--hs-secondary`) — so an application overrides only the step its seed defeats. Set seeds on `:root`: a step
77
+ derived there does not follow a seed set further down the tree. Status colours (info · success · warning · danger)
78
+ never follow the seeds, so a red brand and «danger» stay two colours.
79
+
80
+ **Presets.** The balance of the whole screen — the temperature of the neutrals, the distance between canvas,
81
+ surface, lines and text, whether the main action is the accent or ink, and how dark the shell is. Each has a light
82
+ and a dark side (`theme="dark"`):
83
+
84
+ | `<html hs-palette="…">` | Neutrals | Main action | Sidebar | Current tab |
85
+ |---|---|---|---|---|
86
+ | *(none)* `cool` | cool grey | accent fill | light | accent |
87
+ | `warm` | warm grey | ink | light, neutral current item | ink |
88
+ | `contrast` | cool grey | accent fill | dark | accent |
89
+
90
+ One filled button per view under every preset — the rest are outlined, plain or in a menu.
91
+
92
+ **Fixed by the house** (token by token overridable): the 24/18/15/14/13/12/11 type ladder in Pretendard,
93
+ 32/36/40 controls, 8px controls and menu items, 10px cards, pill status tags, 44px table rows (36px compact),
94
+ a 240px sidebar of 38px rows.
79
95
 
80
96
  The type ladder is the `--u-text-*-size` steps plus two house steps derived from them —
81
97
  `--hs-text-dense-size` (between body and label: table cells, definition lists, callouts) and
82
- `--hs-text-hero-size` (display + 6px: a headline figure). Every pattern reads a step, so rescaling the
98
+ `--hs-text-hero-size` (display + 4px: a headline figure). Every pattern reads a step, so rescaling the
83
99
  ladder (a larger mode, a product system) carries the patterns along.
84
100
 
85
101
  ### Text outside components
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@iyulab/house-style",
3
- "version": "0.7.0",
3
+ "version": "0.8.1",
4
4
  "description": "The iyulab house theme: a CSS-only theme for @iyulab components — tokens, component recipes, line-of-business layout patterns and a Tailwind CSS v4 preset — plus the guide site that documents it.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -58,9 +58,9 @@
58
58
  "pretendard": "^1.3.9"
59
59
  },
60
60
  "peerDependencies": {
61
- "@iyulab/components": "^2.26.0",
61
+ "@iyulab/components": "^2.29.0",
62
62
  "@iyulab/flex-table": ">=0.41.0",
63
- "@iyulab/modern-app": ">=0.54.0"
63
+ "@iyulab/modern-app": ">=0.56.0"
64
64
  },
65
65
  "peerDependenciesMeta": {
66
66
  "@iyulab/flex-table": {
@@ -71,13 +71,13 @@
71
71
  }
72
72
  },
73
73
  "devDependencies": {
74
- "@iyulab/components": "^2.26.0",
74
+ "@iyulab/components": "^2.29.0",
75
75
  "@iyulab/data-components": "^0.51.0",
76
- "@iyulab/enterprise": "^0.34.1",
77
- "@iyulab/flex-table": "^0.67.0",
78
- "@iyulab/modern-app": "^0.54.0",
76
+ "@iyulab/enterprise": "^0.35.0",
77
+ "@iyulab/flex-table": "^0.68.0",
78
+ "@iyulab/modern-app": "^0.56.0",
79
79
  "@iyulab/router": "^0.24.0",
80
- "@iyulab/u-widgets": "^0.34.0",
80
+ "@iyulab/u-widgets": "^0.35.0",
81
81
  "@lit/react": "^1.0.8",
82
82
  "@types/node": "^26.1.1",
83
83
  "@types/react": "^19.3.0",
@@ -93,25 +93,28 @@
93
93
  color: var(--u-link-txt-color);
94
94
  }
95
95
 
96
- /* ── Button — 28 / 32 / 40, medium weight ─────────────────────────────── *
96
+ /* ── Button — 32 / 36 / 40, semibold ──────────────────────────────────── *
97
97
  * Height follows the components' own formula (1.5em line + 2 × padding + 2px border), so
98
98
  * these are padding values, not fixed heights: text and icon-only buttons stay equal.
99
- * The font sizes are terms of that height formula (the control ladder 28/32/40), not steps of
99
+ * The font sizes are terms of that height formula (the control ladder 32/36/40), not steps of
100
100
  * the text ladder — tying them to `--u-text-*` would change control heights whenever the text
101
101
  * ladder is rescaled. They are steps of the control density instead (`--u-density`, the
102
- * components' size axis): at the default 14px they are 12 / 13 / 14 and the heights 28/32/40;
102
+ * components' size axis): at the default 14px they are 12 / 14 / 14 and the heights 32/36/40;
103
103
  * a host that raises the density (a field or touch mode) grows all three together and in order.
104
- * Padding is in em for the same reason — the n/13, n/12, n/14 factors give the px values at the
104
+ * Padding is in em for the same reason — the n/12 and n/14 factors give the px values at the
105
105
  * default density exactly. (sm takes the components' own size: density × 6/7.) */
106
+ u-button {
107
+ font-weight: 600;
108
+ }
106
109
  u-button:not([size]),
107
110
  u-button[size='md'] {
108
- font-size: calc(var(--u-density, 14px) * 13 / 14);
109
- --btn-padding-block: calc(5.25em / 13); /* 13px: 19.5 + 10.5 + 2 = 32 */
110
- --btn-padding-inline: calc(12em / 13);
111
+ font-size: var(--u-density, 14px);
112
+ --btn-padding-block: calc(6.5em / 14); /* 14px: 21 + 13 + 2 = 36 */
113
+ --btn-padding-inline: calc(14em / 14);
111
114
  }
112
115
  u-button[size='sm'] {
113
- --btn-padding-block: calc(4em / 12); /* 12px: 18 + 8 + 2 = 28 */
114
- --btn-padding-inline: calc(8em / 12);
116
+ --btn-padding-block: calc(6em / 12); /* 12px: 18 + 12 + 2 = 32 */
117
+ --btn-padding-inline: calc(10em / 12);
115
118
  }
116
119
  u-button[size='lg'] {
117
120
  font-size: var(--u-density, 14px);
@@ -122,20 +125,21 @@ u-button[appearance='link'] {
122
125
  --btn-padding-inline: 0;
123
126
  }
124
127
 
125
- /* ── Text fields — the same three heights as buttons: 28 / 32 / 40.
128
+ /* ── Text fields — the same three heights as buttons: 32 / 36 / 40.
126
129
  * (Components 2.0 renders fields at 14px by default, so a field no longer grows with the
127
130
  * surrounding text; the house sets the box padding per size.) ── */
128
131
  :is(u-input, u-select, u-textarea, u-date-picker, u-date-range-picker)[size='sm'] {
129
- --u-field-padding-block: calc(4em / 12); /* 12px: 18 + 8 + 2 = 28 */
132
+ --u-field-padding-block: calc(6em / 12); /* 12px: 18 + 12 + 2 = 32 */
130
133
  }
131
134
  :is(u-input, u-select, u-textarea, u-date-picker, u-date-range-picker)[size='lg'] {
132
135
  font-size: var(--u-density, 14px); /* with the density, like md — the step is the padding */
133
136
  --u-field-padding-block: calc(8.5em / 14); /* 14px: 21 + 17 + 2 = 40 */
134
137
  }
135
138
 
136
- /* ── Tag — 22px labels (12px text · 1px block padding · hairline) to sit inside 40px rows.
139
+ /* ── Tag — 22px pills (12px text · 1px block padding · hairline) to sit inside 44px rows.
137
140
  * A status label is the default `soft` appearance (tint, no outline) with `dot`. ── */
138
141
  u-tag {
142
+ border-radius: var(--u-radius-pill, 9999px);
139
143
  --tag-padding-block: 1px;
140
144
  --tag-padding-inline: 8px;
141
145
  --tag-gap: 5px;
@@ -147,7 +151,7 @@ u-tag[size='sm'] {
147
151
  --tag-gap: 4px;
148
152
  }
149
153
 
150
- /* ── Field — a labelled field is a form row: 40px controls (the side-sheet and edit-form size; toolbars keep 32px
154
+ /* ── Field — a labelled field is a form row: 40px controls (the side-sheet and edit-form size; toolbars keep 36px
151
155
  * with unlabelled controls), the label at the label step in the weak colour, the help line in the weaker colour.
152
156
  * A control's own size wins (`size="sm"` / `"lg"` set the padding on the control itself). ── */
153
157
  u-field {
@@ -224,13 +228,21 @@ u-rich-table::part(selection-info) {
224
228
  color: var(--u-txt-color);
225
229
  }
226
230
 
227
- /* ── Tab strip of links (saved views) — ink, not blue: the current view is ink text with an ink underline,
228
- * at the dense text step. ── */
231
+ /* ── Tabs — the current tab is the preset's tab colour (the accent ink; ink under `warm`) with a 2px bar, at the
232
+ * body step, 10px × 12px; the others stay in the weak ink. The same for a strip of links (saved views) and a tab
233
+ * panel. ── */
229
234
  u-tab-nav {
230
- --tab-nav-color: var(--u-txt-color);
231
- font-size: var(--hs-text-dense-size, 13px);
235
+ --tab-nav-color: var(--hs-tab-current-color);
236
+ }
237
+ u-tab-panel {
238
+ --tab-panel-color: var(--hs-tab-current-color);
232
239
  }
233
- u-tab-nav u-tab[active] {
240
+ :is(u-tab-nav, u-tab-panel) u-tab {
241
+ font-size: var(--u-text-body-size, 14px);
242
+ --tab-padding-block: 10px;
243
+ --tab-padding-inline: 12px;
244
+ }
245
+ :is(u-tab-nav, u-tab-panel) u-tab[active] {
234
246
  font-weight: 600;
235
247
  }
236
248
 
@@ -282,15 +294,15 @@ u-drawer::part(body) {
282
294
  scrollbar-color: var(--u-border-color-strong) transparent;
283
295
  }
284
296
 
285
- /* ── Data table (`@iyulab/flex-table`) — 40px rows, 13px cells, quiet header,
297
+ /* ── Data table (`@iyulab/flex-table`) — 44px rows, body-size cells, a quiet 13px semibold header,
286
298
  * no zebra (hover and selection carry the row), tabular figures. ──────── */
287
299
  flex-table {
288
300
  --ft-font-family: var(--u-font-base);
289
- --ft-font-size: var(--hs-text-dense-size, 13px);
301
+ --ft-font-size: var(--u-text-body-size, 14px);
290
302
  --ft-row-height: var(--hs-row);
291
303
  --ft-cell-padding-inline: 12px;
292
- --ft-header-font-size: var(--u-text-label-size, 12px);
293
- --ft-header-font-weight: 500;
304
+ --ft-header-font-size: var(--hs-text-dense-size, 13px);
305
+ --ft-header-font-weight: 600;
294
306
  --ft-header-text-color: var(--u-txt-color-weak);
295
307
  --ft-header-bg: var(--u-bg-color-raised);
296
308
  --ft-row-odd-bg: var(--u-bg-color);
@@ -313,11 +325,39 @@ u-page-header::part(subtitle) {
313
325
  color: var(--u-txt-color-weak);
314
326
  }
315
327
 
328
+ /* ── Info field (`@iyulab/modern-app`) — a KPI figure (`size="xl"`) is the house hero step, 28 at the 24 display.
329
+ * A tile (`appearance="tile"`) is already a card surface — border, card corner, surface — from the shared tokens. ── */
330
+ u-info-field {
331
+ --info-field-xl-size: var(--hs-text-hero-size);
332
+ }
333
+
316
334
  /* ── Sidebar layout — the sidebar is a surface separated from the canvas by a hairline.
317
335
  * (The page already sits on `--u-canvas-bg-color`; padding is `--app-main-padding`.) ── */
318
336
  u-sidebar-layout::part(sidebar) {
319
337
  border-inline-end: 1px solid var(--u-border-color);
320
338
  }
339
+ /* The `contrast` preset's dark shell — the sidebar re-scopes the neutral roles to the shell values, so its links,
340
+ * groups, section titles and any menu it opens read as one dark surface (custom properties inherit through the
341
+ * shadow tree). The light presets leave the sidebar on the page's own roles. */
342
+ :root[hs-palette='contrast'] u-sidebar-layout::part(sidebar) {
343
+ --u-bg-color: var(--hs-shell-bg);
344
+ --u-panel-bg-color: var(--hs-shell-raised);
345
+ --u-input-bg-color: var(--hs-shell-raised);
346
+ --u-bg-color-raised: var(--hs-shell-raised);
347
+ --u-bg-color-hover: var(--hs-shell-hover);
348
+ --u-bg-color-active: var(--hs-shell-active);
349
+ --u-border-color: var(--hs-shell-line);
350
+ --u-border-color-weak: var(--hs-shell-line);
351
+ --u-txt-color: var(--hs-shell-ink);
352
+ --u-txt-color-hover: var(--hs-shell-ink);
353
+ --u-txt-color-active: var(--hs-shell-ink);
354
+ --u-txt-color-weak: var(--hs-shell-ink-weak);
355
+ --u-txt-color-weaker: var(--hs-shell-ink-weaker);
356
+ --u-icon-color: var(--hs-shell-ink-weak);
357
+ --u-icon-color-hover: var(--hs-shell-ink);
358
+ --u-icon-color-active: var(--hs-shell-ink);
359
+ border-inline-end-color: var(--hs-shell-line);
360
+ }
321
361
  @media print {
322
362
  u-sidebar-layout::part(main) {
323
363
  background: none;
@@ -15,7 +15,7 @@
15
15
  * edit = side sheet with .hs-fieldset sections and a pinned u-action-bar (summary slot)
16
16
  *
17
17
  * Rules the patterns encode:
18
- * · one ink (primary) button per view — the rest are outlined, plain or in a menu
18
+ * · one filled (main-action) button per view — the rest are outlined, plain or in a menu
19
19
  * · conditions fold, results open — the table starts in the top third of the screen
20
20
  * · numbers right-aligned in tabular figures; an empty value is an em dash
21
21
  * · row actions appear on hover or selection, never as a red trash can on every row
@@ -30,16 +30,17 @@
30
30
  .hs-page {
31
31
  display: flex;
32
32
  flex-direction: column;
33
- gap: var(--u-space-lg, 16px);
33
+ gap: var(--hs-section-gap, 20px);
34
34
  min-width: 0;
35
35
  }
36
36
 
37
37
 
38
- /* ── Plain table — for small server-rendered tables that do not need a grid ── */
38
+ /* ── Plain table — for small server-rendered tables that do not need a grid ──
39
+ * 44px body-size rows, a 13px semibold header; the first and last cells keep the card's 20px edge. */
39
40
  .hs-table {
40
41
  width: 100%;
41
42
  border-collapse: collapse;
42
- font-size: var(--hs-text-dense-size, 13px);
43
+ font-size: var(--u-text-body-size, 14px);
43
44
  line-height: 20px;
44
45
  color: var(--u-txt-color);
45
46
  }
@@ -47,31 +48,33 @@
47
48
  padding: var(--u-space-sm, 8px) var(--u-space-md, 12px);
48
49
  text-align: start;
49
50
  white-space: nowrap;
50
- font-size: var(--u-text-label-size, 12px);
51
- line-height: 16px;
52
- font-weight: 500;
51
+ font-size: var(--hs-text-dense-size, 13px);
52
+ line-height: 18px;
53
+ font-weight: 600;
53
54
  color: var(--u-txt-color-weak);
54
55
  background: var(--u-bg-color-raised);
55
56
  border-bottom: 1px solid var(--u-border-color);
56
57
  }
57
58
  .hs-table td {
58
- height: var(--hs-row, 40px);
59
+ height: var(--hs-row, 44px);
59
60
  padding: 0 var(--u-space-md, 12px);
60
61
  border-bottom: 1px solid var(--u-border-color);
61
62
  vertical-align: middle;
62
63
  }
64
+ .hs-table :is(th, td):first-child { padding-inline-start: var(--u-space-xl, 20px); }
65
+ .hs-table :is(th, td):last-child { padding-inline-end: var(--u-space-xl, 20px); }
63
66
  .hs-table tbody tr:last-child td { border-bottom: 0; }
64
67
  .hs-table tbody tr:hover td { background: var(--u-bg-color-hover); }
65
68
  .hs-table tr[aria-selected='true'] td { background: var(--u-primary-bg-color); }
66
69
  .hs-table tfoot td {
67
- height: var(--hs-row, 40px);
70
+ height: var(--hs-row, 44px);
68
71
  font-weight: 600;
69
72
  }
70
73
  /* One line separates the totals from the body — not one per totals row (subtotal · tax · total). */
71
74
  .hs-table tfoot tr:first-child td { border-top: 1px solid var(--u-border-color); }
72
75
  /* Totals rows follow the compact height too (`tfoot td` would otherwise win on specificity). */
73
76
  .hs-table--compact td,
74
- .hs-table--compact tfoot td { height: var(--hs-row-compact, 32px); }
77
+ .hs-table--compact tfoot td { height: var(--hs-row-compact, 36px); }
75
78
  .hs-table--top td { vertical-align: top; padding-block: var(--u-space-sm, 8px); height: auto; }
76
79
  /* Cells (`th` too — header text aligns with its values) */
77
80
  .hs-num,
@@ -97,7 +100,7 @@ a.hs-id:hover,
97
100
  .hs-table .hs-continued td.hs-keep { color: var(--u-txt-color); }
98
101
  /* A group header row with its subtotal. */
99
102
  .hs-table .hs-group td {
100
- height: 32px;
103
+ height: var(--hs-row-compact, 36px);
101
104
  background: var(--u-bg-color-raised);
102
105
  font-size: var(--u-text-label-size, 12px);
103
106
  font-weight: 600;
@@ -139,7 +142,7 @@ tr[aria-selected='true'] .hs-row-actions {
139
142
  .hs-detail__cols {
140
143
  display: flex;
141
144
  flex-wrap: wrap;
142
- gap: var(--u-space-lg, 16px);
145
+ gap: var(--hs-section-gap, 20px);
143
146
  align-items: flex-start;
144
147
  }
145
148
  .hs-detail__cols > * {
@@ -153,7 +156,7 @@ tr[aria-selected='true'] .hs-row-actions {
153
156
  .hs-grid-cards {
154
157
  display: grid;
155
158
  grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
156
- gap: var(--u-space-lg, 16px);
159
+ gap: var(--hs-section-gap, 20px);
157
160
  align-items: start;
158
161
  }
159
162
 
@@ -14,12 +14,13 @@
14
14
  * utilities (a utility on an element still wins), and still below your unlayered CSS.
15
15
  *
16
16
  * Also maps the house roles onto utilities that read the live `--u-*` tokens (`@theme inline`),
17
- * so dark mode and brand overrides reach Tailwind classes with no rebuild:
17
+ * so dark mode, the preset (`hs-palette`) and the seed colours reach Tailwind classes with no rebuild:
18
18
  *
19
19
  * bg-canvas · bg-surface · bg-raised · bg-selected · border-line · border-line-strong
20
20
  * text-ink · text-ink-weak · text-ink-weaker · text-link · ring-focus
21
21
  * {text,bg,border}-{info,success,warning,danger} (text = readable step) · bg-{role}-soft
22
- * rounded-control (6) · rounded-card (10) · shadow-raise · shadow-overlay · font-sans
22
+ * {bg,text}-accent · bg-accent-soft · text-accent-ink · text-on-accent (and the same for secondary)
23
+ * rounded-control (8) · rounded-card (10) · rounded-pill · shadow-raise · shadow-overlay · font-sans
23
24
  *
24
25
  * Existing markup full of `text-gray-*` / `bg-slate-*`: add `tailwind-neutrals.css` too.
25
26
  * ========================================================================== */
@@ -42,9 +43,14 @@
42
43
  --color-focus: var(--u-focus-ring-color);
43
44
  --color-action: var(--u-primary-color);
44
45
  --color-on-action: var(--u-primary-txt-color);
45
- --color-brand: var(--hs-brand);
46
- --color-brand-soft: var(--hs-brand-soft);
47
- --color-brand-ink: var(--hs-brand-ink);
46
+ --color-accent: var(--hs-accent-fill);
47
+ --color-accent-soft: var(--hs-accent-soft);
48
+ --color-accent-ink: var(--hs-accent-ink);
49
+ --color-on-accent: var(--hs-accent-contrast);
50
+ --color-secondary: var(--hs-secondary-fill);
51
+ --color-secondary-soft: var(--hs-secondary-soft);
52
+ --color-secondary-ink: var(--hs-secondary-ink);
53
+ --color-on-secondary: var(--hs-secondary-contrast);
48
54
 
49
55
  --color-info: var(--u-info-color-strong);
50
56
  --color-info-soft: var(--u-info-bg-color);
@@ -58,6 +64,7 @@
58
64
  --radius-control: var(--u-radius-lg);
59
65
  --radius-card: var(--u-radius-xl);
60
66
  --radius-badge: var(--u-radius-md);
67
+ --radius-pill: var(--u-radius-pill, 9999px);
61
68
 
62
69
  --shadow-raise: var(--u-shadow-sm);
63
70
  --shadow-overlay: var(--u-shadow-lg);
package/styles/tokens.css CHANGED
@@ -6,28 +6,303 @@
6
6
  *
7
7
  * built-in defaults (iyu.base) < this theme (iyu.house) < your application (unlayered)
8
8
  *
9
- * An application brands the house by overriding a handful of tokens with plain `:root` rules —
10
- * see "Brand" below. No specificity tricks are needed.
9
+ * Three layers of decision:
11
10
  *
12
- * Character — a quiet, dense line-of-business look:
13
- * · neutral first: warm greys carry ~90% of the screen; colour is for status and brand only
14
- * · one ink action per view (`--u-primary-*` is ink, not blue)
15
- * · blue is reserved for interaction: links, focus, selection
16
- * · depth by surface and a hairline, shadows only on things that float
17
- * · numbers first: tabular figures everywhere
11
+ * the application two seed colours — `--hs-accent` (identity, the main action, the current item, links,
12
+ * focus) and `--hs-secondary` (a second emphasis: the second chart series, a
13
+ * `color="secondary"` tag or button). Every step the components need (a fill, a soft tint,
14
+ * a readable ink, the dark-mode steps) is derived from the seed here.
15
+ * a preset the balance of the whole screen — the temperature of the neutrals, how far apart canvas,
16
+ * surface, lines and text sit, whether the main action is the accent or ink, and how dark
17
+ * the shell is. `<html hs-palette="…">`: `cool` (no attribute — the default) · `warm` ·
18
+ * `contrast`. Each preset has a light and a dark side (`theme="dark"`).
19
+ * the house shape, size and type (the scale block at the end) — the same under every preset.
18
20
  *
19
- * Contrast (light, against the surface #FFFFFF unless noted): body ink 17.4 · secondary 6.5 ·
20
- * tertiary 5.6 · link 6.2 · status text on its own tint ≥ 5.5 · input border 3.2 (WCAG 1.4.11).
21
- * The numbers are re-measured by `tests/contrast.test.mjs`.
21
+ * Status colours (info · success · warning · danger) belong to the preset, never to the seeds: a red brand
22
+ * must not make «danger» and «brand» one colour.
23
+ *
24
+ * The derivations use CSS relative colour syntax and `color-mix()` (Chromium 119 · Safari 16.4 · Firefox 128).
25
+ * They cannot promise contrast for every seed (a pale yellow cannot carry white text), so each derived step is
26
+ * its own token an application can set: `--hs-accent-fill` · `-contrast` · `-soft` · `-ink`. Set seeds on
27
+ * `:root` — a token derived on `:root` does not follow a seed set further down the tree.
28
+ *
29
+ * Contrast and perception floors for every preset × mode are re-measured by `tests/tokens.test.mjs`.
22
30
  * ========================================================================== */
23
31
 
24
32
  @layer iyu.base, iyu.house;
25
33
 
26
34
  @layer iyu.house {
27
35
 
28
- /* ── light (and no theme attribute) ─────────────────────────────────────── */
36
+ /* ── cool · light — the default (no `hs-palette`) ───────────────────────── */
29
37
  :root {
30
- /* Neutral ramp — warm grey. Most role tokens derive from it. */
38
+ /* Seeds — an application sets these (and, only if a seed defeats a derivation, the derived steps). */
39
+ --hs-accent: #1F5FBF;
40
+ --hs-secondary: #B55F40;
41
+
42
+ /* Derived steps, light */
43
+ --hs-accent-fill: oklch(from var(--hs-accent) min(l, 0.55) c h); /* the seed, darkened only as far as white text needs */
44
+ --hs-accent-contrast: #FFFFFF;
45
+ --hs-accent-soft: color-mix(in oklab, var(--hs-accent) 11%, var(--u-bg-color));
46
+ --hs-accent-ink: oklch(from var(--hs-accent) min(l, 0.5) c h);
47
+ --hs-secondary-fill: oklch(from var(--hs-secondary) min(l, 0.55) c h);
48
+ --hs-secondary-contrast: #FFFFFF;
49
+ --hs-secondary-soft: color-mix(in oklab, var(--hs-secondary) 12%, var(--u-bg-color));
50
+ --hs-secondary-ink: oklch(from var(--hs-secondary) min(l, 0.48) c h);
51
+
52
+ /* Neutral ramp — cool grey. Most role tokens derive from it. */
53
+ --u-neutral-0: #FFFFFF;
54
+ --u-neutral-50: #F5F6F8;
55
+ --u-neutral-100: #EEF0F3;
56
+ --u-neutral-200: #E6E8EC;
57
+ --u-neutral-300: #E0E0E0;
58
+ --u-neutral-400: #C9CDD3;
59
+ --u-neutral-500: #858C96;
60
+ --u-neutral-600: #6B6F75;
61
+ --u-neutral-700: #666666;
62
+ --u-neutral-800: #333232;
63
+ --u-neutral-900: #1F2023;
64
+ --u-neutral-1000: #111214;
65
+
66
+ /* Depth — canvas (page) < surface (cards, tables, controls) < floating (shadow) */
67
+ --u-canvas-bg-color: #F5F6F8;
68
+ --u-bg-color: #FFFFFF;
69
+ --u-panel-bg-color: #FFFFFF;
70
+ --u-input-bg-color: #FFFFFF;
71
+ --u-bg-color-raised: #F5F6F8; /* table header, toolbar strip, read-only field */
72
+ --u-bg-color-raised-hover: #E9EBEF;
73
+ --u-bg-color-hover: #EEF0F3;
74
+ --u-bg-color-active: #E6E8EC;
75
+ --u-bg-color-disabled: #F5F6F8;
76
+ --u-overlay-bg-color: rgba(17, 20, 24, 0.36);
77
+
78
+ /* Lines — a hairline for surfaces, a firmer line for the edge of a field (≥ 3:1, WCAG 1.4.11) */
79
+ --u-border-color: #E0E0E0;
80
+ --u-border-color-weak: #EAEBED;
81
+ --u-border-color-strong: #858C96;
82
+ --u-border-color-hover: #858C96;
83
+ --u-input-border-color: #858C96;
84
+ --u-input-border-color-hover: #5C6168;
85
+ --u-input-border-color-focus: var(--hs-accent-fill);
86
+
87
+ /* Ink */
88
+ --u-txt-color: #333232;
89
+ --u-txt-color-strong: #1F2023;
90
+ --u-txt-color-weak: #666666;
91
+ --u-txt-color-weaker: #6B6F75;
92
+ --u-txt-color-hover: #333232;
93
+ --u-txt-color-active: #333232;
94
+ --u-txt-color-disabled: #A6ABB2;
95
+ --u-icon-color: #666666;
96
+ --u-icon-color-hover: #333232;
97
+ --u-icon-color-active: #333232;
98
+
99
+ /* Action — the accent. One filled action per view; the rest are outlined. */
100
+ --u-primary-color-weakest: color-mix(in oklab, var(--hs-accent-fill) 16%, var(--u-bg-color));
101
+ --u-primary-color-weaker: color-mix(in oklab, var(--hs-accent-fill) 30%, var(--u-bg-color));
102
+ --u-primary-color-weak: color-mix(in oklab, var(--hs-accent-fill) 55%, var(--u-bg-color));
103
+ --u-primary-color: var(--hs-accent-fill);
104
+ --u-primary-color-strong: var(--hs-accent-ink);
105
+ --u-primary-txt-color: var(--hs-accent-contrast);
106
+ --u-primary-bg-color: var(--hs-accent-soft); /* selected rows, current item */
107
+
108
+ /* Second emphasis — the secondary seed (`color="secondary"`) */
109
+ --u-secondary-color-weakest: color-mix(in oklab, var(--hs-secondary-fill) 16%, var(--u-bg-color));
110
+ --u-secondary-color-weaker: color-mix(in oklab, var(--hs-secondary-fill) 30%, var(--u-bg-color));
111
+ --u-secondary-color-weak: color-mix(in oklab, var(--hs-secondary-fill) 55%, var(--u-bg-color));
112
+ --u-secondary-color: var(--hs-secondary-fill);
113
+ --u-secondary-color-strong: var(--hs-secondary-ink);
114
+ --u-secondary-txt-color: var(--hs-secondary-contrast);
115
+ --u-secondary-bg-color: var(--hs-secondary-soft);
116
+
117
+ /* Interaction — links, focus and the mark of a chosen option (a checked box or radio) follow the accent */
118
+ --u-link-txt-color: var(--hs-accent-ink);
119
+ --u-focus-ring-color: var(--hs-accent-fill);
120
+ --u-selection-color: var(--hs-accent-fill);
121
+ --u-selection-txt-color: var(--hs-accent-contrast); /* the check drawn on that fill */
122
+
123
+ /* Chart series — eight, in order (slot one first, never cycled). Slot one is the accent and slot two the
124
+ secondary, each held to the series lightness band; the other six are muted, warm-leaning hues chosen as a
125
+ set: OKLCH L 0.50–0.68, chroma ≥ 0.10, neighbours ΔE ≥ 15 for full colour vision and ≥ 8 under red–green
126
+ deficiency (Machado 2009), every slot at least ΔE 10 from a status colour — measured with the default seeds.
127
+ Slot six (light olive) is 2.8:1 on white — six or more series need a legend or labels. */
128
+ --u-chart-color-1: oklch(from var(--hs-accent) clamp(0.5, l, 0.66) c h);
129
+ --u-chart-color-2: oklch(from var(--hs-secondary) clamp(0.5, l, 0.66) c h);
130
+ --u-chart-color-3: #14938D;
131
+ --u-chart-color-4: #AF8020;
132
+ --u-chart-color-5: #7D4B92;
133
+ --u-chart-color-6: #86A645;
134
+ --u-chart-color-7: #C46E83;
135
+ --u-chart-color-8: #834B14;
136
+
137
+ /* Status — `-color-strong` is the text step (on surface and on its own tint), `-bg-color`
138
+ the tint, `-color` the solid fill (with `-txt-color` on top). */
139
+ --u-info-color-weakest: #D6E3FB;
140
+ --u-info-color-weaker: #A9C4F6;
141
+ --u-info-color-weak: #5B8DE8;
142
+ --u-info-color: #1F5FCC;
143
+ --u-info-color-strong: #1A55B8;
144
+ --u-info-bg-color: #E8F0FD;
145
+ --u-info-txt-color: #FFFFFF;
146
+
147
+ --u-success-color-weakest: #CDEBD6;
148
+ --u-success-color-weaker: #A3D8B4;
149
+ --u-success-color-weak: #4AA26A;
150
+ --u-success-color: #1A7F37;
151
+ --u-success-color-strong: #176F31;
152
+ --u-success-bg-color: #E6F4EA;
153
+ --u-success-txt-color: #FFFFFF;
154
+
155
+ --u-warning-color-weakest: #FBEBCF;
156
+ --u-warning-color-weaker: #F0D9B5;
157
+ --u-warning-color-weak: #E8B25A;
158
+ --u-warning-color: #E59A24;
159
+ --u-warning-color-strong: #A15C00;
160
+ --u-warning-bg-color: #FEF5E6;
161
+ --u-warning-txt-color: #1F2023;
162
+
163
+ --u-danger-color-weakest: #F9D4D0;
164
+ --u-danger-color-weaker: #F2AAA2;
165
+ --u-danger-color-weak: #DE6A5D;
166
+ --u-danger-color: #B42318;
167
+ --u-danger-color-strong: #B42318;
168
+ --u-danger-bg-color: #FDECEA;
169
+ --u-danger-txt-color: #FFFFFF;
170
+
171
+ /* Elevation — surfaces are flat (a hairline); one real shadow for things that float */
172
+ --u-shadow-color-weaker: rgba(16, 19, 24, 0.04);
173
+ --u-shadow-color-weak: rgba(16, 19, 24, 0.06);
174
+ --u-shadow-color-normal: rgba(16, 19, 24, 0.10);
175
+ --u-shadow-color-strong: rgba(16, 19, 24, 0.14);
176
+ --u-shadow-color-stronger: rgba(16, 19, 24, 0.18);
177
+ --u-shadow-sm: 0 1px 2px rgba(16, 19, 24, 0.06);
178
+ --u-shadow-md: 0 1px 2px rgba(16, 19, 24, 0.06);
179
+ --u-shadow-lg: 0 12px 32px rgba(16, 19, 24, 0.14), 0 2px 6px rgba(16, 19, 24, 0.06);
180
+ --u-shadow-xl: 0 16px 40px rgba(16, 19, 24, 0.18), 0 2px 8px rgba(16, 19, 24, 0.08);
181
+
182
+ /* Shell (`@iyulab/modern-app`) — a light sidebar on the surface; the current item is the accent tint */
183
+ --app-sidebar-bg: var(--u-bg-color);
184
+ --app-sidebar-fg: var(--u-txt-color);
185
+ --app-header-bg: var(--u-bg-color);
186
+ --app-header-fg: var(--u-txt-color);
187
+ --app-sidebar-active-bg: var(--hs-accent-soft);
188
+ --app-sidebar-active-fg: var(--hs-accent-ink);
189
+ --app-sidebar-group-active-fg: var(--hs-accent-ink);
190
+
191
+ /* The current tab — its text and its 2px bar */
192
+ --hs-tab-current-color: var(--hs-accent-ink);
193
+ }
194
+
195
+ /* ── cool · dark ─────────────────────────────────────────────────────────── */
196
+ :root[theme="dark"] {
197
+ --hs-accent-fill: oklch(from var(--hs-accent) max(l, 0.7) c h);
198
+ --hs-accent-contrast: #0E1013;
199
+ --hs-accent-soft: color-mix(in oklab, var(--hs-accent) 22%, var(--u-bg-color));
200
+ --hs-accent-ink: oklch(from var(--hs-accent) max(l, 0.8) c h);
201
+ --hs-secondary-fill: oklch(from var(--hs-secondary) max(l, 0.7) c h);
202
+ --hs-secondary-contrast: #0E1013;
203
+ --hs-secondary-soft: color-mix(in oklab, var(--hs-secondary) 24%, var(--u-bg-color));
204
+ --hs-secondary-ink: oklch(from var(--hs-secondary) max(l, 0.8) c h);
205
+
206
+ --u-neutral-0: #0E1013;
207
+ --u-neutral-50: #111418;
208
+ --u-neutral-100: #181B20;
209
+ --u-neutral-200: #1F2329;
210
+ --u-neutral-300: #2A2F36;
211
+ --u-neutral-400: #3D434C;
212
+ --u-neutral-500: #646B76;
213
+ --u-neutral-600: #8D939B;
214
+ --u-neutral-700: #A2A8B0;
215
+ --u-neutral-800: #C6CBD1;
216
+ --u-neutral-900: #E4E6E9;
217
+ --u-neutral-1000: #FFFFFF;
218
+
219
+ --u-canvas-bg-color: #111418;
220
+ --u-bg-color: #181B20;
221
+ --u-panel-bg-color: #181B20;
222
+ --u-input-bg-color: #181B20;
223
+ --u-bg-color-raised: #1F2329;
224
+ --u-bg-color-raised-hover: #282D34;
225
+ --u-bg-color-hover: #22262D;
226
+ --u-bg-color-active: #2A2F37;
227
+ --u-bg-color-disabled: #1C1F24;
228
+ --u-overlay-bg-color: rgba(0, 0, 0, 0.56);
229
+
230
+ --u-border-color: #2A2F36;
231
+ --u-border-color-weak: #23272D;
232
+ --u-border-color-strong: #646B76;
233
+ --u-border-color-hover: #646B76;
234
+ --u-input-border-color: #6B727D;
235
+ --u-input-border-color-hover: #A2A8B0;
236
+
237
+ --u-txt-color: #E4E6E9;
238
+ --u-txt-color-strong: #FFFFFF;
239
+ --u-txt-color-weak: #A2A8B0;
240
+ --u-txt-color-weaker: #8D939B;
241
+ --u-txt-color-hover: #E4E6E9;
242
+ --u-txt-color-active: #E4E6E9;
243
+ --u-txt-color-disabled: #575D66;
244
+ --u-icon-color: #A2A8B0;
245
+ --u-icon-color-hover: #E4E6E9;
246
+ --u-icon-color-active: #E4E6E9;
247
+
248
+ /* Chart series, dark — the same hues stepped into the dark band (L 0.52–0.66), every slot ≥ 3:1 on the
249
+ surface and the raised surface. */
250
+ --u-chart-color-1: oklch(from var(--hs-accent) clamp(0.6, l, 0.66) c h);
251
+ --u-chart-color-2: oklch(from var(--hs-secondary) clamp(0.6, l, 0.66) c h);
252
+ --u-chart-color-3: #04A19B;
253
+ --u-chart-color-4: #B8892D;
254
+ --u-chart-color-5: #9760AF;
255
+ --u-chart-color-6: #57833D;
256
+ --u-chart-color-7: #CF7188;
257
+ --u-chart-color-8: #9A5F1A;
258
+
259
+ --u-info-color-weakest: #1F3152;
260
+ --u-info-color-weaker: #2C4A7E;
261
+ --u-info-color-weak: #5E8BE0;
262
+ --u-info-color: #8DB3F7;
263
+ --u-info-color-strong: #8DB3F7;
264
+ --u-info-bg-color: #1B2A44;
265
+ --u-info-txt-color: #0E1013;
266
+
267
+ --u-success-color-weakest: #1A3826;
268
+ --u-success-color-weaker: #2B5A3E;
269
+ --u-success-color-weak: #4FB07D;
270
+ --u-success-color: #7DD8A5;
271
+ --u-success-color-strong: #7DD8A5;
272
+ --u-success-bg-color: #16301F;
273
+ --u-success-txt-color: #0E1013;
274
+
275
+ --u-warning-color-weakest: #3F2E12;
276
+ --u-warning-color-weaker: #5E451A;
277
+ --u-warning-color-weak: #C99140;
278
+ --u-warning-color: #F2B65A;
279
+ --u-warning-color-strong: #F2B65A;
280
+ --u-warning-bg-color: #3A2A10;
281
+ --u-warning-txt-color: #0E1013;
282
+
283
+ --u-danger-color-weakest: #4A1F1B;
284
+ --u-danger-color-weaker: #6E2C26;
285
+ --u-danger-color-weak: #E0675C;
286
+ --u-danger-color: #FF8A80;
287
+ --u-danger-color-strong: #FF8A80;
288
+ --u-danger-bg-color: #3D1A17;
289
+ --u-danger-txt-color: #1A0D0B;
290
+
291
+ --u-shadow-color-weaker: rgba(0, 0, 0, 0.24);
292
+ --u-shadow-color-weak: rgba(0, 0, 0, 0.32);
293
+ --u-shadow-color-normal: rgba(0, 0, 0, 0.40);
294
+ --u-shadow-color-strong: rgba(0, 0, 0, 0.50);
295
+ --u-shadow-color-stronger: rgba(0, 0, 0, 0.60);
296
+ --u-shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.40);
297
+ --u-shadow-md: 0 1px 2px rgba(0, 0, 0, 0.40);
298
+ --u-shadow-lg: 0 12px 32px rgba(0, 0, 0, 0.55), 0 2px 6px rgba(0, 0, 0, 0.40);
299
+ --u-shadow-xl: 0 16px 40px rgba(0, 0, 0, 0.60), 0 2px 8px rgba(0, 0, 0, 0.45);
300
+ }
301
+
302
+ /* ── warm · light — the house before 0.8: warm grey, an ink main action, the accent for interaction only ── */
303
+ :root[hs-palette="warm"] {
304
+ --hs-accent: #2F6FEB;
305
+
31
306
  --u-neutral-0: #FFFFFF;
32
307
  --u-neutral-50: #F6F6F4;
33
308
  --u-neutral-100: #F1F1EF;
@@ -41,28 +316,24 @@
41
316
  --u-neutral-900: #18191B;
42
317
  --u-neutral-1000: #0E0F10;
43
318
 
44
- /* Depth — canvas (page) < surface (cards, tables, controls) < floating (shadow) */
45
319
  --u-canvas-bg-color: #F6F6F4;
46
320
  --u-bg-color: #FFFFFF;
47
321
  --u-panel-bg-color: #FFFFFF;
48
322
  --u-input-bg-color: #FFFFFF;
49
- --u-bg-color-raised: #F1F1EF; /* table header, toolbar strip, read-only field */
323
+ --u-bg-color-raised: #F1F1EF;
50
324
  --u-bg-color-raised-hover: #E5E5E2;
51
325
  --u-bg-color-hover: #F1F1EF;
52
326
  --u-bg-color-active: #EAEAE7;
53
327
  --u-bg-color-disabled: #F1F1EF;
54
328
  --u-overlay-bg-color: rgba(24, 25, 27, 0.32);
55
329
 
56
- /* Lines */
57
330
  --u-border-color: #E3E3DF;
58
331
  --u-border-color-weak: #ECECE9;
59
332
  --u-border-color-strong: #8F9296;
60
333
  --u-border-color-hover: #8F9296;
61
334
  --u-input-border-color: #8F9296;
62
335
  --u-input-border-color-hover: #5B5E63;
63
- --u-input-border-color-focus: #2F6FEB;
64
336
 
65
- /* Ink */
66
337
  --u-txt-color: #18191B;
67
338
  --u-txt-color-strong: #0E0F10;
68
339
  --u-txt-color-weak: #5B5E63;
@@ -74,44 +345,13 @@
74
345
  --u-icon-color-hover: #18191B;
75
346
  --u-icon-color-active: #18191B;
76
347
 
77
- /* Action — ink. The primary button is near-black; selection stays blue. */
348
+ /* Action — ink. The primary button is near-black; selection stays the accent. */
78
349
  --u-primary-color-weakest: #E3E3DF;
79
350
  --u-primary-color-weaker: #C8C9C5;
80
351
  --u-primary-color-weak: #5B5E63;
81
352
  --u-primary-color: #18191B;
82
353
  --u-primary-color-strong: #18191B;
83
354
  --u-primary-txt-color: #FFFFFF;
84
- --u-primary-bg-color: #EEF3FD; /* selected rows, current item */
85
-
86
- /* Interaction — blue, for links, focus and the mark of a chosen option (a checked box or radio) */
87
- --u-link-txt-color: #1F5FCC;
88
- --u-focus-ring-color: #2F6FEB;
89
- --u-selection-color: #2F6FEB;
90
- --u-selection-txt-color: #FFFFFF; /* the check drawn on that fill */
91
-
92
- /* Chart series — eight, in order (slot one first, never cycled). Slot one is the interaction blue, so a
93
- one-series chart draws in the colour the rest of the screen uses for "this is the thing"; the other seven
94
- are muted, warm-leaning hues chosen as a set: OKLCH L 0.50–0.68, chroma ≥ 0.10, neighbours ΔE ≥ 15 for
95
- full colour vision and ≥ 8 under red–green deficiency (Machado 2009), every slot at least ΔE 10 from a
96
- status colour. Slot six (light olive) is 2.8:1 on white — six or more series need a legend or labels. */
97
- --u-chart-color-1: #2F6FEB;
98
- --u-chart-color-2: #B55F40;
99
- --u-chart-color-3: #14938D;
100
- --u-chart-color-4: #AF8020;
101
- --u-chart-color-5: #7D4B92;
102
- --u-chart-color-6: #86A645;
103
- --u-chart-color-7: #C46E83;
104
- --u-chart-color-8: #834B14;
105
-
106
- /* Status — `-color-strong` is the text step (on surface and on its own tint), `-bg-color`
107
- the tint, `-color` the solid fill (with `-txt-color` on top). */
108
- --u-info-color-weakest: #D6E3FB;
109
- --u-info-color-weaker: #A9C4F6;
110
- --u-info-color-weak: #5B8DE8;
111
- --u-info-color: #1F5FCC;
112
- --u-info-color-strong: #1A55B8;
113
- --u-info-bg-color: #E8F0FD;
114
- --u-info-txt-color: #FFFFFF;
115
355
 
116
356
  --u-success-color-weakest: #CDEBD9;
117
357
  --u-success-color-weaker: #A3D8BA;
@@ -119,54 +359,28 @@
119
359
  --u-success-color: #1B7A44;
120
360
  --u-success-color-strong: #1B6E3E;
121
361
  --u-success-bg-color: #E6F4EC;
122
- --u-success-txt-color: #FFFFFF;
123
362
 
124
- --u-warning-color-weakest: #FBEBCF;
125
363
  --u-warning-color-weaker: #F5D9A3;
126
- --u-warning-color-weak: #E8B25A;
127
- --u-warning-color: #E59A24;
128
364
  --u-warning-color-strong: #8A5100;
129
365
  --u-warning-bg-color: #FDF3E1;
130
366
  --u-warning-txt-color: #18191B;
131
367
 
132
- --u-danger-color-weakest: #F9D4D0;
133
- --u-danger-color-weaker: #F2AAA2;
134
- --u-danger-color-weak: #DE6A5D;
135
- --u-danger-color: #B42318;
136
- --u-danger-color-strong: #B42318;
137
- --u-danger-bg-color: #FDECEA;
138
- --u-danger-txt-color: #FFFFFF;
139
-
140
- /* Elevation — a hairline lift for surfaces, one real shadow for things that float */
141
368
  --u-shadow-color-weaker: rgba(16, 17, 19, 0.04);
142
369
  --u-shadow-color-weak: rgba(16, 17, 19, 0.06);
143
370
  --u-shadow-color-normal: rgba(16, 17, 19, 0.10);
144
371
  --u-shadow-color-strong: rgba(16, 17, 19, 0.14);
145
372
  --u-shadow-color-stronger: rgba(16, 17, 19, 0.18);
146
- --u-shadow-sm: 0 1px 2px rgba(16, 17, 19, 0.06);
147
- --u-shadow-md: 0 1px 2px rgba(16, 17, 19, 0.06);
148
- --u-shadow-lg: 0 12px 32px rgba(16, 17, 19, 0.14), 0 2px 6px rgba(16, 17, 19, 0.06);
149
- --u-shadow-xl: 0 16px 40px rgba(16, 17, 19, 0.18), 0 2px 8px rgba(16, 17, 19, 0.08);
150
-
151
- /* Shell (`@iyulab/modern-app`) */
152
- --app-sidebar-bg: var(--u-bg-color);
153
- --app-sidebar-fg: var(--u-txt-color-weak);
154
- --app-header-bg: var(--u-bg-color);
155
- --app-header-fg: var(--u-txt-color);
156
- --app-sidebar-width: 232px;
157
- --app-sidebar-active-bg: var(--hs-brand-soft);
158
- --app-sidebar-active-fg: var(--hs-brand-ink);
159
- --app-sidebar-group-active-fg: var(--hs-brand-ink);
160
-
161
- /* Brand — the application's identity colour. The house default is neutral, so an
162
- unbranded app looks finished; an application sets these three and nothing else. */
163
- --hs-brand: #18191B;
164
- --hs-brand-soft: #EAEAE7;
165
- --hs-brand-ink: #18191B;
373
+
374
+ /* The shell keeps the neutral current item; the current tab is ink */
375
+ --app-sidebar-fg: var(--u-txt-color-weak);
376
+ --app-sidebar-active-bg: var(--u-bg-color-active);
377
+ --app-sidebar-active-fg: var(--u-txt-color);
378
+ --app-sidebar-group-active-fg: var(--u-txt-color);
379
+ --hs-tab-current-color: var(--u-txt-color);
166
380
  }
167
381
 
168
- /* ── dark ───────────────────────────────────────────────────────────────── */
169
- :root[theme="dark"] {
382
+ /* ── warm · dark ─────────────────────────────────────────────────────────── */
383
+ :root[hs-palette="warm"][theme="dark"] {
170
384
  --u-neutral-0: #0E0F10;
171
385
  --u-neutral-50: #121314;
172
386
  --u-neutral-100: #1A1B1D;
@@ -197,7 +411,6 @@
197
411
  --u-border-color-hover: #6A6D72;
198
412
  --u-input-border-color: #6A6D72;
199
413
  --u-input-border-color-hover: #A3A6AB;
200
- --u-input-border-color-focus: #6E9CF7;
201
414
 
202
415
  --u-txt-color: #EDEDEC;
203
416
  --u-txt-color-strong: #FFFFFF;
@@ -216,32 +429,6 @@
216
429
  --u-primary-color: #EDEDEC;
217
430
  --u-primary-color-strong: #EDEDEC;
218
431
  --u-primary-txt-color: #18191B;
219
- --u-primary-bg-color: #1C2638;
220
-
221
- --u-link-txt-color: #8DB3F7;
222
- --u-focus-ring-color: #6E9CF7;
223
- --u-selection-color: #6E9CF7;
224
- --u-selection-txt-color: #0E0F10;
225
-
226
- /* Chart series, dark — the same hues stepped into the dark band (L 0.52–0.66), every slot ≥ 3:1 on the
227
- surface and the raised surface. Slot one is the dark interaction blue taken to the band's top (#6E9CF7
228
- itself is lighter than a filled mark should be on this surface). */
229
- --u-chart-color-1: #628FEA;
230
- --u-chart-color-2: #C97152;
231
- --u-chart-color-3: #04A19B;
232
- --u-chart-color-4: #B8892D;
233
- --u-chart-color-5: #9760AF;
234
- --u-chart-color-6: #57833D;
235
- --u-chart-color-7: #CF7188;
236
- --u-chart-color-8: #9A5F1A;
237
-
238
- --u-info-color-weakest: #1F3152;
239
- --u-info-color-weaker: #2C4A7E;
240
- --u-info-color-weak: #5E8BE0;
241
- --u-info-color: #8DB3F7;
242
- --u-info-color-strong: #8DB3F7;
243
- --u-info-bg-color: #1B2A44;
244
- --u-info-txt-color: #0E0F10;
245
432
 
246
433
  --u-success-color-weakest: #1A3826;
247
434
  --u-success-color-weaker: #2B5A3E;
@@ -249,58 +436,74 @@
249
436
  --u-success-color: #7DD8A5;
250
437
  --u-success-color-strong: #7DD8A5;
251
438
  --u-success-bg-color: #16301F;
252
- --u-success-txt-color: #0E0F10;
253
439
 
254
- --u-warning-color-weakest: #3F2E12;
255
440
  --u-warning-color-weaker: #5E451A;
256
- --u-warning-color-weak: #C99140;
257
- --u-warning-color: #F2B65A;
258
441
  --u-warning-color-strong: #F2B65A;
259
442
  --u-warning-bg-color: #3A2A10;
260
443
  --u-warning-txt-color: #0E0F10;
261
444
 
262
- --u-danger-color-weakest: #4A1F1B;
263
- --u-danger-color-weaker: #6E2C26;
264
- --u-danger-color-weak: #E0675C;
265
- --u-danger-color: #FF8A80;
266
- --u-danger-color-strong: #FF8A80;
267
- --u-danger-bg-color: #3D1A17;
268
- --u-danger-txt-color: #1A0D0B;
269
-
270
445
  --u-shadow-color-weaker: rgba(0, 0, 0, 0.24);
271
446
  --u-shadow-color-weak: rgba(0, 0, 0, 0.32);
272
447
  --u-shadow-color-normal: rgba(0, 0, 0, 0.40);
273
448
  --u-shadow-color-strong: rgba(0, 0, 0, 0.50);
274
449
  --u-shadow-color-stronger: rgba(0, 0, 0, 0.60);
275
- --u-shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.40);
276
- --u-shadow-md: 0 1px 2px rgba(0, 0, 0, 0.40);
277
- --u-shadow-lg: 0 12px 32px rgba(0, 0, 0, 0.55), 0 2px 6px rgba(0, 0, 0, 0.40);
278
- --u-shadow-xl: 0 16px 40px rgba(0, 0, 0, 0.60), 0 2px 8px rgba(0, 0, 0, 0.45);
450
+ }
451
+
452
+ /* ── contrast · light — the cool screen with a dark shell ───────────────── *
453
+ * The sidebar re-scopes the neutral roles to these shell values (components.css), so everything drawn inside
454
+ * it — links, groups, a menu it opens — reads as one dark surface. */
455
+ :root[hs-palette="contrast"] {
456
+ --hs-shell-bg: #1F2937;
457
+ --hs-shell-raised: #273244;
458
+ --hs-shell-hover: #2B3647;
459
+ --hs-shell-active: #334155;
460
+ --hs-shell-line: #334155;
461
+ --hs-shell-ink: #E5E7EB;
462
+ --hs-shell-ink-weak: #A9B1BD;
463
+ --hs-shell-ink-weaker: #9AA3AF;
464
+ --hs-shell-current-bg: color-mix(in oklab, var(--hs-accent) 42%, var(--hs-shell-bg));
465
+ --hs-shell-current-ink: #FFFFFF;
466
+
467
+ --app-sidebar-bg: var(--hs-shell-bg);
468
+ --app-sidebar-fg: var(--hs-shell-ink);
469
+ --app-sidebar-active-bg: var(--hs-shell-current-bg);
470
+ --app-sidebar-active-fg: var(--hs-shell-current-ink);
471
+ --app-sidebar-group-active-fg: var(--hs-shell-current-ink);
472
+ }
279
473
 
280
- --hs-brand: #EDEDEC;
281
- --hs-brand-soft: #2A2B2E;
282
- --hs-brand-ink: #EDEDEC;
474
+ /* ── contrast · dark ─────────────────────────────────────────────────────── */
475
+ :root[hs-palette="contrast"][theme="dark"] {
476
+ --hs-shell-bg: #0B0F14;
477
+ --hs-shell-raised: #131A22;
478
+ --hs-shell-hover: #18202A;
479
+ --hs-shell-active: #212B37;
480
+ --hs-shell-line: #1E2630;
481
+ --hs-shell-ink: #E4E6E9;
482
+ --hs-shell-ink-weak: #A2A8B0;
483
+ --hs-shell-ink-weaker: #8D939B;
484
+ --hs-shell-current-ink: #FFFFFF;
283
485
  }
284
486
 
285
- /* ── mode-independent scale: shape, type, spacing ───────────────────────── */
487
+ /* ── mode-independent scale: shape, size, type ──────────────────────────── */
286
488
  :root {
287
- /* Shape — badge 4 · control 6 · card and table 10 */
489
+ /* Shape — mark 4 · control and menu item 8 · card and table 10 · status pill */
288
490
  --u-radius-sm: 3px;
289
491
  --u-radius-md: 4px;
290
- --u-radius-lg: 6px;
492
+ --u-radius-lg: 8px;
291
493
  --u-radius-xl: 10px;
292
494
  --u-radius-2xl: 12px;
293
495
  --u-radius-3xl: 16px;
294
496
 
295
- /* Type — Pretendard; one ladder 22 / 18 / 15 / 14 / 13 / 12 / 11. Korean gets no positive
296
- tracking, so only the largest step is tightened and the overline stays near zero. */
497
+ /* Type — Pretendard; one ladder 24 / 18 / 15 / 14 / 13 / 12 / 11. Korean gets no positive
498
+ tracking in running text, so only the largest step is tightened; the overline (a group label) is
499
+ spaced a little. */
297
500
  --u-font-base: 'Pretendard Variable', Pretendard, -apple-system, BlinkMacSystemFont, 'Apple SD Gothic Neo',
298
501
  'Noto Sans KR', 'Malgun Gothic', 'Segoe UI', system-ui, sans-serif;
299
502
  --u-font-mono: 'JetBrains Mono', D2Coding, ui-monospace, 'Cascadia Code', Menlo, Consolas, monospace;
300
503
 
301
- --u-text-display-size: 22px;
504
+ --u-text-display-size: 24px;
302
505
  --u-text-display-weight: 700;
303
- --u-text-display-leading: 1.364;
506
+ --u-text-display-leading: 1.333;
304
507
  --u-text-display-tracking: -0.01em;
305
508
  --u-text-title-size: 18px;
306
509
  --u-text-title-weight: 700;
@@ -320,42 +523,44 @@
320
523
  --u-text-label-tracking: 0;
321
524
  --u-text-caption-size: 12px;
322
525
  --u-text-caption-weight: 400;
323
- --u-text-caption-leading: 1.5;
526
+ --u-text-caption-leading: 1.333;
324
527
  --u-text-caption-tracking: 0;
325
528
  --u-text-overline-size: 11px;
326
529
  --u-text-overline-weight: 600;
327
- --u-text-overline-leading: 1.455;
328
- --u-text-overline-tracking: 0.02em;
530
+ --u-text-overline-leading: 1.273;
531
+ --u-text-overline-tracking: 0.04em;
329
532
 
330
533
  /* House-only scale — control heights and table rows (read by components.css · patterns.css) */
331
- --hs-control-sm: 28px;
332
- --hs-control-md: 32px;
534
+ --hs-control-sm: 32px;
535
+ --hs-control-md: 36px;
333
536
  --hs-control-lg: 40px;
334
- --hs-row: 40px;
335
- --hs-row-compact: 32px;
336
- --hs-page-padding: 24px;
537
+ --hs-row: 44px;
538
+ --hs-row-compact: 36px;
539
+ --hs-page-padding: 20px 32px 48px;
540
+ --hs-section-gap: 20px;
337
541
 
338
542
  /* House type steps beyond the shared ladder — derived from it, so a rescaled ladder (density, a product system)
339
- carries them along. Dense = the step between body and label (13 at 14/12): table cells, definition lists,
340
- callouts. Hero = a headline figure, display + 6 (28 at 22). */
543
+ carries them along. Dense = the step between body and label (13 at 14/12): secondary lines, table headers,
544
+ breadcrumbs. Hero = a headline figure, display + 4 (28 at 24). */
341
545
  --hs-text-dense-size: calc((var(--u-text-body-size) + var(--u-text-label-size)) / 2);
342
- --hs-text-hero-size: calc(var(--u-text-display-size) + 6px);
546
+ --hs-text-hero-size: calc(var(--u-text-display-size) + 4px);
343
547
 
344
548
  /* Prose measure — a running paragraph stops around 70 characters a line (`.hs-prose`). */
345
549
  --hs-measure: 70ch;
346
550
 
347
- /* Field box (`@iyulab/components`) — 32px fields beside 32px buttons; 40px inside form sections. In em, so the box
348
- follows the control density with the buttons (resolved at the field: 4.5px / 12px at the default 14px). */
349
- --u-field-padding-block: calc(4.5em / 14);
551
+ /* Field box (`@iyulab/components`) — 36px fields beside 36px buttons; 40px inside form sections. In em, so the box
552
+ follows the control density with the buttons (resolved at the field: 6.5px / 12px at the default 14px). */
553
+ --u-field-padding-block: calc(6.5em / 14);
350
554
  --u-field-padding-inline: calc(12em / 14);
351
- --u-field-radius: 6px;
555
+ --u-field-radius: 8px;
352
556
 
353
- /* Shell (`@iyulab/modern-app`) — 32px navigation rows, 16px icons, a flat current item */
557
+ /* Shell (`@iyulab/modern-app`) — a 240px sidebar of 38px rows with 18px icons, a flat current item */
354
558
  --app-main-padding: var(--hs-page-padding);
355
- --app-sidebar-item-radius: 6px;
356
- --app-sidebar-item-gap: 8px;
357
- --app-sidebar-item-padding: 6px 12px;
358
- --app-sidebar-icon-size: 16px;
559
+ --app-sidebar-width: 240px;
560
+ --app-sidebar-item-radius: 8px;
561
+ --app-sidebar-item-gap: 10px;
562
+ --app-sidebar-item-padding: 8px 10px;
563
+ --app-sidebar-icon-size: 18px;
359
564
  --app-sidebar-active-shadow: none;
360
565
  --app-sidebar-active-shadow-hover: none;
361
566
  }