@codemieai/code 0.2.0 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +17 -6
- package/dist/agents/plugins/claude/plugin/skills/codemie-analytics/SKILL.md +641 -0
- package/dist/agents/plugins/claude/plugin/skills/codemie-analytics/references/leaderboard-dashboard-report.md +225 -0
- package/dist/agents/plugins/claude/plugin/skills/codemie-analytics/references/people-spending-dashboard-report.md +746 -0
- package/dist/agents/plugins/claude/plugin/skills/codemie-analytics/references/people-spending-dashboard-template.html +3270 -0
- package/dist/agents/plugins/claude/plugin/skills/codemie-analytics/scripts/analytics-cli.js +893 -0
- package/dist/agents/plugins/claude/plugin/skills/codemie-analytics/scripts/inspect-schema.js +211 -0
- package/dist/agents/plugins/claude/plugin/skills/codemie-html-report/README.md +39 -0
- package/dist/agents/plugins/claude/plugin/skills/codemie-html-report/SKILL.md +117 -26
- package/dist/agents/plugins/claude/plugin/skills/codemie-html-report/scripts/inject-css.js +40 -0
- package/dist/agents/plugins/claude/plugin/skills/codemie-html-report/scripts/inject-data.js +68 -0
- package/dist/agents/plugins/claude/plugin/skills/codemie-html-report/style-guide/css/bundle.css +1 -0
- package/dist/agents/plugins/claude/plugin/skills/codemie-sdk/SKILL.md +240 -0
- package/dist/agents/plugins/claude/plugin/skills/codemie-sdk/examples/assistants.md +256 -0
- package/dist/agents/plugins/claude/plugin/skills/codemie-sdk/examples/categories.md +101 -0
- package/dist/agents/plugins/claude/plugin/skills/codemie-sdk/examples/datasources.md +401 -0
- package/dist/agents/plugins/claude/plugin/skills/codemie-sdk/examples/integrations.md +242 -0
- package/dist/agents/plugins/claude/plugin/skills/codemie-sdk/examples/skills.md +191 -0
- package/dist/agents/plugins/claude/plugin/skills/codemie-sdk/examples/users.md +38 -0
- package/dist/agents/plugins/claude/plugin/skills/codemie-sdk/examples/workflows.md +151 -0
- package/dist/cli/commands/profile/display.d.ts.map +1 -1
- package/dist/cli/commands/profile/display.js +1 -0
- package/dist/cli/commands/profile/display.js.map +1 -1
- package/dist/cli/commands/proxy/connectors/desktop.d.ts.map +1 -1
- package/dist/cli/commands/proxy/connectors/desktop.js +20 -10
- package/dist/cli/commands/proxy/connectors/desktop.js.map +1 -1
- package/dist/cli/commands/sdk/assistants.d.ts +3 -0
- package/dist/cli/commands/sdk/assistants.d.ts.map +1 -0
- package/dist/cli/commands/sdk/assistants.js +211 -0
- package/dist/cli/commands/sdk/assistants.js.map +1 -0
- package/dist/cli/commands/sdk/categories.d.ts +3 -0
- package/dist/cli/commands/sdk/categories.d.ts.map +1 -0
- package/dist/cli/commands/sdk/categories.js +186 -0
- package/dist/cli/commands/sdk/categories.js.map +1 -0
- package/dist/cli/commands/sdk/datasources.d.ts +3 -0
- package/dist/cli/commands/sdk/datasources.d.ts.map +1 -0
- package/dist/cli/commands/sdk/datasources.js +276 -0
- package/dist/cli/commands/sdk/datasources.js.map +1 -0
- package/dist/cli/commands/sdk/index.d.ts +3 -0
- package/dist/cli/commands/sdk/index.d.ts.map +1 -0
- package/dist/cli/commands/sdk/index.js +23 -0
- package/dist/cli/commands/sdk/index.js.map +1 -0
- package/dist/cli/commands/sdk/integrations.d.ts +3 -0
- package/dist/cli/commands/sdk/integrations.d.ts.map +1 -0
- package/dist/cli/commands/sdk/integrations.js +220 -0
- package/dist/cli/commands/sdk/integrations.js.map +1 -0
- package/dist/cli/commands/sdk/llm.d.ts +3 -0
- package/dist/cli/commands/sdk/llm.d.ts.map +1 -0
- package/dist/cli/commands/sdk/llm.js +48 -0
- package/dist/cli/commands/sdk/llm.js.map +1 -0
- package/dist/cli/commands/sdk/services/assistants.d.ts +13 -0
- package/dist/cli/commands/sdk/services/assistants.d.ts.map +1 -0
- package/dist/cli/commands/sdk/services/assistants.js +60 -0
- package/dist/cli/commands/sdk/services/assistants.js.map +1 -0
- package/dist/cli/commands/sdk/services/categories.d.ts +8 -0
- package/dist/cli/commands/sdk/services/categories.d.ts.map +1 -0
- package/dist/cli/commands/sdk/services/categories.js +19 -0
- package/dist/cli/commands/sdk/services/categories.js.map +1 -0
- package/dist/cli/commands/sdk/services/datasources.d.ts +33 -0
- package/dist/cli/commands/sdk/services/datasources.d.ts.map +1 -0
- package/dist/cli/commands/sdk/services/datasources.js +268 -0
- package/dist/cli/commands/sdk/services/datasources.js.map +1 -0
- package/dist/cli/commands/sdk/services/index.d.ts +6 -0
- package/dist/cli/commands/sdk/services/index.d.ts.map +1 -0
- package/dist/cli/commands/sdk/services/index.js +6 -0
- package/dist/cli/commands/sdk/services/index.js.map +1 -0
- package/dist/cli/commands/sdk/services/integrations.d.ts +27 -0
- package/dist/cli/commands/sdk/services/integrations.d.ts.map +1 -0
- package/dist/cli/commands/sdk/services/integrations.js +59 -0
- package/dist/cli/commands/sdk/services/integrations.js.map +1 -0
- package/dist/cli/commands/sdk/services/llm.d.ts +4 -0
- package/dist/cli/commands/sdk/services/llm.d.ts.map +1 -0
- package/dist/cli/commands/sdk/services/llm.js +7 -0
- package/dist/cli/commands/sdk/services/llm.js.map +1 -0
- package/dist/cli/commands/sdk/services/skills.d.ts +23 -0
- package/dist/cli/commands/sdk/services/skills.d.ts.map +1 -0
- package/dist/cli/commands/sdk/services/skills.js +69 -0
- package/dist/cli/commands/sdk/services/skills.js.map +1 -0
- package/dist/cli/commands/sdk/services/users.d.ts +4 -0
- package/dist/cli/commands/sdk/services/users.d.ts.map +1 -0
- package/dist/cli/commands/sdk/services/users.js +7 -0
- package/dist/cli/commands/sdk/services/users.js.map +1 -0
- package/dist/cli/commands/sdk/services/workflows.d.ts +7 -0
- package/dist/cli/commands/sdk/services/workflows.d.ts.map +1 -0
- package/dist/cli/commands/sdk/services/workflows.js +34 -0
- package/dist/cli/commands/sdk/services/workflows.js.map +1 -0
- package/dist/cli/commands/sdk/skills.d.ts +3 -0
- package/dist/cli/commands/sdk/skills.d.ts.map +1 -0
- package/dist/cli/commands/sdk/skills.js +492 -0
- package/dist/cli/commands/sdk/skills.js.map +1 -0
- package/dist/cli/commands/sdk/users.d.ts +3 -0
- package/dist/cli/commands/sdk/users.d.ts.map +1 -0
- package/dist/cli/commands/sdk/users.js +81 -0
- package/dist/cli/commands/sdk/users.js.map +1 -0
- package/dist/cli/commands/sdk/utils/cli-utils.d.ts +35 -0
- package/dist/cli/commands/sdk/utils/cli-utils.d.ts.map +1 -0
- package/dist/cli/commands/sdk/utils/cli-utils.js +110 -0
- package/dist/cli/commands/sdk/utils/cli-utils.js.map +1 -0
- package/dist/cli/commands/sdk/utils/datasource-types.d.ts +9 -0
- package/dist/cli/commands/sdk/utils/datasource-types.d.ts.map +1 -0
- package/dist/cli/commands/sdk/utils/datasource-types.js +61 -0
- package/dist/cli/commands/sdk/utils/datasource-types.js.map +1 -0
- package/dist/cli/commands/sdk/utils/file-utils.d.ts +8 -0
- package/dist/cli/commands/sdk/utils/file-utils.d.ts.map +1 -0
- package/dist/cli/commands/sdk/utils/file-utils.js +21 -0
- package/dist/cli/commands/sdk/utils/file-utils.js.map +1 -0
- package/dist/cli/commands/sdk/utils/render.d.ts +82 -0
- package/dist/cli/commands/sdk/utils/render.d.ts.map +1 -0
- package/dist/cli/commands/sdk/utils/render.js +149 -0
- package/dist/cli/commands/sdk/utils/render.js.map +1 -0
- package/dist/cli/commands/sdk/workflows.d.ts +3 -0
- package/dist/cli/commands/sdk/workflows.d.ts.map +1 -0
- package/dist/cli/commands/sdk/workflows.js +170 -0
- package/dist/cli/commands/sdk/workflows.js.map +1 -0
- package/dist/cli/index.js +2 -0
- package/dist/cli/index.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* inspect-schema.js <data-dir>
|
|
4
|
+
*
|
|
5
|
+
* Reads all .json files in <data-dir> and prints a compact schema to stdout.
|
|
6
|
+
* Designed to give Claude enough structural information to write data extraction
|
|
7
|
+
* code without reading raw API responses into the conversation context.
|
|
8
|
+
*
|
|
9
|
+
* Output format (per file):
|
|
10
|
+
* - primitives: "number" | "boolean" | "string ~ 'sample'"
|
|
11
|
+
* - arrays: { _type: "array", _count: N, _item: <item-schema> }
|
|
12
|
+
* - objects: { key: <schema>, ... }
|
|
13
|
+
* - null fields: "null" or "<type> | null" when nullable across samples
|
|
14
|
+
*
|
|
15
|
+
* Usage:
|
|
16
|
+
* node inspect-schema.js /tmp/codemie-analytics-20260507/
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import { readFileSync, writeFileSync, readdirSync } from 'fs';
|
|
20
|
+
import { join } from 'path';
|
|
21
|
+
|
|
22
|
+
const MAX_STRING_PREVIEW = 50;
|
|
23
|
+
const NULL_CHECK_SAMPLES = 5;
|
|
24
|
+
// For type-diversity detection: scan all items in small arrays, first N in large ones.
|
|
25
|
+
const TYPE_CHECK_SAMPLES = 20;
|
|
26
|
+
|
|
27
|
+
// Fields whose complete vocabulary is always emitted regardless of array context.
|
|
28
|
+
const ALWAYS_ENUMERATE = new Set([
|
|
29
|
+
'type', 'format', 'classification', 'tier_name',
|
|
30
|
+
'weekday', 'range', 'client_name', 'dimension_id',
|
|
31
|
+
]);
|
|
32
|
+
|
|
33
|
+
// Values containing these chars are entity/path identifiers, not vocabulary — skip them.
|
|
34
|
+
const ENTITY_VALUE_RE = /[@/]/;
|
|
35
|
+
|
|
36
|
+
// UUID-shaped strings are entity IDs, not vocabulary.
|
|
37
|
+
const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-/i;
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Returns the set of fields in itemSchema that should be fully enumerated.
|
|
41
|
+
*
|
|
42
|
+
* Rules:
|
|
43
|
+
* - ALWAYS_ENUMERATE fields: always included.
|
|
44
|
+
* - 'id': only when the parent item has a 'label' key (metrics or columns context),
|
|
45
|
+
* ensuring we capture metric/column identifiers without enumerating entity IDs.
|
|
46
|
+
*/
|
|
47
|
+
function getEnumerableFields(itemSchema) {
|
|
48
|
+
const keys = new Set();
|
|
49
|
+
for (const key of Object.keys(itemSchema)) {
|
|
50
|
+
if (ALWAYS_ENUMERATE.has(key)) keys.add(key);
|
|
51
|
+
}
|
|
52
|
+
if ('id' in itemSchema && 'label' in itemSchema) {
|
|
53
|
+
keys.add('id');
|
|
54
|
+
}
|
|
55
|
+
return keys;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Returns the bare type category of an already-inferred schema string.
|
|
60
|
+
* e.g. "string ~ 'foo'" → "string", "number" → "number", "null" → "null"
|
|
61
|
+
*/
|
|
62
|
+
function typeCategory(schema) {
|
|
63
|
+
if (schema === 'null') return 'null';
|
|
64
|
+
if (schema === 'number' || schema === 'boolean') return schema;
|
|
65
|
+
if (typeof schema === 'string' && schema.startsWith('string')) return 'string';
|
|
66
|
+
if (schema && typeof schema === 'object' && schema._type === 'array') return 'array';
|
|
67
|
+
if (schema && typeof schema === 'object') return 'object';
|
|
68
|
+
return String(schema);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function infer(value) {
|
|
72
|
+
if (value === null || value === undefined) return 'null';
|
|
73
|
+
|
|
74
|
+
const t = typeof value;
|
|
75
|
+
|
|
76
|
+
if (t === 'boolean') return 'boolean';
|
|
77
|
+
if (t === 'number') return 'number';
|
|
78
|
+
|
|
79
|
+
if (t === 'string') {
|
|
80
|
+
const preview = value.length > MAX_STRING_PREVIEW
|
|
81
|
+
? value.slice(0, MAX_STRING_PREVIEW) + '...'
|
|
82
|
+
: value;
|
|
83
|
+
return `string ~ '${preview}'`;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (Array.isArray(value)) {
|
|
87
|
+
if (value.length === 0) return { _type: 'array', _count: 0, _item: 'unknown' };
|
|
88
|
+
|
|
89
|
+
const itemSchema = infer(value[0]);
|
|
90
|
+
|
|
91
|
+
// Scan multiple items to detect nullable fields AND type diversity.
|
|
92
|
+
// For small arrays scan everything; for large ones sample the first N.
|
|
93
|
+
if (
|
|
94
|
+
itemSchema !== null &&
|
|
95
|
+
typeof itemSchema === 'object' &&
|
|
96
|
+
!Array.isArray(itemSchema) &&
|
|
97
|
+
value.length > 1
|
|
98
|
+
) {
|
|
99
|
+
const sampleSize = Math.min(
|
|
100
|
+
Math.max(NULL_CHECK_SAMPLES, TYPE_CHECK_SAMPLES),
|
|
101
|
+
value.length
|
|
102
|
+
);
|
|
103
|
+
const samples = value.slice(0, sampleSize);
|
|
104
|
+
|
|
105
|
+
for (const key of Object.keys(itemSchema)) {
|
|
106
|
+
if (typeof itemSchema[key] !== 'string') continue; // skip nested objects/arrays
|
|
107
|
+
if (itemSchema[key] === 'null') continue;
|
|
108
|
+
|
|
109
|
+
const baseCategory = typeCategory(itemSchema[key]);
|
|
110
|
+
const seenCategories = new Set([baseCategory]);
|
|
111
|
+
|
|
112
|
+
for (const sample of samples.slice(1)) {
|
|
113
|
+
const v = sample[key];
|
|
114
|
+
if (v === null || v === undefined) {
|
|
115
|
+
seenCategories.add('null');
|
|
116
|
+
} else {
|
|
117
|
+
seenCategories.add(typeCategory(infer(v)));
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const nonNullTypes = [...seenCategories].filter(c => c !== 'null');
|
|
122
|
+
const hasNull = seenCategories.has('null') || itemSchema[key].includes('| null');
|
|
123
|
+
|
|
124
|
+
if (nonNullTypes.length > 1) {
|
|
125
|
+
// Multiple distinct types observed — drop string preview, show union
|
|
126
|
+
itemSchema[key] = nonNullTypes.join(' | ') + (hasNull ? ' | null' : '');
|
|
127
|
+
} else if (hasNull && !itemSchema[key].includes('| null')) {
|
|
128
|
+
itemSchema[key] += ' | null';
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Pass 2: enumerate vocabulary fields across ALL items
|
|
133
|
+
const enumerableFields = getEnumerableFields(itemSchema);
|
|
134
|
+
for (const key of enumerableFields) {
|
|
135
|
+
if (typeof itemSchema[key] !== 'string') continue;
|
|
136
|
+
if (!itemSchema[key].startsWith('string')) continue;
|
|
137
|
+
|
|
138
|
+
const uniqueVals = new Set();
|
|
139
|
+
for (const item of value) {
|
|
140
|
+
const v = item[key];
|
|
141
|
+
if (typeof v === 'string' && !UUID_RE.test(v) && !ENTITY_VALUE_RE.test(v)) {
|
|
142
|
+
uniqueVals.add(v);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Always-enumerate fields emit even a single observed value; id requires ≥ 2.
|
|
147
|
+
const minUnique = ALWAYS_ENUMERATE.has(key) ? 1 : 2;
|
|
148
|
+
if (uniqueVals.size >= minUnique && uniqueVals.size <= 50) {
|
|
149
|
+
const sorted = [...uniqueVals].sort();
|
|
150
|
+
const preview = sorted.map(v => `'${v}'`).join(' | ');
|
|
151
|
+
const nullable = itemSchema[key].includes('| null');
|
|
152
|
+
itemSchema[key] = `string (enum) ~ ${preview}${nullable ? ' | null' : ''}`;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
return { _type: 'array', _count: value.length, _item: itemSchema };
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
if (t === 'object') {
|
|
161
|
+
const schema = {};
|
|
162
|
+
for (const [k, v] of Object.entries(value)) {
|
|
163
|
+
schema[k] = infer(v);
|
|
164
|
+
}
|
|
165
|
+
return schema;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
return t;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function processFile(filePath) {
|
|
172
|
+
let raw;
|
|
173
|
+
try {
|
|
174
|
+
raw = JSON.parse(readFileSync(filePath, 'utf8'));
|
|
175
|
+
} catch (e) {
|
|
176
|
+
return { _error: `Failed to parse: ${e.message}` };
|
|
177
|
+
}
|
|
178
|
+
return infer(raw);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
const dataDir = process.argv[2];
|
|
182
|
+
|
|
183
|
+
if (!dataDir) {
|
|
184
|
+
console.error('Usage: node inspect-schema.js <data-dir>');
|
|
185
|
+
process.exit(1);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
let files;
|
|
189
|
+
try {
|
|
190
|
+
files = readdirSync(dataDir).filter(f => f.endsWith('.json') && !f.endsWith('.schema.json')).sort();
|
|
191
|
+
} catch (e) {
|
|
192
|
+
console.error(`Cannot read directory: ${dataDir}\n${e.message}`);
|
|
193
|
+
process.exit(1);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
if (files.length === 0) {
|
|
197
|
+
console.error(`No .json files found in: ${dataDir}`);
|
|
198
|
+
process.exit(1);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
const written = [];
|
|
202
|
+
for (const file of files) {
|
|
203
|
+
const schema = processFile(join(dataDir, file));
|
|
204
|
+
const schemaFile = file.replace(/\.json$/, '.schema.json');
|
|
205
|
+
const schemaPath = join(dataDir, schemaFile);
|
|
206
|
+
writeFileSync(schemaPath, JSON.stringify(schema, null, 2));
|
|
207
|
+
written.push(` ✓ ${schemaFile}`);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
console.log(`Schemas written to: ${dataDir}`);
|
|
211
|
+
console.log(written.join('\n'));
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# CodeMie HTML Report — Style Guide
|
|
2
|
+
|
|
3
|
+
## CSS Bundle
|
|
4
|
+
|
|
5
|
+
`css/bundle.css` is a pre-built, minified concatenation of all 8 design-system CSS files.
|
|
6
|
+
It is committed to the repo so report generation requires no build step at runtime.
|
|
7
|
+
|
|
8
|
+
**Do not edit `bundle.css` directly.** Edit the individual source files instead, then rebuild.
|
|
9
|
+
|
|
10
|
+
### Source files (order matters)
|
|
11
|
+
|
|
12
|
+
| File | What it covers |
|
|
13
|
+
|------|---------------|
|
|
14
|
+
| `css/tokens.css` | CSS custom properties — colors, spacing, radii, shadows, gradients |
|
|
15
|
+
| `css/base.css` | Reset, body, scrollbar, code blocks, links, focus ring |
|
|
16
|
+
| `css/typography.css` | Headings h1–h6, text size/weight/color utilities |
|
|
17
|
+
| `css/buttons.css` | All button variants and sizes |
|
|
18
|
+
| `css/forms.css` | input, textarea, select, checkbox, radio, switch |
|
|
19
|
+
| `css/components.css` | card, badge, alert, avatar, stat-card, chip, empty-state, etc. |
|
|
20
|
+
| `css/layout.css` | table, tabs, pagination, modal, nav-sidebar, app-shell |
|
|
21
|
+
| `css/utilities.css` | flex, grid, gap, padding, margin, width, overflow, border |
|
|
22
|
+
|
|
23
|
+
### Rebuilding bundle.css
|
|
24
|
+
|
|
25
|
+
Run this command from the repo root whenever any source CSS file changes:
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
npx clean-css-cli -o src/agents/plugins/claude/plugin/skills/codemie-html-report/style-guide/css/bundle.css \
|
|
29
|
+
src/agents/plugins/claude/plugin/skills/codemie-html-report/style-guide/css/tokens.css \
|
|
30
|
+
src/agents/plugins/claude/plugin/skills/codemie-html-report/style-guide/css/base.css \
|
|
31
|
+
src/agents/plugins/claude/plugin/skills/codemie-html-report/style-guide/css/typography.css \
|
|
32
|
+
src/agents/plugins/claude/plugin/skills/codemie-html-report/style-guide/css/buttons.css \
|
|
33
|
+
src/agents/plugins/claude/plugin/skills/codemie-html-report/style-guide/css/forms.css \
|
|
34
|
+
src/agents/plugins/claude/plugin/skills/codemie-html-report/style-guide/css/components.css \
|
|
35
|
+
src/agents/plugins/claude/plugin/skills/codemie-html-report/style-guide/css/layout.css \
|
|
36
|
+
src/agents/plugins/claude/plugin/skills/codemie-html-report/style-guide/css/utilities.css
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Commit the updated `bundle.css` alongside the source CSS change.
|
|
@@ -17,32 +17,49 @@ description: >
|
|
|
17
17
|
|
|
18
18
|
You are building a standalone HTML page that visually matches the CodeMie (EPAM AI/Run) product UI. The design system is a dark-first, professional theme with Inter font, subtle borders, and semantic color tokens. Every page you produce should feel like a native screen of the CodeMie platform.
|
|
19
19
|
|
|
20
|
-
## Step 1 —
|
|
20
|
+
## Step 1 — CSS placeholder
|
|
21
21
|
|
|
22
|
-
|
|
22
|
+
**Do NOT read any CSS files. Do NOT inline any CSS yourself.**
|
|
23
23
|
|
|
24
|
-
|
|
25
|
-
|------|---------------|
|
|
26
|
-
| `tokens.css` | All CSS custom properties (colors, spacing, radii, shadows, gradients) |
|
|
27
|
-
| `base.css` | Reset, body, scrollbar, code blocks, links, focus ring |
|
|
28
|
-
| `typography.css` | Headings h1-h6, text size/weight/color utilities |
|
|
29
|
-
| `buttons.css` | btn-primary, btn-secondary, btn-base, btn-delete, btn-tertiary, btn-magical, sizes |
|
|
30
|
-
| `forms.css` | input, textarea, select, checkbox, radio, switch |
|
|
31
|
-
| `components.css` | card, badge, tag, alert, avatar, spinner, progress, tooltip, stat-card, chip, empty-state |
|
|
32
|
-
| `layout.css` | table, tabs, pagination, modal, nav-sidebar, app-shell |
|
|
33
|
-
| `utilities.css` | flex, grid, gap, padding, margin, width, overflow, position, border, shadow |
|
|
24
|
+
In the `<style>` block write exactly this one token as the only content:
|
|
34
25
|
|
|
35
|
-
|
|
26
|
+
```css
|
|
27
|
+
/* __CODEMIE_CSS__ */
|
|
28
|
+
```
|
|
36
29
|
|
|
37
|
-
|
|
30
|
+
A post-processing script will replace this token with the full design system CSS after you write the file. All component classes are documented in Steps 3 and 4 — use them freely without reading the source files.
|
|
31
|
+
|
|
32
|
+
## Step 1.5 — Data placeholders (analytics pipeline only — skip for standalone use)
|
|
38
33
|
|
|
39
|
-
**
|
|
34
|
+
> **Backwards compatibility:** This step applies **only** when this skill is invoked
|
|
35
|
+
> from the **codemie-analytics** skill as part of its report pipeline. If you are
|
|
36
|
+
> generating a standalone HTML page directly for a user request, **skip this step
|
|
37
|
+
> entirely** and embed any data inline as regular JS variables.
|
|
40
38
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
39
|
+
When invoked from **codemie-analytics**, all JS data arrays must use
|
|
40
|
+
`/*__DATA:name__*/` placeholders instead of inline values. The analytics skill's
|
|
41
|
+
`inject-data.js` step replaces these after the HTML file is written.
|
|
42
|
+
|
|
43
|
+
```html
|
|
44
|
+
<script>
|
|
45
|
+
/* Data is injected by inject-data.js after this file is written — do NOT hardcode arrays */
|
|
46
|
+
const LEADERBOARD = /*__DATA:leaderboard-top__*/;
|
|
47
|
+
const SUMMARIES = /*__DATA:summaries__*/;
|
|
48
|
+
const LLM_DATA = /*__DATA:llms-usage__*/;
|
|
49
|
+
</script>
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Rules for analytics-pipeline placeholders:
|
|
53
|
+
- **The placeholder name must exactly match the JSON filename without the `.json` extension.**
|
|
54
|
+
`/*__DATA:leaderboard-top__*/` is only replaced when `leaderboard-top.json` exists.
|
|
55
|
+
Wrong name → the placeholder is silently skipped by `inject-data.js`.
|
|
56
|
+
- Every JS variable populated from an API response must use a placeholder.
|
|
57
|
+
- Hardcoded lookup tables (tier colours, dimension labels, etc.) are fine as regular JS.
|
|
58
|
+
- **Never mix placeholders with inline data** in the same variable declaration.
|
|
59
|
+
|
|
60
|
+
## Step 2 — Page skeleton (fully self-contained)
|
|
61
|
+
|
|
62
|
+
**CRITICAL: Every HTML file you produce must be a single self-contained file.** Do NOT use `<link>` tags. Write the `/* __CODEMIE_CSS__ */` placeholder in the `<style>` block — the inject-css.js script will inline the full design system CSS after you write the file.
|
|
46
63
|
|
|
47
64
|
```html
|
|
48
65
|
<!DOCTYPE html>
|
|
@@ -52,12 +69,7 @@ Workflow:
|
|
|
52
69
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
53
70
|
<title>PAGE TITLE</title>
|
|
54
71
|
<style>
|
|
55
|
-
/*
|
|
56
|
-
@import url('https://fonts.googleapis.com/css2?family=Inter:ital,wght@0,400;0,500;0,600;0,700;1,400&family=JetBrains+Mono:wght@400;500&display=swap');
|
|
57
|
-
|
|
58
|
-
/* PASTE FULL CONTENTS OF: tokens.css, base.css, typography.css,
|
|
59
|
-
buttons.css, forms.css, components.css, layout.css, utilities.css */
|
|
60
|
-
|
|
72
|
+
/* __CODEMIE_CSS__ */
|
|
61
73
|
/* === Page-specific styles (use CSS variables, not hex colors) === */
|
|
62
74
|
</style>
|
|
63
75
|
</head>
|
|
@@ -271,3 +283,82 @@ body.p-6 > .container
|
|
|
271
283
|
```
|
|
272
284
|
|
|
273
285
|
This pattern matches the analytics dashboard layout in the live CodeMie product and works for most reporting use cases.
|
|
286
|
+
|
|
287
|
+
## Final Step — Inject CSS
|
|
288
|
+
|
|
289
|
+
After writing the HTML file, run this command to replace the placeholder with the full
|
|
290
|
+
design system bundle and make the report self-contained:
|
|
291
|
+
|
|
292
|
+
```bash
|
|
293
|
+
node ${CLAUDE_PLUGIN_ROOT}/skills/codemie-html-report/scripts/inject-css.js <path-to-the-html-file-you-just-wrote>
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
For example:
|
|
297
|
+
```bash
|
|
298
|
+
node ${CLAUDE_PLUGIN_ROOT}/skills/codemie-html-report/scripts/inject-css.js reports/leaderboard-2026-Q1.html
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
Expected output: `✓ CSS injected into reports/leaderboard-2026-Q1.html`
|
|
302
|
+
|
|
303
|
+
## Final Step — Inject Data (analytics pipeline only — skip for standalone use)
|
|
304
|
+
|
|
305
|
+
> This step applies **only** when invoked from the **codemie-analytics** skill. Standalone
|
|
306
|
+
> HTML generation embeds data inline and must skip this step.
|
|
307
|
+
|
|
308
|
+
After the HTML file is written, run `inject-data.js` to replace every `/*__DATA:name__*/`
|
|
309
|
+
placeholder with the matching JSON file from the temp directory:
|
|
310
|
+
|
|
311
|
+
```bash
|
|
312
|
+
node ${CLAUDE_PLUGIN_ROOT}/skills/codemie-html-report/scripts/inject-data.js \
|
|
313
|
+
<path-to-html> <temp-dir>
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
For example:
|
|
317
|
+
```bash
|
|
318
|
+
node ${CLAUDE_PLUGIN_ROOT}/skills/codemie-html-report/scripts/inject-data.js \
|
|
319
|
+
reports/2026-05-07-leaderboard/leaderboard.html \
|
|
320
|
+
reports/2026-05-07-leaderboard/temp/
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
**Placeholder names must exactly match the JSON filenames** (without `.json`):
|
|
324
|
+
- `/*__DATA:leaderboard-top__*/` is replaced from `leaderboard-top.json`
|
|
325
|
+
- `/*__DATA:summaries__*/` is replaced from `summaries.json`
|
|
326
|
+
|
|
327
|
+
A wrong name means the placeholder is silently skipped. If no placeholders are matched at
|
|
328
|
+
all, the script exits with an error.
|
|
329
|
+
|
|
330
|
+
Expected output:
|
|
331
|
+
```
|
|
332
|
+
✓ injected leaderboard-top
|
|
333
|
+
✓ injected summaries
|
|
334
|
+
✓ 2 data block(s) injected into reports/2026-05-07-leaderboard/leaderboard.html
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
**Do not run inject-data.js before the HTML file exists.**
|
|
338
|
+
Run inject-css.js after inject-data.js.
|
|
339
|
+
|
|
340
|
+
## Final Step — Temp file cleanup (analytics pipeline only — skip for standalone use)
|
|
341
|
+
|
|
342
|
+
> **Backwards compatibility:** This step applies **only** when this skill is invoked
|
|
343
|
+
> from the **codemie-analytics** skill. Standalone HTML generation has no temp
|
|
344
|
+
> directory and must skip this step.
|
|
345
|
+
|
|
346
|
+
After the CSS is injected and the report is complete, **always ask the user**:
|
|
347
|
+
|
|
348
|
+
> The report is ready at `<path>`. The `temp/` directory (`<OUT>`) contains the raw
|
|
349
|
+
> API response files used to build it (~N files). Would you like to delete it?
|
|
350
|
+
|
|
351
|
+
If the user says **yes**, delete the temp directory:
|
|
352
|
+
|
|
353
|
+
```bash
|
|
354
|
+
rm -rf "<OUT>"
|
|
355
|
+
# e.g. rm -rf "reports/2026-05-07-executive-spending/temp"
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
Confirm deletion:
|
|
359
|
+
```
|
|
360
|
+
✓ Temp files deleted → reports/2026-05-07-executive-spending/temp
|
|
361
|
+
```
|
|
362
|
+
|
|
363
|
+
If the user says **no** (or does not respond), leave the directory intact and note its
|
|
364
|
+
location so they can inspect or re-use the raw data later.
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { readFileSync, writeFileSync, existsSync } from 'fs';
|
|
3
|
+
import { join, dirname, resolve } from 'path';
|
|
4
|
+
import { fileURLToPath } from 'url';
|
|
5
|
+
|
|
6
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
7
|
+
|
|
8
|
+
const htmlPath = process.argv[2];
|
|
9
|
+
|
|
10
|
+
if (!htmlPath) {
|
|
11
|
+
console.error('Usage: node inject-css.js <path-to-html-file>');
|
|
12
|
+
console.error('Example: node inject-css.js reports/leaderboard-2026-Q1.html');
|
|
13
|
+
process.exit(1);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const resolvedHtml = resolve(htmlPath);
|
|
17
|
+
const bundlePath = join(__dirname, '..', 'style-guide', 'css', 'bundle.css');
|
|
18
|
+
|
|
19
|
+
if (!existsSync(resolvedHtml)) {
|
|
20
|
+
console.error(`Error: HTML file not found: ${resolvedHtml}`);
|
|
21
|
+
process.exit(1);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
if (!existsSync(bundlePath)) {
|
|
25
|
+
console.error(`Error: bundle.css not found at ${bundlePath}`);
|
|
26
|
+
console.error('Rebuild it — see style-guide/README.md for the command.');
|
|
27
|
+
process.exit(1);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const html = readFileSync(resolvedHtml, 'utf8');
|
|
31
|
+
|
|
32
|
+
if (!html.includes('/* __CODEMIE_CSS__ */')) {
|
|
33
|
+
console.error(`Error: placeholder "/* __CODEMIE_CSS__ */" not found in ${resolvedHtml}`);
|
|
34
|
+
console.error('The HTML file must contain: <style>/* __CODEMIE_CSS__ */</style>');
|
|
35
|
+
process.exit(1);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const css = readFileSync(bundlePath, 'utf8');
|
|
39
|
+
writeFileSync(resolvedHtml, html.replace('/* __CODEMIE_CSS__ */', css), 'utf8');
|
|
40
|
+
console.log(`✓ CSS injected into ${resolvedHtml}`);
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { readFileSync, writeFileSync, existsSync, readdirSync, statSync } from 'fs';
|
|
3
|
+
import { join, resolve, basename, extname } from 'path';
|
|
4
|
+
|
|
5
|
+
const [htmlPath, ...sources] = process.argv.slice(2);
|
|
6
|
+
|
|
7
|
+
if (!htmlPath || sources.length === 0) {
|
|
8
|
+
console.error('Usage: node inject-data.js <path-to-html-file> <json-file-or-dir> [...]');
|
|
9
|
+
console.error('Example: node inject-data.js reports/report.html reports/temp/');
|
|
10
|
+
process.exit(1);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const resolvedHtml = resolve(htmlPath);
|
|
14
|
+
|
|
15
|
+
if (!existsSync(resolvedHtml)) {
|
|
16
|
+
console.error(`Error: HTML file not found: ${resolvedHtml}`);
|
|
17
|
+
process.exit(1);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// Collect all JSON files from files and/or directories
|
|
21
|
+
const jsonFiles = [];
|
|
22
|
+
for (const src of sources) {
|
|
23
|
+
const resolved = resolve(src);
|
|
24
|
+
if (!existsSync(resolved)) {
|
|
25
|
+
console.error(`Error: source not found: ${resolved}`);
|
|
26
|
+
process.exit(1);
|
|
27
|
+
}
|
|
28
|
+
if (statSync(resolved).isDirectory()) {
|
|
29
|
+
for (const entry of readdirSync(resolved)) {
|
|
30
|
+
if (extname(entry) === '.json' && !entry.endsWith('.schema.json')) jsonFiles.push(join(resolved, entry));
|
|
31
|
+
}
|
|
32
|
+
} else {
|
|
33
|
+
if (extname(resolved) !== '.json') {
|
|
34
|
+
console.error(`Error: not a JSON file: ${resolved}`);
|
|
35
|
+
process.exit(1);
|
|
36
|
+
}
|
|
37
|
+
jsonFiles.push(resolved);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (jsonFiles.length === 0) {
|
|
42
|
+
console.error('Error: no JSON files found in the provided sources');
|
|
43
|
+
process.exit(1);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
let html = readFileSync(resolvedHtml, 'utf8');
|
|
47
|
+
|
|
48
|
+
let injected = 0;
|
|
49
|
+
for (const jsonFile of jsonFiles) {
|
|
50
|
+
const name = basename(jsonFile, '.json');
|
|
51
|
+
const placeholder = `/*__DATA:${name}__*/`;
|
|
52
|
+
if (!html.includes(placeholder)) {
|
|
53
|
+
console.warn(` warn: no placeholder for "${name}" — skipping`);
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
56
|
+
const data = readFileSync(jsonFile, 'utf8').trim();
|
|
57
|
+
html = html.replaceAll(placeholder, data);
|
|
58
|
+
console.log(` ✓ injected ${name}`);
|
|
59
|
+
injected++;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (injected === 0) {
|
|
63
|
+
console.error('Error: no placeholders were matched — HTML unchanged');
|
|
64
|
+
process.exit(1);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
writeFileSync(resolvedHtml, html, 'utf8');
|
|
68
|
+
console.log(`✓ ${injected} data block(s) injected into ${resolvedHtml}`);
|
package/dist/agents/plugins/claude/plugin/skills/codemie-html-report/style-guide/css/bundle.css
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
@import url(https://fonts.googleapis.com/css2?family=Inter:ital,wght@0,400;0,500;0,600;0,700;1,400&family=JetBrains+Mono:wght@400;500&display=swap);:root{--font-sans:'Inter','Geist',-apple-system,BlinkMacSystemFont,'Segoe UI',Arial,sans-serif;--font-mono:'JetBrains Mono','GeistMono','Fira Code','Courier New',monospace;--text-xs-1:0.625rem;--text-xs:0.75rem;--text-sm-1:0.813rem;--text-sm:0.875rem;--text-base:1rem;--text-h1:2rem;--text-h2:1.5rem;--text-h3:1rem;--text-h4:0.875rem;--text-h5:0.75rem;--lh-h1:2rem;--lh-h2:1.5rem;--lh-h3:1.3125rem;--lh-h4:1.125rem;--lh-h5:1rem;--radius-sm:4px;--radius-md:6px;--radius-lg:8px;--radius-xl:12px;--radius-2xl:16px;--radius-full:9999px;--space-0-5:0.125rem;--space-1:0.25rem;--space-1-5:0.375rem;--space-2:0.5rem;--space-2-5:0.625rem;--space-3:0.75rem;--space-4:1rem;--space-5:1.25rem;--space-6:1.5rem;--space-8:2rem;--shadow-sm:0 1px 2px rgba(0, 0, 0, 0.4);--shadow-md:0 4px 6px -1px rgba(0, 0, 0, 0.5),0 2px 4px -2px rgba(0, 0, 0, 0.3);--shadow-lg:0 10px 15px -3px rgba(0, 0, 0, 0.6),0 4px 6px -4px rgba(0, 0, 0, 0.4);--shadow-sidebar:-1px 0 0 0 rgba(255, 255, 255, 0.1);--transition-fast:120ms ease;--transition-base:200ms ease;--transition-slow:300ms ease;--navbar-width:72px;--navbar-width-expanded:196px;--sidebar-width:308px;--layout-header-height:56px;--card-height:158px;--z-dropdown:30;--z-sticky:40;--z-modal:50;--z-toast:60}:root,:root.dark{--color-bg-page:#1A1A1A;--color-bg-sidebar:#151515;--color-bg-card:#151515;--color-bg-nav:#000000;--color-bg-elevated:#2E2E2E;--color-bg-secondary:#212224;--color-bg-tertiary:#2E3033;--color-bg-quaternary:#333436;--color-bg-hover:#212224;--color-bg-hover-strong:#333436;--color-bg-input:#1A1A1A;--color-bg-input-prefix:#333436;--color-bg-btn-primary:#20222E;--color-bg-btn-primary-h:#262941;--color-bg-pagination-active:#212224;--color-text-primary:#FFFFFF;--color-text-secondary:#F2F0EF;--color-text-tertiary:#CCCCCC;--color-text-muted:#BBBBBB;--color-text-placeholder:#CCCCCC;--color-text-link:#F2F0EF;--color-text-link-hover:#F2F0EF;--color-text-inverse:#FFFFFF;--color-text-heading:#BBBBBB;--color-text-nav:#CCCCCC;--color-border-primary:#333436;--color-border-secondary:#47484A;--color-border-structural:#333436;--color-border-subtle:#4C4C4C;--color-border-accent:#FFFFFF;--color-border-focus:#FFFFFF;--color-border-error:#F9303C;--color-border-panel:#333436;--color-border-btn-secondary:#333436;--color-border-btn-secondary-h:transparent;--color-icon-primary:#FFFFFF;--color-icon-secondary:#CCCCCC;--color-icon-tertiary:#999999;--color-icon-error:#F9303C;--color-success:#259F4C;--color-success-bg:#1B271F;--color-success-border:#259F4C;--color-success-text:#259F4C;--color-error:#F9303C;--color-error-bg:#262121;--color-error-border:#FE3B4C;--color-error-text:#FE3B4C;--color-warning:#F5A534;--color-warning-bg:#492B00;--color-warning-border:#663B00;--color-warning-text:#F5A534;--color-info:#2297F6;--color-info-bg:#002442;--color-info-border:#003A69;--color-info-text:#2297F6;--color-purple:#C084FC;--color-purple-bg:#2D1B3D;--color-purple-border:#2D1B3D;--color-purple-text:#F3E8FF;--color-cyan:#06B6D4;--color-cyan-bg:#003942;--color-cyan-border:#005866;--status-not-started-text:#A0A0A0;--status-not-started-bg:#333333;--status-not-started-border:#4C4C4C;--status-in-progress-text:#2297F6;--status-in-progress-bg:#002442;--status-in-progress-border:#003A69;--status-pending-text:#06B6D4;--status-pending-bg:#003942;--status-pending-border:#005866;--status-success-text:#259F4C;--status-success-bg:#1B271F;--status-success-border:#259F4C;--status-error-text:#FE3B4C;--status-error-bg:#262121;--status-error-border:#FE3B4C;--status-warning-text:#F5A534;--status-warning-bg:#492B00;--status-warning-border:#663B00;--status-advanced-text:#C084FC;--status-advanced-bg:#2D1B3D;--status-advanced-border:#2D1B3D;--gradient-primary-btn:linear-gradient(90deg, #672D92, #547CCC);--gradient-brand:linear-gradient(152deg, #0078C2, #0047FF, #8453D2);--gradient-magical:linear-gradient(90deg, #672D92, #5677C8);--gradient-purple-radial:radial-gradient(271.77% 163.1% at 50% -10.71%, #200E32 0%, #9E00FF 75.14%, #EC56FF 100%);--gradient-switch-off:linear-gradient(to right, #BBB, #666);--gradient-switch-on:linear-gradient(to right, #672C92, #547CCC);--blue-25:#F1F8FF;--blue-50:#D5E7FC;--blue-100:#B2D7FF;--blue-300:#2297F6;--blue-400:#007AFF;--blue-500:#4E32FF;--blue-550:#0C4DAF;--blue-600:#003A69;--blue-800:#002442}.light{--color-bg-page:#F9F9F9;--color-bg-sidebar:#FBFBFB;--color-bg-card:#FFFFFF;--color-bg-nav:#FBFBFB;--color-bg-elevated:#FFFFFF;--color-bg-secondary:#FFFFFF;--color-bg-tertiary:#FBFBFB;--color-bg-quaternary:#B2D7FF;--color-bg-hover:#D5E7FC;--color-bg-hover-strong:#D5E7FC;--color-bg-input:#FFFFFF;--color-bg-input-prefix:#EEEEEE;--color-bg-btn-primary:#D5E7FC;--color-bg-btn-primary-h:#B2D7FF;--color-bg-pagination-active:#D5E7FC;--color-text-primary:#333333;--color-text-secondary:#333333;--color-text-tertiary:#333333;--color-text-muted:#666666;--color-text-placeholder:#999999;--color-text-link:#007AFF;--color-text-link-hover:#0C4DAF;--color-text-inverse:#FFFFFF;--color-text-heading:#007AFF;--color-text-nav:#FFFFFF;--color-border-primary:#CCCCCC;--color-border-secondary:#BBBBBB;--color-border-structural:#E5E5E5;--color-border-subtle:#999999;--color-border-accent:#007AFF;--color-border-focus:#000000;--color-border-error:#F9303C;--color-border-panel:#E5E5E5;--color-border-btn-secondary:transparent;--color-border-btn-secondary-h:#007AFF;--color-icon-primary:#666666;--color-icon-secondary:#333333;--color-icon-tertiary:#707070;--color-icon-error:#F9303C;--color-success:#259F4C;--color-success-bg:#E6F7E6;--color-success-border:#259F4C;--color-success-text:#259F4C;--color-error:#F9303C;--color-error-bg:#F0E2E3;--color-error-border:#FE3B4C;--color-error-text:#FE3B4C;--color-warning:#F5A534;--color-warning-bg:#FAF2E7;--color-warning-border:#F5A534;--color-warning-text:#F5A534;--color-info:#2297F6;--color-info-bg:#D5E7FC;--color-info-border:#2297F6;--color-info-text:#2297F6;--color-purple:#8B5CF6;--color-purple-bg:#F3E8FF;--color-purple-border:#F3E8FF;--color-purple-text:#C084FC;--color-cyan:#06B6D4;--color-cyan-bg:#DFFAFF;--color-cyan-border:#005866;--status-not-started-text:#A0A0A0;--status-not-started-bg:#EEEEEE;--status-not-started-border:#999999;--status-in-progress-text:#2297F6;--status-in-progress-bg:#D5E7FC;--status-in-progress-border:#2297F6;--status-pending-text:#06B6D4;--status-pending-bg:#DFFAFF;--status-pending-border:#06B6D4;--status-success-text:#259F4C;--status-success-bg:#E6F7E6;--status-success-border:#259F4C;--status-error-text:#FE3B4C;--status-error-bg:#F0E2E3;--status-error-border:#FE3B4C;--status-warning-text:#F5A534;--status-warning-bg:#FAF2E7;--status-warning-border:#F5A534;--status-advanced-text:#8B5CF6;--status-advanced-bg:#F3E8FF;--status-advanced-border:#C084FC;--gradient-primary-btn:linear-gradient(90deg, #3676f7, #cc22f2);--gradient-magical:linear-gradient(90deg, #3676f7, #cc22f2);--gradient-switch-off:linear-gradient(to right, #fff, #fff);--gradient-switch-on:linear-gradient(to right, #007AFF, #007AFF);--shadow-sm:0 1px 2px rgba(0, 0, 0, 0.06);--shadow-md:0 4px 6px -1px rgba(0, 0, 0, 0.08),0 2px 4px -2px rgba(0, 0, 0, 0.06);--shadow-lg:0 10px 15px -3px rgba(0, 0, 0, 0.1),0 4px 6px -4px rgba(0, 0, 0, 0.08)}*,::after,::before{box-sizing:border-box;margin:0;padding:0}html{font-size:16px;-webkit-text-size-adjust:100%;scroll-behavior:smooth}body{font-family:var(--font-sans);font-size:var(--text-sm);line-height:1.5;color:var(--color-text-primary);background-color:var(--color-bg-page);-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}a{color:var(--color-text-link);text-decoration:none;transition:color var(--transition-fast)}a:hover{color:var(--color-text-link-hover);text-decoration:underline}::-webkit-scrollbar{width:6px;height:6px}::-webkit-scrollbar-track{background:0 0}::-webkit-scrollbar-thumb{background:var(--color-border-subtle);border-radius:var(--radius-full)}::-webkit-scrollbar-thumb:hover{background:var(--color-border-secondary)}::selection{background:rgba(34,151,246,.3);color:var(--color-text-primary)}:focus-visible{outline:2px solid var(--color-border-focus);outline-offset:2px;border-radius:var(--radius-sm)}img,svg,video{display:block;max-width:100%}ol,ul{list-style:none}code,kbd,pre,samp{font-family:var(--font-mono)}code{font-size:.875em;background:var(--color-bg-quaternary);color:var(--color-text-secondary);padding:.1em .4em;border-radius:var(--radius-sm);border:1px solid var(--color-border-primary)}pre{background:var(--color-bg-quaternary);border:1px solid var(--color-border-primary);border-radius:var(--radius-lg);padding:var(--space-4);overflow-x:auto;font-size:var(--text-xs);line-height:1.6;color:var(--color-text-secondary)}pre code{background:0 0;border:none;padding:0;font-size:inherit}.divider,hr{border:none;border-top:1px solid var(--color-border-structural);margin:var(--space-4) 0}.container{width:100%;max-width:1200px;margin:0 auto;padding:0 var(--space-4)}.section{padding:var(--space-8) 0}.icon-xs{width:12px;height:12px;flex-shrink:0}.icon-sm{width:16px;height:16px;flex-shrink:0}.icon-md{width:18px;height:18px;flex-shrink:0}.icon-lg{width:20px;height:20px;flex-shrink:0}.icon-xl{width:24px;height:24px;flex-shrink:0}.icon-2xl{width:32px;height:32px;flex-shrink:0}.h1,h1{font-size:var(--text-h1);line-height:var(--lh-h1);font-weight:700;color:var(--color-text-primary);letter-spacing:-.02em}.h2,h2{font-size:var(--text-h2);line-height:var(--lh-h2);font-weight:600;color:var(--color-text-primary)}.h3,h3{font-size:var(--text-h3);line-height:var(--lh-h3);font-weight:600;color:var(--color-text-primary)}.h4,h4{font-size:var(--text-h4);line-height:var(--lh-h4);font-weight:600;color:var(--color-text-primary)}.h5,h5{font-size:var(--text-h5);line-height:var(--lh-h5);font-weight:600;color:var(--color-text-primary)}.h6,h6{font-size:var(--text-xs);line-height:1.4;font-weight:600;color:var(--color-text-muted);text-transform:uppercase;letter-spacing:.05em}.section-label{font-size:var(--text-xs);font-weight:600;color:var(--color-text-heading);text-transform:uppercase;letter-spacing:.05em;margin-bottom:var(--space-2)}.text-2xl{font-size:1.5rem}.text-xl{font-size:1.25rem}.text-lg{font-size:1.125rem}.text-base{font-size:var(--text-base)}.text-sm{font-size:var(--text-sm)}.text-sm-1{font-size:var(--text-sm-1)}.text-xs{font-size:var(--text-xs)}.text-xs-1{font-size:var(--text-xs-1)}.font-normal{font-weight:400}.font-medium{font-weight:500}.font-semibold{font-weight:600}.font-bold{font-weight:700}.text-primary{color:var(--color-text-primary)}.text-secondary{color:var(--color-text-secondary)}.text-tertiary{color:var(--color-text-tertiary)}.text-muted{color:var(--color-text-muted)}.text-link{color:var(--color-text-link)}.text-inverse{color:var(--color-text-inverse)}.text-success{color:var(--color-success-text)}.text-error{color:var(--color-error-text)}.text-warning{color:var(--color-warning-text)}.text-info{color:var(--color-info-text)}.uppercase{text-transform:uppercase}.capitalize{text-transform:capitalize}.lowercase{text-transform:lowercase}.truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.line-clamp-2{display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}.line-clamp-3{display:-webkit-box;-webkit-line-clamp:3;-webkit-box-orient:vertical;overflow:hidden}.nowrap{white-space:nowrap}.text-left{text-align:left}.text-center{text-align:center}.text-right{text-align:right}p{color:var(--color-text-secondary);font-size:var(--text-sm);line-height:1.6}p+p{margin-top:var(--space-3)}.caption{font-size:var(--text-xs);color:var(--color-text-muted);line-height:1.4}.mono{font-family:var(--font-mono);font-size:.9em}.btn{display:inline-flex;align-items:center;justify-content:center;gap:var(--space-1-5);font-family:var(--font-sans);font-weight:600;white-space:nowrap;border-radius:var(--radius-lg);border:1px solid transparent;cursor:pointer;transition:background-color var(--transition-base),border-color var(--transition-base),color var(--transition-base),opacity var(--transition-base);text-decoration:none;user-select:none;outline:0;position:relative;overflow:hidden;-webkit-font-smoothing:antialiased}.btn:focus-visible{outline:2px solid var(--color-border-focus);outline-offset:2px}.btn:disabled,.btn[aria-disabled=true]{cursor:not-allowed;opacity:.5;pointer-events:none}.btn-sm{height:24px;padding:0 var(--space-1-5);gap:var(--space-1);font-size:var(--text-xs);line-height:20px}.btn-md{height:28px;padding:0 var(--space-2);gap:var(--space-1-5);font-size:var(--text-xs);line-height:24px}.btn-lg{height:44px;padding:0 var(--space-4);gap:var(--space-2-5);font-size:var(--text-sm);line-height:28px}.btn-primary{background-color:var(--color-bg-btn-primary);color:var(--color-text-secondary);border:1px solid transparent;background-image:linear-gradient(var(--color-bg-btn-primary),var(--color-bg-btn-primary)),var(--gradient-primary-btn);background-origin:border-box;background-clip:padding-box,border-box}.btn-primary:hover:not(:disabled){background-image:linear-gradient(var(--color-bg-btn-primary-h),var(--color-bg-btn-primary-h)),var(--gradient-primary-btn)}.btn-secondary{background-color:var(--color-bg-secondary);color:var(--color-text-secondary);border-color:var(--color-border-btn-secondary)}.btn-secondary:hover:not(:disabled){background-color:var(--color-bg-hover-strong);border-color:var(--color-border-btn-secondary-h)}.btn-base{background-color:var(--color-bg-secondary);color:var(--color-text-primary);border-color:var(--color-border-structural)}.btn-base:hover:not(:disabled){background-color:var(--color-border-structural)}.btn-action{background-color:var(--color-bg-btn-primary);color:var(--color-text-secondary);border-color:var(--color-bg-input-prefix)}.btn-action:hover:not(:disabled){border-color:var(--color-border-btn-secondary-h);background-color:var(--color-bg-btn-primary-h)}.btn-delete{background-color:rgba(254,59,76,.1);color:var(--color-error-text);border-color:var(--color-error-border)}.btn-delete:hover:not(:disabled){background-color:rgba(254,59,76,.15);border-color:var(--color-error)}.btn-ghost,.btn-tertiary{background-color:transparent;color:var(--color-text-primary);border-color:transparent}.btn-ghost:hover:not(:disabled),.btn-tertiary:hover:not(:disabled){background-color:var(--color-border-structural)}.btn-magical{background:var(--gradient-magical);color:var(--color-text-inverse);border-color:var(--color-border-structural)}.btn-magical:hover:not(:disabled){filter:brightness(1.1)}.btn-link{background:0 0;border-color:transparent;color:var(--color-text-link);padding-left:0;padding-right:0;font-weight:500;height:auto}.btn-link:hover:not(:disabled){text-decoration:underline}.btn-icon{padding:0;aspect-ratio:1}.btn-icon.btn-sm{width:24px}.btn-icon.btn-md{width:28px}.btn-icon.btn-lg{width:44px}.btn-full{width:100%}.btn-loading{cursor:wait;pointer-events:none}.btn-loading::after{content:'';position:absolute;inset:0;background:linear-gradient(90deg,transparent 0,rgba(255,255,255,.12) 50%,transparent 100%);background-size:200% 100%;animation:btn-shimmer 1.5s infinite linear}@keyframes btn-shimmer{from{background-position:-200% 0}to{background-position:200% 0}}.btn-group{display:inline-flex;gap:0}.btn-group .btn{border-radius:0}.btn-group .btn:first-child{border-radius:var(--radius-lg) 0 0 var(--radius-lg)}.btn-group .btn:last-child{border-radius:0 var(--radius-lg) var(--radius-lg) 0}.btn-group .btn:not(:first-child){margin-left:-1px}.form-group{display:flex;flex-direction:column;gap:var(--space-1)}.form-label{display:flex;align-items:center;gap:var(--space-0-5);font-size:var(--text-xs);font-weight:500;color:var(--color-text-muted)}.form-label .required{color:var(--color-error);margin-left:1px}.input-wrapper{display:flex;align-items:stretch;min-height:32px;max-height:32px;border:1px solid var(--color-border-primary);border-radius:var(--radius-lg);background-color:var(--color-bg-input);transition:border-color var(--transition-base),box-shadow var(--transition-base);overflow:hidden}.input-wrapper:hover:not(.input-disabled){border-color:var(--color-border-secondary)}.input-wrapper:focus-within:not(.input-disabled){border-color:var(--color-border-secondary)}.input-wrapper.input-error{border-color:var(--color-border-error)}.input-wrapper.input-disabled{opacity:.6;cursor:not-allowed}.form-input{flex:1;background:0 0;border:none;outline:0;font-family:var(--font-sans);font-size:var(--text-sm);color:var(--color-text-primary);padding:0 var(--space-2);min-width:0}.form-input::placeholder{color:var(--color-text-placeholder)}.form-input:disabled{cursor:not-allowed}.input,input.input{display:block;width:100%;height:32px;padding:0 var(--space-2);background-color:var(--color-bg-input);border:1px solid var(--color-border-primary);border-radius:var(--radius-lg);font-family:var(--font-sans);font-size:var(--text-sm);color:var(--color-text-primary);outline:0;transition:border-color var(--transition-base)}.input::placeholder,input.input::placeholder{color:var(--color-text-placeholder)}.input:hover,input.input:hover{border-color:var(--color-border-secondary)}.input:focus,input.input:focus{border-color:var(--color-border-secondary)}.input.input-error,input.input.input-error{border-color:var(--color-border-error)}.input:disabled,input.input:disabled{opacity:.6;cursor:not-allowed}.input-lg{height:40px;font-size:var(--text-base);padding:0 var(--space-3)}.input-prefix,.input-suffix{display:flex;align-items:center;padding:0 var(--space-2);font-size:var(--text-xs);color:var(--color-text-tertiary);background-color:var(--color-bg-input-prefix);white-space:nowrap;flex-shrink:0;border-right:1px solid var(--color-border-subtle)}.input-suffix{border-right:none;border-left:1px solid var(--color-border-subtle)}.input-adornment{display:flex;align-items:center;padding:0 var(--space-2);color:var(--color-icon-tertiary);flex-shrink:0}.textarea,textarea.textarea{display:block;width:100%;min-height:80px;max-height:384px;padding:var(--space-2-5) var(--space-3);background-color:var(--color-bg-input);border:1px solid var(--color-border-primary);border-radius:var(--radius-lg);font-family:var(--font-sans);font-size:var(--text-sm);color:var(--color-text-primary);outline:0;resize:vertical;line-height:1.5;transition:border-color var(--transition-base)}.textarea::placeholder,textarea.textarea::placeholder{color:var(--color-text-placeholder)}.textarea:hover,textarea.textarea:hover{border-color:var(--color-border-secondary)}.textarea:focus,textarea.textarea:focus{border-color:var(--color-border-secondary)}.textarea.textarea-error,textarea.textarea.textarea-error{border-color:var(--color-border-error)}.textarea:disabled,textarea.textarea:disabled{background-color:var(--color-bg-secondary);color:var(--color-text-tertiary);cursor:not-allowed;opacity:.7}.select-trigger{display:flex;align-items:center;justify-content:space-between;height:32px;padding:0 var(--space-2);background-color:var(--color-bg-input);border:1px solid var(--color-border-primary);border-radius:var(--radius-lg);font-family:var(--font-sans);font-size:var(--text-sm);color:var(--color-text-primary);cursor:pointer;user-select:none;transition:border-color var(--transition-base);gap:var(--space-2)}.select-trigger:hover{border-color:var(--color-border-secondary)}.select-trigger.select-open{border-color:var(--color-border-secondary)}.select-trigger .select-arrow{color:var(--color-icon-tertiary);flex-shrink:0;transition:transform var(--transition-base)}.select-trigger.select-open .select-arrow{transform:rotate(180deg)}select.select{display:block;width:100%;height:32px;padding:0 var(--space-2);background-color:var(--color-bg-input);border:1px solid var(--color-border-primary);border-radius:var(--radius-lg);font-family:var(--font-sans);font-size:var(--text-sm);color:var(--color-text-primary);outline:0;cursor:pointer;appearance:none;background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='%23999' stroke-width='2'%3E%3Cpath d='M6 9l6 6 6-6'/%3E%3C/svg%3E");background-repeat:no-repeat;background-position:right 8px center;padding-right:28px}select.select:hover{border-color:var(--color-border-secondary)}select.select:focus{border-color:var(--color-border-secondary)}.select-panel{position:absolute;z-index:var(--z-dropdown);min-width:160px;max-width:256px;margin-top:var(--space-2);padding:var(--space-1-5);background-color:var(--color-bg-elevated);border:1px solid var(--color-border-panel);border-radius:var(--radius-lg);box-shadow:var(--shadow-md);max-height:240px;overflow-y:auto}.select-option{display:block;padding:var(--space-1-5) var(--space-2-5);font-size:var(--text-sm);color:var(--color-text-primary);border-radius:var(--radius-md);cursor:pointer;transition:background-color var(--transition-fast);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.select-option:hover{background-color:var(--color-bg-hover-strong)}.select-option.selected{background-color:var(--color-bg-hover);font-weight:500}.checkbox-wrapper{display:inline-flex;align-items:center;gap:var(--space-2);cursor:pointer;user-select:none}.checkbox-wrapper input[type=checkbox]{position:absolute;opacity:0;width:0;height:0}.checkbox-box{display:flex;align-items:center;justify-content:center;width:20px;height:20px;flex-shrink:0;border-radius:var(--radius-md);border:2px solid var(--color-border-primary);background-color:var(--color-bg-elevated);transition:border-color var(--transition-fast),background-color var(--transition-fast)}.checkbox-wrapper:hover .checkbox-box{border-color:var(--color-border-secondary)}.checkbox-wrapper input[type=checkbox]:checked+.checkbox-box{border-color:var(--color-text-primary);background-color:var(--color-text-primary)}.checkbox-check{display:none;width:12px;height:12px;color:var(--color-bg-page)}.checkbox-wrapper input[type=checkbox]:checked+.checkbox-box .checkbox-check{display:block}.checkbox-label{font-size:var(--text-sm);color:var(--color-text-primary)}.checkbox-wrapper.disabled{opacity:.6;cursor:not-allowed;pointer-events:none}.radio-wrapper{display:inline-flex;align-items:center;gap:var(--space-2);cursor:pointer;user-select:none}.radio-wrapper input[type=radio]{position:absolute;opacity:0;width:0;height:0}.radio-dot{display:flex;align-items:center;justify-content:center;width:18px;height:18px;flex-shrink:0;border-radius:var(--radius-full);border:2px solid var(--color-border-primary);background-color:var(--color-bg-elevated);transition:border-color var(--transition-fast),background-color var(--transition-fast)}.radio-dot::after{content:'';width:8px;height:8px;border-radius:var(--radius-full);background-color:var(--color-bg-page);opacity:0;transition:opacity var(--transition-fast)}.radio-wrapper:hover .radio-dot{border-color:var(--color-border-secondary)}.radio-wrapper input[type=radio]:checked+.radio-dot{border-color:var(--color-text-primary);background-color:var(--color-text-primary)}.radio-wrapper input[type=radio]:checked+.radio-dot::after{opacity:1}.radio-label{font-size:var(--text-sm);color:var(--color-text-primary)}.switch-wrapper{display:inline-flex;align-items:center;gap:var(--space-2);cursor:pointer;user-select:none}.switch-wrapper input[type=checkbox]{position:absolute;opacity:0;width:0;height:0}.switch-track{position:relative;width:32px;height:16px;border-radius:var(--radius-full);background:var(--gradient-switch-off);border:1px solid var(--color-border-primary);transition:background var(--transition-base),border-color var(--transition-base);flex-shrink:0}.switch-wrapper input[type=checkbox]:checked+.switch-track{background:var(--gradient-switch-on);border-color:var(--color-border-accent)}.switch-knob{position:absolute;top:1px;left:2px;width:12px;height:12px;border-radius:var(--radius-full);background-color:#ccc;transition:transform var(--transition-base),background-color var(--transition-base);pointer-events:none}.switch-wrapper input[type=checkbox]:checked~.switch-track .switch-knob{transform:translateX(16px);background-color:#fff}.switch-track.switch-on .switch-knob{transform:translateX(16px);background-color:#fff}.switch-label{font-size:var(--text-xs);color:var(--color-text-muted);transition:color var(--transition-fast)}.switch-wrapper:hover .switch-label{color:var(--color-border-accent)}.form-error{font-size:var(--text-sm);color:var(--color-error-text);display:flex;align-items:center;gap:var(--space-1);margin-top:var(--space-0-5)}.form-helper{font-size:var(--text-xs);color:var(--color-text-muted);margin-top:var(--space-0-5)}.search-input-wrapper{position:relative;display:flex;align-items:center}.search-input-wrapper .search-icon{position:absolute;left:var(--space-2);color:var(--color-icon-tertiary);pointer-events:none}.search-input-wrapper input{padding-left:28px}.card{display:flex;flex-direction:column;background-color:var(--color-bg-card);border:1px solid var(--color-border-structural);border-radius:var(--radius-xl);overflow:hidden;transition:border-color var(--transition-base),box-shadow var(--transition-base)}.card:hover{border-color:var(--color-border-secondary)}.card-fixed{height:var(--card-height)}.card-header{display:flex;align-items:center;justify-content:space-between;padding:var(--space-4);border-bottom:1px solid var(--color-border-structural)}.card-body{flex:1;padding:var(--space-4);overflow:hidden}.card-footer{padding:var(--space-3) var(--space-4);border-top:1px solid var(--color-border-structural);display:flex;align-items:center;gap:var(--space-2)}.card-title{font-size:var(--text-base);font-weight:600;color:var(--color-text-primary);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.card-subtitle{font-size:var(--text-xs);color:var(--color-text-muted);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.card-description{font-size:var(--text-xs);color:var(--color-text-tertiary);display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}.panel{background-color:var(--color-bg-secondary);border:1px solid var(--color-border-panel);border-radius:var(--radius-lg);overflow:hidden}.panel-header{display:flex;align-items:center;justify-content:space-between;padding:var(--space-3) var(--space-4);border-bottom:1px solid var(--color-border-structural);background-color:var(--color-bg-tertiary)}.panel-body{padding:var(--space-4)}.badge{display:inline-flex;align-items:center;gap:var(--space-1-5);padding:0 var(--space-2);height:17px;line-height:17px;border-radius:var(--radius-full);border:1px solid;font-size:var(--text-xs-1);font-weight:700;text-transform:uppercase;letter-spacing:.04em;white-space:nowrap;user-select:none;flex-shrink:0}.badge-dot{width:7px;height:7px;border-radius:var(--radius-full);flex-shrink:0;display:inline-block}.badge-not-started{background-color:var(--status-not-started-bg);color:var(--status-not-started-text);border-color:var(--status-not-started-border)}.badge-not-started .badge-dot{background-color:var(--status-not-started-text)}.badge-in-progress{background-color:var(--status-in-progress-bg);color:var(--status-in-progress-text);border-color:var(--status-in-progress-border)}.badge-in-progress .badge-dot{background-color:var(--status-in-progress-text);animation:badge-pulse 2s cubic-bezier(.4,0,.6,1) infinite}.badge-pending{background-color:var(--status-pending-bg);color:var(--status-pending-text);border-color:var(--status-pending-border)}.badge-pending .badge-dot{background-color:var(--status-pending-text)}.badge-success{background-color:var(--status-success-bg);color:var(--status-success-text);border-color:var(--status-success-border)}.badge-success .badge-dot{background-color:var(--status-success-text)}.badge-error{background-color:var(--status-error-bg);color:var(--status-error-text);border-color:var(--status-error-border)}.badge-error .badge-dot{background-color:var(--status-error-text)}.badge-warning{background-color:var(--status-warning-bg);color:var(--status-warning-text);border-color:var(--status-warning-border)}.badge-warning .badge-dot{background-color:var(--status-warning-text)}.badge-advanced{background-color:var(--status-advanced-bg);color:var(--status-advanced-text);border-color:var(--status-advanced-border)}.badge-advanced .badge-dot{background-color:var(--status-advanced-text)}@keyframes badge-pulse{0%,100%{opacity:1}50%{opacity:.4}}.tag{display:inline-flex;align-items:center;gap:var(--space-1);padding:var(--space-1) var(--space-2);border-radius:var(--radius-lg);border:1px solid var(--color-border-secondary);background-color:var(--color-bg-secondary);font-size:var(--text-xs);font-weight:600;color:var(--color-text-tertiary);white-space:nowrap;user-select:none}.tag-sm{padding:1px var(--space-1-5);font-size:var(--text-xs-1);border-radius:var(--radius-md)}.tag-blue{background-color:var(--color-info-bg);border-color:var(--color-info-border);color:var(--color-info-text)}.tag-green{background-color:var(--color-success-bg);border-color:var(--color-success-border);color:var(--color-success-text)}.tag-red{background-color:var(--color-error-bg);border-color:var(--color-error-border);color:var(--color-error-text)}.tag-yellow{background-color:var(--color-warning-bg);border-color:var(--color-warning-border);color:var(--color-warning-text)}.tag-purple{background-color:var(--color-purple-bg);border-color:var(--color-purple-border);color:var(--color-purple-text)}.alert{display:flex;align-items:flex-start;gap:var(--space-2);padding:var(--space-2) var(--space-3);border-radius:var(--radius-md);border:1px solid;font-size:var(--text-xs);line-height:1.5}.alert-icon{flex-shrink:0;margin-top:1px}.alert-info{background-color:var(--color-info-bg);border-color:var(--color-info-border);color:var(--color-info-text)}.alert-success{background-color:var(--color-success-bg);border-color:var(--color-success-border);color:var(--color-success-text)}.alert-warning{background-color:var(--color-warning-bg);border-color:var(--color-warning-border);color:var(--color-warning-text)}.alert-error{background-color:var(--color-error-bg);border-color:var(--color-error-border);color:var(--color-error-text)}.avatar{display:flex;align-items:center;justify-content:center;border-radius:var(--radius-full);border:1px solid var(--color-border-secondary);background-color:var(--color-bg-elevated);overflow:hidden;flex-shrink:0;user-select:none;font-weight:600;color:#fff;font-size:var(--text-xs)}.avatar img{width:100%;height:100%;object-fit:cover;border-radius:var(--radius-full)}.avatar-xs{width:20px;height:20px;font-size:8px;border-width:1px}.avatar-sm{width:24px;height:24px;font-size:9px;border-width:1px}.avatar-md{width:32px;height:32px;font-size:var(--text-xs)}.avatar-chat{width:40px;height:40px;font-size:var(--text-sm)}.avatar-lg{width:72px;height:72px;font-size:var(--text-h3);border-width:2px}.avatar-xl{width:96px;height:96px;font-size:var(--text-h2);border-width:2px}.avatar-modal{width:176px;height:176px;font-size:var(--text-h1);border-width:2px}.avatar-color-0{background-color:#aa47bc}.avatar-color-1{background-color:#7a1fa2}.avatar-color-2{background-color:#6b8592}.avatar-color-3{background-color:#465a65}.avatar-color-4{background-color:#ec407a}.avatar-color-5{background-color:#c2175b}.avatar-color-6{background-color:#5c6bc0}.avatar-color-7{background-color:#0288d1}.avatar-color-8{background-color:#00579c}.avatar-color-9{background-color:#0098a6}.avatar-color-10{background-color:#00887a}.avatar-color-11{background-color:#004c3f}.avatar-color-12{background-color:#689f39}.avatar-color-13{background-color:#33691e}.avatar-color-14{background-color:#8c6e63}.avatar-color-15{background-color:#5d4038}.avatar-color-16{background-color:#7e57c2}.avatar-color-17{background-color:#512da7}.avatar-color-18{background-color:#ef6c00}.avatar-color-19{background-color:#f5511e}.avatar-color-20{background-color:#aa3410}.avatar-group{display:flex;align-items:center}.avatar-group .avatar{margin-left:-8px}.avatar-group .avatar:first-child{margin-left:0}.spinner{display:inline-block;border-radius:var(--radius-full);border-style:solid;border-color:var(--color-border-secondary);border-top-color:var(--color-text-primary);animation:spinner-spin .8s linear infinite;flex-shrink:0}.spinner-xs{width:12px;height:12px;border-width:1.5px}.spinner-sm{width:16px;height:16px;border-width:2px}.spinner-md{width:24px;height:24px;border-width:2px}.spinner-lg{width:32px;height:32px;border-width:3px}.spinner-xl{width:48px;height:48px;border-width:3px}.spinner-page{display:flex;justify-content:center;align-items:center;min-height:200px}@keyframes spinner-spin{to{transform:rotate(360deg)}}.progress-track{position:relative;overflow:hidden;background-color:var(--color-bg-tertiary);border-radius:68px;border:1px solid var(--color-border-secondary);width:85px;height:14px}.progress-fill{height:100%;background:var(--gradient-primary-btn);border-radius:68px;transition:width .2s ease-out}.progress-label{position:absolute;left:50%;top:50%;transform:translate(-50%,-50%);font-size:9px;line-height:10px;font-weight:600;color:var(--color-text-primary);white-space:nowrap}.progress-bar{width:100%;height:4px;background-color:var(--color-bg-tertiary);border-radius:var(--radius-full);overflow:hidden}.progress-bar .progress-fill{height:4px;border-radius:var(--radius-full)}.tooltip-wrapper{position:relative;display:inline-flex}.tooltip{position:absolute;z-index:var(--z-toast);bottom:calc(100% + 8px);left:50%;transform:translateX(-50%);background-color:var(--color-bg-elevated);color:var(--color-text-primary);padding:var(--space-2) var(--space-3);border-radius:var(--radius-md);font-size:var(--text-xs);line-height:1;white-space:nowrap;box-shadow:var(--shadow-md);pointer-events:none;opacity:0;transition:opacity var(--transition-fast)}.tooltip-wrapper:hover .tooltip{opacity:1}.tooltip-right{bottom:auto;top:50%;left:calc(100% + 8px);transform:translateY(-50%)}.tooltip-bottom{bottom:auto;top:calc(100% + 8px)}.empty-state{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:var(--space-8) var(--space-4);text-align:center;gap:var(--space-3)}.empty-state-icon{color:var(--color-icon-tertiary);margin-bottom:var(--space-1)}.empty-state-title{font-size:var(--text-h4);font-weight:600;color:var(--color-text-primary)}.empty-state-description{font-size:var(--text-sm);color:var(--color-text-muted);max-width:320px}.chip{display:inline-flex;align-items:center;gap:var(--space-1);padding:2px var(--space-1-5);border-radius:var(--radius-full);background-color:var(--color-bg-quaternary);border:1px solid var(--color-border-primary);font-size:var(--text-xs);color:var(--color-text-secondary)}.chip-close{display:flex;align-items:center;justify-content:center;width:14px;height:14px;border-radius:var(--radius-full);background:0 0;border:none;color:var(--color-icon-tertiary);cursor:pointer;padding:0;transition:color var(--transition-fast),background-color var(--transition-fast)}.chip-close:hover{color:var(--color-text-primary);background-color:var(--color-bg-hover)}.stat-card{display:flex;flex-direction:column;gap:var(--space-1);padding:var(--space-3) var(--space-4);background-color:var(--color-bg-card);border:1px solid var(--color-border-structural);border-radius:var(--radius-xl);min-width:0}.stat-card-label{font-size:var(--text-xs-1);font-weight:700;text-transform:uppercase;letter-spacing:.04em;color:var(--color-text-muted);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.stat-card-value{font-size:var(--text-h2);font-weight:700;line-height:1.2;color:var(--color-text-primary)}.stat-card-desc{font-size:var(--text-xs);color:var(--color-text-muted);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.stat-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(200px,1fr));gap:var(--space-3)}.dl-grid{display:grid;grid-template-columns:max-content 1fr;gap:var(--space-2) var(--space-4);font-size:var(--text-sm)}.dl-grid dt{color:var(--color-text-muted);font-weight:500;white-space:nowrap}.dl-grid dd{color:var(--color-text-primary)}.table-wrapper{width:100%;overflow-x:auto}.table,table.table{width:100%;border-collapse:separate;border-spacing:0;font-size:var(--text-xs);line-height:1.3}.table thead tr th{text-align:left;padding:var(--space-2-5) var(--space-4);font-weight:600;color:var(--color-text-primary);background-color:var(--color-bg-quaternary);border-top:1px solid var(--color-border-structural);border-bottom:1px solid var(--color-border-structural);white-space:nowrap;position:sticky;top:0;z-index:1}.table thead tr th:first-child{border-left:1px solid var(--color-border-structural);border-radius:var(--radius-lg) 0 0 0}.table thead tr th:last-child{border-right:1px solid var(--color-border-structural);border-radius:0 var(--radius-lg) 0 0}.table tbody tr td{padding:var(--space-3) var(--space-4);color:var(--color-text-secondary);border-bottom:1px solid var(--color-border-structural);vertical-align:middle}.table tbody tr:hover td{background-color:var(--color-bg-hover)}.table tbody tr:last-child td{border-bottom:none}.th-sortable{cursor:pointer;user-select:none;gap:var(--space-1)}.th-sortable:hover{color:var(--color-text-link)}.sort-icon{display:inline-flex;flex-direction:column;gap:1px;color:var(--color-icon-tertiary);flex-shrink:0}.th-sortable.sort-asc .sort-icon,.th-sortable.sort-desc .sort-icon{color:var(--color-text-primary)}.td-number{text-align:right;font-variant-numeric:tabular-nums}.tabs{display:flex;flex-direction:column}.tabs-list{display:flex;align-items:stretch;border-bottom:1px solid var(--color-border-panel);flex-shrink:0}.tab-item{display:inline-flex;align-items:center;justify-content:center;gap:var(--space-2);padding:var(--space-2) var(--space-2);padding-bottom:var(--space-4);font-size:var(--text-sm);color:var(--color-text-primary);cursor:pointer;border-bottom:2px solid transparent;transition:border-color var(--transition-base),color var(--transition-base);text-decoration:none;white-space:nowrap;user-select:none;background:0 0;border-top:none;border-left:none;border-right:none;font-family:var(--font-sans)}.tab-item:hover{border-bottom-color:var(--color-text-tertiary)}.tab-item.active,.tab-item[aria-selected=true]{border-bottom-color:var(--color-text-primary);font-weight:600;cursor:default}.tabs-sm .tab-item{font-size:var(--text-xs);padding:var(--space-2-5);padding-bottom:var(--space-2-5)}.tabs-panel{flex:1;padding-top:var(--space-4)}.pagination{display:flex;align-items:center;gap:var(--space-2);font-size:var(--text-sm);padding:var(--space-3) 0;border-top:1px solid var(--color-border-structural)}.pagination-info{color:var(--color-text-muted);font-size:var(--text-h5);margin-right:auto}.page-btn{display:flex;align-items:center;justify-content:center;height:32px;min-width:32px;padding:0 var(--space-2);background-color:var(--color-bg-secondary);border:1px solid var(--color-border-structural);border-radius:var(--radius-lg);color:var(--color-text-link);font-size:var(--text-h5);cursor:pointer;transition:border-color var(--transition-fast),background-color var(--transition-fast);user-select:none;text-decoration:none;font-family:var(--font-sans);font-weight:400}.page-btn:hover:not(.disabled){border-color:var(--color-border-accent)}.page-btn.active{border-color:var(--color-border-accent);background-color:var(--color-bg-pagination-active);font-weight:600}.page-btn.disabled{opacity:.4;cursor:not-allowed;pointer-events:none}.modal-overlay{position:fixed;inset:0;background-color:rgba(0,0,0,.5);display:flex;align-items:center;justify-content:center;z-index:var(--z-modal);padding:var(--space-4)}.modal{display:flex;flex-direction:column;background-color:var(--color-bg-elevated);border:1px solid var(--color-border-panel);border-radius:var(--radius-lg);box-shadow:var(--shadow-lg);max-height:95vh;width:100%;overflow:hidden}.modal-sm{max-width:400px}.modal-md{max-width:512px}.modal-lg{max-width:720px}.modal-xl{max-width:1024px}.modal-full{max-width:90vw}.modal-header{display:flex;align-items:center;justify-content:space-between;gap:var(--space-4);padding:var(--space-3) var(--space-4);border-bottom:1px solid var(--color-border-structural);flex-shrink:0}.modal-title{font-size:var(--text-base);font-weight:600;color:var(--color-text-primary)}.modal-close{display:flex;align-items:center;justify-content:center;width:28px;height:28px;border-radius:var(--radius-lg);border:none;background:0 0;color:var(--color-icon-tertiary);cursor:pointer;transition:color var(--transition-fast),background-color var(--transition-fast);flex-shrink:0}.modal-close:hover{color:var(--color-text-primary);background-color:var(--color-bg-quaternary)}.modal-body{flex:1;padding:var(--space-4);overflow-y:auto}.modal-footer{display:flex;align-items:center;justify-content:flex-end;gap:var(--space-3);padding:var(--space-4);border-top:1px solid var(--color-border-structural);flex-shrink:0;background-color:var(--color-bg-elevated);border-radius:0 0 var(--radius-lg) var(--radius-lg)}.nav-sidebar{display:flex;flex-direction:column;gap:var(--space-0-5);padding:var(--space-2)}.nav-item{display:flex;align-items:center;gap:var(--space-2);padding:var(--space-2) var(--space-3);border-radius:var(--radius-lg);font-size:var(--text-sm);color:var(--color-text-nav);cursor:pointer;transition:background-color var(--transition-fast),color var(--transition-fast);text-decoration:none;user-select:none;white-space:nowrap;overflow:hidden}.nav-item:hover{background-color:var(--color-bg-hover);color:var(--color-text-primary)}.nav-item.active{background-color:var(--color-bg-secondary);color:var(--color-text-primary);font-weight:500}.nav-item .nav-icon{flex-shrink:0;color:var(--color-icon-tertiary);transition:color var(--transition-fast)}.nav-item.active .nav-icon,.nav-item:hover .nav-icon{color:var(--color-icon-primary)}.nav-group-label{padding:var(--space-2) var(--space-3) var(--space-1);font-size:var(--text-xs);font-weight:600;color:var(--color-text-muted);text-transform:uppercase;letter-spacing:.05em}.app-shell{display:flex;min-height:100vh}.app-navbar{width:var(--navbar-width);min-height:100vh;background-color:var(--color-bg-nav);display:flex;flex-direction:column;align-items:center;padding:var(--space-3) 0;flex-shrink:0;border-right:1px solid var(--color-border-structural)}.app-sidebar{width:var(--sidebar-width);min-height:100vh;background-color:var(--color-bg-sidebar);display:flex;flex-direction:column;border-right:1px solid var(--color-border-structural);flex-shrink:0}.app-content{flex:1;display:flex;flex-direction:column;min-width:0;overflow:hidden}.app-header{height:var(--layout-header-height);display:flex;align-items:center;padding:0 var(--space-4);border-bottom:1px solid var(--color-border-structural);background-color:var(--color-bg-page);flex-shrink:0;gap:var(--space-4)}.app-main{flex:1;padding:var(--space-4);overflow-y:auto}.hidden{display:none!important}.block{display:block}.inline{display:inline}.inline-block{display:inline-block}.flex{display:flex}.inline-flex{display:inline-flex}.grid{display:grid}.flex-col{flex-direction:column}.flex-row{flex-direction:row}.flex-wrap{flex-wrap:wrap}.flex-nowrap{flex-wrap:nowrap}.flex-1{flex:1}.flex-auto{flex:auto}.flex-none{flex:none}.flex-shrink-0{flex-shrink:0}.grow{flex-grow:1}.items-start{align-items:flex-start}.items-center{align-items:center}.items-end{align-items:flex-end}.items-stretch{align-items:stretch}.justify-start{justify-content:flex-start}.justify-center{justify-content:center}.justify-end{justify-content:flex-end}.justify-between{justify-content:space-between}.grid-2{grid-template-columns:repeat(2,minmax(0,1fr))}.grid-3{grid-template-columns:repeat(3,minmax(0,1fr))}.grid-4{grid-template-columns:repeat(4,minmax(0,1fr))}.grid-auto-fill-sm{grid-template-columns:repeat(auto-fill,minmax(200px,1fr))}.grid-auto-fill-md{grid-template-columns:repeat(auto-fill,minmax(280px,1fr))}.grid-auto-fill-lg{grid-template-columns:repeat(auto-fill,minmax(320px,1fr))}.gap-1{gap:var(--space-1)}.gap-2{gap:var(--space-2)}.gap-3{gap:var(--space-3)}.gap-4{gap:var(--space-4)}.gap-5{gap:var(--space-5)}.gap-6{gap:var(--space-6)}.gap-8{gap:var(--space-8)}.gap-x-2{column-gap:var(--space-2)}.gap-x-3{column-gap:var(--space-3)}.gap-x-4{column-gap:var(--space-4)}.gap-y-2{row-gap:var(--space-2)}.gap-y-3{row-gap:var(--space-3)}.gap-y-4{row-gap:var(--space-4)}.p-1{padding:var(--space-1)}.p-2{padding:var(--space-2)}.p-3{padding:var(--space-3)}.p-4{padding:var(--space-4)}.p-6{padding:var(--space-6)}.p-8{padding:var(--space-8)}.px-2{padding-left:var(--space-2);padding-right:var(--space-2)}.px-3{padding-left:var(--space-3);padding-right:var(--space-3)}.px-4{padding-left:var(--space-4);padding-right:var(--space-4)}.py-1{padding-top:var(--space-1);padding-bottom:var(--space-1)}.py-2{padding-top:var(--space-2);padding-bottom:var(--space-2)}.py-3{padding-top:var(--space-3);padding-bottom:var(--space-3)}.py-4{padding-top:var(--space-4);padding-bottom:var(--space-4)}.pt-2{padding-top:var(--space-2)}.pt-4{padding-top:var(--space-4)}.pt-8{padding-top:var(--space-8)}.pb-2{padding-bottom:var(--space-2)}.pb-4{padding-bottom:var(--space-4)}.m-auto{margin:auto}.mx-auto{margin-left:auto;margin-right:auto}.ml-auto{margin-left:auto}.mr-auto{margin-right:auto}.mt-1{margin-top:var(--space-1)}.mt-2{margin-top:var(--space-2)}.mt-3{margin-top:var(--space-3)}.mt-4{margin-top:var(--space-4)}.mt-6{margin-top:var(--space-6)}.mt-8{margin-top:var(--space-8)}.mb-1{margin-bottom:var(--space-1)}.mb-2{margin-bottom:var(--space-2)}.mb-3{margin-bottom:var(--space-3)}.mb-4{margin-bottom:var(--space-4)}.mb-6{margin-bottom:var(--space-6)}.w-full{width:100%}.w-auto{width:auto}.h-full{height:100%}.min-h-screen{min-height:100vh}.min-w-0{min-width:0}.max-w-sm{max-width:384px}.max-w-md{max-width:512px}.max-w-lg{max-width:720px}.max-w-xl{max-width:960px}.max-w-full{max-width:100%}.overflow-hidden{overflow:hidden}.overflow-auto{overflow:auto}.overflow-x-auto{overflow-x:auto}.overflow-y-auto{overflow-y:auto}.overflow-visible{overflow:visible}.relative{position:relative}.absolute{position:absolute}.fixed{position:fixed}.sticky{position:sticky}.inset-0{inset:0}.top-0{top:0}.bottom-0{bottom:0}.left-0{left:0}.right-0{right:0}.z-10{z-index:10}.z-20{z-index:20}.z-30{z-index:var(--z-dropdown)}.z-50{z-index:var(--z-modal)}.z-60{z-index:var(--z-toast)}.rounded-sm{border-radius:var(--radius-sm)}.rounded-md{border-radius:var(--radius-md)}.rounded-lg{border-radius:var(--radius-lg)}.rounded-xl{border-radius:var(--radius-xl)}.rounded-2xl{border-radius:var(--radius-2xl)}.rounded-full{border-radius:var(--radius-full)}.bg-page{background-color:var(--color-bg-page)}.bg-card{background-color:var(--color-bg-card)}.bg-elevated{background-color:var(--color-bg-elevated)}.bg-secondary{background-color:var(--color-bg-secondary)}.bg-tertiary{background-color:var(--color-bg-tertiary)}.bg-success{background-color:var(--color-success-bg)}.bg-error{background-color:var(--color-error-bg)}.bg-warning{background-color:var(--color-warning-bg)}.bg-info{background-color:var(--color-info-bg)}.bg-brand{background:var(--gradient-brand)}.bg-magical{background:var(--gradient-magical)}.bg-purple-radial{background:var(--gradient-purple-radial)}.border{border:1px solid var(--color-border-structural)}.border-top{border-top:1px solid var(--color-border-structural)}.border-bottom{border-bottom:1px solid var(--color-border-structural)}.border-left{border-left:1px solid var(--color-border-structural)}.border-right{border-right:1px solid var(--color-border-structural)}.border-none{border:none}.cursor-pointer{cursor:pointer}.cursor-default{cursor:default}.cursor-not-allowed{cursor:not-allowed}.cursor-wait{cursor:wait}.pointer-none{pointer-events:none}.opacity-0{opacity:0}.opacity-50{opacity:.5}.opacity-60{opacity:.6}.opacity-100{opacity:1}.select-none{user-select:none}.select-text{user-select:text}.shadow-sm{box-shadow:var(--shadow-sm)}.shadow-md{box-shadow:var(--shadow-md)}.shadow-lg{box-shadow:var(--shadow-lg)}.transition{transition:all var(--transition-base)}.transition-colors{transition:color var(--transition-base),background-color var(--transition-base),border-color var(--transition-base)}
|