picocrank 1.23.1 → 1.25.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/AGENTS.md +2 -0
- package/docs/filter-query-v1.md +180 -0
- package/logo.svg +7 -7
- package/package.json +7 -5
- package/public/themes/README.md +19 -2
- package/public/themes/index.json +1 -5
- package/styles/dark-theme.css +16 -1
- package/supplemental-themes/README.md +9 -0
- package/supplemental-themes/catppuccin-latte-frappe/theme.css +276 -0
- package/supplemental-themes/dracula-alucard/theme.css +258 -0
- package/supplemental-themes/gruvbox-dark-light/theme.css +264 -0
- package/supplemental-themes/index.json +17 -0
- package/{public/themes → supplemental-themes}/waffles/theme.css +99 -0
- package/vite.config.js +91 -0
- package/vue/components/FormField.vue +69 -1
- package/vue/components/Header.vue +3 -3
- package/vue/components/Login.vue +1 -3
- package/vue/components/Section.vue +45 -14
- package/vue/components/Sidebar.vue +2 -1
- package/vue/components/SidebarNavLink.vue +6 -0
- package/vue/components/Table.vue +1113 -73
- package/vue/components/TableColumnFilterPopover.vue +263 -44
- package/vue/components/TableColumnOptionsPopover.vue +1116 -0
- package/vue/components/ThemeSwitcher.vue +119 -0
- package/vue/components/TopBar.vue +2 -2
- package/vue/composables/tableColumnPresets.js +296 -0
- package/vue/composables/tableFilterQuery.js +202 -0
- package/vue/composables/tableFilters.js +167 -3
- package/vue/composables/useCustomTheme.js +167 -24
- package/vue/data/examplePeople.js +2 -6
- package/vue/examples/App.vue +1 -1
- package/vue/examples/FormExample.vue +40 -6
- package/vue/examples/LoginExample.vue +1 -3
- package/vue/examples/TableExample.vue +329 -11
- package/vue/examples/TableRemoteExample.vue +112 -4
- package/vue/examples/ThemePreview.vue +17 -31
- package/public/themes/catppuccin/theme.css +0 -195
- package/public/themes/dracula/theme.css +0 -181
package/AGENTS.md
CHANGED
|
@@ -5,6 +5,8 @@ PicoCrank is a Vue component library demo built on the [Femtocrank](https://gith
|
|
|
5
5
|
## UI patterns
|
|
6
6
|
|
|
7
7
|
- [Buttons with icons](docs/buttons-with-icons.md) — Femtocrank `.inline-icon`, Hugeicons, karma variants (icon + text and icon-only).
|
|
8
|
+
- [Table filterQuery v1](docs/filter-query-v1.md) — canonical remote filter wire format for `Table` / `fetchRows`.
|
|
9
|
+
- Drop-in themes: `ThemeSwitcher` + `useCustomTheme()`; supplemental themes (Catppuccin, Dracula / Alucard, Gruvbox, waffles) bundled by default, loaded only when `includeSupplementalThemes` is set.
|
|
8
10
|
|
|
9
11
|
## Examples app
|
|
10
12
|
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
# Table filterQuery v1
|
|
2
|
+
|
|
3
|
+
Canonical wire format for PicoCrank `Table` column filters. Use **`filterQuery`** in remote integrations (`fetchRows`, server persistence). The table builds this object from UI state; client-side tables apply it with `applyFilterQuery`.
|
|
4
|
+
|
|
5
|
+
Implementation: [`vue/composables/tableFilterQuery.js`](../vue/composables/tableFilterQuery.js).
|
|
6
|
+
|
|
7
|
+
Live reference: [`vue/examples/TableRemoteExample.vue`](../vue/examples/TableRemoteExample.vue) (`fetchRows` logs `query.filterQuery`).
|
|
8
|
+
|
|
9
|
+
## Query payload
|
|
10
|
+
|
|
11
|
+
Remote `fetchRows` and `@query-change` (when `fetchRows` is set) receive:
|
|
12
|
+
|
|
13
|
+
```ts
|
|
14
|
+
{
|
|
15
|
+
page: number
|
|
16
|
+
pageSize: number
|
|
17
|
+
sortBy: string | null
|
|
18
|
+
sortDir: 'asc' | 'desc'
|
|
19
|
+
filterQuery: FilterQueryV1
|
|
20
|
+
}
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
> **Deprecation:** In **remote mode**, `filters` is **not** included in the query object. Read `filterQuery` only. Local (non-remote) `@query-change` still emits `filters` for backward compatibility; prefer `filterQuery` for new code.
|
|
24
|
+
|
|
25
|
+
## FilterQueryV1 shape
|
|
26
|
+
|
|
27
|
+
```json
|
|
28
|
+
{
|
|
29
|
+
"version": 1,
|
|
30
|
+
"match": "all",
|
|
31
|
+
"columns": [
|
|
32
|
+
{
|
|
33
|
+
"key": "name",
|
|
34
|
+
"match": "all",
|
|
35
|
+
"filters": [
|
|
36
|
+
{
|
|
37
|
+
"id": "filter-…",
|
|
38
|
+
"name": "Managers",
|
|
39
|
+
"enabled": true,
|
|
40
|
+
"type": "text",
|
|
41
|
+
"operator": "contains",
|
|
42
|
+
"value": "Alice"
|
|
43
|
+
}
|
|
44
|
+
]
|
|
45
|
+
}
|
|
46
|
+
]
|
|
47
|
+
}
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
| Field | Type | Description |
|
|
51
|
+
|-------|------|-------------|
|
|
52
|
+
| `version` | `1` | Schema version. Always `1` for v1. |
|
|
53
|
+
| `match` | `'all' \| 'any'` | How to combine **columns**. `'all'` = every column group must match (AND). `'any'` = at least one column group matches (OR). UI-built queries use `'all'`. |
|
|
54
|
+
| `columns` | array | Non-empty list of per-column filter groups. Unknown or non-filterable columns are dropped on normalize. |
|
|
55
|
+
| `columns[].key` | string | Header `key`. |
|
|
56
|
+
| `columns[].match` | `'all' \| 'any'` | How to combine **filters within the column**. `'all'` = every enabled filter must match (AND). `'any'` = at least one enabled filter matches (OR). UI-built queries use `'all'`. |
|
|
57
|
+
| `columns[].filters` | array | One or more filter entries (see below). |
|
|
58
|
+
|
|
59
|
+
### Filter entry fields
|
|
60
|
+
|
|
61
|
+
| Field | Required | Description |
|
|
62
|
+
|-------|----------|-------------|
|
|
63
|
+
| `type` | yes | `'text'`, `'number'`, `'boolean'`, or `'select'`. Usually matches header `filterType`. |
|
|
64
|
+
| `enabled` | no | Default `true`. Disabled entries are ignored when applying. |
|
|
65
|
+
| `id` | no | Stable id for UI toggling; safe to ignore on the server. |
|
|
66
|
+
| `name` | no | Optional display label from the filter popover. |
|
|
67
|
+
| `operator` | text/number | See operators below. |
|
|
68
|
+
| `value` | text/number/boolean | Comparison value. |
|
|
69
|
+
| `values` | select | Array of selected values (OR within the select filter: row value must match **any** selected value). |
|
|
70
|
+
|
|
71
|
+
Inactive or invalid entries (empty text, invalid number, empty select) are omitted when the query is built from UI state.
|
|
72
|
+
|
|
73
|
+
## Filter types and operators
|
|
74
|
+
|
|
75
|
+
### Text (`type: 'text'`)
|
|
76
|
+
|
|
77
|
+
Case-insensitive comparison on stringified cell values.
|
|
78
|
+
|
|
79
|
+
| `operator` | Meaning |
|
|
80
|
+
|------------|---------|
|
|
81
|
+
| `contains` | Cell contains substring (default). |
|
|
82
|
+
| `equals` | Cell equals string (case-insensitive). |
|
|
83
|
+
| `startsWith` | Cell starts with string. |
|
|
84
|
+
| `empty` | Cell is empty / whitespace only (`value` ignored). |
|
|
85
|
+
|
|
86
|
+
### Number (`type: 'number'`)
|
|
87
|
+
|
|
88
|
+
Numeric comparison; non-numeric cells do not match.
|
|
89
|
+
|
|
90
|
+
| `operator` | Meaning |
|
|
91
|
+
|------------|---------|
|
|
92
|
+
| `eq` | Equal (default). |
|
|
93
|
+
| `gt` | Greater than. |
|
|
94
|
+
| `gte` | Greater or equal. |
|
|
95
|
+
| `lt` | Less than. |
|
|
96
|
+
| `lte` | Less or equal. |
|
|
97
|
+
|
|
98
|
+
### Boolean (`type: 'boolean'`)
|
|
99
|
+
|
|
100
|
+
| `value` | Meaning |
|
|
101
|
+
|---------|---------|
|
|
102
|
+
| `true` | Cell is truthy. |
|
|
103
|
+
| `false` | Cell is falsy. |
|
|
104
|
+
|
|
105
|
+
### Select (`type: 'select'`)
|
|
106
|
+
|
|
107
|
+
| Field | Meaning |
|
|
108
|
+
|-------|---------|
|
|
109
|
+
| `values` | Non-empty array. Row matches if cell value equals **any** entry (strict or string-coerced equality). |
|
|
110
|
+
|
|
111
|
+
## Matching semantics (summary)
|
|
112
|
+
|
|
113
|
+
1. For each **column** in `columns`, evaluate enabled filters using that column’s `match` (`all` / `any`).
|
|
114
|
+
2. Combine column results using top-level `match` (`all` / `any`).
|
|
115
|
+
3. Columns with no active filters are ignored.
|
|
116
|
+
|
|
117
|
+
Default UI behavior: **AND** across columns, **AND** across filters on the same column.
|
|
118
|
+
|
|
119
|
+
## JavaScript helpers
|
|
120
|
+
|
|
121
|
+
```js
|
|
122
|
+
import {
|
|
123
|
+
buildFilterQuery,
|
|
124
|
+
normalizeFilterQuery,
|
|
125
|
+
applyFilterQuery,
|
|
126
|
+
filterQueryToFilters,
|
|
127
|
+
countActiveFilterQuery,
|
|
128
|
+
isFilterQueryActive,
|
|
129
|
+
cloneFilterQuery,
|
|
130
|
+
FILTER_QUERY_VERSION,
|
|
131
|
+
} from '../composables/tableFilterQuery.js'
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
| Function | Purpose |
|
|
135
|
+
|----------|---------|
|
|
136
|
+
| `buildFilterQuery(filters, headers, rows)` | UI storage `{ [columnKey]: entries[] }` → `FilterQueryV1`. |
|
|
137
|
+
| `normalizeFilterQuery(filterQuery, headers, rows)` | Validate, drop unknown columns, normalize filter specs. |
|
|
138
|
+
| `applyFilterQuery(rows, headers, filterQuery)` | Client-side row filtering (same semantics as the table). |
|
|
139
|
+
| `filterQueryToFilters(filterQuery, headers, rows)` | Convert back to UI storage shape (e.g. after loading from an API). |
|
|
140
|
+
| `countActiveFilterQuery(filterQuery, headers, rows)` | Count enabled, active filters. |
|
|
141
|
+
|
|
142
|
+
## Remote `fetchRows` example
|
|
143
|
+
|
|
144
|
+
```js
|
|
145
|
+
async function fetchRows(query, { signal }) {
|
|
146
|
+
// query.filterQuery — use this, not query.filters
|
|
147
|
+
const res = await api.listRows({
|
|
148
|
+
page: query.page,
|
|
149
|
+
pageSize: query.pageSize,
|
|
150
|
+
sortBy: query.sortBy,
|
|
151
|
+
sortDir: query.sortDir,
|
|
152
|
+
filterQuery: query.filterQuery,
|
|
153
|
+
signal,
|
|
154
|
+
})
|
|
155
|
+
return { rows: res.rows, total: res.total }
|
|
156
|
+
}
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
To mirror client semantics in Node without reimplementing operators, reuse `applyFilterQuery` on an in-memory dataset (as in `TableRemoteExample`), or port the logic to your query layer.
|
|
160
|
+
|
|
161
|
+
## Layout presets vs filterQuery
|
|
162
|
+
|
|
163
|
+
Saved layouts (localStorage or `layoutPresets` callbacks) store **`filters`** (UI storage shape) inside `state`, not `filterQuery`. When a preset is applied, the table rebuilds `filterQuery` automatically. Server-side layout persistence can store either:
|
|
164
|
+
|
|
165
|
+
- **`state.filters`** — round-trips the table UI exactly, or
|
|
166
|
+
- **`filterQuery`** — if you control load/save; convert with `filterQueryToFilters` before `setFilters` if needed.
|
|
167
|
+
|
|
168
|
+
## Header configuration
|
|
169
|
+
|
|
170
|
+
Set `filterType` on headers for remote tables (empty `:data` cannot infer types):
|
|
171
|
+
|
|
172
|
+
```js
|
|
173
|
+
{ key: 'age', label: 'Age', filterable: true, filterType: 'number' }
|
|
174
|
+
{ key: 'city', label: 'City', filterable: true, filterType: 'select', filterOptions: […] }
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
## Versioning
|
|
178
|
+
|
|
179
|
+
- **v1** (`version: 1`): current schema.
|
|
180
|
+
- Servers should ignore unknown top-level fields and reject unsupported `version` values explicitly if strict validation is required.
|
package/logo.svg
CHANGED
|
@@ -8,8 +8,8 @@
|
|
|
8
8
|
sodipodi:docname="logo.svg"
|
|
9
9
|
inkscape:version="1.4.2 (ebf0e940d0, 2025-05-08)"
|
|
10
10
|
inkscape:export-filename="logo.png"
|
|
11
|
-
inkscape:export-xdpi="
|
|
12
|
-
inkscape:export-ydpi="
|
|
11
|
+
inkscape:export-xdpi="96"
|
|
12
|
+
inkscape:export-ydpi="96"
|
|
13
13
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
|
14
14
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
|
15
15
|
xmlns="http://www.w3.org/2000/svg"
|
|
@@ -26,12 +26,12 @@
|
|
|
26
26
|
inkscape:pagecheckerboard="1"
|
|
27
27
|
inkscape:deskcolor="#d1d1d1"
|
|
28
28
|
inkscape:zoom="2"
|
|
29
|
-
inkscape:cx="
|
|
29
|
+
inkscape:cx="113.75"
|
|
30
30
|
inkscape:cy="158.25"
|
|
31
|
-
inkscape:window-width="
|
|
32
|
-
inkscape:window-height="
|
|
33
|
-
inkscape:window-x="
|
|
34
|
-
inkscape:window-y="
|
|
31
|
+
inkscape:window-width="2160"
|
|
32
|
+
inkscape:window-height="3806"
|
|
33
|
+
inkscape:window-x="6000"
|
|
34
|
+
inkscape:window-y="514"
|
|
35
35
|
inkscape:window-maximized="1"
|
|
36
36
|
inkscape:current-layer="g2" />
|
|
37
37
|
<g
|
package/package.json
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "picocrank",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.25.0",
|
|
4
4
|
"description": "A set of zero dependency Vue components built on the Femtocrank global CSS theme.",
|
|
5
5
|
"main": "vite.config.js",
|
|
6
6
|
"scripts": {
|
|
7
|
+
"dev": "vite",
|
|
7
8
|
"build": "vite build --base './'",
|
|
9
|
+
"build:minimal": "VITE_INCLUDE_SUPPLEMENTAL_THEMES=false vite build --base './'",
|
|
8
10
|
"preview": "vite preview",
|
|
9
11
|
"test": "echo \"Error: no test specified\" && exit 1"
|
|
10
12
|
},
|
|
@@ -18,12 +20,12 @@
|
|
|
18
20
|
"url": "https://github.com/jamesread/picocrank/issues"
|
|
19
21
|
},
|
|
20
22
|
"dependencies": {
|
|
21
|
-
"@hugeicons/core-free-icons": "^4.
|
|
22
|
-
"@hugeicons/vue": "^1.0.
|
|
23
|
+
"@hugeicons/core-free-icons": "^4.3.0",
|
|
24
|
+
"@hugeicons/vue": "^1.0.8",
|
|
23
25
|
"@vitejs/plugin-vue": "^6.0.8",
|
|
24
|
-
"femtocrank": "^2.
|
|
26
|
+
"femtocrank": "^2.6.0",
|
|
25
27
|
"unplugin-vue-components": "^32.1.0",
|
|
26
|
-
"vite": "^8.2.
|
|
28
|
+
"vite": "^8.2.2",
|
|
27
29
|
"vue": "^3.5.41",
|
|
28
30
|
"vue-router": "^5.2.0"
|
|
29
31
|
},
|
package/public/themes/README.md
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
# Drop-in themes
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
App themes are **not** part of the PicoCrank Vite/JS CSS bundle. Drop a folder under `public/themes/` and restart the dev server (or rebuild) to make it discoverable.
|
|
4
|
+
|
|
5
|
+
**Supplemental themes** (Catppuccin Latte / Frappé, Dracula / Alucard, Gruvbox (Dark+Light), waffles) live in `supplemental-themes/` at the repo root. They are **included in the default build** but are **not listed or loaded** unless the app sets `includeSupplementalThemes: true` on `ThemeSwitcher` or `useCustomTheme()`.
|
|
4
6
|
|
|
5
7
|
## Layout
|
|
6
8
|
|
|
@@ -24,6 +26,21 @@ body {
|
|
|
24
26
|
}
|
|
25
27
|
```
|
|
26
28
|
|
|
29
|
+
For supplemental themes, use `/supplemental-themes/<name>/…`.
|
|
30
|
+
|
|
27
31
|
## Switching
|
|
28
32
|
|
|
29
|
-
|
|
33
|
+
Use `ThemeSwitcher` or `useCustomTheme()`:
|
|
34
|
+
|
|
35
|
+
```vue
|
|
36
|
+
<!-- App themes only (default) -->
|
|
37
|
+
<ThemeSwitcher />
|
|
38
|
+
|
|
39
|
+
<!-- Also list bundled supplemental themes -->
|
|
40
|
+
<ThemeSwitcher :include-supplemental-themes="true" />
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
- `includeSupplementalThemes` (default `false`) — list and load supplemental themes when present.
|
|
44
|
+
- `VITE_INCLUDE_SUPPLEMENTAL_THEMES=false` — omit supplemental theme files from `dist` (`npm run build:minimal`).
|
|
45
|
+
|
|
46
|
+
Light/dark mode (header sun/moon) is separate from drop-in themes.
|
package/public/themes/index.json
CHANGED
package/styles/dark-theme.css
CHANGED
|
@@ -5,6 +5,7 @@ html[data-theme="dark"] {
|
|
|
5
5
|
--border-color: #595959;
|
|
6
6
|
--hover-background-color: #1d345c;
|
|
7
7
|
--hover-text-color: #fff;
|
|
8
|
+
--text-color: #ddd;
|
|
8
9
|
--header-hover-background-color: #1d345c;
|
|
9
10
|
--standout-bg-color: #1f1f1f;
|
|
10
11
|
--disabled-background-color: #333;
|
|
@@ -111,6 +112,18 @@ html[data-theme="dark"] {
|
|
|
111
112
|
color: white;
|
|
112
113
|
}
|
|
113
114
|
|
|
115
|
+
& table thead th.sortable:hover,
|
|
116
|
+
& table thead th.filtered,
|
|
117
|
+
& table thead th.menu-open {
|
|
118
|
+
color: var(--text-color);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
& th.actions .actions-menu-trigger button:hover,
|
|
122
|
+
& th.actions .actions-menu-trigger button:focus-visible {
|
|
123
|
+
background-color: var(--hover-background-color);
|
|
124
|
+
color: var(--text-color);
|
|
125
|
+
}
|
|
126
|
+
|
|
114
127
|
& footer span {
|
|
115
128
|
background-color: #444;
|
|
116
129
|
}
|
|
@@ -177,8 +190,10 @@ html[data-theme="dark"] {
|
|
|
177
190
|
box-shadow: 0 0 .5em #121212;
|
|
178
191
|
}
|
|
179
192
|
|
|
180
|
-
& aside :is(ul, menu) li a:hover
|
|
193
|
+
& aside :is(ul, menu) li a:hover,
|
|
194
|
+
& aside :is(ul, menu) li a:focus-visible {
|
|
181
195
|
background-color: var(--hover-background-color);
|
|
196
|
+
color: var(--hover-text-color, #fff);
|
|
182
197
|
}
|
|
183
198
|
|
|
184
199
|
& dt {
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# PicoCrank supplemental themes
|
|
2
|
+
|
|
3
|
+
Optional drop-in themes (Catppuccin Latte / Frappé, Dracula / Alucard, Gruvbox (Dark+Light), waffles) shipped alongside the default build unless disabled.
|
|
4
|
+
|
|
5
|
+
- **Build / dev assets:** included by default. Set `VITE_INCLUDE_SUPPLEMENTAL_THEMES=false` for a minimal build (`npm run build:minimal`).
|
|
6
|
+
- **Runtime:** opt in with `includeSupplementalThemes` on `ThemeSwitcher` or `useCustomTheme()` (default `false`). Client apps do not list or apply these themes unless explicitly enabled.
|
|
7
|
+
- **Catppuccin Latte / Frappé:** one theme folder (`catppuccin-latte-frappe`); header light/dark toggle switches between Latte (light) and Frappé (dark).
|
|
8
|
+
- **Gruvbox (Dark+Light):** one theme folder (`gruvbox-dark-light`); header light/dark toggle switches between Gruvbox Dark and Light.
|
|
9
|
+
- **Dracula / Alucard:** one theme folder (`dracula-alucard`); header light/dark toggle switches between Dracula (dark) and Alucard (light).
|
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Catppuccin Latte / Frappé supplemental theme for PicoCrank / Femtocrank.
|
|
3
|
+
* Palette: https://catppuccin.com/palette/
|
|
4
|
+
*
|
|
5
|
+
* Light mode (header toggle) uses Latte; dark mode uses Frappé.
|
|
6
|
+
*
|
|
7
|
+
* Asset URLs must be absolute, e.g.:
|
|
8
|
+
* background-image: url('/supplemental-themes/catppuccin-latte-frappe/background.png');
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
html[data-theme="dark"] {
|
|
12
|
+
color-scheme: dark;
|
|
13
|
+
|
|
14
|
+
--ctp-rosewater: #f2d5cf;
|
|
15
|
+
--ctp-flamingo: #eebebe;
|
|
16
|
+
--ctp-pink: #f4b8e4;
|
|
17
|
+
--ctp-mauve: #ca9ee6;
|
|
18
|
+
--ctp-red: #e78284;
|
|
19
|
+
--ctp-maroon: #ea999c;
|
|
20
|
+
--ctp-peach: #ef9f76;
|
|
21
|
+
--ctp-yellow: #e5c890;
|
|
22
|
+
--ctp-green: #a6d189;
|
|
23
|
+
--ctp-teal: #81c8be;
|
|
24
|
+
--ctp-sky: #99d1db;
|
|
25
|
+
--ctp-sapphire: #85c1dc;
|
|
26
|
+
--ctp-blue: #8caaee;
|
|
27
|
+
--ctp-lavender: #babbf1;
|
|
28
|
+
--ctp-text: #c6d0f5;
|
|
29
|
+
--ctp-subtext1: #b5bfe2;
|
|
30
|
+
--ctp-subtext0: #a5adce;
|
|
31
|
+
--ctp-overlay2: #949cbb;
|
|
32
|
+
--ctp-overlay1: #838ba7;
|
|
33
|
+
--ctp-overlay0: #737994;
|
|
34
|
+
--ctp-surface2: #626880;
|
|
35
|
+
--ctp-surface1: #51576d;
|
|
36
|
+
--ctp-surface0: #414559;
|
|
37
|
+
--ctp-base: #303446;
|
|
38
|
+
--ctp-mantle: #292c3c;
|
|
39
|
+
--ctp-crust: #232634;
|
|
40
|
+
--ctp-shadow: rgba(35, 38, 52, 0.65);
|
|
41
|
+
--ctp-shadow-strong: rgba(35, 38, 52, 0.9);
|
|
42
|
+
--ctp-good-fg: var(--ctp-crust);
|
|
43
|
+
|
|
44
|
+
--text-color: var(--ctp-text);
|
|
45
|
+
--border-color: var(--ctp-surface1);
|
|
46
|
+
--standout-bg-color: var(--ctp-mantle);
|
|
47
|
+
--hover-background-color: var(--ctp-surface0);
|
|
48
|
+
--hover-text-color: var(--ctp-text);
|
|
49
|
+
--header-hover-background-color: var(--ctp-surface1);
|
|
50
|
+
--header-hover-text-color: var(--ctp-text);
|
|
51
|
+
--header-text-color: var(--ctp-text);
|
|
52
|
+
--disabled-background-color: var(--ctp-surface0);
|
|
53
|
+
--disabled-text-color: var(--ctp-overlay0);
|
|
54
|
+
|
|
55
|
+
--karma-good: var(--ctp-green);
|
|
56
|
+
--karma-bad: var(--ctp-red);
|
|
57
|
+
--karma-warning: var(--ctp-yellow);
|
|
58
|
+
--karma-severe: var(--ctp-maroon);
|
|
59
|
+
--karma-important: var(--ctp-peach);
|
|
60
|
+
--karma-note: var(--ctp-sapphire);
|
|
61
|
+
--karma-info: var(--ctp-surface1);
|
|
62
|
+
--karma-old: var(--ctp-overlay0);
|
|
63
|
+
|
|
64
|
+
--karma-good-fg: var(--ctp-green);
|
|
65
|
+
--karma-bad-fg: var(--ctp-red);
|
|
66
|
+
--karma-warning-fg: var(--ctp-yellow);
|
|
67
|
+
--karma-severe-fg: var(--ctp-maroon);
|
|
68
|
+
--karma-important-fg: var(--ctp-peach);
|
|
69
|
+
--karma-note-fg: var(--ctp-sapphire);
|
|
70
|
+
--karma-info-fg: var(--ctp-subtext0);
|
|
71
|
+
--karma-old-fg: var(--ctp-overlay1);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
html[data-theme="light"] {
|
|
75
|
+
color-scheme: light;
|
|
76
|
+
|
|
77
|
+
--ctp-rosewater: #dc8a78;
|
|
78
|
+
--ctp-flamingo: #dd7878;
|
|
79
|
+
--ctp-pink: #ea76cb;
|
|
80
|
+
--ctp-mauve: #8839ef;
|
|
81
|
+
--ctp-red: #d20f39;
|
|
82
|
+
--ctp-maroon: #e64553;
|
|
83
|
+
--ctp-peach: #fe640b;
|
|
84
|
+
--ctp-yellow: #df8e1d;
|
|
85
|
+
--ctp-green: #40a02b;
|
|
86
|
+
--ctp-teal: #179299;
|
|
87
|
+
--ctp-sky: #04a5e5;
|
|
88
|
+
--ctp-sapphire: #209fb5;
|
|
89
|
+
--ctp-blue: #1e66f5;
|
|
90
|
+
--ctp-lavender: #7287fd;
|
|
91
|
+
--ctp-text: #4c4f69;
|
|
92
|
+
--ctp-subtext1: #5c5f77;
|
|
93
|
+
--ctp-subtext0: #6c6f85;
|
|
94
|
+
--ctp-overlay2: #7c7f93;
|
|
95
|
+
--ctp-overlay1: #8c8fa1;
|
|
96
|
+
--ctp-overlay0: #9ca0b0;
|
|
97
|
+
--ctp-surface2: #acb0be;
|
|
98
|
+
--ctp-surface1: #bcc0cc;
|
|
99
|
+
--ctp-surface0: #ccd0da;
|
|
100
|
+
--ctp-base: #eff1f5;
|
|
101
|
+
--ctp-mantle: #e6e9ef;
|
|
102
|
+
--ctp-crust: #dce0e8;
|
|
103
|
+
--ctp-shadow: rgba(76, 79, 105, 0.12);
|
|
104
|
+
--ctp-shadow-strong: rgba(76, 79, 105, 0.16);
|
|
105
|
+
--ctp-good-fg: var(--ctp-base);
|
|
106
|
+
|
|
107
|
+
--text-color: var(--ctp-text);
|
|
108
|
+
--border-color: var(--ctp-surface1);
|
|
109
|
+
--standout-bg-color: var(--ctp-mantle);
|
|
110
|
+
--hover-background-color: var(--ctp-surface0);
|
|
111
|
+
--hover-text-color: var(--ctp-text);
|
|
112
|
+
--header-hover-background-color: var(--ctp-surface0);
|
|
113
|
+
--header-hover-text-color: var(--ctp-text);
|
|
114
|
+
--header-text-color: var(--ctp-text);
|
|
115
|
+
--disabled-background-color: var(--ctp-surface0);
|
|
116
|
+
--disabled-text-color: var(--ctp-overlay0);
|
|
117
|
+
|
|
118
|
+
--karma-good: var(--ctp-green);
|
|
119
|
+
--karma-bad: var(--ctp-red);
|
|
120
|
+
--karma-warning: var(--ctp-yellow);
|
|
121
|
+
--karma-severe: var(--ctp-maroon);
|
|
122
|
+
--karma-important: var(--ctp-peach);
|
|
123
|
+
--karma-note: var(--ctp-sapphire);
|
|
124
|
+
--karma-info: var(--ctp-surface1);
|
|
125
|
+
--karma-old: var(--ctp-overlay0);
|
|
126
|
+
|
|
127
|
+
--karma-good-fg: var(--ctp-green);
|
|
128
|
+
--karma-bad-fg: var(--ctp-red);
|
|
129
|
+
--karma-warning-fg: var(--ctp-yellow);
|
|
130
|
+
--karma-severe-fg: var(--ctp-maroon);
|
|
131
|
+
--karma-important-fg: var(--ctp-peach);
|
|
132
|
+
--karma-note-fg: var(--ctp-sapphire);
|
|
133
|
+
--karma-info-fg: var(--ctp-subtext0);
|
|
134
|
+
--karma-old-fg: var(--ctp-overlay1);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
body {
|
|
138
|
+
background-color: var(--ctp-base);
|
|
139
|
+
color: var(--ctp-text);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
header {
|
|
143
|
+
background-color: var(--ctp-crust);
|
|
144
|
+
border-bottom: 1px solid var(--ctp-surface0);
|
|
145
|
+
box-shadow: none;
|
|
146
|
+
color: var(--ctp-text);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
header a,
|
|
150
|
+
header a:visited,
|
|
151
|
+
header button,
|
|
152
|
+
header .button,
|
|
153
|
+
header h1,
|
|
154
|
+
header .topbar-link,
|
|
155
|
+
header .topbar-link:visited {
|
|
156
|
+
color: var(--header-text-color, var(--ctp-text));
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
#sidebar-toggler-button,
|
|
160
|
+
#sidebar-toggler-button.neutral,
|
|
161
|
+
header .theme-toggle,
|
|
162
|
+
header .theme-toggle.neutral,
|
|
163
|
+
header .search-trigger,
|
|
164
|
+
header .search-trigger.neutral,
|
|
165
|
+
header .user-info {
|
|
166
|
+
background-color: transparent;
|
|
167
|
+
border: 0;
|
|
168
|
+
color: var(--header-text-color, var(--ctp-text));
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
#sidebar-toggler-button:hover,
|
|
172
|
+
header .theme-toggle:hover,
|
|
173
|
+
header .search-trigger:hover,
|
|
174
|
+
header .user-info:hover {
|
|
175
|
+
background-color: var(--header-hover-background-color);
|
|
176
|
+
color: var(--header-hover-text-color);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
aside,
|
|
180
|
+
#sidebar {
|
|
181
|
+
background-color: var(--ctp-mantle);
|
|
182
|
+
color: var(--ctp-text);
|
|
183
|
+
border-color: var(--ctp-surface0);
|
|
184
|
+
box-shadow: 0.15em 0 0.6em var(--ctp-shadow);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
aside a,
|
|
188
|
+
aside a:visited {
|
|
189
|
+
color: var(--ctp-text);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
aside a:hover,
|
|
193
|
+
aside a:focus-visible,
|
|
194
|
+
aside :is(ul, menu) li a:hover,
|
|
195
|
+
aside :is(ul, menu) li a:focus-visible,
|
|
196
|
+
aside .nav-section-header:hover,
|
|
197
|
+
aside .nav-section-header:focus-visible {
|
|
198
|
+
background-color: var(--ctp-surface0);
|
|
199
|
+
color: var(--ctp-text);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
section {
|
|
203
|
+
background-color: var(--ctp-mantle);
|
|
204
|
+
color: var(--ctp-text);
|
|
205
|
+
box-shadow: 0 0 0.5em var(--ctp-shadow-strong);
|
|
206
|
+
border-color: var(--ctp-surface0);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
section .section-header,
|
|
210
|
+
section .section-subheader {
|
|
211
|
+
background-color: var(--ctp-mantle);
|
|
212
|
+
border-bottom-color: var(--ctp-surface0);
|
|
213
|
+
color: var(--ctp-text);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
button,
|
|
217
|
+
.button {
|
|
218
|
+
background-color: var(--ctp-surface0);
|
|
219
|
+
border: 1px solid var(--ctp-surface1);
|
|
220
|
+
color: var(--ctp-text);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
button:hover,
|
|
224
|
+
.button:hover {
|
|
225
|
+
background-color: var(--ctp-surface1);
|
|
226
|
+
color: var(--ctp-text);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
button.good,
|
|
230
|
+
.button.good {
|
|
231
|
+
background-color: var(--ctp-green);
|
|
232
|
+
border-color: var(--ctp-green);
|
|
233
|
+
color: var(--ctp-good-fg);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
button.bad,
|
|
237
|
+
.button.bad {
|
|
238
|
+
background-color: var(--ctp-red);
|
|
239
|
+
border-color: var(--ctp-red);
|
|
240
|
+
color: var(--ctp-crust);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
button.warning,
|
|
244
|
+
.button.warning {
|
|
245
|
+
background-color: var(--ctp-yellow);
|
|
246
|
+
border-color: var(--ctp-yellow);
|
|
247
|
+
color: var(--ctp-crust);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
button.neutral,
|
|
251
|
+
.button.neutral {
|
|
252
|
+
background-color: var(--ctp-surface1);
|
|
253
|
+
border-color: var(--ctp-surface2);
|
|
254
|
+
color: var(--ctp-text);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
input,
|
|
258
|
+
select,
|
|
259
|
+
textarea {
|
|
260
|
+
background-color: var(--ctp-surface0);
|
|
261
|
+
border: 1px solid var(--ctp-surface1);
|
|
262
|
+
color: var(--ctp-text);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
a {
|
|
266
|
+
color: var(--ctp-blue);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
footer {
|
|
270
|
+
color: var(--ctp-subtext0);
|
|
271
|
+
border-top-color: var(--ctp-surface0);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
.subtle {
|
|
275
|
+
color: var(--ctp-subtext0);
|
|
276
|
+
}
|