@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.
Files changed (117) hide show
  1. package/README.md +17 -6
  2. package/dist/agents/plugins/claude/plugin/skills/codemie-analytics/SKILL.md +641 -0
  3. package/dist/agents/plugins/claude/plugin/skills/codemie-analytics/references/leaderboard-dashboard-report.md +225 -0
  4. package/dist/agents/plugins/claude/plugin/skills/codemie-analytics/references/people-spending-dashboard-report.md +746 -0
  5. package/dist/agents/plugins/claude/plugin/skills/codemie-analytics/references/people-spending-dashboard-template.html +3270 -0
  6. package/dist/agents/plugins/claude/plugin/skills/codemie-analytics/scripts/analytics-cli.js +893 -0
  7. package/dist/agents/plugins/claude/plugin/skills/codemie-analytics/scripts/inspect-schema.js +211 -0
  8. package/dist/agents/plugins/claude/plugin/skills/codemie-html-report/README.md +39 -0
  9. package/dist/agents/plugins/claude/plugin/skills/codemie-html-report/SKILL.md +117 -26
  10. package/dist/agents/plugins/claude/plugin/skills/codemie-html-report/scripts/inject-css.js +40 -0
  11. package/dist/agents/plugins/claude/plugin/skills/codemie-html-report/scripts/inject-data.js +68 -0
  12. package/dist/agents/plugins/claude/plugin/skills/codemie-html-report/style-guide/css/bundle.css +1 -0
  13. package/dist/agents/plugins/claude/plugin/skills/codemie-sdk/SKILL.md +240 -0
  14. package/dist/agents/plugins/claude/plugin/skills/codemie-sdk/examples/assistants.md +256 -0
  15. package/dist/agents/plugins/claude/plugin/skills/codemie-sdk/examples/categories.md +101 -0
  16. package/dist/agents/plugins/claude/plugin/skills/codemie-sdk/examples/datasources.md +401 -0
  17. package/dist/agents/plugins/claude/plugin/skills/codemie-sdk/examples/integrations.md +242 -0
  18. package/dist/agents/plugins/claude/plugin/skills/codemie-sdk/examples/skills.md +191 -0
  19. package/dist/agents/plugins/claude/plugin/skills/codemie-sdk/examples/users.md +38 -0
  20. package/dist/agents/plugins/claude/plugin/skills/codemie-sdk/examples/workflows.md +151 -0
  21. package/dist/cli/commands/profile/display.d.ts.map +1 -1
  22. package/dist/cli/commands/profile/display.js +1 -0
  23. package/dist/cli/commands/profile/display.js.map +1 -1
  24. package/dist/cli/commands/proxy/connectors/desktop.d.ts.map +1 -1
  25. package/dist/cli/commands/proxy/connectors/desktop.js +20 -10
  26. package/dist/cli/commands/proxy/connectors/desktop.js.map +1 -1
  27. package/dist/cli/commands/sdk/assistants.d.ts +3 -0
  28. package/dist/cli/commands/sdk/assistants.d.ts.map +1 -0
  29. package/dist/cli/commands/sdk/assistants.js +211 -0
  30. package/dist/cli/commands/sdk/assistants.js.map +1 -0
  31. package/dist/cli/commands/sdk/categories.d.ts +3 -0
  32. package/dist/cli/commands/sdk/categories.d.ts.map +1 -0
  33. package/dist/cli/commands/sdk/categories.js +186 -0
  34. package/dist/cli/commands/sdk/categories.js.map +1 -0
  35. package/dist/cli/commands/sdk/datasources.d.ts +3 -0
  36. package/dist/cli/commands/sdk/datasources.d.ts.map +1 -0
  37. package/dist/cli/commands/sdk/datasources.js +276 -0
  38. package/dist/cli/commands/sdk/datasources.js.map +1 -0
  39. package/dist/cli/commands/sdk/index.d.ts +3 -0
  40. package/dist/cli/commands/sdk/index.d.ts.map +1 -0
  41. package/dist/cli/commands/sdk/index.js +23 -0
  42. package/dist/cli/commands/sdk/index.js.map +1 -0
  43. package/dist/cli/commands/sdk/integrations.d.ts +3 -0
  44. package/dist/cli/commands/sdk/integrations.d.ts.map +1 -0
  45. package/dist/cli/commands/sdk/integrations.js +220 -0
  46. package/dist/cli/commands/sdk/integrations.js.map +1 -0
  47. package/dist/cli/commands/sdk/llm.d.ts +3 -0
  48. package/dist/cli/commands/sdk/llm.d.ts.map +1 -0
  49. package/dist/cli/commands/sdk/llm.js +48 -0
  50. package/dist/cli/commands/sdk/llm.js.map +1 -0
  51. package/dist/cli/commands/sdk/services/assistants.d.ts +13 -0
  52. package/dist/cli/commands/sdk/services/assistants.d.ts.map +1 -0
  53. package/dist/cli/commands/sdk/services/assistants.js +60 -0
  54. package/dist/cli/commands/sdk/services/assistants.js.map +1 -0
  55. package/dist/cli/commands/sdk/services/categories.d.ts +8 -0
  56. package/dist/cli/commands/sdk/services/categories.d.ts.map +1 -0
  57. package/dist/cli/commands/sdk/services/categories.js +19 -0
  58. package/dist/cli/commands/sdk/services/categories.js.map +1 -0
  59. package/dist/cli/commands/sdk/services/datasources.d.ts +33 -0
  60. package/dist/cli/commands/sdk/services/datasources.d.ts.map +1 -0
  61. package/dist/cli/commands/sdk/services/datasources.js +268 -0
  62. package/dist/cli/commands/sdk/services/datasources.js.map +1 -0
  63. package/dist/cli/commands/sdk/services/index.d.ts +6 -0
  64. package/dist/cli/commands/sdk/services/index.d.ts.map +1 -0
  65. package/dist/cli/commands/sdk/services/index.js +6 -0
  66. package/dist/cli/commands/sdk/services/index.js.map +1 -0
  67. package/dist/cli/commands/sdk/services/integrations.d.ts +27 -0
  68. package/dist/cli/commands/sdk/services/integrations.d.ts.map +1 -0
  69. package/dist/cli/commands/sdk/services/integrations.js +59 -0
  70. package/dist/cli/commands/sdk/services/integrations.js.map +1 -0
  71. package/dist/cli/commands/sdk/services/llm.d.ts +4 -0
  72. package/dist/cli/commands/sdk/services/llm.d.ts.map +1 -0
  73. package/dist/cli/commands/sdk/services/llm.js +7 -0
  74. package/dist/cli/commands/sdk/services/llm.js.map +1 -0
  75. package/dist/cli/commands/sdk/services/skills.d.ts +23 -0
  76. package/dist/cli/commands/sdk/services/skills.d.ts.map +1 -0
  77. package/dist/cli/commands/sdk/services/skills.js +69 -0
  78. package/dist/cli/commands/sdk/services/skills.js.map +1 -0
  79. package/dist/cli/commands/sdk/services/users.d.ts +4 -0
  80. package/dist/cli/commands/sdk/services/users.d.ts.map +1 -0
  81. package/dist/cli/commands/sdk/services/users.js +7 -0
  82. package/dist/cli/commands/sdk/services/users.js.map +1 -0
  83. package/dist/cli/commands/sdk/services/workflows.d.ts +7 -0
  84. package/dist/cli/commands/sdk/services/workflows.d.ts.map +1 -0
  85. package/dist/cli/commands/sdk/services/workflows.js +34 -0
  86. package/dist/cli/commands/sdk/services/workflows.js.map +1 -0
  87. package/dist/cli/commands/sdk/skills.d.ts +3 -0
  88. package/dist/cli/commands/sdk/skills.d.ts.map +1 -0
  89. package/dist/cli/commands/sdk/skills.js +492 -0
  90. package/dist/cli/commands/sdk/skills.js.map +1 -0
  91. package/dist/cli/commands/sdk/users.d.ts +3 -0
  92. package/dist/cli/commands/sdk/users.d.ts.map +1 -0
  93. package/dist/cli/commands/sdk/users.js +81 -0
  94. package/dist/cli/commands/sdk/users.js.map +1 -0
  95. package/dist/cli/commands/sdk/utils/cli-utils.d.ts +35 -0
  96. package/dist/cli/commands/sdk/utils/cli-utils.d.ts.map +1 -0
  97. package/dist/cli/commands/sdk/utils/cli-utils.js +110 -0
  98. package/dist/cli/commands/sdk/utils/cli-utils.js.map +1 -0
  99. package/dist/cli/commands/sdk/utils/datasource-types.d.ts +9 -0
  100. package/dist/cli/commands/sdk/utils/datasource-types.d.ts.map +1 -0
  101. package/dist/cli/commands/sdk/utils/datasource-types.js +61 -0
  102. package/dist/cli/commands/sdk/utils/datasource-types.js.map +1 -0
  103. package/dist/cli/commands/sdk/utils/file-utils.d.ts +8 -0
  104. package/dist/cli/commands/sdk/utils/file-utils.d.ts.map +1 -0
  105. package/dist/cli/commands/sdk/utils/file-utils.js +21 -0
  106. package/dist/cli/commands/sdk/utils/file-utils.js.map +1 -0
  107. package/dist/cli/commands/sdk/utils/render.d.ts +82 -0
  108. package/dist/cli/commands/sdk/utils/render.d.ts.map +1 -0
  109. package/dist/cli/commands/sdk/utils/render.js +149 -0
  110. package/dist/cli/commands/sdk/utils/render.js.map +1 -0
  111. package/dist/cli/commands/sdk/workflows.d.ts +3 -0
  112. package/dist/cli/commands/sdk/workflows.d.ts.map +1 -0
  113. package/dist/cli/commands/sdk/workflows.js +170 -0
  114. package/dist/cli/commands/sdk/workflows.js.map +1 -0
  115. package/dist/cli/index.js +2 -0
  116. package/dist/cli/index.js.map +1 -1
  117. 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 — Read the CSS files
