@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 +157 -0
- package/css/README.md +90 -0
- package/css/app.css +100 -0
- package/css/bases.css +86 -0
- package/css/charts.css +152 -0
- package/css/components/button.css +143 -0
- package/css/index.css +63 -0
- package/css/menus.css +165 -0
- package/css/palettes.css +292 -0
- package/css/primitives.css +224 -0
- package/css/styles.css +203 -0
- package/css/surfaces.css +364 -0
- package/css/tailwind.css +163 -0
- package/css/tokens.css +627 -0
- package/css/typeset.css +274 -0
- package/dist/controller.d.ts +56 -0
- package/dist/controller.d.ts.map +1 -0
- package/dist/controller.js +256 -0
- package/dist/controller.js.map +1 -0
- package/dist/index.d.ts +17 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +13 -0
- package/dist/index.js.map +1 -0
- package/dist/preset.d.ts +90 -0
- package/dist/preset.d.ts.map +1 -0
- package/dist/preset.js +86 -0
- package/dist/preset.js.map +1 -0
- package/dist/registry.d.ts +252 -0
- package/dist/registry.d.ts.map +1 -0
- package/dist/registry.js +238 -0
- package/dist/registry.js.map +1 -0
- package/dist/token-names.d.ts +512 -0
- package/dist/token-names.d.ts.map +1 -0
- package/dist/token-names.js +7 -0
- package/dist/token-names.js.map +1 -0
- package/package.json +40 -0
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
|
+
}
|