opencastle 0.7.0 → 0.8.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 (93) hide show
  1. package/README.md +30 -3
  2. package/bin/cli.mjs +2 -0
  3. package/dist/cli/adapters/claude-code.d.ts +2 -5
  4. package/dist/cli/adapters/claude-code.d.ts.map +1 -1
  5. package/dist/cli/adapters/claude-code.js +12 -251
  6. package/dist/cli/adapters/claude-code.js.map +1 -1
  7. package/dist/cli/adapters/cursor.d.ts.map +1 -1
  8. package/dist/cli/adapters/cursor.js +3 -17
  9. package/dist/cli/adapters/cursor.js.map +1 -1
  10. package/dist/cli/adapters/frontmatter.d.ts +26 -0
  11. package/dist/cli/adapters/frontmatter.d.ts.map +1 -0
  12. package/dist/cli/adapters/frontmatter.js +40 -0
  13. package/dist/cli/adapters/frontmatter.js.map +1 -0
  14. package/dist/cli/adapters/index.d.ts +5 -0
  15. package/dist/cli/adapters/index.d.ts.map +1 -0
  16. package/dist/cli/adapters/index.js +9 -0
  17. package/dist/cli/adapters/index.js.map +1 -0
  18. package/dist/cli/adapters/opencode.d.ts +2 -5
  19. package/dist/cli/adapters/opencode.d.ts.map +1 -1
  20. package/dist/cli/adapters/opencode.js +12 -250
  21. package/dist/cli/adapters/opencode.js.map +1 -1
  22. package/dist/cli/adapters/single-file-base.d.ts +40 -0
  23. package/dist/cli/adapters/single-file-base.d.ts.map +1 -0
  24. package/dist/cli/adapters/single-file-base.js +246 -0
  25. package/dist/cli/adapters/single-file-base.js.map +1 -0
  26. package/dist/cli/dashboard.d.ts.map +1 -1
  27. package/dist/cli/dashboard.js +3 -2
  28. package/dist/cli/dashboard.js.map +1 -1
  29. package/dist/cli/detect.d.ts.map +1 -1
  30. package/dist/cli/detect.js +13 -11
  31. package/dist/cli/detect.js.map +1 -1
  32. package/dist/cli/doctor.d.ts +3 -0
  33. package/dist/cli/doctor.d.ts.map +1 -0
  34. package/dist/cli/doctor.js +205 -0
  35. package/dist/cli/doctor.js.map +1 -0
  36. package/dist/cli/init.d.ts.map +1 -1
  37. package/dist/cli/init.js +31 -19
  38. package/dist/cli/init.js.map +1 -1
  39. package/dist/cli/run/schema.d.ts +1 -5
  40. package/dist/cli/run/schema.d.ts.map +1 -1
  41. package/dist/cli/run/schema.js +6 -330
  42. package/dist/cli/run/schema.js.map +1 -1
  43. package/dist/cli/run.d.ts.map +1 -1
  44. package/dist/cli/run.js +14 -1
  45. package/dist/cli/run.js.map +1 -1
  46. package/dist/cli/types.d.ts +0 -5
  47. package/dist/cli/types.d.ts.map +1 -1
  48. package/dist/cli/update.d.ts.map +1 -1
  49. package/dist/cli/update.js +4 -17
  50. package/dist/cli/update.js.map +1 -1
  51. package/package.json +7 -2
  52. package/src/cli/adapters/claude-code.ts +13 -304
  53. package/src/cli/adapters/cursor.ts +3 -23
  54. package/src/cli/adapters/frontmatter.ts +47 -0
  55. package/src/cli/adapters/index.ts +13 -0
  56. package/src/cli/adapters/opencode.ts +12 -301
  57. package/src/cli/adapters/single-file-base.ts +320 -0
  58. package/src/cli/dashboard.ts +3 -2
  59. package/src/cli/detect.ts +19 -15
  60. package/src/cli/doctor.ts +235 -0
  61. package/src/cli/init.ts +31 -24
  62. package/src/cli/run/schema.ts +7 -365
  63. package/src/cli/run.ts +17 -1
  64. package/src/cli/types.ts +0 -6
  65. package/src/cli/update.ts +5 -23
  66. package/src/dashboard/dist/_astro/{index.CWVzbF4T.css → index.Bnq19_1M.css} +1 -1
  67. package/src/dashboard/dist/index.html +170 -11
  68. package/src/dashboard/node_modules/.vite/deps/_metadata.json +6 -6
  69. package/src/dashboard/seed-data/reviews.ndjson +6 -0
  70. package/src/dashboard/src/pages/index.astro +213 -10
  71. package/src/dashboard/src/styles/dashboard.css +196 -0
  72. package/src/orchestrator/agent-workflows/bug-fix.md +2 -2
  73. package/src/orchestrator/agent-workflows/data-pipeline.md +8 -8
  74. package/src/orchestrator/agent-workflows/database-migration.md +2 -2
  75. package/src/orchestrator/agent-workflows/feature-implementation.md +12 -5
  76. package/src/orchestrator/agent-workflows/performance-optimization.md +2 -2
  77. package/src/orchestrator/agent-workflows/refactoring.md +2 -2
  78. package/src/orchestrator/agent-workflows/schema-changes.md +2 -2
  79. package/src/orchestrator/agent-workflows/security-audit.md +2 -2
  80. package/src/orchestrator/agents/data-expert.agent.md +2 -2
  81. package/src/orchestrator/agents/researcher.agent.md +0 -16
  82. package/src/orchestrator/agents/team-lead.agent.md +17 -6
  83. package/src/orchestrator/customizations/AGENT-PERFORMANCE.md +1 -3
  84. package/src/orchestrator/prompts/bootstrap-customizations.prompt.md +1 -1
  85. package/src/orchestrator/prompts/bug-fix.prompt.md +11 -6
  86. package/src/orchestrator/prompts/implement-feature.prompt.md +9 -4
  87. package/src/orchestrator/prompts/quick-refinement.prompt.md +9 -5
  88. package/src/orchestrator/prompts/resolve-pr-comments.prompt.md +18 -4
  89. package/src/orchestrator/skills/agent-hooks/SKILL.md +4 -2
  90. package/src/orchestrator/skills/fast-review/SKILL.md +15 -4
  91. package/src/orchestrator/skills/self-improvement/SKILL.md +1 -1
  92. package/src/orchestrator/skills/validation-gates/SKILL.md +152 -15
  93. package/src/orchestrator/prompts/metrics-report.prompt.md +0 -144
@@ -1,375 +1,17 @@
1
1
  import { readFile } from 'node:fs/promises'
2
- import type { TaskSpec, ParseResult, ValidationResult } from '../types.js'
3
-
4
- /**
5
- * Minimal YAML parser for task spec files.
6
- * Handles: key-value, lists, nested objects, block scalars (|), comments, quoted strings.
7
- * Does NOT handle: anchors, aliases, flow mappings, merge keys, tags.
8
- */
2
+ import { parse as yamlParse } from 'yaml'
3
+ import type { TaskSpec, ValidationResult } from '../types.js'
9
4
 
10
5
  /**
11
6
  * Parse a YAML string into a JS object.
7
+ * Uses the `yaml` npm package for full YAML 1.2 compliance.
12
8
  */
