@vestow/theme 0.1.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 ADDED
@@ -0,0 +1,157 @@
1
+ # `@vestow/theme`
2
+
3
+ Theme của WoodBlock, trọn gói: **giá trị** token dạng CSS thuần, và **tầng chạy**
4
+ đọc lựa chọn đã lưu rồi ghi chín trục lên `<html>`.
5
+
6
+ | | dạng | nạp thế nào |
7
+ |---|---|---|
8
+ | giá trị token, chín trục theme, tầng token component | CSS thuần | `@import "@vestow/theme/css/index.css"` |
9
+ | entry cho package component (Tailwind + `@layer` dùng chung) | CSS | `@import "@vestow/theme/css/app.css"` |
10
+ | registry chín trục, controller, `definePreset` | TypeScript | `import { themeController } from '@vestow/theme'` |
11
+
12
+ Hai nửa độc lập: nạp CSS không kéo về một byte JavaScript, và import JS không kéo
13
+ về một byte CSS. Một app Angular chỉ cần `@import` bộ token thì không phải chạm
14
+ tới nửa kia.
15
+
16
+ Phần TS là vanilla — không import framework nào, không chạm `document` ở tầng
17
+ module, nên import được trong SSR.
18
+
19
+ ## Ba entry CSS, xếp từ dưới lên
20
+
21
+ | file | thêm gì | ai nạp |
22
+ |---|---|---|
23
+ | `css/index.css` | 8 file token, **CSS thuần** — không một at-rule Tailwind nào | app không dùng Tailwind |
24
+ | `css/tailwind.css` | `@custom-variant dark` + `@theme inline` | app dùng Tailwind, tự khai `@layer` |
25
+ | `css/app.css` | `@import "tailwindcss" source(none)`, `tw-animate-css`, và các khối `@layer` dùng chung (`.surface`, `.scroll-edge`, `.app-backdrop`, reset base) | `@vestow/vue` |
26
+
27
+ Hai file **đứng ngoài** `index.css` vì cái giá đo được: `charts.css` (3.084 B, trục
28
+ `data-chart`) và `typeset.css` (2.703 B, lớp văn xuôi `.typeset`). App nào cần thì tự
29
+ `@import`.
30
+
31
+ `css/primitives.css` là file **TỰ SINH** từ `tailwindcss/theme.css` — đừng sửa tay;
32
+ `pnpm -F @vestow/theme gen` sinh lại, và `pnpm lint` bắt file cũ.
33
+
34
+ ## Controller
35
+
36
+ ```ts
37
+ import { themeController } from '@vestow/theme'
38
+
39
+ themeController.init() // đọc localStorage, áp lên <html>, nghe `storage`
40
+ themeController.setPalette('solar')
41
+ themeController.subscribe(state => console.log(state.isDark))
42
+ ```
43
+
44
+ Vỏ bọc cho từng framework chỉ còn việc nối `subscribe()` vào hệ reactivity của
45
+ nó. Bản Vue ở `vue/packages/ui/src/composables/useThemeController.ts` dài 48
46
+ dòng — đó là toàn bộ phần code riêng của Vue cho chín trục.
47
+
48
+ Viết vỏ bọc mới thì đọc `src/controller.ts` trước; **đừng viết lại logic**. Chỉ
49
+ cần một bên quên `style.colorScheme` hay gõ sai khoá localStorage là người dùng
50
+ đổi framework sẽ mất lựa chọn đã lưu — mà cả hai lỗi đó đều không làm gì gãy,
51
+ chỉ âm thầm sai.
52
+
53
+ ## `definePreset`
54
+
55
+ Ghi đè token lúc chạy, cùng vai với `definePreset` của PrimeVue. Dùng cho đúng
56
+ một ca: **giá trị chỉ biết lúc chạy** — màu thương hiệu đọc từ API, theme người
57
+ dùng lưu trong tài khoản, bản xem trước trong trình dựng theme. Ngoài ca đó thì
58
+ viết `:root { --primary: … }` trong CSS của app vẫn gọn hơn.
59
+
60
+ ```ts
61
+ import { definePreset } from '@vestow/theme'
62
+
63
+ definePreset({
64
+ base: { radius: '2px' },
65
+ light: { primary: { $: 'oklch(0.55 0.2 30)', foreground: '#fff' } },
66
+ dark: { primary: '#7dd3fc' },
67
+ })
68
+ ```
69
+
70
+ Tên token có kiểu: gõ sai là **lỗi biên dịch** kèm gợi ý "did you mean", không
71
+ phải một biến rỗng và một cái nút trong suốt. 221 tên, sinh từ chính CSS của
72
+ `@vestow/theme`.
73
+
74
+ ### Hai dạng khoá, dùng lẫn nhau được
75
+
76
+ | | |
77
+ |---|---|
78
+ | **cây** | `{ button: { radius: '2px' } }` — dạng `button.border.radius` của PrimeVue |
79
+ | **phẳng** | `{ '--button-radius': '2px' }` — đúng tên CSS |
80
+
81
+ Không mơ hồ, vì tên custom property luôn bắt đầu bằng `--` còn nhánh thì không
82
+ bao giờ. Trộn hai dạng trong một object cũng được.
83
+
84
+ Dạng phẳng **không bỏ đi**: nó là thứ duy nhất viết được một tên tính lúc chạy,
85
+ và khi debug thì nó đọc thẳng ra tên CSS thật, khỏi phải dịch ngược.
86
+
87
+ **Khoá `$` là giá trị của chính nhánh.** Cần nó vì **56/221** tên vừa là lá vừa
88
+ là nhánh — `--accent` cạnh `--accent-foreground`, `--button-radius` cạnh
89
+ `--button-radius-sm` — mà một khoá không thể vừa mang chuỗi vừa mang object.
90
+ Đúng vai `root` trong preset của PrimeVue, chỉ ngắn hơn và không bao giờ đụng
91
+ một đoạn tên thật.
92
+
93
+ ```ts
94
+ { button: { radius: '2px' } } // chỉ --button-radius
95
+ { button: { radius: { $: '2px', sm: '4px' } } } // cả --button-radius và -sm
96
+ ```
97
+
98
+ Chuỗi trần đặt vào một nhánh là **lối tắt cho `$`**.
99
+
100
+ **Cây SUY RA từ tên CSS, không phải ngược lại.** Đó là chỗ khác PrimeVue: bên họ
101
+ tên CSS sinh từ object cấu hình, ở đây object sinh từ CSS — nên không có nguồn
102
+ thứ hai để mà trôi, và thêm một token vào CSS là cây tự có nhánh mới.
103
+
104
+ **Render phía server thì dùng `presetToCss()`** — bản thuần, không đụng DOM.
105
+ Nhúng kết quả vào một thẻ `<style id="woodblock-preset">` ngay trong HTML trả về:
106
+ trang không chớp màu, và `definePreset()` phía trình duyệt nhận lại đúng thẻ đó
107
+ thay vì thêm thẻ thứ hai.
108
+
109
+ Ba điều phải biết trước khi dùng, chi tiết ở đầu `src/preset.ts`:
110
+
111
+ - **Giá trị là chuỗi CSS, JS không hiểu nó.** Cố ý — token ở đây viết bằng
112
+ `color-mix(in oklab, …)` và `oklch(from …)`; mô hình hoá lại mấy phép đó trong
113
+ JS là viết lại một engine màu, và đã đo được là nó ra số khác trình duyệt.
114
+ - **Preset ghi ở `:root`**, nên nó không đè được token mà một rule khác khai lại
115
+ trên phần tử CON — khai báo trực tiếp luôn thắng giá trị thừa kế.
116
+ - **Đặc hiệu là `:root` lặp 5 lần**, con số ĐO chứ không chọn cho tròn: rule đặc
117
+ hiệu nhất đang khai token ở tầng `:root` là (0,4,0).
118
+
119
+ ## Chín trục
120
+
121
+ | trục | ghi lên `<html>` | khoá localStorage | mặc định |
122
+ |---|---|---|---|
123
+ | chế độ màu | class `.dark` + `style.color-scheme` | `hyperion-color-mode` | `system` |
124
+ | bảng màu | `data-palette` | `hyperion-palette` | `hyperion` |
125
+ | bề mặt | `data-surface` | `hyperion-surface` | `solid` |
126
+ | hình khối | `data-style` | `hyperion-style` | `vega` |
127
+ | họ trung tính | `data-base` | `hyperion-base` | `slate` |
128
+ | bộ màu biểu đồ | `data-chart` | `hyperion-chart-palette` | `theme` |
129
+ | màu menu | `data-menu-color` | `hyperion-menu-color` | `default` |
130
+ | bề mặt menu | `data-menu-appearance` | `hyperion-menu-appearance` | `solid` |
131
+ | highlight menu | `data-menu-accent` | `hyperion-menu-accent` | `subtle` |
132
+
133
+ Giá trị mặc định **không có khối CSS nào**, nên controller gỡ hẳn thuộc tính
134
+ thay vì ghi vào. Các trục **độc lập** — Solar + glass hay Nebula + glass đều
135
+ dùng được.
136
+
137
+ Tiền tố `hyperion-` là một định danh lưu trữ, không phải tên thương hiệu. Đổi nó
138
+ là xoá lựa chọn đã lưu của mọi người dùng hiện có.
139
+
140
+ ## Lệnh
141
+
142
+ ```
143
+ pnpm -F @vestow/theme gen # sinh lại primitives.css + token-names.ts từ CSS
144
+ pnpm -F @vestow/theme check # kiểm nó không trôi (chạy sẵn trong `pnpm lint`)
145
+ pnpm -F @vestow/theme build # biên dịch TS ra dist/ (chỉ cần khi publish)
146
+ ```
147
+
148
+ ## Publish
149
+
150
+ `exports` trỏ vào `src/` cho tiện phát triển trong workspace; `publishConfig`
151
+ đổi sang `dist/` lúc `pnpm publish`, và `prepack` chạy `gen` + `build` trước.
152
+
153
+ `files` đóng gói `css/` và `dist/`. Phần CSS đi thẳng, không qua build; phần TS
154
+ thì `prepack` biên dịch trước.
155
+
156
+ Hai dependency runtime — `tailwindcss` và `tw-animate-css` — chỉ phục vụ
157
+ `css/app.css`. App nạp `css/index.css` (CSS thuần) không chạm tới chúng.
package/css/README.md ADDED
@@ -0,0 +1,90 @@
1
+ # `@vestow/theme/css`
2
+
3
+ Nửa **CSS** của theme: token màu, bo tròn, vòng focus, chín trục theme, và tầng
4
+ token theo component. Không phụ thuộc framework nào, và `css/index.css` không
5
+ phụ thuộc cả Tailwind.
6
+
7
+ Nửa kia — registry, controller, `definePreset` — là TypeScript và nằm ở
8
+ [`../README.md`](../README.md). Hai nửa độc lập: nạp CSS không kéo về một byte
9
+ JavaScript nào, và ngược lại.
10
+
11
+ Điều gì KHÔNG nằm trong đây: component, và bất cứ thứ gì biết tới Vue. Ngoại lệ
12
+ duy nhất là `app.css` — nó có `@apply` và `@import "tailwindcss"`, đúng vì nó là
13
+ entry mà package component nạp, chứ không phải một file token.
14
+
15
+ ## Nạp CSS
16
+
17
+ **App dùng Tailwind v4:**
18
+
19
+ ```css
20
+ @import "tailwindcss" source(none);
21
+ @import "@vestow/theme/css/tailwind.css";
22
+ @source "./";
23
+ ```
24
+
25
+ **Mọi chỗ còn lại** (Angular, plain CSS, CSS-in-JS, SCSS…):
26
+
27
+ ```css
28
+ @import "@vestow/theme/css/index.css";
29
+ ```
30
+
31
+ Hai entry nạp cùng bộ file; bản `tailwind.css` chỉ thêm `@custom-variant dark`
32
+ và khối `@theme inline` ánh xạ token sang utility (`bg-background`,
33
+ `rounded-lg`, `shadow-focus`…).
34
+
35
+ **Không cần `theme(static)`.** Các nấc màu gốc (`--color-blue-600`…) do
36
+ `css/primitives.css` cung cấp — file tự sinh, chép đúng giá trị từ
37
+ `tailwindcss/theme.css`. Trước đây token trỏ vào bảng màu của Tailwind nên phải
38
+ bật `theme(static)` để nó đừng cây-rung; bỏ được luôn cả ràng buộc đó lẫn
39
+ 11,4 KB CSS thô (3,2 KB sau gzip).
40
+
41
+ Package này chỉ khai **biến**, không có base style. Nạp xong `<body>` vẫn là Times New
42
+ Roman cho tới khi bạn tự viết `font-family: var(--font-sans)` — bản Vue làm việc đó trong
43
+ `vue/packages/ui/src/styles/index.css`. Đó là cố ý: base style là quyết định của từng app.
44
+
45
+ ### Thứ tự nạp
46
+
47
+ `primitives` → `tokens` → `palettes` → `surfaces`. Cả ba lần ghi đè đều dựa vào
48
+ **thứ tự khai báo**, không dựa vào specificity, nên đảo thứ tự là hỏng âm thầm.
49
+ `css/index.css` đã xếp sẵn — đừng nạp lẻ từng file.
50
+
51
+ ## Ba trục đầu tiên, và chỗ điều khiển chúng
52
+
53
+ Chín trục theme đều bật bằng một thuộc tính trên `<html>`. Đặt tay cũng được:
54
+
55
+ ```html
56
+ <html class="dark" data-palette="solar" data-style="lyra">
57
+ ```
58
+
59
+ Muốn đổi lúc chạy, lưu lựa chọn, và đồng bộ giữa các tab thì dùng
60
+ `@vestow/theme` — nó là vỏ vanilla quanh đúng những thuộc tính này.
61
+
62
+ | trục | ghi lên `<html>` | mặc định |
63
+ |---|---|---|
64
+ | chế độ màu | class `.dark` + `style.color-scheme` | `system` |
65
+ | bảng màu | `data-palette` | `hyperion` |
66
+ | bề mặt | `data-surface` | `solid` |
67
+ | hình khối | `data-style` | `vega` |
68
+
69
+ Giá trị mặc định **không có khối CSS nào**, nên gỡ hẳn thuộc tính là về mặc định.
70
+ Các trục **độc lập** với nhau — Solar + glass hay Nebula + glass đều dùng được.
71
+
72
+ ## Lệnh
73
+
74
+ ```
75
+ pnpm -F @vestow/theme gen # sinh lại css/primitives.css
76
+ pnpm -F @vestow/theme check # kiểm token (chạy sẵn trong `pnpm lint` ở root)
77
+ ```
78
+
79
+ Không còn lệnh `build`: package này không ship JavaScript nữa.
80
+
81
+ `check` chặn năm lỗi đã xảy ra thật — xem đầu `scripts/check-tokens.mjs`.
82
+
83
+ ## Publish
84
+
85
+ Package này **không có bước build**, nên `exports` giống nhau ở cả hai chế độ —
86
+ CSS chép thẳng. `prepack` chỉ chạy `gen` để `css/primitives.css` không đi publish
87
+ ở trạng thái cũ.
88
+
89
+ Đó cũng là cái được rõ nhất của đợt tách: trước đây nó mang một `dist/` TypeScript
90
+ mà một người tiêu thụ chỉ cần CSS không bao giờ chạm tới.
package/css/app.css ADDED
@@ -0,0 +1,100 @@
1
+ /* =============================================================================
2
+ Entry CSS mà PACKAGE COMPONENT nạp — Tailwind, token, và các khối @layer
3
+ không thuộc framework nào.
4
+
5
+ @import "@vestow/theme/css/app.css";
6
+ @source "../"; -- vùng quét của chính package nạp file này
7
+
8
+ Ba tầng, xếp từ dưới lên:
9
+ 1. `index.css` — token CSS thuần, nạp được cả khi không có Tailwind.
10
+ 2. `tailwind.css` — thêm `@custom-variant dark` và `@theme inline`.
11
+ 3. file này — thêm Tailwind, `tw-animate-css`, và các khối @layer
12
+ dùng chung: .surface, .scroll-edge, .app-backdrop,
13
+ .scroll-area-x, reset base.
14
+
15
+ Ba class đầu là mặt của THEME KÍNH (`data-surface="glass"`) nên chúng ở đây
16
+ chứ không ở phía package component: chúng đọc `--surface-filter`,
17
+ `--surface-backdrop` và `--header-surface`, tức chúng là người đọc của token.
18
+
19
+ KHÔNG khai `@source` ở đây. Package nào nạp file này thì tự khai vùng của
20
+ chính nó — `@source` phân giải tương đối theo file CSS CHỨA nó, nên khai từ
21
+ đây là trỏ vào `packages/theme`, nơi không có một class Tailwind nào.
22
+
23
+ source(none): tắt auto content-detection của Tailwind v4. Mọi vùng code phải
24
+ khai `@source` tường minh -> deterministic, và không quét nhầm cả monorepo.
25
+ ========================================================================== */
26
+
27
+ @import "tailwindcss" source(none);
28
+ @import "tw-animate-css";
29
+ @import "./tailwind.css";
30
+
31
+ @layer components {
32
+ /* Đánh dấu một element là "bề mặt": card, popover, sheet, header…
33
+ Ngoài theme glass thì --surface-filter là none nên rule này vô hại. */
34
+ .surface {
35
+ -webkit-backdrop-filter: var(--surface-filter);
36
+ backdrop-filter: var(--surface-filter);
37
+ }
38
+
39
+ /* "Scroll edge effect" theo tài liệu Liquid Glass của Apple: bar dính phải
40
+ làm mờ dần nội dung cuộn qua bên dưới, nếu không chữ trong bar đè lên nội
41
+ dung có độ sáng bất kỳ. Nền trong mờ thôi là chưa đủ.
42
+ Dải chuyển tiếp nằm NGOÀI bar (top: 100%) nên không ăn vào chiều cao 52px. */
43
+ .scroll-edge::after {
44
+ content: "";
45
+ position: absolute;
46
+ inset-inline: 0;
47
+ top: 100%;
48
+ height: 22px;
49
+ pointer-events: none;
50
+ background: linear-gradient(to bottom, var(--header-surface), transparent);
51
+ }
52
+
53
+ /* Vỏ ngoài cùng của layout. Kính cần có thứ gì đó phía sau để làm mờ, mà vỏ
54
+ layout lại tô `bg-background` đục kín cả viewport -> gradient đặt ở <body>
55
+ sẽ bị che sạch. Phải vẽ ngay trên vỏ.
56
+ `fixed` để nền đứng yên khi cuộn, đúng cảm giác kính trượt trên nền. */
57
+ .app-backdrop {
58
+ background-image: var(--surface-backdrop);
59
+ background-attachment: fixed;
60
+ }
61
+ }
62
+
63
+ @layer base {
64
+ * {
65
+ @apply border-border outline-ring/50;
66
+ }
67
+
68
+ html {
69
+ @apply antialiased;
70
+ -webkit-text-size-adjust: 100%;
71
+ }
72
+
73
+ body {
74
+ @apply bg-background text-foreground;
75
+ font-feature-settings: "rlig" 1, "calt" 1;
76
+ }
77
+
78
+ /* NGƯỜI ĐỌC của `--font-heading`. Không có rule này thì token đó là lời hứa
79
+ suông — Tailwind chỉ đặt `--default-font-family` từ `--font-sans`, còn thẻ
80
+ tiêu đề thì thừa kế chứ không tự hỏi han gì.
81
+
82
+ Đặt ở `@layer base` nên bất kỳ class `font-*` nào trên chính thẻ đó cũng
83
+ thắng, không cần `!important`. */
84
+ h1, h2, h3, h4, h5, h6 {
85
+ font-family: var(--font-heading);
86
+ }
87
+
88
+ /* Scrollbar hợp với token, không hardcode màu */
89
+ * {
90
+ scrollbar-color: var(--border) transparent;
91
+ scrollbar-width: thin;
92
+ }
93
+ }
94
+
95
+ @layer utilities {
96
+ /* Dùng cho vùng nội dung có thể tràn ngang (bảng, code block) */
97
+ .scroll-area-x {
98
+ @apply overflow-x-auto overscroll-x-contain;
99
+ }
100
+ }
package/css/bases.css ADDED
@@ -0,0 +1,86 @@
1
+ /* =============================================================================
2
+ Trục HỌ TRUNG TÍNH — `data-base`, trục theme thứ sáu.
3
+
4
+ `data-palette` chọn SẮC CHÍNH (primary + nhóm trung tính đi kèm); trục này
5
+ tách riêng phần trung tính ra, để một giao diện primary tím vẫn có thể đứng
6
+ trên nền xám ấm. shadcn gọi hai thứ đó là "Theme" và "Base Color".
7
+
8
+ MỖI KHỐI Ở ĐÂY CHỈ KHAI HAI TOKEN, và đó không phải chỗ rút gọn — đó là toàn
9
+ bộ cơ chế. `tokens.css` dựng mọi mặt trung tính bằng
10
+
11
+ oklch(from var(--color-<họ>-<nấc>) l var(--tint-c-<nấc>) var(--tint-hue))
12
+
13
+ tức nấc gốc CHỈ góp độ sáng, còn chroma và hue là token. Đo được: chín họ
14
+ trung tính của Tailwind chênh nhau ≤ 2,3 điểm L ở mọi nấc (98,4–98,8 ở nấc
15
+ 50; 20,5–22,8 ở nấc 900), nên đổi họ thực chất là đổi đúng `--tint-hue`.
16
+
17
+ `neutral` không có hue để mà đổi — nó tắt trục bằng `--tint-scale: 0`, đúng
18
+ cách bảng Eclipse đang làm.
19
+
20
+ KẸP GAMUT: trần chroma của sRGB tụt sâu ở dải hue 180–225 và quanh vùng lục.
21
+ Ba họ chạm trần nên phải kẹp tay, lấy 85% trần đo được
22
+ (`node packages/theme/scripts/measure-bases.mjs`):
23
+
24
+ | họ | hue | trần @L .208 | trần @L .129 |
25
+ |-------|-------|--------------|--------------|
26
+ | slate | 265,8 | 0,145 | 0,096 |
27
+ | mauve | 322,1 | 0,103 | 0,073 |
28
+ | zinc | 285,9 | 0,125 | 0,084 |
29
+ | taupe | 43,1 | 0,067 | 0,054 |
30
+ | stone | 56,0 | 0,055 | 0,044 |
31
+ | olive | 107,4 | 0,048 | 0,038 |
32
+ | mist | 223,9 | 0,041 | 0,031 |
33
+
34
+ Bốn họ đầu vượt mức mặc định 0,042 nên để nguyên; ba họ cuối bị kẹp. Con số
35
+ kẹp của `stone` ra đúng 0,038 — trùng khít giá trị mà bảng Solar đã dùng từ
36
+ trước, tức phép đo này tái lập được lựa chọn cũ chứ không phải bịa ra mới.
37
+
38
+ `slate` là mặc định nên KHÔNG có khối — cùng lẽ với `hyperion` / `solid` /
39
+ `default` / `theme`.
40
+
41
+ ĐỘ ĐẶC HIỆU: attribute lặp ba lần. `palettes.css` khai `--tint-hue` bên trong
42
+ `:root:not(.dark)[data-palette=…]` — (0,3,0) — nên một `[data-base]` trần sẽ
43
+ thua và trục này im lặng không có tác dụng. Cùng cái bẫy của `styles.css` và
44
+ `charts.css`.
45
+ ========================================================================== */
46
+
47
+ [data-base="zinc"][data-base="zinc"][data-base="zinc"] {
48
+ --tint-hue: 285.885;
49
+ --tint-scale: 1;
50
+ }
51
+
52
+ [data-base="stone"][data-base="stone"][data-base="stone"] {
53
+ --tint-hue: 56.043;
54
+ --tint-scale: 1;
55
+ --tint-c-950: 0.038;
56
+ }
57
+
58
+ [data-base="mist"][data-base="mist"][data-base="mist"] {
59
+ --tint-hue: 223.9;
60
+ --tint-scale: 1;
61
+ --tint-c-900: 0.035;
62
+ --tint-c-950: 0.026;
63
+ }
64
+
65
+ [data-base="taupe"][data-base="taupe"][data-base="taupe"] {
66
+ --tint-hue: 43.1;
67
+ --tint-scale: 1;
68
+ }
69
+
70
+ [data-base="olive"][data-base="olive"][data-base="olive"] {
71
+ --tint-hue: 107.4;
72
+ --tint-scale: 1;
73
+ --tint-c-900: 0.041;
74
+ --tint-c-950: 0.032;
75
+ }
76
+
77
+ [data-base="mauve"][data-base="mauve"][data-base="mauve"] {
78
+ --tint-hue: 322.12;
79
+ --tint-scale: 1;
80
+ }
81
+
82
+ /* Xám thuần. `--tint-scale: 0` nhân mọi `--tint-c-*` về 0, nên không cần khai
83
+ `--tint-hue`: ở chroma 0 thì hue là số vô nghĩa. */
84
+ [data-base="neutral"][data-base="neutral"][data-base="neutral"] {
85
+ --tint-scale: 0;
86
+ }
package/css/charts.css ADDED
@@ -0,0 +1,152 @@
1
+ /* =============================================================================
2
+ Trục MÀU BIỂU ĐỒ — `data-chart`, trục theme thứ năm.
3
+
4
+ FILE NÀY KHÔNG NẰM TRONG `index.css`. App nào cần thì tự nạp:
5
+
6
+ @import "@vestow/theme/css/charts.css";
7
+
8
+ Nó nặng 3.084 byte CSS thô mà chỉ chạm vào biểu đồ, nên app không có biểu đồ
9
+ đừng trả tiền cho nó — cùng phép cân đã đẩy `typeset.css` ra ngoài. Quên nạp
10
+ thì không gãy gì: `--chart-*` vẫn đi theo bảng màu, chỉ là `data-chart` không
11
+ có tác dụng. Nhưng `themeController` VẪN ghi thuộc tính đó lên `<html>`, nên
12
+ app phơi ô chọn ra giao diện mà quên `@import` sẽ thấy ô chọn đổi còn màu thì
13
+ không.
14
+
15
+ Nó trả lời một câu hỏi khác hẳn bốn trục kia: `data-palette` nói giao diện ám
16
+ sắc gì, còn cái này nói NĂM ĐƯỜNG trong biểu đồ tô màu gì. Hai thứ đó thường
17
+ đi cùng nhau, nhưng không phải luôn luôn — một dashboard theo bảng màu Solar
18
+ vẫn có thể muốn biểu đồ đơn sắc để in đen trắng.
19
+
20
+ Mặc định là "đi theo bảng màu": KHÔNG khai `data-chart` thì `palettes.css`
21
+ quyết định, đúng như hôm nay. Đó cũng là lý do ở đây không có khối
22
+ `[data-chart="theme"]` — giá trị mặc định của một trục không bao giờ có khối
23
+ CSS, cùng lẽ với `hyperion` / `solid` / `default`.
24
+
25
+ NĂM BỘ Ở ĐÂY CHÍNH LÀ NĂM BỘ CỦA NĂM BẢNG MÀU, chép sang chứ không nghĩ mới.
26
+ Chúng đã chạy thật, đã đo, và `check-tokens.mjs` (luật 6) đối chiếu từng giá
27
+ trị giữa file này với `palettes.css` — chép tay mà lệch là lint đỏ.
28
+
29
+ Số đo, tương phản trên mặt card / khoảng cách luminance nhỏ nhất trong bộ
30
+ (`node packages/theme/scripts/measure-charts.mjs`):
31
+
32
+ | bộ | sáng | tối |
33
+ |---------|-----------------|-----------------|
34
+ | blue | 3,12 · 0,045 | 4,05 · 0,062 |
35
+ | amber | 3,19 · 0,048 | 3,75 · 0,072 |
36
+ | violet | 3,12 · 0,044 | 4,34 · 0,042 |
37
+ | teal | 3,22 · 0,034 | 3,61 · 0,075 |
38
+ | neutral | 4,73 · 0,016 | 3,77 · 0,125 |
39
+
40
+ Không bộ nào dưới 3:1 (WCAG 1.4.11). `neutral` có khoảng cách sáng nhỏ nhất
41
+ ở chế độ sáng và đó là bản chất của một bộ đơn sắc — nó bù bằng tương phản
42
+ cao nhất trong năm bộ.
43
+
44
+ ĐỘ ĐẶC HIỆU: attribute lặp BA lần, cùng cái bẫy của `styles.css`.
45
+ `palettes.css` khai `--chart-*` bên trong `:root:not(.dark)[data-palette=…]`
46
+ — (0,3,0). Một `[data-chart="x"]` trần là (0,1,0) nên bảng màu thắng và trục
47
+ này im lặng không có tác dụng. Lặp ba lần đưa nó lên trên, và `index.css`
48
+ nạp file này SAU `palettes.css`.
49
+
50
+ Không khai `--chart-*-foreground`: `tokens.css` suy chúng ra từ `--chart-N`
51
+ bằng `oklch(from …)`, nên đổi bộ ở đây là màu chữ trên cột tự đi theo.
52
+ ========================================================================== */
53
+
54
+ /* --- blue — bộ gốc trong tokens.css, cũng là bộ của bảng Hyperion ---------- */
55
+
56
+ :root:not(.dark)[data-chart="blue"][data-chart="blue"][data-chart="blue"],
57
+ .light[data-chart="blue"][data-chart="blue"][data-chart="blue"] {
58
+ --chart-1: var(--color-blue-600);
59
+ --chart-2: var(--color-cyan-600);
60
+ --chart-3: var(--color-violet-900);
61
+ --chart-4: var(--color-sky-800);
62
+ --chart-5: var(--color-indigo-400);
63
+ }
64
+
65
+ :root.dark[data-chart="blue"][data-chart="blue"][data-chart="blue"],
66
+ .dark[data-chart="blue"][data-chart="blue"][data-chart="blue"] {
67
+ --chart-1: var(--color-blue-400);
68
+ --chart-2: var(--color-cyan-400);
69
+ --chart-3: var(--color-violet-500);
70
+ --chart-4: var(--color-sky-400);
71
+ --chart-5: var(--color-indigo-400);
72
+ }
73
+
74
+ /* --- amber — bộ của bảng Solar -------------------------------------------- */
75
+
76
+ :root:not(.dark)[data-chart="amber"][data-chart="amber"][data-chart="amber"],
77
+ .light[data-chart="amber"][data-chart="amber"][data-chart="amber"] {
78
+ --chart-1: var(--color-orange-700);
79
+ --chart-2: var(--color-yellow-800);
80
+ --chart-3: var(--color-rose-500);
81
+ --chart-4: var(--color-red-900);
82
+ --chart-5: var(--color-amber-600);
83
+ }
84
+
85
+ :root.dark[data-chart="amber"][data-chart="amber"][data-chart="amber"],
86
+ .dark[data-chart="amber"][data-chart="amber"][data-chart="amber"] {
87
+ --chart-1: var(--color-orange-600);
88
+ --chart-2: var(--color-yellow-400);
89
+ --chart-3: var(--color-rose-400);
90
+ --chart-4: var(--color-red-600);
91
+ --chart-5: var(--color-amber-500);
92
+ }
93
+
94
+ /* --- violet — bộ của bảng Nebula ------------------------------------------ */
95
+
96
+ :root:not(.dark)[data-chart="violet"][data-chart="violet"][data-chart="violet"],
97
+ .light[data-chart="violet"][data-chart="violet"][data-chart="violet"] {
98
+ --chart-1: var(--color-purple-600);
99
+ --chart-2: var(--color-pink-500);
100
+ --chart-3: var(--color-indigo-400);
101
+ --chart-4: var(--color-fuchsia-800);
102
+ --chart-5: var(--color-violet-500);
103
+ }
104
+
105
+ :root.dark[data-chart="violet"][data-chart="violet"][data-chart="violet"],
106
+ .dark[data-chart="violet"][data-chart="violet"][data-chart="violet"] {
107
+ --chart-1: var(--color-purple-500);
108
+ --chart-2: var(--color-pink-300);
109
+ --chart-3: var(--color-indigo-300);
110
+ --chart-4: var(--color-fuchsia-500);
111
+ --chart-5: var(--color-violet-400);
112
+ }
113
+
114
+ /* --- teal — bộ của bảng Aurora -------------------------------------------- */
115
+
116
+ :root:not(.dark)[data-chart="teal"][data-chart="teal"][data-chart="teal"],
117
+ .light[data-chart="teal"][data-chart="teal"][data-chart="teal"] {
118
+ --chart-1: var(--color-teal-700);
119
+ --chart-2: var(--color-sky-900);
120
+ --chart-3: var(--color-green-600);
121
+ --chart-4: var(--color-cyan-800);
122
+ --chart-5: var(--color-emerald-600);
123
+ }
124
+
125
+ :root.dark[data-chart="teal"][data-chart="teal"][data-chart="teal"],
126
+ .dark[data-chart="teal"][data-chart="teal"][data-chart="teal"] {
127
+ --chart-1: var(--color-teal-600);
128
+ --chart-2: var(--color-sky-300);
129
+ --chart-3: var(--color-green-700);
130
+ --chart-4: var(--color-cyan-500);
131
+ --chart-5: var(--color-emerald-400);
132
+ }
133
+
134
+ /* --- neutral — bộ của bảng Eclipse, dùng khi bản in phải là đen trắng ------ */
135
+
136
+ :root:not(.dark)[data-chart="neutral"][data-chart="neutral"][data-chart="neutral"],
137
+ .light[data-chart="neutral"][data-chart="neutral"][data-chart="neutral"] {
138
+ --chart-1: var(--color-neutral-800);
139
+ --chart-2: var(--color-neutral-500);
140
+ --chart-3: var(--color-neutral-600);
141
+ --chart-4: var(--color-neutral-700);
142
+ --chart-5: var(--color-neutral-950);
143
+ }
144
+
145
+ :root.dark[data-chart="neutral"][data-chart="neutral"][data-chart="neutral"],
146
+ .dark[data-chart="neutral"][data-chart="neutral"][data-chart="neutral"] {
147
+ --chart-1: var(--color-neutral-300);
148
+ --chart-2: var(--color-neutral-100);
149
+ --chart-3: var(--color-neutral-200);
150
+ --chart-4: var(--color-neutral-400);
151
+ --chart-5: var(--color-neutral-500);
152
+ }