tokenrace 0.1.19 → 0.1.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.html
CHANGED
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
<meta charset="UTF-8" />
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
6
|
<title>tokenrace</title>
|
|
7
|
-
<script type="module" crossorigin src="/assets/index-
|
|
8
|
-
<link rel="stylesheet" crossorigin href="/assets/index-
|
|
7
|
+
<script type="module" crossorigin src="/assets/index-CclozGtU.js"></script>
|
|
8
|
+
<link rel="stylesheet" crossorigin href="/assets/index-BimQXpAH.css">
|
|
9
9
|
</head>
|
|
10
10
|
<body>
|
|
11
11
|
<div id="root"></div>
|
package/package.json
CHANGED
package/src/api-routes.js
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
|
|
12
12
|
import { Router } from 'express'
|
|
13
13
|
import {
|
|
14
|
-
getStatus, getSummary, getTimeseries, getProjects,
|
|
14
|
+
getStatus, getSummary, getTimeseries, getTimeseriesByProject, getProjects,
|
|
15
15
|
getSessions, getUnlabeledSessions, getSessionEvents,
|
|
16
16
|
getEvents, getTools, getAgents, getModels,
|
|
17
17
|
labelSession, ignoreSession, reset, resetProject
|
|
@@ -114,6 +114,11 @@ export function createRouter({ port = 1337 } = {}) {
|
|
|
114
114
|
res.json(getTimeseries(req.query.metric, req.query.from, req.query.bucket))
|
|
115
115
|
})
|
|
116
116
|
|
|
117
|
+
/** GET /api/timeseries/by-project — serie temporal desglosada por proyecto, acepta ?metric, ?from, ?bucket */
|
|
118
|
+
router.get('/api/timeseries/by-project', (req, res) => {
|
|
119
|
+
res.json(getTimeseriesByProject(req.query.metric, req.query.from, req.query.bucket))
|
|
120
|
+
})
|
|
121
|
+
|
|
117
122
|
/** GET /api/projects — proyectos con métricas, acepta ?from */
|
|
118
123
|
router.get('/api/projects', (req, res) => {
|
|
119
124
|
res.json(getProjects(req.query.from))
|
package/src/store.js
CHANGED
|
@@ -409,15 +409,15 @@ export function processEvent({ eventName, timestamp, severity, attributes }) {
|
|
|
409
409
|
session.apiRequests++
|
|
410
410
|
}
|
|
411
411
|
|
|
412
|
-
// ── Tool stats ──
|
|
413
|
-
if (eventName === '
|
|
412
|
+
// ── Tool stats de sesión ──
|
|
413
|
+
if (eventName === 'tool_result') {
|
|
414
414
|
session.toolCalls++
|
|
415
415
|
}
|
|
416
416
|
}
|
|
417
417
|
|
|
418
|
-
// ── Tool stats globales ──
|
|
419
|
-
if (eventName === '
|
|
420
|
-
const toolName = attributes['
|
|
418
|
+
// ── Tool stats globales — Claude Code envía tool_result (no tool_use) ──
|
|
419
|
+
if (eventName === 'tool_result') {
|
|
420
|
+
const toolName = attributes['tool_name'] ?? attributes['tool.name'] ?? 'unknown'
|
|
421
421
|
|
|
422
422
|
if (!state.tools.has(toolName)) {
|
|
423
423
|
state.tools.set(toolName, { count: 0, successes: 0, totalDurationMs: 0 })
|
|
@@ -425,12 +425,27 @@ export function processEvent({ eventName, timestamp, severity, attributes }) {
|
|
|
425
425
|
const tool = state.tools.get(toolName)
|
|
426
426
|
tool.count++
|
|
427
427
|
|
|
428
|
-
if (attributes.success === true || attributes
|
|
428
|
+
if (attributes.success === true || attributes.success === 'true') {
|
|
429
429
|
tool.successes++
|
|
430
430
|
}
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
431
|
+
const durMs = Number(attributes['duration_ms'] ?? attributes['tool.duration_ms'] ?? 0)
|
|
432
|
+
if (durMs > 0) tool.totalDurationMs += durMs
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
// ── Subagentes — Claude Code envía subagent_completed ──
|
|
436
|
+
if (eventName === 'subagent_completed') {
|
|
437
|
+
const seq = attributes['event.sequence'] ?? Date.now()
|
|
438
|
+
const agentId = `${sessionId ?? 'unknown'}:${seq}`
|
|
439
|
+
const totalTokens = Number(attributes['total_tokens'] ?? 0)
|
|
440
|
+
state.agents.set(agentId, {
|
|
441
|
+
agentId,
|
|
442
|
+
parentAgentId: null,
|
|
443
|
+
tokensInput: totalTokens,
|
|
444
|
+
tokensOutput: 0,
|
|
445
|
+
cost: estimateCost(attributes['model'] ?? null, totalTokens, 0, 0),
|
|
446
|
+
toolCalls: Number(attributes['total_tool_uses'] ?? 0),
|
|
447
|
+
durationMs: Number(attributes['duration_ms'] ?? 0)
|
|
448
|
+
})
|
|
434
449
|
}
|
|
435
450
|
|
|
436
451
|
state.lastSeen = timestamp
|
|
@@ -694,6 +709,36 @@ export function getTimeseries(metric, from, bucket) {
|
|
|
694
709
|
* Proyectos con sus métricas agregadas filtradas por rango temporal.
|
|
695
710
|
* @param {string} from
|
|
696
711
|
*/
|
|
712
|
+
/**
|
|
713
|
+
* Serie temporal de una métrica desglosada por proyecto.
|
|
714
|
+
* Devuelve [{ timestamp, projects: { [projectName]: value } }]
|
|
715
|
+
*/
|
|
716
|
+
export function getTimeseriesByProject(metric, from, bucket) {
|
|
717
|
+
const minTs = parseTimeRange(from)
|
|
718
|
+
const bucketMs = parseBucket(bucket)
|
|
719
|
+
const points = state.timeseries.get(metric) ?? []
|
|
720
|
+
|
|
721
|
+
// Map: bucketKey → Map<projectName, value>
|
|
722
|
+
const buckets = new Map()
|
|
723
|
+
for (const point of points) {
|
|
724
|
+
if (point.ts < minTs) continue
|
|
725
|
+
const sid = point.labels['session.id']
|
|
726
|
+
if (sid && state.ignoredSessions.has(sid)) continue
|
|
727
|
+
const proj = resolveProject(sid, point.labels.project) ?? '(sin proyecto)'
|
|
728
|
+
const key = Math.floor(point.ts / bucketMs) * bucketMs
|
|
729
|
+
if (!buckets.has(key)) buckets.set(key, new Map())
|
|
730
|
+
const byProj = buckets.get(key)
|
|
731
|
+
byProj.set(proj, (byProj.get(proj) ?? 0) + point.value)
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
return Array.from(buckets.entries())
|
|
735
|
+
.map(([timestamp, byProj]) => ({
|
|
736
|
+
timestamp,
|
|
737
|
+
projects: Object.fromEntries(byProj),
|
|
738
|
+
}))
|
|
739
|
+
.sort((a, b) => a.timestamp - b.timestamp)
|
|
740
|
+
}
|
|
741
|
+
|
|
697
742
|
export function getProjects(from) {
|
|
698
743
|
const minTs = parseTimeRange(from)
|
|
699
744
|
const result = []
|
|
@@ -856,14 +901,14 @@ export function getTools(from) {
|
|
|
856
901
|
avgDurationMs: stats.count > 0 ? stats.totalDurationMs / stats.count : 0
|
|
857
902
|
})).sort((a, b) => b.count - a.count)
|
|
858
903
|
|
|
859
|
-
// Tasa de aprobación/rechazo desde
|
|
904
|
+
// Tasa de aprobación/rechazo desde tool_result (Claude Code usa decision_type)
|
|
860
905
|
let approved = 0
|
|
861
906
|
let rejected = 0
|
|
862
907
|
for (const event of state.events) {
|
|
863
908
|
if (event.timestamp < minTs) continue
|
|
864
|
-
if (event.eventName !== '
|
|
865
|
-
if (event.attributes
|
|
866
|
-
if (event.attributes
|
|
909
|
+
if (event.eventName !== 'tool_result') continue
|
|
910
|
+
if (event.attributes['decision_type'] === 'accept') approved++
|
|
911
|
+
else if (event.attributes['decision_type'] === 'reject') rejected++
|
|
867
912
|
}
|
|
868
913
|
|
|
869
914
|
return { usage, decisionRate: { approved, rejected } }
|
|
@@ -918,7 +963,9 @@ export function saveSync() {
|
|
|
918
963
|
events: state.events,
|
|
919
964
|
eventIndex: state.eventIndex,
|
|
920
965
|
totalEvents: state.totalEvents,
|
|
921
|
-
startTime: state.startTime
|
|
966
|
+
startTime: state.startTime,
|
|
967
|
+
tools: Array.from(state.tools.entries()),
|
|
968
|
+
agents: Array.from(state.agents.values())
|
|
922
969
|
}
|
|
923
970
|
|
|
924
971
|
fs.writeFileSync(dataFile, JSON.stringify(data), { mode: 0o600 })
|
|
@@ -989,6 +1036,20 @@ export function loadFromDisk() {
|
|
|
989
1036
|
state.startTime = data.startTime
|
|
990
1037
|
}
|
|
991
1038
|
|
|
1039
|
+
// Restaurar stats de herramientas
|
|
1040
|
+
if (Array.isArray(data.tools)) {
|
|
1041
|
+
for (const [toolName, stats] of data.tools) {
|
|
1042
|
+
state.tools.set(toolName, stats)
|
|
1043
|
+
}
|
|
1044
|
+
}
|
|
1045
|
+
|
|
1046
|
+
// Restaurar agentes
|
|
1047
|
+
if (Array.isArray(data.agents)) {
|
|
1048
|
+
for (const agent of data.agents) {
|
|
1049
|
+
state.agents.set(agent.agentId, agent)
|
|
1050
|
+
}
|
|
1051
|
+
}
|
|
1052
|
+
|
|
992
1053
|
// Re-aplicar mappings a sesiones (timeseries no necesitan propagación:
|
|
993
1054
|
// resolveProject() consulta sessionMappings primero en todos los lectores)
|
|
994
1055
|
for (const [sessionId, project] of state.sessionMappings.entries()) {
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
*,:before,:after{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(59 130 246 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }::backdrop{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(59 130 246 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }*,:before,:after{box-sizing:border-box;border-width:0;border-style:solid;border-color:#e5e7eb}:before,:after{--tw-content: ""}html,:host{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;-o-tab-size:4;tab-size:4;font-family:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji",Segoe UI Symbol,"Noto Color Emoji";font-feature-settings:normal;font-variation-settings:normal;-webkit-tap-highlight-color:transparent}body{margin:0;line-height:inherit}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:JetBrains Mono,Fira Code,Menlo,monospace;font-feature-settings:normal;font-variation-settings:normal;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-feature-settings:inherit;font-variation-settings:inherit;font-size:100%;font-weight:inherit;line-height:inherit;letter-spacing:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}button,input:where([type=button]),input:where([type=reset]),input:where([type=submit]){-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dl,dd,h1,h2,h3,h4,h5,h6,hr,figure,p,pre{margin:0}fieldset{margin:0;padding:0}legend{padding:0}ol,ul,menu{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{opacity:1;color:#9ca3af}input::placeholder,textarea::placeholder{opacity:1;color:#9ca3af}button,[role=button]{cursor:pointer}:disabled{cursor:default}img,svg,video,canvas,audio,iframe,embed,object{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]:where(:not([hidden=until-found])){display:none}.absolute{position:absolute}.relative{position:relative}.sticky{position:sticky}.right-2{right:.5rem}.top-0{top:0}.top-10{top:2.5rem}.top-2{top:.5rem}.z-40{z-index:40}.z-50{z-index:50}.mx-auto{margin-left:auto;margin-right:auto}.mb-1{margin-bottom:.25rem}.mb-2{margin-bottom:.5rem}.mb-4{margin-bottom:1rem}.ml-2{margin-left:.5rem}.ml-auto{margin-left:auto}.mt-0\.5{margin-top:.125rem}.mt-1{margin-top:.25rem}.inline-block{display:inline-block}.flex{display:flex}.inline-flex{display:inline-flex}.table{display:table}.grid{display:grid}.h-1\.5{height:.375rem}.h-10{height:2.5rem}.h-2{height:.5rem}.h-48{height:12rem}.h-\[600px\]{height:600px}.h-full{height:100%}.max-h-40{max-height:10rem}.min-h-\[60vh\]{min-height:60vh}.min-h-screen{min-height:100vh}.w-2{width:.5rem}.w-20{width:5rem}.w-24{width:6rem}.w-32{width:8rem}.w-6{width:1.5rem}.w-full{width:100%}.min-w-0{min-width:0px}.min-w-\[640px\]{min-width:640px}.min-w-\[720px\]{min-width:720px}.max-w-screen-2xl{max-width:1536px}.max-w-xl{max-width:36rem}.flex-1{flex:1 1 0%}.shrink-0{flex-shrink:0}@keyframes pulse{50%{opacity:.5}}.animate-pulse{animation:pulse 2s cubic-bezier(.4,0,.6,1) infinite}.cursor-pointer{cursor:pointer}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.grid-cols-6{grid-template-columns:repeat(6,minmax(0,1fr))}.flex-col{flex-direction:column}.flex-wrap{flex-wrap:wrap}.items-start{align-items:flex-start}.items-center{align-items:center}.justify-center{justify-content:center}.justify-between{justify-content:space-between}.gap-1{gap:.25rem}.gap-2{gap:.5rem}.gap-3{gap:.75rem}.gap-4{gap:1rem}.space-y-0\.5>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(.125rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.125rem * var(--tw-space-y-reverse))}.space-y-2>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(.5rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.5rem * var(--tw-space-y-reverse))}.space-y-3>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(.75rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.75rem * var(--tw-space-y-reverse))}.space-y-6>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(1.5rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(1.5rem * var(--tw-space-y-reverse))}.overflow-hidden{overflow:hidden}.overflow-x-auto{overflow-x:auto}.overflow-y-auto{overflow-y:auto}.whitespace-nowrap{white-space:nowrap}.whitespace-pre{white-space:pre}.rounded{border-radius:.25rem}.rounded-full{border-radius:9999px}.rounded-lg{border-radius:.5rem}.border{border-width:1px}.border-b{border-bottom-width:1px}.border-b-2{border-bottom-width:2px}.border-accent-green{--tw-border-opacity: 1;border-color:rgb(255 107 53 / var(--tw-border-opacity, 1))}.border-bg-border{--tw-border-opacity: 1;border-color:rgb(26 26 26 / var(--tw-border-opacity, 1))}.border-transparent{border-color:transparent}.bg-accent-green{--tw-bg-opacity: 1;background-color:rgb(255 107 53 / var(--tw-bg-opacity, 1))}.bg-accent-purple{--tw-bg-opacity: 1;background-color:rgb(168 85 247 / var(--tw-bg-opacity, 1))}.bg-accent-teal{--tw-bg-opacity: 1;background-color:rgb(0 212 170 / var(--tw-bg-opacity, 1))}.bg-accent-yellow{--tw-bg-opacity: 1;background-color:rgb(251 191 36 / var(--tw-bg-opacity, 1))}.bg-bg-base{--tw-bg-opacity: 1;background-color:rgb(20 25 31 / var(--tw-bg-opacity, 1))}.bg-bg-card,.bg-bg-subtle{--tw-bg-opacity: 1;background-color:rgb(33 38 43 / var(--tw-bg-opacity, 1))}.bg-text-muted{--tw-bg-opacity: 1;background-color:rgb(68 68 68 / var(--tw-bg-opacity, 1))}.p-1{padding:.25rem}.p-3{padding:.75rem}.p-4{padding:1rem}.p-6{padding:1.5rem}.p-8{padding:2rem}.px-1{padding-left:.25rem;padding-right:.25rem}.px-2{padding-left:.5rem;padding-right:.5rem}.px-3{padding-left:.75rem;padding-right:.75rem}.px-4{padding-left:1rem;padding-right:1rem}.px-6{padding-left:1.5rem;padding-right:1.5rem}.py-0\.5{padding-top:.125rem;padding-bottom:.125rem}.py-1{padding-top:.25rem;padding-bottom:.25rem}.py-2{padding-top:.5rem;padding-bottom:.5rem}.py-3{padding-top:.75rem;padding-bottom:.75rem}.text-left{text-align:left}.text-center{text-align:center}.text-right{text-align:right}.font-mono{font-family:JetBrains Mono,Fira Code,Menlo,monospace}.text-2xl{font-size:1.5rem;line-height:2rem}.text-4xl{font-size:2.25rem;line-height:2.5rem}.text-lg{font-size:1.125rem;line-height:1.75rem}.text-sm{font-size:.875rem;line-height:1.25rem}.text-xs{font-size:.75rem;line-height:1rem}.font-bold{font-weight:700}.font-medium{font-weight:500}.font-semibold{font-weight:600}.uppercase{text-transform:uppercase}.tracking-wider{letter-spacing:.05em}.text-accent-blue{--tw-text-opacity: 1;color:rgb(77 166 255 / var(--tw-text-opacity, 1))}.text-accent-green,.text-accent-orange{--tw-text-opacity: 1;color:rgb(255 107 53 / var(--tw-text-opacity, 1))}.text-accent-purple{--tw-text-opacity: 1;color:rgb(168 85 247 / var(--tw-text-opacity, 1))}.text-accent-teal{--tw-text-opacity: 1;color:rgb(0 212 170 / var(--tw-text-opacity, 1))}.text-accent-yellow{--tw-text-opacity: 1;color:rgb(251 191 36 / var(--tw-text-opacity, 1))}.text-black{--tw-text-opacity: 1;color:rgb(0 0 0 / var(--tw-text-opacity, 1))}.text-text-muted{--tw-text-opacity: 1;color:rgb(68 68 68 / var(--tw-text-opacity, 1))}.text-text-primary{--tw-text-opacity: 1;color:rgb(255 255 255 / var(--tw-text-opacity, 1))}.text-text-secondary{--tw-text-opacity: 1;color:rgb(136 136 136 / var(--tw-text-opacity, 1))}.opacity-50{opacity:.5}.opacity-60{opacity:.6}.outline-none{outline:2px solid transparent;outline-offset:2px}.filter{filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.transition-all{transition-property:all;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-colors{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}:root{--bg-base: #14191F;--bg-card: #21262B;--bg-card-hover: #272d33;--bg-border: #1a1a1a;--bg-subtle: #21262B;--text-primary: #ffffff;--text-secondary: #888888;--text-muted: #444444;--accent-green: #ff6b35;--accent-blue: #4da6ff;--accent-purple: #a855f7;--accent-orange: #ff6b35;--accent-teal: #00d4aa;--accent-yellow: #fbbf24}*{box-sizing:border-box}html,body,#root{height:100%;margin:0;padding:0;background-color:#14191f;color:#fff}body{font-family:-apple-system,BlinkMacSystemFont,Segoe UI,sans-serif;-webkit-font-smoothing:antialiased}::-webkit-scrollbar{width:6px;height:6px}::-webkit-scrollbar-track{background:#0d0d0d}::-webkit-scrollbar-thumb{background:#333;border-radius:3px}::-webkit-scrollbar-thumb:hover{background:#444}.hover\:border-accent-green:hover{--tw-border-opacity: 1;border-color:rgb(255 107 53 / var(--tw-border-opacity, 1))}.hover\:bg-bg-base:hover{--tw-bg-opacity: 1;background-color:rgb(20 25 31 / var(--tw-bg-opacity, 1))}.hover\:bg-bg-card:hover{--tw-bg-opacity: 1;background-color:rgb(33 38 43 / var(--tw-bg-opacity, 1))}.hover\:bg-bg-card-hover:hover{--tw-bg-opacity: 1;background-color:rgb(39 45 51 / var(--tw-bg-opacity, 1))}.hover\:text-accent-orange:hover{--tw-text-opacity: 1;color:rgb(255 107 53 / var(--tw-text-opacity, 1))}.hover\:text-text-primary:hover{--tw-text-opacity: 1;color:rgb(255 255 255 / var(--tw-text-opacity, 1))}.hover\:text-text-secondary:hover{--tw-text-opacity: 1;color:rgb(136 136 136 / var(--tw-text-opacity, 1))}.focus\:border-accent-green:focus{--tw-border-opacity: 1;border-color:rgb(255 107 53 / var(--tw-border-opacity, 1))}.disabled\:opacity-40:disabled{opacity:.4}@media(min-width:768px){.md\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.md\:grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}}@media(min-width:1024px){.lg\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}}
|