20
+ ## Step 1 — CSS placeholder
21
21
 
22
- Read **all 8 CSS files** from `${CLAUDE_PLUGIN_ROOT}/skills/codemie-html-report/style-guide/css/` you will inline them all:
22
+ **Do NOT read any CSS files. Do NOT inline any CSS yourself.**
23
23
 
24
- | File | What it covers |
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
- All files are located at: `${CLAUDE_PLUGIN_ROOT}/skills/codemie-html-report/style-guide/css/<filename>`
26
+ ```css
27
+ /* __CODEMIE_CSS__ */
28
+ ```
36
29
 
37
- ## Step 2Page skeleton (fully self-contained)
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
- **CRITICAL: Every HTML file you produce must be a single self-contained file.** Do NOT use `<link>` tags pointing to external `.css` files. Instead, inline the entire CodeMie design system directly inside a `<style>` block.
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
- Workflow:
42
- 1. Read all 8 CSS files from `${CLAUDE_PLUGIN_ROOT}/skills/codemie-html-report/style-guide/css/`.
43
- 2. Concatenate their contents in order: tokens base → typography → buttons → forms → components → layout → utilities.
44
- 3. Paste the full concatenated CSS into the `<style>` tag in `<head>`.
45
- 4. Keep the `@import url('https://fonts.googleapis.com/...')` line from `tokens.css` at the very top of the `<style>` block (Google Fonts CDN is acceptable as an external dependency).
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
- /* === CodeMie Design System — inlined for portability === */
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}`);
@@ -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)}