@cfasim-ui/docs 0.3.11
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/LICENSE +201 -0
- package/charts/ChartMenu/ChartMenu.vue +140 -0
- package/charts/ChartMenu/download.ts +44 -0
- package/charts/ChartTooltip/ChartTooltip.vue +97 -0
- package/charts/ChoroplethMap/ChoroplethMap.md +398 -0
- package/charts/ChoroplethMap/ChoroplethMap.vue +777 -0
- package/charts/ChoroplethMap/hsaMapping.ts +4116 -0
- package/charts/DataTable/DataTable.md +143 -0
- package/charts/DataTable/DataTable.vue +277 -0
- package/charts/LineChart/LineChart.md +472 -0
- package/charts/LineChart/LineChart.vue +1216 -0
- package/charts/index.ts +23 -0
- package/charts/tooltip-position.ts +49 -0
- package/components/Box/Box.md +49 -0
- package/components/Box/Box.vue +52 -0
- package/components/Button/Button.md +67 -0
- package/components/Button/Button.vue +81 -0
- package/components/Expander/Expander.md +34 -0
- package/components/Expander/Expander.vue +95 -0
- package/components/Hint/Hint.md +29 -0
- package/components/Hint/Hint.vue +83 -0
- package/components/Icon/Icon.md +67 -0
- package/components/Icon/Icon.vue +112 -0
- package/components/LightDarkToggle/LightDarkToggle.vue +49 -0
- package/components/NumberInput/NumberInput.md +305 -0
- package/components/NumberInput/NumberInput.vue +531 -0
- package/components/SelectBox/SelectBox.md +110 -0
- package/components/SelectBox/SelectBox.vue +195 -0
- package/components/SidebarLayout/SidebarLayout.md +104 -0
- package/components/SidebarLayout/SidebarLayout.vue +466 -0
- package/components/Spinner/Spinner.md +51 -0
- package/components/Spinner/Spinner.vue +55 -0
- package/components/TextInput/TextInput.md +82 -0
- package/components/TextInput/TextInput.vue +94 -0
- package/components/Toggle/Toggle.md +81 -0
- package/components/Toggle/Toggle.vue +81 -0
- package/components/index.ts +15 -0
- package/index.json +121 -0
- package/package.json +24 -0
- package/pyodide/index.ts +7 -0
- package/pyodide/pyodide.worker.ts +233 -0
- package/pyodide/pyodideWorkerApi.ts +102 -0
- package/pyodide/useModel.ts +86 -0
- package/pyodide/vitePlugin.js +51 -0
- package/shared/ModelOutput.ts +88 -0
- package/shared/csv.ts +22 -0
- package/shared/index.ts +24 -0
- package/shared/transferUtils.ts +126 -0
- package/shared/useUrlParams.ts +296 -0
- package/theme/all.js +5 -0
- package/theme/base.css +176 -0
- package/theme/cfasim.css +3 -0
- package/theme/theme.css +113 -0
- package/theme/themes/cdc.css +22 -0
- package/theme/utilities.css +518 -0
- package/wasm/index.ts +2 -0
- package/wasm/useModel.ts +53 -0
- package/wasm/vitePlugin.js +35 -0
- package/wasm/wasm.worker.ts +74 -0
- package/wasm/wasmWorkerApi.ts +38 -0
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
import { isRef, onMounted, onUnmounted, toRaw, watch, type Ref } from "vue";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Minimal shape of vue-router's `Router` that `useUrlParams` needs.
|
|
5
|
+
* Typed structurally so `@cfasim-ui/shared` does not depend on vue-router.
|
|
6
|
+
*/
|
|
7
|
+
export interface UrlParamsRouter {
|
|
8
|
+
replace(to: { query: Record<string, string> }): unknown;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Minimal shape of vue-router's `RouteLocationNormalized` that
|
|
13
|
+
* `useUrlParams` reads to hydrate initial state. `query` is typed as
|
|
14
|
+
* `Record<string, unknown>` because vue-router yields `string | string[] |
|
|
15
|
+
* null`; `deserialize` silently ignores non-string entries.
|
|
16
|
+
*/
|
|
17
|
+
export interface UrlParamsRoute {
|
|
18
|
+
query: Record<string, unknown>;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Defaults can be:
|
|
23
|
+
* - a plain object (sync, always available),
|
|
24
|
+
* - a Ref<T> (reactive; useful when defaults are computed),
|
|
25
|
+
* - a getter `() => T | undefined` (useful for async loads; return
|
|
26
|
+
* `undefined` until defaults are ready, then call `hydrate()` from
|
|
27
|
+
* the consumer).
|
|
28
|
+
*/
|
|
29
|
+
export type DefaultsInput<T> = T | Ref<T> | (() => T | undefined);
|
|
30
|
+
|
|
31
|
+
export type UrlParamsOptions<T> = {
|
|
32
|
+
debounceMs?: number;
|
|
33
|
+
/**
|
|
34
|
+
* Optional vue-router integration. Pass `useRouter()` and `useRoute()` from
|
|
35
|
+
* the calling component. When provided, URL reads/writes go through the
|
|
36
|
+
* router so `route.query` stays reactive. When omitted, the composable
|
|
37
|
+
* uses the browser History API directly.
|
|
38
|
+
*/
|
|
39
|
+
router?: UrlParamsRouter;
|
|
40
|
+
route?: UrlParamsRoute;
|
|
41
|
+
/**
|
|
42
|
+
* Only sync these keys. Useful when `defaults` contains labels, flags, or
|
|
43
|
+
* other fields the user never edits.
|
|
44
|
+
*/
|
|
45
|
+
include?: (keyof T)[];
|
|
46
|
+
/**
|
|
47
|
+
* Sync all keys except these. Ignored if `include` is provided.
|
|
48
|
+
*/
|
|
49
|
+
ignore?: (keyof T)[];
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
export type ResetOptions = {
|
|
53
|
+
/** Whether to clear the URL query in addition to resetting params. Default: true. */
|
|
54
|
+
clearUrl?: boolean;
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
export function serialize(value: unknown): string {
|
|
58
|
+
if (Array.isArray(value)) return value.join(",");
|
|
59
|
+
return String(value);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export function deserialize(raw: string, defaultValue: unknown): unknown {
|
|
63
|
+
if (typeof defaultValue === "boolean") return raw === "true";
|
|
64
|
+
if (typeof defaultValue === "number") {
|
|
65
|
+
const n = Number(raw);
|
|
66
|
+
return Number.isNaN(n) ? defaultValue : n;
|
|
67
|
+
}
|
|
68
|
+
if (Array.isArray(defaultValue)) {
|
|
69
|
+
return raw.split(",").map((s) => {
|
|
70
|
+
const n = Number(s);
|
|
71
|
+
return Number.isNaN(n) ? s : n;
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
return raw;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export function paramsToQuery<T extends object>(
|
|
78
|
+
params: T,
|
|
79
|
+
defaults: T,
|
|
80
|
+
): Record<string, string> {
|
|
81
|
+
const query: Record<string, string> = {};
|
|
82
|
+
for (const key of Object.keys(defaults) as (keyof T & string)[]) {
|
|
83
|
+
const serialized = serialize(params[key]);
|
|
84
|
+
const defaultSerialized = serialize(defaults[key]);
|
|
85
|
+
if (serialized !== defaultSerialized) {
|
|
86
|
+
query[key] = serialized;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
return query;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export function queryToParams<T extends object>(
|
|
93
|
+
query: Record<string, unknown>,
|
|
94
|
+
defaults: T,
|
|
95
|
+
): Partial<T> {
|
|
96
|
+
const result: Record<string, unknown> = {};
|
|
97
|
+
const defaultsRecord = defaults as Record<string, unknown>;
|
|
98
|
+
for (const [key, raw] of Object.entries(query)) {
|
|
99
|
+
if (!(key in defaultsRecord)) continue;
|
|
100
|
+
if (typeof raw !== "string") continue;
|
|
101
|
+
result[key] = deserialize(raw, defaultsRecord[key]);
|
|
102
|
+
}
|
|
103
|
+
return result as Partial<T>;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function readLocationQuery(): Record<string, string> {
|
|
107
|
+
const out: Record<string, string> = {};
|
|
108
|
+
const search = new URLSearchParams(window.location.search);
|
|
109
|
+
for (const [k, v] of search) out[k] = v;
|
|
110
|
+
return out;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function writeLocationQuery(query: Record<string, string>) {
|
|
114
|
+
const params = new URLSearchParams();
|
|
115
|
+
for (const [k, v] of Object.entries(query)) params.set(k, v);
|
|
116
|
+
const qs = params.toString();
|
|
117
|
+
const url =
|
|
118
|
+
window.location.pathname + (qs ? `?${qs}` : "") + window.location.hash;
|
|
119
|
+
// Preserve whatever state object the current history entry holds (vue-router
|
|
120
|
+
// stores its own bookkeeping there) so we don't clobber it.
|
|
121
|
+
window.history.replaceState(window.history.state, "", url);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function toGetter<T>(input: DefaultsInput<T>): () => T | undefined {
|
|
125
|
+
if (typeof input === "function") return input as () => T | undefined;
|
|
126
|
+
if (isRef(input)) return () => (input as Ref<T>).value;
|
|
127
|
+
return () => input as T;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function filterKeys<T extends object>(
|
|
131
|
+
obj: T,
|
|
132
|
+
include?: (keyof T)[],
|
|
133
|
+
ignore?: (keyof T)[],
|
|
134
|
+
): T {
|
|
135
|
+
if (!include && !ignore) return obj;
|
|
136
|
+
const src = obj as Record<string, unknown>;
|
|
137
|
+
const out: Record<string, unknown> = {};
|
|
138
|
+
for (const k of Object.keys(src)) {
|
|
139
|
+
const key = k as keyof T;
|
|
140
|
+
if (include) {
|
|
141
|
+
if (include.includes(key)) out[k] = src[k];
|
|
142
|
+
} else if (ignore) {
|
|
143
|
+
if (!ignore.includes(key)) out[k] = src[k];
|
|
144
|
+
} else {
|
|
145
|
+
out[k] = src[k];
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
return out as T;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Syncs a reactive params object with the URL query string. Only values
|
|
153
|
+
* that differ from defaults appear in the URL. Accepts either a `ref()`
|
|
154
|
+
* or a `reactive()` object.
|
|
155
|
+
*
|
|
156
|
+
* For async defaults (e.g. loaded from WASM/network), pass a getter or ref
|
|
157
|
+
* that returns `undefined` until ready, and call `hydrate()` once defaults
|
|
158
|
+
* are available. The composable also attempts hydration on mount; the
|
|
159
|
+
* first successful attempt "locks in" writes going forward.
|
|
160
|
+
*
|
|
161
|
+
* Browser back/forward and external `route.query` changes are picked up
|
|
162
|
+
* automatically (via `popstate` or a route watcher).
|
|
163
|
+
*
|
|
164
|
+
* By default, uses the browser History API directly. Consumers using
|
|
165
|
+
* vue-router can pass `{ router: useRouter(), route: useRoute() }` so that
|
|
166
|
+
* `route.query` stays reactive.
|
|
167
|
+
*/
|
|
168
|
+
export function useUrlParams<T extends object>(
|
|
169
|
+
params: T | Ref<T>,
|
|
170
|
+
defaults: DefaultsInput<T>,
|
|
171
|
+
options: UrlParamsOptions<T> = {},
|
|
172
|
+
) {
|
|
173
|
+
const debounceMs = options.debounceMs ?? 300;
|
|
174
|
+
const { router, route, include, ignore } = options;
|
|
175
|
+
const getDefaults = toGetter<T>(defaults);
|
|
176
|
+
const scopedDefaults = (): T | undefined => {
|
|
177
|
+
const d = getDefaults();
|
|
178
|
+
return d === undefined ? undefined : filterKeys(d, include, ignore);
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
function readQuery(): Record<string, unknown> {
|
|
182
|
+
if (route) return route.query;
|
|
183
|
+
return readLocationQuery();
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function writeQuery(query: Record<string, string>) {
|
|
187
|
+
if (router) {
|
|
188
|
+
router.replace({ query });
|
|
189
|
+
} else {
|
|
190
|
+
writeLocationQuery(query);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function read(): T {
|
|
195
|
+
return isRef(params) ? params.value : params;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
function apply(overrides: Partial<T>, d: T) {
|
|
199
|
+
// Layer order (later wins): current params -> defaults -> URL overrides.
|
|
200
|
+
// Starting from current preserves keys outside the sync scope (i.e.
|
|
201
|
+
// those filtered out by `include`/`ignore`), which matters when `params`
|
|
202
|
+
// is a ref and the whole object gets replaced below. `toRaw` unwraps
|
|
203
|
+
// reactive proxies so `structuredClone` doesn't choke.
|
|
204
|
+
const current = toRaw(read());
|
|
205
|
+
const merged = {
|
|
206
|
+
...structuredClone(current),
|
|
207
|
+
...structuredClone(toRaw(d)),
|
|
208
|
+
...overrides,
|
|
209
|
+
} as T;
|
|
210
|
+
if (isRef(params)) {
|
|
211
|
+
params.value = merged;
|
|
212
|
+
} else {
|
|
213
|
+
Object.assign(params, merged);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
let hydrated = false;
|
|
218
|
+
|
|
219
|
+
function hydrate(): boolean {
|
|
220
|
+
const d = scopedDefaults();
|
|
221
|
+
if (d === undefined) return false;
|
|
222
|
+
const overrides = queryToParams(readQuery(), d);
|
|
223
|
+
if (Object.keys(overrides).length > 0) apply(overrides, d);
|
|
224
|
+
hydrated = true;
|
|
225
|
+
return true;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
function syncFromUrl() {
|
|
229
|
+
if (!hydrated) {
|
|
230
|
+
hydrate();
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
233
|
+
const d = scopedDefaults();
|
|
234
|
+
if (d === undefined) return;
|
|
235
|
+
const overrides = queryToParams(readQuery(), d);
|
|
236
|
+
apply(overrides, d);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
onMounted(() => {
|
|
240
|
+
hydrate();
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
let debounceTimer: ReturnType<typeof setTimeout> | null = null;
|
|
244
|
+
|
|
245
|
+
watch(
|
|
246
|
+
() => read(),
|
|
247
|
+
() => {
|
|
248
|
+
if (!hydrated) return;
|
|
249
|
+
if (debounceTimer) clearTimeout(debounceTimer);
|
|
250
|
+
debounceTimer = setTimeout(() => {
|
|
251
|
+
const d = scopedDefaults();
|
|
252
|
+
if (d === undefined) return;
|
|
253
|
+
writeQuery(paramsToQuery(read(), d));
|
|
254
|
+
}, debounceMs);
|
|
255
|
+
},
|
|
256
|
+
{ deep: true },
|
|
257
|
+
);
|
|
258
|
+
|
|
259
|
+
// React to external query changes (back/forward, programmatic nav).
|
|
260
|
+
let stopRouteWatch: (() => void) | null = null;
|
|
261
|
+
function onPopState() {
|
|
262
|
+
syncFromUrl();
|
|
263
|
+
}
|
|
264
|
+
onMounted(() => {
|
|
265
|
+
if (route) {
|
|
266
|
+
stopRouteWatch = watch(
|
|
267
|
+
() => route.query,
|
|
268
|
+
() => syncFromUrl(),
|
|
269
|
+
{ deep: true },
|
|
270
|
+
);
|
|
271
|
+
} else {
|
|
272
|
+
window.addEventListener("popstate", onPopState);
|
|
273
|
+
}
|
|
274
|
+
});
|
|
275
|
+
onUnmounted(() => {
|
|
276
|
+
if (stopRouteWatch) stopRouteWatch();
|
|
277
|
+
else window.removeEventListener("popstate", onPopState);
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
function reset(opts: ResetOptions = {}) {
|
|
281
|
+
const { clearUrl = true } = opts;
|
|
282
|
+
const d = scopedDefaults();
|
|
283
|
+
if (d === undefined) return;
|
|
284
|
+
apply({}, d);
|
|
285
|
+
if (clearUrl) {
|
|
286
|
+
if (debounceTimer) clearTimeout(debounceTimer);
|
|
287
|
+
writeQuery({});
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
onUnmounted(() => {
|
|
292
|
+
if (debounceTimer) clearTimeout(debounceTimer);
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
return { reset, hydrate };
|
|
296
|
+
}
|
package/theme/all.js
ADDED
package/theme/base.css
ADDED
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
@layer base {
|
|
2
|
+
*,
|
|
3
|
+
*::before,
|
|
4
|
+
*::after {
|
|
5
|
+
box-sizing: border-box;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
/* Root & Color Scheme */
|
|
9
|
+
|
|
10
|
+
:root {
|
|
11
|
+
color-scheme: light dark;
|
|
12
|
+
-webkit-text-size-adjust: 100%;
|
|
13
|
+
scroll-behavior: smooth;
|
|
14
|
+
/* Add this if you have scrollable content */
|
|
15
|
+
/* scrollbar-gutter: stable; */
|
|
16
|
+
--text-wrap-heading: balance;
|
|
17
|
+
--text-wrap-body: balance;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/* Accessibility */
|
|
21
|
+
|
|
22
|
+
:target {
|
|
23
|
+
scroll-margin-block: 5ex;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
@media (prefers-reduced-motion: reduce) {
|
|
27
|
+
html {
|
|
28
|
+
scroll-behavior: auto;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/* Typography */
|
|
33
|
+
|
|
34
|
+
body {
|
|
35
|
+
min-block-size: 100svb;
|
|
36
|
+
line-height: 1.5;
|
|
37
|
+
margin: 0;
|
|
38
|
+
hanging-punctuation: first allow-end last;
|
|
39
|
+
overflow-wrap: break-word;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
h1,
|
|
43
|
+
h2,
|
|
44
|
+
h3,
|
|
45
|
+
h4,
|
|
46
|
+
h5,
|
|
47
|
+
h6 {
|
|
48
|
+
line-height: 1.1;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
p,
|
|
52
|
+
li,
|
|
53
|
+
figcaption {
|
|
54
|
+
text-wrap: pretty;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
h1,
|
|
58
|
+
h2,
|
|
59
|
+
h3,
|
|
60
|
+
h4,
|
|
61
|
+
h5,
|
|
62
|
+
h6,
|
|
63
|
+
figcaption {
|
|
64
|
+
text-wrap: balance;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
a:not([class]) {
|
|
68
|
+
color: currentColor;
|
|
69
|
+
text-decoration-skip-ink: auto;
|
|
70
|
+
text-decoration-thickness: 1px;
|
|
71
|
+
text-underline-offset: 0.15em;
|
|
72
|
+
text-decoration-color: color-mix(in srgb, currentColor, transparent 67%);
|
|
73
|
+
|
|
74
|
+
&:hover {
|
|
75
|
+
text-decoration-color: currentColor;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
pre,
|
|
80
|
+
code {
|
|
81
|
+
tab-size: 2;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/* Lists */
|
|
85
|
+
|
|
86
|
+
ul[role="list"],
|
|
87
|
+
ol[role="list"] {
|
|
88
|
+
list-style: none;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/* Forms */
|
|
92
|
+
|
|
93
|
+
input,
|
|
94
|
+
button,
|
|
95
|
+
textarea,
|
|
96
|
+
select {
|
|
97
|
+
font: inherit;
|
|
98
|
+
/* https://adactio.com/journal/21027 */
|
|
99
|
+
hanging-punctuation: none;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
textarea:not([rows]) {
|
|
103
|
+
min-height: 10em;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/* Media */
|
|
107
|
+
|
|
108
|
+
img,
|
|
109
|
+
video,
|
|
110
|
+
picture,
|
|
111
|
+
canvas,
|
|
112
|
+
svg {
|
|
113
|
+
max-width: 100%;
|
|
114
|
+
display: block;
|
|
115
|
+
height: auto;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/* Base styles (applied after reset) */
|
|
120
|
+
|
|
121
|
+
:root {
|
|
122
|
+
font-family: var(--font-family);
|
|
123
|
+
line-height: var(--line-height-normal);
|
|
124
|
+
color: var(--color-text);
|
|
125
|
+
background-color: var(--color-bg-0);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
body {
|
|
129
|
+
font-size: var(--font-size-md);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
:root,
|
|
133
|
+
body {
|
|
134
|
+
overflow: hidden;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
h1,
|
|
138
|
+
h2,
|
|
139
|
+
h3,
|
|
140
|
+
h4,
|
|
141
|
+
h5,
|
|
142
|
+
h6 {
|
|
143
|
+
font-family: var(--font-family-heading);
|
|
144
|
+
font-weight: var(--font-weight-heading);
|
|
145
|
+
line-height: var(--line-height-tight);
|
|
146
|
+
margin-bottom: var(--space-3);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
h1 {
|
|
150
|
+
font-size: var(--font-size-2xl);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
h2 {
|
|
154
|
+
font-size: var(--font-size-xl);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
p {
|
|
158
|
+
margin-bottom: var(--space-2);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
section {
|
|
162
|
+
margin-bottom: var(--space-6);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
code {
|
|
166
|
+
font-family: var(--font-family-mono);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
a {
|
|
170
|
+
color: var(--color-link);
|
|
171
|
+
text-decoration: none;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
a:hover {
|
|
175
|
+
text-decoration: underline;
|
|
176
|
+
}
|
package/theme/cfasim.css
ADDED
package/theme/theme.css
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
:root {
|
|
2
|
+
color-scheme: light dark;
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
:root.light {
|
|
6
|
+
color-scheme: light;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
:root.dark {
|
|
10
|
+
color-scheme: dark;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
:root {
|
|
14
|
+
/* Primary */
|
|
15
|
+
--color-primary: light-dark(#0066ff, #4d94ff);
|
|
16
|
+
--color-primary-hover: light-dark(#0052cc, #66a3ff);
|
|
17
|
+
--color-primary-active: light-dark(#0047b3, #3385ff);
|
|
18
|
+
--color-text-on-primary: #ffffff;
|
|
19
|
+
|
|
20
|
+
/* Backgrounds */
|
|
21
|
+
--color-bg-0: light-dark(#ffffff, #1a1a1a);
|
|
22
|
+
--color-bg-1: light-dark(#f8f9fa, #2d2d2d);
|
|
23
|
+
--color-bg-2: light-dark(#e9ecef, #3d3d3d);
|
|
24
|
+
--color-bg-3: light-dark(#dee2e6, #4d4d4d);
|
|
25
|
+
|
|
26
|
+
/* Text */
|
|
27
|
+
--color-text: light-dark(#212529, #f8f9fa);
|
|
28
|
+
--color-text-secondary: light-dark(#495057, #adb5bd);
|
|
29
|
+
--color-text-tertiary: light-dark(#adb5bd, #6c757d);
|
|
30
|
+
--color-text-disabled: light-dark(#ced4da, #495057);
|
|
31
|
+
|
|
32
|
+
/* Links */
|
|
33
|
+
--color-link: var(--color-primary);
|
|
34
|
+
|
|
35
|
+
/* Borders */
|
|
36
|
+
--color-border: light-dark(#dee2e6, #3a3f44);
|
|
37
|
+
--color-border-header: light-dark(#c4c9ce, #555d64);
|
|
38
|
+
--color-border-hover: light-dark(#adb5bd, #555d64);
|
|
39
|
+
--color-border-focus: var(--color-primary);
|
|
40
|
+
|
|
41
|
+
/* Status */
|
|
42
|
+
--color-error: light-dark(#dc3545, #ff6b6b);
|
|
43
|
+
--color-success: light-dark(#28a745, #40c057);
|
|
44
|
+
--color-warning: light-dark(#ffc107, #ffd43b);
|
|
45
|
+
|
|
46
|
+
/* Typography */
|
|
47
|
+
--font-family:
|
|
48
|
+
-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial,
|
|
49
|
+
sans-serif;
|
|
50
|
+
--font-family-heading: var(--font-family);
|
|
51
|
+
--font-weight-heading: bold;
|
|
52
|
+
--font-family-mono: "Consolas", "Monaco", "Courier New", monospace;
|
|
53
|
+
--font-size-xs: 0.75rem;
|
|
54
|
+
--font-size-sm: 0.875rem;
|
|
55
|
+
--font-size-md: 1rem;
|
|
56
|
+
--font-size-lg: 1.25rem;
|
|
57
|
+
--font-size-xl: 1.5rem;
|
|
58
|
+
--font-size-2xl: 2rem;
|
|
59
|
+
--line-height-tight: 1.25;
|
|
60
|
+
--line-height-normal: 1.5;
|
|
61
|
+
|
|
62
|
+
/* Spacing (rem — consistent regardless of font-size context) */
|
|
63
|
+
--space-1: 0.25rem;
|
|
64
|
+
--space-2: 0.5rem;
|
|
65
|
+
--space-3: 0.75rem;
|
|
66
|
+
--space-4: 1rem;
|
|
67
|
+
--space-6: 1.5rem;
|
|
68
|
+
--space-8: 2rem;
|
|
69
|
+
--space-12: 3rem;
|
|
70
|
+
--space-20: 5rem;
|
|
71
|
+
|
|
72
|
+
/* Radius */
|
|
73
|
+
--radius-sm: 0.25rem;
|
|
74
|
+
--radius-md: 0.375rem;
|
|
75
|
+
--radius-lg: 0.5rem;
|
|
76
|
+
--radius-full: 9999px;
|
|
77
|
+
|
|
78
|
+
/* Shadows */
|
|
79
|
+
--shadow-sm: light-dark(
|
|
80
|
+
0 1px 2px 0 rgba(0, 0, 0, 0.05),
|
|
81
|
+
0 1px 2px 0 rgba(0, 0, 0, 0.3)
|
|
82
|
+
);
|
|
83
|
+
--shadow-md: light-dark(
|
|
84
|
+
0 4px 6px -1px rgba(0, 0, 0, 0.1),
|
|
85
|
+
0 4px 6px -1px rgba(0, 0, 0, 0.4)
|
|
86
|
+
);
|
|
87
|
+
--shadow-focus: light-dark(
|
|
88
|
+
0 0 0 3px rgba(0, 102, 255, 0.2),
|
|
89
|
+
0 0 0 3px rgba(77, 148, 255, 0.3)
|
|
90
|
+
);
|
|
91
|
+
|
|
92
|
+
/* Transitions */
|
|
93
|
+
--transition-fast: 150ms ease-in-out;
|
|
94
|
+
--transition-normal: 250ms ease-in-out;
|
|
95
|
+
|
|
96
|
+
/* Box variants */
|
|
97
|
+
--color-box-info-bg: light-dark(#e6eefa, #1a2a45);
|
|
98
|
+
--color-box-info-text: light-dark(#1a3a6b, #8fb8f0);
|
|
99
|
+
--color-box-success-bg: light-dark(#ecfdf5, #1a3d2a);
|
|
100
|
+
--color-box-success-text: light-dark(#065f46, #a8f0c8);
|
|
101
|
+
--color-box-warning-bg: light-dark(#fef6e7, #3d3210);
|
|
102
|
+
--color-box-warning-text: light-dark(#7a5d0e, #f5d882);
|
|
103
|
+
--color-box-error-bg: light-dark(#fef2f2, #3d1a1a);
|
|
104
|
+
--color-box-error-text: light-dark(#991b1b, #f0a8a8);
|
|
105
|
+
--color-baseline-bg: light-dark(#fef3e6, #3d2a10);
|
|
106
|
+
--color-baseline-text: light-dark(#7a3d00, #f5c882);
|
|
107
|
+
|
|
108
|
+
/* Component sizes (em — scales with font-size context) */
|
|
109
|
+
--input-height: 2.5em;
|
|
110
|
+
--button-height: 2.5em;
|
|
111
|
+
--sidebar-width: 400px;
|
|
112
|
+
--toggle-size: 2.5rem;
|
|
113
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
[data-theme="cdc"] {
|
|
2
|
+
--font-family:
|
|
3
|
+
"Nunito", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica,
|
|
4
|
+
Arial, sans-serif;
|
|
5
|
+
--font-family-heading:
|
|
6
|
+
"Poppins", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica,
|
|
7
|
+
Arial, sans-serif;
|
|
8
|
+
--font-weight-heading: 200;
|
|
9
|
+
--font-size-md: 1.125rem;
|
|
10
|
+
--font-size-2xl: 2.625rem;
|
|
11
|
+
--color-bg-0: light-dark(#ffffff, #0f1e21);
|
|
12
|
+
--color-bg-1: light-dark(#f4fbfc, #162a2e);
|
|
13
|
+
--color-bg-2: light-dark(#e9f5f8, #1e373c);
|
|
14
|
+
--color-bg-3: light-dark(#dff2f6, #264349);
|
|
15
|
+
--color-primary: light-dark(#007a99, #33b5d6);
|
|
16
|
+
--color-primary-hover: light-dark(#006680, #4dc4e0);
|
|
17
|
+
--color-primary-active: light-dark(#005c73, #1fa8cc);
|
|
18
|
+
--color-link: light-dark(#005ea2, #73b3e7);
|
|
19
|
+
--color-border: light-dark(#c5e4ec, #264349);
|
|
20
|
+
--color-border-hover: light-dark(#a0d4e0, #3a5d64);
|
|
21
|
+
--color-border-focus: var(--color-primary);
|
|
22
|
+
}
|