13
9
  export function parseYaml(text: string): Record<string, unknown> {
14
- const lines = text.split('\n')
15
- return parseBlock(lines, 0, -1).value as Record<string, unknown>
16
- }
17
-
18
- /**
19
- * Remove inline comments and trim trailing whitespace.
20
- * Respects quoted strings — won't strip # inside quotes.
21
- */
22
- function stripInlineComment(line: string): string {
23
- let inSingle = false
24
- let inDouble = false
25
- for (let i = 0; i < line.length; i++) {
26
- const ch = line[i]
27
- if (ch === "'" && !inDouble) inSingle = !inSingle
28
- else if (ch === '"' && !inSingle) inDouble = !inDouble
29
- else if (ch === '#' && !inSingle && !inDouble) {
30
- // Must be preceded by whitespace (or be at start)
31
- if (i === 0 || /\s/.test(line[i - 1])) {
32
- return line.slice(0, i).trimEnd()
33
- }
34
- }
35
- }
36
- return line.trimEnd()
37
- }
38
-
39
- /**
40
- * Measure indent level (number of leading spaces).
41
- */
42
- function indentOf(line: string): number {
43
- const m = line.match(/^( *)/)
44
- return m ? m[1].length : 0
45
- }
46
-
47
- /**
48
- * Unquote a string value (strip surrounding quotes).
49
- */
50
- function unquote(val: string): string {
51
- if (
52
- (val.startsWith('"') && val.endsWith('"')) ||
53
- (val.startsWith("'") && val.endsWith("'"))
54
- ) {
55
- return val.slice(1, -1)
10
+ const result = yamlParse(text)
11
+ if (!result || typeof result !== 'object') {
12
+ throw new Error('YAML must be a mapping at the top level')
56
13
  }
57
- return val
58
- }
59
-
60
- /**
61
- * Cast a scalar value to its JS type.
62
- */
63
- function castScalar(raw: string): string | number | boolean | null {
64
- const val = raw.trim()
65
- if (val === '' || val === '~' || val === 'null') return null
66
- if (val === 'true') return true
67
- if (val === 'false') return false
68
- if (/^-?\d+$/.test(val)) return parseInt(val, 10)
69
- if (/^-?\d+\.\d+$/.test(val)) return parseFloat(val)
70
- return unquote(val)
71
- }
72
-
73
- /**
74
- * Parse a block of YAML lines starting at `startIdx` with minimum indent `parentIndent`.
75
- * Returns { value, nextIndex }.
76
- */
77
- function parseBlock(lines: string[], startIdx: number, parentIndent: number): ParseResult {
78
- let i = startIdx
79
-
80
- // Skip blank / comment-only lines
81
- while (i < lines.length) {
82
- const stripped = lines[i].trimStart()
83
- if (stripped === '' || stripped.startsWith('#')) {
84
- i++
85
- continue
86
- }
87
- break
88
- }
89
- if (i >= lines.length) return { value: null, nextIndex: i }
90
-
91
- const firstLine = stripInlineComment(lines[i])
92
- const firstIndent = indentOf(firstLine)
93
- if (firstIndent <= parentIndent) return { value: null, nextIndex: i }
94
-
95
- const trimmedFirst = firstLine.trimStart()
96
-
97
- // Detect whether this block is a list or a mapping
98
- if (trimmedFirst.startsWith('- ') || trimmedFirst === '-') {
99
- return parseList(lines, i, firstIndent)
100
- }
101
- return parseMapping(lines, i, firstIndent)
102
- }
103
-
104
- /**
105
- * Parse a YAML list block.
106
- */
107
- function parseList(lines: string[], startIdx: number, blockIndent: number): ParseResult {
108
- const result: unknown[] = []
109
- let i = startIdx
110
-
111
- while (i < lines.length) {
112
- // Skip blanks / comments
113
- const raw = lines[i]
114
- const stripped = raw.trimStart()
115
- if (stripped === '' || stripped.startsWith('#')) {
116
- i++
117
- continue
118
- }
119
-
120
- const indent = indentOf(raw)
121
- if (indent < blockIndent) break
122
- if (indent > blockIndent) break // Shouldn't happen at list level
123
-
124
- const line = stripInlineComment(raw)
125
- const trimmed = line.trimStart()
126
-
127
- if (!trimmed.startsWith('- ') && trimmed !== '-') break
128
-
129
- // Content after "- "
130
- const after = trimmed === '-' ? '' : trimmed.slice(2).trim()
131
- i++
132
-
133
- if (after === '' || after.endsWith(':')) {
134
- // List item is a nested mapping or empty
135
- // Check if the next non-empty lines at deeper indent form an object
136
- // If after ends with ':', it's the first key in a mapping
137
- const obj: Record<string, unknown> = {}
138
- if (after.endsWith(':')) {
139
- const key = after.slice(0, -1).trim()
140
- // Value on next lines or empty
141
- const nested = parseValueAfterColon('', lines, i, blockIndent + 2)
142
- obj[key] = nested.value
143
- i = nested.nextIndex
144
- }
145
- // Collect remaining keys at the deeper indent
146
- const sub = parseItemBody(lines, i, blockIndent + 2)
147
- Object.assign(obj, (sub.value as Record<string, unknown>) || {})
148
- i = sub.nextIndex
149
- result.push(Object.keys(obj).length > 0 ? obj : null)
150
- } else if (after.includes(': ') || after.endsWith(':')) {
151
- // Inline mapping start: "- key: value"
152
- const colonIdx = after.indexOf(':')
153
- const key = after.slice(0, colonIdx).trim()
154
- const rest = after.slice(colonIdx + 1).trim()
155
- const obj: Record<string, unknown> = {}
156
-
157
- if (rest === '' || rest === '|') {
158
- const nested = parseValueAfterColon(rest, lines, i, blockIndent + 2)
159
- obj[key] = nested.value
160
- i = nested.nextIndex
161
- } else {
162
- obj[key] = castScalar(rest)
163
- }
164
-
165
- // Collect remaining keys at deeper indent
166
- const sub = parseItemBody(lines, i, blockIndent + 2)
167
- Object.assign(obj, (sub.value as Record<string, unknown>) || {})
168
- i = sub.nextIndex
169
- result.push(obj)
170
- } else if (after.startsWith('[') && after.endsWith(']')) {
171
- // Inline flow sequence
172
- result.push(parseFlowSequence(after))
173
- } else {
174
- // Simple scalar list item
175
- result.push(castScalar(after))
176
- }
177
- }
178
-
179
- return { value: result, nextIndex: i }
180
- }
181
-
182
- /**
183
- * Parse the body (remaining keys) of a list item at a given indent.
184
- */
185
- function parseItemBody(lines: string[], startIdx: number, minIndent: number): ParseResult {
186
- let i = startIdx
187
- const obj: Record<string, unknown> = {}
188
-
189
- while (i < lines.length) {
190
- const raw = lines[i]
191
- const stripped = raw.trimStart()
192
- if (stripped === '' || stripped.startsWith('#')) {
193
- i++
194
- continue
195
- }
196
-
197
- const indent = indentOf(raw)
198
- if (indent < minIndent) break
199
-
200
- const line = stripInlineComment(raw)
201
- const trimmed = line.trimStart()
202
-
203
- // If this is a list item at this indent, it belongs to a parent list
204
- if (trimmed.startsWith('- ')) break
205
-
206
- const colonIdx = trimmed.indexOf(':')
207
- if (colonIdx === -1) {
208
- i++
209
- continue
210
- }
211
-
212
- const key = trimmed.slice(0, colonIdx).trim()
213
- const rest = trimmed.slice(colonIdx + 1).trim()
214
- i++
215
-
216
- const nested = parseValueAfterColon(rest, lines, i, indent)
217
- obj[key] = nested.value
218
- i = nested.nextIndex
219
- }
220
-
221
- return { value: Object.keys(obj).length > 0 ? obj : null, nextIndex: i }
222
- }
223
-
224
- /**
225
- * Parse a YAML mapping block.
226
- */
227
- function parseMapping(lines: string[], startIdx: number, blockIndent: number): ParseResult {
228
- const result: Record<string, unknown> = {}
229
- let i = startIdx
230
-
231
- while (i < lines.length) {
232
- const raw = lines[i]
233
- const stripped = raw.trimStart()
234
- if (stripped === '' || stripped.startsWith('#')) {
235
- i++
236
- continue
237
- }
238
-
239
- const indent = indentOf(raw)
240
- if (indent < blockIndent) break
241
- if (indent > blockIndent) {
242
- i++
243
- continue
244
- }
245
-
246
- const line = stripInlineComment(raw)
247
- const trimmed = line.trimStart()
248
-
249
- const colonIdx = trimmed.indexOf(':')
250
- if (colonIdx === -1) {
251
- i++
252
- continue
253
- }
254
-
255
- const key = trimmed.slice(0, colonIdx).trim()
256
- const rest = trimmed.slice(colonIdx + 1).trim()
257
- i++
258
-
259
- const nested = parseValueAfterColon(rest, lines, i, blockIndent)
260
- result[key] = nested.value
261
- i = nested.nextIndex
262
- }
263
-
264
- return { value: result, nextIndex: i }
265
- }
266
-
267
- /**
268
- * Parse the value after a colon — could be inline scalar, block scalar (|),
269
- * nested mapping, or nested list.
270
- */
271
- function parseValueAfterColon(
272
- rest: string,
273
- lines: string[],
274
- nextIdx: number,
275
- parentIndent: number
276
- ): ParseResult {
277
- // Block scalar
278
- if (rest === '|') {
279
- return parseBlockScalar(lines, nextIdx, parentIndent)
280
- }
281
-
282
- // Flow sequence [a, b, c]
283
- if (rest.startsWith('[') && rest.endsWith(']')) {
284
- return { value: parseFlowSequence(rest), nextIndex: nextIdx }
285
- }
286
-
287
- // Inline scalar value present
288
- if (rest !== '') {
289
- return { value: castScalar(rest), nextIndex: nextIdx }
290
- }
291
-
292
- // Empty after colon — check for nested block
293
- const nested = parseBlock(lines, nextIdx, parentIndent)
294
- if (nested.value !== null) {
295
- return nested
296
- }
297
-
298
- return { value: null, nextIndex: nextIdx }
299
- }
300
-
301
- /**
302
- * Parse a block scalar (| indicator).
303
- * Collects all lines with indent greater than the parent.
304
- */
305
- function parseBlockScalar(lines: string[], startIdx: number, parentIndent: number): ParseResult {
306
- let i = startIdx
307
- const collected: string[] = []
308
- let blockIndent = -1
309
-
310
- while (i < lines.length) {
311
- const raw = lines[i]
312
-
313
- // Blank line inside block scalar — preserve it
314
- if (raw.trim() === '') {
315
- collected.push('')
316
- i++
317
- continue
318
- }
319
-
320
- const indent = indentOf(raw)
321
- if (blockIndent === -1) {
322
- // First content line determines the block indent
323
- if (indent <= parentIndent) break
324
- blockIndent = indent
325
- }
326
-
327
- if (indent < blockIndent) break
328
-
329
- collected.push(raw.slice(blockIndent))
330
- i++
331
- }
332
-
333
- // Remove trailing blank lines
334
- while (collected.length > 0 && collected[collected.length - 1] === '') {
335
- collected.pop()
336
- }
337
-
338
- return { value: collected.join('\n') + '\n', nextIndex: i }
339
- }
340
-
341
- /**
342
- * Parse a flow sequence: [item1, item2, item3]
343
- * Handles quoted strings that may contain commas.
344
- */
345
- function parseFlowSequence(text: string): Array<string | number | boolean | null> {
346
- const inner = text.slice(1, -1).trim()
347
- if (inner === '') return []
348
-
349
- const items: string[] = []
350
- let current = ''
351
- let inQuote: string | null = null
352
-
353
- for (let i = 0; i < inner.length; i++) {
354
- const ch = inner[i]
355
- if (inQuote) {
356
- if (ch === inQuote) {
357
- inQuote = null
358
- } else {
359
- current += ch
360
- }
361
- } else if (ch === '"' || ch === "'") {
362
- inQuote = ch
363
- } else if (ch === ',') {
364
- items.push(current.trim())
365
- current = ''
366
- } else {
367
- current += ch
368
- }
369
- }
370
- if (current.trim()) items.push(current.trim())
371
-
372
- return items.map(castScalar)
14
+ return result as Record<string, unknown>
373
15
  }
374
16
 
375
17
  // ── Schema validation ──────────────────────────────────────────────
package/src/cli/run.ts CHANGED
@@ -114,9 +114,25 @@ export default async function run({ args }: CliContext): Promise<void> {
114
114
  const adapter = await getAdapter(spec.adapter)
115
115
  const available = await adapter.isAvailable()
116
116
  if (!available) {
117
+ const hints: Record<string, string> = {
118
+ 'claude-code':
119
+ ' Install: npm install -g @anthropic-ai/claude-code\n' +
120
+ ' Docs: https://docs.anthropic.com/en/docs/claude-code',
121
+ copilot:
122
+ ' Install: npm install -g @anthropic-ai/claude-code (or use VS Code)\n' +
123
+ ' The Copilot CLI is bundled with GitHub Copilot in VS Code.\n' +
124
+ ' Docs: https://docs.github.com/en/copilot',
125
+ cursor:
126
+ ' The Cursor agent CLI ships with the Cursor editor.\n' +
127
+ ' Install Cursor from https://cursor.com and ensure the\n' +
128
+ ' "agent" command is on your PATH (Cursor > Install CLI).',
129
+ }
130
+ const cliName = spec.adapter === 'claude-code' ? 'claude' : spec.adapter
131
+ const hint = hints[spec.adapter] ?? ''
117
132
  console.error(
118
133
  ` ✗ Adapter "${spec.adapter}" is not available.\n` +
119
- ` Make sure the "${spec.adapter === 'claude-code' ? 'claude' : spec.adapter}" CLI is installed and on your PATH.`
134
+ ` Make sure the "${cliName}" CLI is installed and on your PATH.\n` +
135
+ hint
120
136
  )
121
137
  process.exit(1)
122
138
  }
package/src/cli/types.ts CHANGED
@@ -233,12 +233,6 @@ export interface RunOptions {
233
233
  help: boolean;
234
234
  }
235
235
 
236
- /** Parse result from YAML block parser. */
237
- export interface ParseResult {
238
- value: unknown;
239
- nextIndex: number;
240
- }
241
-
242
236
  /** Validation result. */
243
237
  export interface ValidationResult {
244
238
  valid: boolean;
package/src/cli/update.ts CHANGED
@@ -2,27 +2,9 @@ import { resolve } from 'node:path'
2
2
  import { readFile } from 'node:fs/promises'
3
3
  import { readManifest, writeManifest } from './manifest.js'
4
4
  import { confirm, closePrompts, c } from './prompt.js'
5
- import { isLegacyStack, migrateStackConfig } from './types.js'
6
- import type { CliContext, IdeAdapter, IdeChoice } from './types.js'
7
-
8
- const ADAPTERS: Record<string, () => Promise<IdeAdapter>> = {
9
- vscode: () => import('./adapters/vscode.js') as Promise<IdeAdapter>,
10
- cursor: () => import('./adapters/cursor.js') as Promise<IdeAdapter>,
11
- 'claude-code': () =>
12
- import('./adapters/claude-code.js') as Promise<IdeAdapter>,
13
- opencode: () =>
14
- import('./adapters/opencode.js') as Promise<IdeAdapter>,
15
- }
16
-
17
- const VALID_IDES = Object.keys(ADAPTERS)
18
-
19
- /** IDE display labels */
20
- const IDE_DISPLAY: Record<IdeChoice, string> = {
21
- vscode: 'VS Code',
22
- cursor: 'Cursor',
23
- 'claude-code': 'Claude Code',
24
- opencode: 'OpenCode',
25
- }
5
+ import { isLegacyStack, migrateStackConfig, IDE_LABELS } from './types.js'
6
+ import { IDE_ADAPTERS, VALID_IDES } from './adapters/index.js'
7
+ import type { CliContext, IdeChoice } from './types.js'
26
8
 
27
9
  export default async function update({
28
10
  pkgRoot,
@@ -65,7 +47,7 @@ export default async function update({
65
47
  return
66
48
  }
67
49
 
68
- const ideNames = ides.map((id) => IDE_DISPLAY[id as IdeChoice] ?? id).join(', ')
50
+ const ideNames = ides.map((id) => IDE_LABELS[id as IdeChoice] ?? id).join(', ')
69
51
  console.log(
70
52
  `\n 🏰 ${c.bold('OpenCastle')} ${dryRun ? 'dry-run' : 'update'}: ${c.dim(`v${manifest.version}`)} → ${c.green(`v${pkg.version}`)}\n`
71
53
  )
@@ -98,7 +80,7 @@ export default async function update({
98
80
  const allManagedPaths = { framework: [] as string[], customizable: [] as string[] }
99
81
 
100
82
  for (const ide of ides) {
101
- const adapter = await ADAPTERS[ide]()
83
+ const adapter = await IDE_ADAPTERS[ide]()
102
84
  const results = await adapter.update(pkgRoot, projectRoot, manifest.stack)
103
85
  totalCopied += results.copied.length
104
86
  totalCreated += results.created.length
@@ -1 +1 @@
1
- :root{--bg-primary: #0a0a0f;--bg-secondary: #111118;--bg-tertiary: #1a1a24;--bg-card: rgba(255, 255, 255, .03);--bg-card-hover: rgba(255, 255, 255, .06);--text-primary: #f0f0f5;--text-secondary: #8a8a9a;--text-tertiary: #5a5a6e;--text-accent: #a78bfa;--gradient-accent: linear-gradient(135deg, #a78bfa 0%, #6366f1 50%, #3b82f6 100%);--gradient-glow: radial-gradient(ellipse 800px 400px at 50% 0%, rgba(99, 102, 241, .12) 0%, transparent 70%);--border-color: rgba(255, 255, 255, .06);--border-accent: rgba(167, 139, 250, .3);--color-success: #22c55e;--color-partial: #f59e0b;--color-failed: #ef4444;--color-redirected: #64748b;--color-premium: #f59e0b;--color-standard: #a78bfa;--color-utility: #3b82f6;--color-economy: #64748b;--accent-blue: #3b82f6;--accent-purple: #a78bfa;--accent-indigo: #6366f1;--max-width: 1280px;--transition-fast: .15s cubic-bezier(.4, 0, .2, 1);--transition-base: .3s cubic-bezier(.4, 0, .2, 1)}*,*:before,*:after{box-sizing:border-box;margin:0;padding:0}html{font-size:16px;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}body{font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Inter,Roboto,Helvetica,Arial,sans-serif;background-color:var(--bg-primary);color:var(--text-primary);line-height:1.6;overflow-x:hidden;min-height:100vh}.dash-header{position:sticky;top:0;z-index:50;background:#0a0a0fd9;backdrop-filter:blur(16px);-webkit-backdrop-filter:blur(16px);border-bottom:1px solid var(--border-color)}.dash-header__inner{max-width:var(--max-width);margin:0 auto;padding:0 24px;height:56px;display:flex;align-items:center;justify-content:space-between}.dash-header__brand{display:flex;align-items:center;gap:10px}.dash-header__icon{width:32px;height:32px;border-radius:8px;object-fit:contain}.dash-header__title{font-size:1rem;font-weight:600;color:var(--text-primary)}.dash-layout{display:flex;max-width:var(--max-width);margin:0 auto;position:relative}.dash-sidebar{position:sticky;top:56px;height:calc(100vh - 56px);width:180px;flex-shrink:0;padding:24px 0 24px 24px;overflow-y:auto;display:none}@media(min-width:1024px){.dash-sidebar{display:block}}.dash-sidebar__list{list-style:none;display:flex;flex-direction:column;gap:2px}.dash-sidebar__link{display:block;padding:8px 16px;font-size:.8125rem;font-weight:500;color:var(--text-tertiary);text-decoration:none;border-radius:8px;transition:color var(--transition-fast),background var(--transition-fast)}.dash-sidebar__link:hover{color:var(--text-secondary);background:#ffffff0a}.dash-sidebar__link--active{color:var(--text-accent);background:#a78bfa14;font-weight:600}.dash-main{flex:1;min-width:0;max-width:var(--max-width);margin:0 auto;padding:24px;display:flex;flex-direction:column;gap:20px;position:relative}.dash-main:before{content:"";position:fixed;top:0;left:50%;transform:translate(-50%);width:100%;height:600px;background:var(--gradient-glow);pointer-events:none;z-index:0}.dash-main>*{position:relative;z-index:1}[data-nav-section]{scroll-margin-top:72px}.kpi-row{display:grid;grid-template-columns:1fr;gap:12px}@media(min-width:480px){.kpi-row{grid-template-columns:repeat(2,1fr)}}@media(min-width:960px){.kpi-row{grid-template-columns:repeat(auto-fit,minmax(160px,1fr))}}.kpi-card{background:var(--bg-secondary);border:1px solid var(--border-color);border-radius:12px;padding:20px 24px;display:flex;flex-direction:column;gap:4px;transition:border-color var(--transition-fast)}.kpi-card:hover{border-color:#ffffff1a}.kpi-card__label{font-size:.75rem;font-weight:500;color:var(--text-tertiary);text-transform:uppercase;letter-spacing:.05em}.kpi-card__value{font-size:2rem;font-weight:700;color:var(--text-primary);line-height:1.2;letter-spacing:-.02em}.kpi-card__sub{font-size:.75rem;color:var(--text-secondary);display:flex;align-items:center;gap:4px}.kpi-trend{font-weight:600}.kpi-trend--up{color:var(--color-success)}.kpi-trend--down{color:var(--color-failed)}.kpi-trend--neutral{color:var(--text-tertiary)}.chart-card{background:var(--bg-secondary);border:1px solid var(--border-color);border-radius:12px;overflow:hidden;transition:border-color var(--transition-fast)}.chart-card:hover{border-color:#ffffff1a}.chart-card__header{padding:20px 24px 8px}.chart-card__title{font-size:.9375rem;font-weight:600;color:var(--text-primary)}.chart-card__desc{font-size:.75rem;color:var(--text-tertiary);margin-top:2px}.chart-card__body{padding:16px 24px 24px;min-height:120px}.chart-card__body--table{padding:0}.charts-row{display:grid;grid-template-columns:1fr;gap:20px}@media(min-width:768px){.charts-row{grid-template-columns:repeat(2,1fr)}}.bar-row{display:flex;align-items:center;gap:12px;padding:6px 0}.bar-row+.bar-row{border-top:1px solid rgba(255,255,255,.03)}.bar-label{font-size:.8125rem;color:var(--text-secondary);width:130px;flex-shrink:0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.bar-track{flex:1;height:24px;background:var(--bg-tertiary);border-radius:6px;display:flex;overflow:hidden}.bar-segment{height:100%;transition:width .8s cubic-bezier(.4,0,.2,1);min-width:0}.bar--success{background:var(--color-success)}.bar--partial{background:var(--color-partial)}.bar--failed{background:var(--color-failed)}.bar--premium{background:var(--color-premium)}.bar--standard{background:var(--color-standard)}.bar--utility{background:var(--color-utility)}.bar--economy{background:var(--color-economy)}.bar--accent{background:var(--accent-blue)}.bar-value{font-size:.8125rem;font-weight:600;color:var(--text-primary);width:36px;text-align:right;flex-shrink:0;font-variant-numeric:tabular-nums}.donut-container{display:flex;align-items:center;justify-content:center;gap:32px;flex-wrap:wrap}.donut-wrap{position:relative;width:180px;height:180px;flex-shrink:0}.donut-svg{width:100%;height:100%}.donut-svg circle{transition:stroke-dasharray .8s cubic-bezier(.4,0,.2,1),stroke-dashoffset .8s cubic-bezier(.4,0,.2,1)}.donut-center{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);text-align:center}.donut-total{display:block;font-size:1.5rem;font-weight:700;color:var(--text-primary);line-height:1}.donut-total-label{display:block;font-size:.6875rem;color:var(--text-tertiary);text-transform:uppercase;letter-spacing:.08em;margin-top:2px}.donut-legend{display:flex;flex-direction:column;gap:10px}.legend-item{display:flex;align-items:center;gap:8px;font-size:.8125rem}.legend-dot{width:10px;height:10px;border-radius:3px;flex-shrink:0}.legend-name{color:var(--text-secondary);text-transform:capitalize}.legend-count{color:var(--text-tertiary);font-variant-numeric:tabular-nums;margin-left:auto}.timeline-svg{width:100%;height:auto;display:block}.timeline-svg text{font-family:inherit}.timeline-legend{display:flex;gap:16px;justify-content:center;margin-top:12px}.timeline-legend__item{display:flex;align-items:center;gap:6px;font-size:.75rem;color:var(--text-tertiary)}.timeline-legend__dot{width:8px;height:8px;border-radius:2px}.pipeline{display:flex;align-items:stretch;gap:0;overflow-x:auto;padding:8px 0}.pipeline-stage{flex:1;min-width:140px;display:flex;flex-direction:column;align-items:center;gap:8px;padding:16px 12px;position:relative}.pipeline-stage:not(:last-child):after{content:"";position:absolute;right:-1px;top:50%;transform:translateY(-50%);width:2px;height:40%;background:var(--border-color)}.pipeline-stage__icon{width:40px;height:40px;border-radius:10px;display:flex;align-items:center;justify-content:center;font-size:1rem}.pipeline-stage__icon--pending{background:#64748b26;color:#94a3b8;border:1px solid rgba(100,116,139,.2)}.pipeline-stage__icon--active{background:#3b82f626;color:#60a5fa;border:1px solid rgba(59,130,246,.3);animation:pulse-glow 2s ease-in-out infinite}.pipeline-stage__icon--review{background:#f59e0b26;color:#fbbf24;border:1px solid rgba(245,158,11,.3)}.pipeline-stage__icon--done{background:#22c55e26;color:#4ade80;border:1px solid rgba(34,197,94,.3)}@keyframes pulse-glow{0%,to{box-shadow:0 0 #3b82f633}50%{box-shadow:0 0 12px 4px #3b82f626}}.pipeline-stage__count{font-size:1.5rem;font-weight:700;color:var(--text-primary);line-height:1}.pipeline-stage__label{font-size:.75rem;color:var(--text-tertiary);text-transform:uppercase;letter-spacing:.04em;font-weight:500}.pipeline-arrow{display:flex;align-items:center;color:var(--text-tertiary);font-size:1.25rem;padding:0 4px;flex-shrink:0}.exec-log{display:flex;flex-direction:column}.exec-step{display:flex;gap:16px;padding:14px 0;position:relative}.exec-step+.exec-step{border-top:1px solid rgba(255,255,255,.03)}.exec-step__indicator{display:flex;flex-direction:column;align-items:center;flex-shrink:0;width:32px}.exec-step__dot{width:24px;height:24px;border-radius:50%;display:flex;align-items:center;justify-content:center;font-size:.6875rem;font-weight:700;flex-shrink:0}.exec-step__dot--success{background:#22c55e26;color:var(--color-success);border:1.5px solid rgba(34,197,94,.3)}.exec-step__dot--partial{background:#f59e0b26;color:var(--color-partial);border:1.5px solid rgba(245,158,11,.3)}.exec-step__dot--failed{background:#ef444426;color:var(--color-failed);border:1.5px solid rgba(239,68,68,.3)}.exec-step__line{flex:1;width:1.5px;background:var(--border-color);margin-top:4px}.exec-step__content{flex:1;min-width:0}.exec-step__header{display:flex;align-items:center;gap:8px;flex-wrap:wrap}.exec-step__agent{font-size:.875rem;font-weight:600;color:var(--text-primary)}.exec-step__badge{display:inline-flex;align-items:center;padding:2px 8px;font-size:.6875rem;font-weight:600;border-radius:100px;text-transform:capitalize}.exec-step__badge--success{background:#22c55e1f;color:var(--color-success);border:1px solid rgba(34,197,94,.2)}.exec-step__badge--partial{background:#f59e0b1f;color:var(--color-partial);border:1px solid rgba(245,158,11,.2)}.exec-step__badge--failed{background:#ef44441f;color:var(--color-failed);border:1px solid rgba(239,68,68,.2)}.exec-step__task{font-size:.8125rem;color:var(--text-secondary);margin-top:4px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.exec-step__meta{display:flex;gap:16px;margin-top:6px;font-size:.6875rem;color:var(--text-tertiary)}.exec-step__meta-item{display:flex;align-items:center;gap:4px}.panel-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(200px,1fr));gap:12px}.panel-item{background:var(--bg-tertiary);border-radius:8px;padding:16px;display:flex;flex-direction:column;gap:8px;border:1px solid transparent;transition:border-color var(--transition-fast)}.panel-item:hover{border-color:var(--border-color)}.panel-item__header{display:flex;align-items:center;justify-content:space-between}.panel-item__key{font-size:.8125rem;font-weight:600;color:var(--text-primary);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.panel-item__verdict{font-size:.6875rem;font-weight:700;padding:2px 8px;border-radius:4px;text-transform:uppercase;letter-spacing:.04em}.panel-item__verdict--pass{background:#22c55e26;color:var(--color-success)}.panel-item__verdict--block{background:#ef444426;color:var(--color-failed)}.panel-item__votes{display:flex;gap:4px}.panel-item__vote{width:24px;height:24px;border-radius:50%;display:flex;align-items:center;justify-content:center;font-size:.625rem;font-weight:700}.panel-item__vote--pass{background:#22c55e1f;color:var(--color-success);border:1px solid rgba(34,197,94,.2)}.panel-item__vote--block{background:#ef44441f;color:var(--color-failed);border:1px solid rgba(239,68,68,.2)}.panel-item__fixes{font-size:.6875rem;color:var(--text-tertiary)}.panel-item__meta{display:flex;flex-wrap:wrap;gap:8px;margin-top:8px;padding-top:8px;border-top:1px solid var(--border-color)}.panel-item__meta-item{font-size:.625rem;color:var(--text-tertiary);white-space:nowrap}.sessions-table{width:100%;border-collapse:collapse;font-size:.8125rem}.sessions-table thead{position:sticky;top:0}.sessions-table th{padding:12px 16px;font-size:.6875rem;font-weight:600;color:var(--text-tertiary);text-align:left;text-transform:uppercase;letter-spacing:.06em;background:var(--bg-tertiary);border-bottom:1px solid var(--border-color)}.sessions-table th:last-child,.sessions-table td:last-child{text-align:right}.sessions-table th:nth-child(5),.sessions-table td:nth-child(5){text-align:right}.sessions-table td{padding:10px 16px;color:var(--text-secondary);border-bottom:1px solid rgba(255,255,255,.03);white-space:nowrap}.sessions-table tr:hover td{background:#ffffff05}.sessions-table .td-agent{font-weight:500;color:var(--text-primary)}.sessions-table .td-task{max-width:260px;overflow:hidden;text-overflow:ellipsis}.outcome-badge{display:inline-flex;align-items:center;padding:3px 10px;font-size:.6875rem;font-weight:600;border-radius:100px;text-transform:capitalize}.outcome-badge--success{background:#22c55e1f;color:var(--color-success);border:1px solid rgba(34,197,94,.2)}.outcome-badge--partial{background:#f59e0b1f;color:var(--color-partial);border:1px solid rgba(245,158,11,.2)}.outcome-badge--failed{background:#ef44441f;color:var(--color-failed);border:1px solid rgba(239,68,68,.2)}.td-num{font-variant-numeric:tabular-nums;text-align:right}.td-issue{font-size:.75rem;color:var(--text-accent);font-weight:500;font-variant-numeric:tabular-nums}.loading-skeleton{display:flex;align-items:center;justify-content:center;min-height:200px;color:var(--text-tertiary);font-size:.8125rem}.loading-skeleton:after{content:"Loading data…";animation:fade-pulse 1.5s ease-in-out infinite}@keyframes fade-pulse{0%,to{opacity:.4}50%{opacity:1}}.empty-state{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:48px 24px;text-align:center;gap:12px}.empty-state__icon{font-size:2rem;opacity:.4}.empty-state__text{font-size:.875rem;color:var(--text-tertiary);max-width:320px}.empty-state--enhanced{padding:56px 32px;gap:16px;border:1px dashed rgba(167,139,250,.15);border-radius:12px;background:radial-gradient(ellipse 300px 200px at 50% 30%,rgba(99,102,241,.04) 0%,transparent 70%),var(--bg-tertiary);position:relative;overflow:hidden}.empty-state--enhanced:before{content:"";position:absolute;inset:0;background:repeating-linear-gradient(0deg,transparent,transparent 23px,rgba(255,255,255,.015) 23px,rgba(255,255,255,.015) 24px);pointer-events:none}.empty-state__icon-wrap{width:64px;height:64px;display:flex;align-items:center;justify-content:center;border-radius:16px;background:#a78bfa0f;border:1px solid rgba(167,139,250,.12);color:var(--text-accent);animation:empty-breathe 4s ease-in-out infinite}@keyframes empty-breathe{0%,to{box-shadow:0 0 #a78bfa14;transform:scale(1)}50%{box-shadow:0 0 20px 4px #a78bfa0f;transform:scale(1.03)}}.empty-state__title{font-size:.9375rem;font-weight:600;color:var(--text-secondary);letter-spacing:-.01em}.empty-state__desc{font-size:.8125rem;color:var(--text-tertiary);max-width:380px;line-height:1.55}.kpi-card__hint{color:var(--text-tertiary);font-style:italic;font-size:.6875rem}.kpi-row--empty .kpi-card{border-style:dashed;border-color:#ffffff0a}.kpi-row--empty .kpi-card__value{color:var(--text-tertiary);opacity:.5}.welcome-banner{position:relative;background:var(--bg-secondary);border:1px solid transparent;border-radius:16px;padding:48px 40px;overflow:hidden;z-index:1}.welcome-banner:before{content:"";position:absolute;inset:-1px;border-radius:16px;padding:1px;background:linear-gradient(135deg,#a78bfa4d,#6366f126,#3b82f61a 60%,#a78bfa33);-webkit-mask:linear-gradient(#fff 0 0) content-box,linear-gradient(#fff 0 0);mask:linear-gradient(#fff 0 0) content-box,linear-gradient(#fff 0 0);-webkit-mask-composite:xor;mask-composite:exclude;pointer-events:none;z-index:0}.welcome-banner__glow{position:absolute;top:-60px;left:50%;transform:translate(-50%);width:500px;height:300px;background:radial-gradient(ellipse at center,rgba(167,139,250,.08) 0%,rgba(99,102,241,.04) 40%,transparent 70%);pointer-events:none;z-index:0}.welcome-banner__content{position:relative;z-index:1;display:flex;flex-direction:column;align-items:center;text-align:center;gap:20px}.welcome-banner__icon{width:72px;height:72px;display:flex;align-items:center;justify-content:center;border-radius:20px;background:#a78bfa14;border:1px solid rgba(167,139,250,.15);color:var(--text-accent);animation:welcome-float 6s ease-in-out infinite}@keyframes welcome-float{0%,to{transform:translateY(0);box-shadow:0 8px 32px #a78bfa14}50%{transform:translateY(-6px);box-shadow:0 16px 48px #a78bfa1f}}.welcome-banner__title{font-size:1.375rem;font-weight:700;color:var(--text-primary);letter-spacing:-.02em;line-height:1.3}.welcome-banner__subtitle{font-size:.9375rem;color:var(--text-secondary);max-width:480px;line-height:1.6}.welcome-banner__steps{display:flex;gap:20px;margin-top:12px;flex-wrap:wrap;justify-content:center}.welcome-step{display:flex;align-items:flex-start;gap:12px;text-align:left;padding:16px 20px;background:#ffffff05;border:1px solid rgba(255,255,255,.05);border-radius:12px;min-width:200px;max-width:220px;transition:border-color var(--transition-fast),background var(--transition-fast)}.welcome-step:hover{border-color:#a78bfa26;background:#ffffff08}.welcome-step__num{width:28px;height:28px;border-radius:8px;display:flex;align-items:center;justify-content:center;font-size:.75rem;font-weight:700;color:var(--text-accent);background:#a78bfa1a;border:1px solid rgba(167,139,250,.2);flex-shrink:0}.welcome-step__text{display:flex;flex-direction:column;gap:3px}.welcome-step__text strong{font-size:.8125rem;font-weight:600;color:var(--text-primary)}.welcome-step__text span{font-size:.75rem;color:var(--text-tertiary);line-height:1.4}@media(max-width:640px){.welcome-banner{padding:32px 24px}.welcome-banner__steps{flex-direction:column;align-items:center}.welcome-step{max-width:100%;width:100%}}@keyframes slide-up{0%{opacity:0;transform:translateY(12px)}to{opacity:1;transform:translateY(0)}}.dash-main>*{animation:slide-up .5s ease-out backwards}.dash-main>*:nth-child(1){animation-delay:0ms}.dash-main>*:nth-child(2){animation-delay:60ms}.dash-main>*:nth-child(3){animation-delay:.12s}.dash-main>*:nth-child(4){animation-delay:.18s}.dash-main>*:nth-child(5){animation-delay:.24s}.dash-main>*:nth-child(6){animation-delay:.3s}.dash-main>*:nth-child(7){animation-delay:.36s}.dash-main>*:nth-child(8){animation-delay:.42s}.dash-main>*:nth-child(9){animation-delay:.48s}.dash-main>*:nth-child(10){animation-delay:.54s}.dash-main>*:nth-child(11){animation-delay:.6s}@media(max-width:640px){.bar-label{width:90px;font-size:.75rem}.donut-container{flex-direction:column;align-items:center}.donut-wrap{width:150px;height:150px}.pipeline{gap:0}.pipeline-stage{min-width:100px;padding:12px 8px}.panel-grid{grid-template-columns:1fr}.sessions-table th:nth-child(3),.sessions-table td:nth-child(3){display:none}}
1
+ :root{--bg-primary: #0a0a0f;--bg-secondary: #111118;--bg-tertiary: #1a1a24;--bg-card: rgba(255, 255, 255, .03);--bg-card-hover: rgba(255, 255, 255, .06);--text-primary: #f0f0f5;--text-secondary: #8a8a9a;--text-tertiary: #5a5a6e;--text-accent: #a78bfa;--gradient-accent: linear-gradient(135deg, #a78bfa 0%, #6366f1 50%, #3b82f6 100%);--gradient-glow: radial-gradient(ellipse 800px 400px at 50% 0%, rgba(99, 102, 241, .12) 0%, transparent 70%);--border-color: rgba(255, 255, 255, .06);--border-accent: rgba(167, 139, 250, .3);--color-success: #22c55e;--color-partial: #f59e0b;--color-failed: #ef4444;--color-redirected: #64748b;--color-premium: #f59e0b;--color-standard: #a78bfa;--color-utility: #3b82f6;--color-economy: #64748b;--accent-blue: #3b82f6;--accent-purple: #a78bfa;--accent-indigo: #6366f1;--max-width: 1280px;--transition-fast: .15s cubic-bezier(.4, 0, .2, 1);--transition-base: .3s cubic-bezier(.4, 0, .2, 1)}*,*:before,*:after{box-sizing:border-box;margin:0;padding:0}html{font-size:16px;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}body{font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Inter,Roboto,Helvetica,Arial,sans-serif;background-color:var(--bg-primary);color:var(--text-primary);line-height:1.6;overflow-x:hidden;min-height:100vh}.dash-header{position:sticky;top:0;z-index:50;background:#0a0a0fd9;backdrop-filter:blur(16px);-webkit-backdrop-filter:blur(16px);border-bottom:1px solid var(--border-color)}.dash-header__inner{max-width:var(--max-width);margin:0 auto;padding:0 24px;height:56px;display:flex;align-items:center;justify-content:space-between}.dash-header__brand{display:flex;align-items:center;gap:10px}.dash-header__icon{width:32px;height:32px;border-radius:8px;object-fit:contain}.dash-header__title{font-size:1rem;font-weight:600;color:var(--text-primary)}.dash-layout{display:flex;max-width:var(--max-width);margin:0 auto;position:relative}.dash-sidebar{position:sticky;top:56px;height:calc(100vh - 56px);width:180px;flex-shrink:0;padding:24px 0 24px 24px;overflow-y:auto;display:none}@media(min-width:1024px){.dash-sidebar{display:block}}.dash-sidebar__list{list-style:none;display:flex;flex-direction:column;gap:2px}.dash-sidebar__link{display:block;padding:8px 16px;font-size:.8125rem;font-weight:500;color:var(--text-tertiary);text-decoration:none;border-radius:8px;transition:color var(--transition-fast),background var(--transition-fast)}.dash-sidebar__link:hover{color:var(--text-secondary);background:#ffffff0a}.dash-sidebar__link--active{color:var(--text-accent);background:#a78bfa14;font-weight:600}.dash-main{flex:1;min-width:0;max-width:var(--max-width);margin:0 auto;padding:24px;display:flex;flex-direction:column;gap:20px;position:relative}.dash-main:before{content:"";position:fixed;top:0;left:50%;transform:translate(-50%);width:100%;height:600px;background:var(--gradient-glow);pointer-events:none;z-index:0}.dash-main>*{position:relative;z-index:1}[data-nav-section]{scroll-margin-top:72px}.kpi-row{display:grid;grid-template-columns:1fr;gap:12px}@media(min-width:480px){.kpi-row{grid-template-columns:repeat(2,1fr)}}@media(min-width:960px){.kpi-row{grid-template-columns:repeat(auto-fit,minmax(160px,1fr))}}.kpi-card{background:var(--bg-secondary);border:1px solid var(--border-color);border-radius:12px;padding:20px 24px;display:flex;flex-direction:column;gap:4px;transition:border-color var(--transition-fast)}.kpi-card:hover{border-color:#ffffff1a}.kpi-card__label{font-size:.75rem;font-weight:500;color:var(--text-tertiary);text-transform:uppercase;letter-spacing:.05em}.kpi-card__value{font-size:2rem;font-weight:700;color:var(--text-primary);line-height:1.2;letter-spacing:-.02em}.kpi-card__sub{font-size:.75rem;color:var(--text-secondary);display:flex;align-items:center;gap:4px}.kpi-trend{font-weight:600}.kpi-trend--up{color:var(--color-success)}.kpi-trend--down{color:var(--color-failed)}.kpi-trend--neutral{color:var(--text-tertiary)}.chart-card{background:var(--bg-secondary);border:1px solid var(--border-color);border-radius:12px;overflow:hidden;transition:border-color var(--transition-fast)}.chart-card:hover{border-color:#ffffff1a}.chart-card__header{padding:20px 24px 8px}.chart-card__title{font-size:.9375rem;font-weight:600;color:var(--text-primary)}.chart-card__desc{font-size:.75rem;color:var(--text-tertiary);margin-top:2px}.chart-card__body{padding:16px 24px 24px;min-height:120px}.chart-card__body--table{padding:0}.charts-row{display:grid;grid-template-columns:1fr;gap:20px}@media(min-width:768px){.charts-row{grid-template-columns:repeat(2,1fr)}}.bar-row{display:flex;align-items:center;gap:12px;padding:6px 0}.bar-row+.bar-row{border-top:1px solid rgba(255,255,255,.03)}.bar-label{font-size:.8125rem;color:var(--text-secondary);width:130px;flex-shrink:0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.bar-track{flex:1;height:24px;background:var(--bg-tertiary);border-radius:6px;display:flex;overflow:hidden}.bar-segment{height:100%;transition:width .8s cubic-bezier(.4,0,.2,1);min-width:0}.bar--success{background:var(--color-success)}.bar--partial{background:var(--color-partial)}.bar--failed{background:var(--color-failed)}.bar--premium{background:var(--color-premium)}.bar--standard{background:var(--color-standard)}.bar--utility{background:var(--color-utility)}.bar--economy{background:var(--color-economy)}.bar--accent{background:var(--accent-blue)}.bar-value{font-size:.8125rem;font-weight:600;color:var(--text-primary);width:36px;text-align:right;flex-shrink:0;font-variant-numeric:tabular-nums}.donut-container{display:flex;align-items:center;justify-content:center;gap:32px;flex-wrap:wrap}.donut-wrap{position:relative;width:180px;height:180px;flex-shrink:0}.donut-svg{width:100%;height:100%}.donut-svg circle{transition:stroke-dasharray .8s cubic-bezier(.4,0,.2,1),stroke-dashoffset .8s cubic-bezier(.4,0,.2,1)}.donut-center{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);text-align:center}.donut-total{display:block;font-size:1.5rem;font-weight:700;color:var(--text-primary);line-height:1}.donut-total-label{display:block;font-size:.6875rem;color:var(--text-tertiary);text-transform:uppercase;letter-spacing:.08em;margin-top:2px}.donut-legend{display:flex;flex-direction:column;gap:10px}.legend-item{display:flex;align-items:center;gap:8px;font-size:.8125rem}.legend-dot{width:10px;height:10px;border-radius:3px;flex-shrink:0}.legend-name{color:var(--text-secondary);text-transform:capitalize}.legend-count{color:var(--text-tertiary);font-variant-numeric:tabular-nums;margin-left:auto}.timeline-svg{width:100%;height:auto;display:block}.timeline-svg text{font-family:inherit}.timeline-legend{display:flex;gap:16px;justify-content:center;margin-top:12px}.timeline-legend__item{display:flex;align-items:center;gap:6px;font-size:.75rem;color:var(--text-tertiary)}.timeline-legend__dot{width:8px;height:8px;border-radius:2px}.pipeline{display:flex;align-items:stretch;gap:0;overflow-x:auto;padding:8px 0}.pipeline-stage{flex:1;min-width:140px;display:flex;flex-direction:column;align-items:center;gap:8px;padding:16px 12px;position:relative}.pipeline-stage:not(:last-child):after{content:"";position:absolute;right:-1px;top:50%;transform:translateY(-50%);width:2px;height:40%;background:var(--border-color)}.pipeline-stage__icon{width:40px;height:40px;border-radius:10px;display:flex;align-items:center;justify-content:center;font-size:1rem}.pipeline-stage__icon--pending{background:#64748b26;color:#94a3b8;border:1px solid rgba(100,116,139,.2)}.pipeline-stage__icon--active{background:#3b82f626;color:#60a5fa;border:1px solid rgba(59,130,246,.3);animation:pulse-glow 2s ease-in-out infinite}.pipeline-stage__icon--review{background:#f59e0b26;color:#fbbf24;border:1px solid rgba(245,158,11,.3)}.pipeline-stage__icon--done{background:#22c55e26;color:#4ade80;border:1px solid rgba(34,197,94,.3)}@keyframes pulse-glow{0%,to{box-shadow:0 0 #3b82f633}50%{box-shadow:0 0 12px 4px #3b82f626}}.pipeline-stage__count{font-size:1.5rem;font-weight:700;color:var(--text-primary);line-height:1}.pipeline-stage__label{font-size:.75rem;color:var(--text-tertiary);text-transform:uppercase;letter-spacing:.04em;font-weight:500}.pipeline-arrow{display:flex;align-items:center;color:var(--text-tertiary);font-size:1.25rem;padding:0 4px;flex-shrink:0}.exec-log{display:flex;flex-direction:column}.exec-step{display:flex;gap:16px;padding:14px 0;position:relative}.exec-step+.exec-step{border-top:1px solid rgba(255,255,255,.03)}.exec-step__indicator{display:flex;flex-direction:column;align-items:center;flex-shrink:0;width:32px}.exec-step__dot{width:24px;height:24px;border-radius:50%;display:flex;align-items:center;justify-content:center;font-size:.6875rem;font-weight:700;flex-shrink:0}.exec-step__dot--success{background:#22c55e26;color:var(--color-success);border:1.5px solid rgba(34,197,94,.3)}.exec-step__dot--partial{background:#f59e0b26;color:var(--color-partial);border:1.5px solid rgba(245,158,11,.3)}.exec-step__dot--failed{background:#ef444426;color:var(--color-failed);border:1.5px solid rgba(239,68,68,.3)}.exec-step__line{flex:1;width:1.5px;background:var(--border-color);margin-top:4px}.exec-step__content{flex:1;min-width:0}.exec-step__header{display:flex;align-items:center;gap:8px;flex-wrap:wrap}.exec-step__agent{font-size:.875rem;font-weight:600;color:var(--text-primary)}.exec-step__badge{display:inline-flex;align-items:center;padding:2px 8px;font-size:.6875rem;font-weight:600;border-radius:100px;text-transform:capitalize}.exec-step__badge--success{background:#22c55e1f;color:var(--color-success);border:1px solid rgba(34,197,94,.2)}.exec-step__badge--partial{background:#f59e0b1f;color:var(--color-partial);border:1px solid rgba(245,158,11,.2)}.exec-step__badge--failed{background:#ef44441f;color:var(--color-failed);border:1px solid rgba(239,68,68,.2)}.exec-step__task{font-size:.8125rem;color:var(--text-secondary);margin-top:4px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.exec-step__meta{display:flex;gap:16px;margin-top:6px;font-size:.6875rem;color:var(--text-tertiary)}.exec-step__meta-item{display:flex;align-items:center;gap:4px}.panel-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(200px,1fr));gap:12px}.panel-item{background:var(--bg-tertiary);border-radius:8px;padding:16px;display:flex;flex-direction:column;gap:8px;border:1px solid transparent;transition:border-color var(--transition-fast)}.panel-item:hover{border-color:var(--border-color)}.panel-item__header{display:flex;align-items:center;justify-content:space-between}.panel-item__key{font-size:.8125rem;font-weight:600;color:var(--text-primary);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.panel-item__verdict{font-size:.6875rem;font-weight:700;padding:2px 8px;border-radius:4px;text-transform:uppercase;letter-spacing:.04em}.panel-item__verdict--pass{background:#22c55e26;color:var(--color-success)}.panel-item__verdict--block{background:#ef444426;color:var(--color-failed)}.panel-item__votes{display:flex;gap:4px}.panel-item__vote{width:24px;height:24px;border-radius:50%;display:flex;align-items:center;justify-content:center;font-size:.625rem;font-weight:700}.panel-item__vote--pass{background:#22c55e1f;color:var(--color-success);border:1px solid rgba(34,197,94,.2)}.panel-item__vote--block{background:#ef44441f;color:var(--color-failed);border:1px solid rgba(239,68,68,.2)}.panel-item__fixes{font-size:.6875rem;color:var(--text-tertiary)}.panel-item__meta{display:flex;flex-wrap:wrap;gap:8px;margin-top:8px;padding-top:8px;border-top:1px solid var(--border-color)}.panel-item__meta-item{font-size:.625rem;color:var(--text-tertiary);white-space:nowrap}.sessions-table{width:100%;border-collapse:collapse;font-size:.8125rem}.sessions-table thead{position:sticky;top:0}.sessions-table th{padding:12px 16px;font-size:.6875rem;font-weight:600;color:var(--text-tertiary);text-align:left;text-transform:uppercase;letter-spacing:.06em;background:var(--bg-tertiary);border-bottom:1px solid var(--border-color)}.sessions-table th:last-child,.sessions-table td:last-child{text-align:right}.sessions-table th:nth-child(5),.sessions-table td:nth-child(5){text-align:right}.sessions-table td{padding:10px 16px;color:var(--text-secondary);border-bottom:1px solid rgba(255,255,255,.03);white-space:nowrap}.sessions-table tr:hover td{background:#ffffff05}.sessions-table .td-agent{font-weight:500;color:var(--text-primary)}.sessions-table .td-task{max-width:260px;overflow:hidden;text-overflow:ellipsis}.outcome-badge{display:inline-flex;align-items:center;padding:3px 10px;font-size:.6875rem;font-weight:600;border-radius:100px;text-transform:capitalize}.outcome-badge--success{background:#22c55e1f;color:var(--color-success);border:1px solid rgba(34,197,94,.2)}.outcome-badge--partial{background:#f59e0b1f;color:var(--color-partial);border:1px solid rgba(245,158,11,.2)}.outcome-badge--failed{background:#ef44441f;color:var(--color-failed);border:1px solid rgba(239,68,68,.2)}.td-num{font-variant-numeric:tabular-nums;text-align:right}.td-issue{font-size:.75rem;color:var(--text-accent);font-weight:500;font-variant-numeric:tabular-nums}.loading-skeleton{display:flex;align-items:center;justify-content:center;min-height:200px;color:var(--text-tertiary);font-size:.8125rem}.loading-skeleton:after{content:"Loading data…";animation:fade-pulse 1.5s ease-in-out infinite}@keyframes fade-pulse{0%,to{opacity:.4}50%{opacity:1}}.empty-state{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:48px 24px;text-align:center;gap:12px}.empty-state__icon{font-size:2rem;opacity:.4}.empty-state__text{font-size:.875rem;color:var(--text-tertiary);max-width:320px}.empty-state--enhanced{padding:56px 32px;gap:16px;border:1px dashed rgba(167,139,250,.15);border-radius:12px;background:radial-gradient(ellipse 300px 200px at 50% 30%,rgba(99,102,241,.04) 0%,transparent 70%),var(--bg-tertiary);position:relative;overflow:hidden}.empty-state--enhanced:before{content:"";position:absolute;inset:0;background:repeating-linear-gradient(0deg,transparent,transparent 23px,rgba(255,255,255,.015) 23px,rgba(255,255,255,.015) 24px);pointer-events:none}.empty-state__icon-wrap{width:64px;height:64px;display:flex;align-items:center;justify-content:center;border-radius:16px;background:#a78bfa0f;border:1px solid rgba(167,139,250,.12);color:var(--text-accent);animation:empty-breathe 4s ease-in-out infinite}@keyframes empty-breathe{0%,to{box-shadow:0 0 #a78bfa14;transform:scale(1)}50%{box-shadow:0 0 20px 4px #a78bfa0f;transform:scale(1.03)}}.empty-state__title{font-size:.9375rem;font-weight:600;color:var(--text-secondary);letter-spacing:-.01em}.empty-state__desc{font-size:.8125rem;color:var(--text-tertiary);max-width:380px;line-height:1.55}.kpi-card__hint{color:var(--text-tertiary);font-style:italic;font-size:.6875rem}.kpi-row--empty .kpi-card{border-style:dashed;border-color:#ffffff0a}.kpi-row--empty .kpi-card__value{color:var(--text-tertiary);opacity:.5}.welcome-banner{position:relative;background:var(--bg-secondary);border:1px solid transparent;border-radius:16px;padding:48px 40px;overflow:hidden;z-index:1}.welcome-banner:before{content:"";position:absolute;inset:-1px;border-radius:16px;padding:1px;background:linear-gradient(135deg,#a78bfa4d,#6366f126,#3b82f61a 60%,#a78bfa33);-webkit-mask:linear-gradient(#fff 0 0) content-box,linear-gradient(#fff 0 0);mask:linear-gradient(#fff 0 0) content-box,linear-gradient(#fff 0 0);-webkit-mask-composite:xor;mask-composite:exclude;pointer-events:none;z-index:0}.welcome-banner__glow{position:absolute;top:-60px;left:50%;transform:translate(-50%);width:500px;height:300px;background:radial-gradient(ellipse at center,rgba(167,139,250,.08) 0%,rgba(99,102,241,.04) 40%,transparent 70%);pointer-events:none;z-index:0}.welcome-banner__content{position:relative;z-index:1;display:flex;flex-direction:column;align-items:center;text-align:center;gap:20px}.welcome-banner__icon{width:72px;height:72px;display:flex;align-items:center;justify-content:center;border-radius:20px;background:#a78bfa14;border:1px solid rgba(167,139,250,.15);color:var(--text-accent);animation:welcome-float 6s ease-in-out infinite}@keyframes welcome-float{0%,to{transform:translateY(0);box-shadow:0 8px 32px #a78bfa14}50%{transform:translateY(-6px);box-shadow:0 16px 48px #a78bfa1f}}.welcome-banner__title{font-size:1.375rem;font-weight:700;color:var(--text-primary);letter-spacing:-.02em;line-height:1.3}.welcome-banner__subtitle{font-size:.9375rem;color:var(--text-secondary);max-width:480px;line-height:1.6}.welcome-banner__steps{display:flex;gap:20px;margin-top:12px;flex-wrap:wrap;justify-content:center}.welcome-step{display:flex;align-items:flex-start;gap:12px;text-align:left;padding:16px 20px;background:#ffffff05;border:1px solid rgba(255,255,255,.05);border-radius:12px;min-width:200px;max-width:220px;transition:border-color var(--transition-fast),background var(--transition-fast)}.welcome-step:hover{border-color:#a78bfa26;background:#ffffff08}.welcome-step__num{width:28px;height:28px;border-radius:8px;display:flex;align-items:center;justify-content:center;font-size:.75rem;font-weight:700;color:var(--text-accent);background:#a78bfa1a;border:1px solid rgba(167,139,250,.2);flex-shrink:0}.welcome-step__text{display:flex;flex-direction:column;gap:3px}.welcome-step__text strong{font-size:.8125rem;font-weight:600;color:var(--text-primary)}.welcome-step__text span{font-size:.75rem;color:var(--text-tertiary);line-height:1.4}@media(max-width:640px){.welcome-banner{padding:32px 24px}.welcome-banner__steps{flex-direction:column;align-items:center}.welcome-step{max-width:100%;width:100%}}@keyframes slide-up{0%{opacity:0;transform:translateY(12px)}to{opacity:1;transform:translateY(0)}}.dash-main>*{animation:slide-up .5s ease-out backwards}.dash-main>*:nth-child(1){animation-delay:0ms}.dash-main>*:nth-child(2){animation-delay:60ms}.dash-main>*:nth-child(3){animation-delay:.12s}.dash-main>*:nth-child(4){animation-delay:.18s}.dash-main>*:nth-child(5){animation-delay:.24s}.dash-main>*:nth-child(6){animation-delay:.3s}.dash-main>*:nth-child(7){animation-delay:.36s}.dash-main>*:nth-child(8){animation-delay:.42s}.dash-main>*:nth-child(9){animation-delay:.48s}.dash-main>*:nth-child(10){animation-delay:.54s}.dash-main>*:nth-child(11){animation-delay:.6s}@media(max-width:640px){.bar-label{width:90px;font-size:.75rem}.donut-container{flex-direction:column;align-items:center}.donut-wrap{width:150px;height:150px}.pipeline{gap:0}.pipeline-stage{min-width:100px;padding:12px 8px}.panel-grid{grid-template-columns:1fr}.sessions-table th:nth-child(3),.sessions-table td:nth-child(3){display:none}}.filter-bar{display:flex;flex-wrap:wrap;gap:12px;align-items:flex-end;padding:16px 20px;background:var(--bg-secondary);border:1px solid var(--border-color);border-radius:12px}.filter-group{display:flex;flex-direction:column;gap:4px;min-width:0}.filter-label{font-size:.6875rem;font-weight:500;color:var(--text-tertiary);text-transform:uppercase;letter-spacing:.05em}.filter-input,.filter-select{height:34px;padding:0 10px;font-size:.8125rem;color:var(--text-primary);background:var(--bg-tertiary);border:1px solid var(--border-color);border-radius:8px;outline:none;transition:border-color var(--transition-fast);font-family:inherit}.filter-input:focus,.filter-select:focus{border-color:var(--border-accent)}.filter-input{width:140px;color-scheme:dark}.filter-select{min-width:140px;cursor:pointer;appearance:none;-webkit-appearance:none;background-image:url("data:image/svg+xml,%3Csvg width='10' height='6' viewBox='0 0 10 6' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M1 1l4 4 4-4' stroke='%235a5a6e' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");background-repeat:no-repeat;background-position:right 10px center;padding-right:28px}.filter-reset{height:34px;font-size:.75rem}.dash-btn{display:inline-flex;align-items:center;gap:6px;padding:6px 14px;font-size:.8125rem;font-weight:500;font-family:inherit;border:none;border-radius:8px;cursor:pointer;transition:background var(--transition-fast),color var(--transition-fast)}.dash-btn--ghost{color:var(--text-secondary);background:#ffffff0f}.dash-btn--ghost:hover{color:var(--text-primary);background:#ffffff1a}.dash-header__actions{display:flex;align-items:center;gap:8px}@media(max-width:480px){.dash-header__inner{padding:0 12px}.dash-main{padding:12px;gap:12px}.kpi-card,.chart-card__header{padding:14px 16px}.chart-card__body{padding:12px 16px 16px}.filter-bar{padding:12px;gap:8px}.filter-input,.filter-select{width:100%;min-width:unset}.filter-group{flex:1 1 calc(50% - 4px)}.filter-reset{width:100%}.dash-header__title{font-size:.875rem}.exec-step__meta{flex-direction:column;gap:2px}.sessions-table th:nth-child(5),.sessions-table td:nth-child(5),.sessions-table th:nth-child(6),.sessions-table td:nth-child(6),.sessions-table th:nth-child(7),.sessions-table td:nth-child(7),.sessions-table th:nth-child(8),.sessions-table td:nth-child(8){display:none}}@media(max-width:768px){.charts-row{grid-template-columns:1fr}.pipeline{flex-wrap:wrap;gap:8px}.pipeline-arrow{display:none}.pipeline-stage{flex:1 1 calc(50% - 4px);min-width:100px}.tier-chart .donut-container,.donut-container{flex-direction:column;align-items:center}.sessions-table{font-size:.75rem}.sessions-table th,.sessions-table td{padding:8px 6px}}