tokenrace 0.1.12 → 0.1.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -0
- package/dist/assets/index-52-EmhaW.css +1 -0
- package/dist/assets/{index-DQk3XNu9.js → index-B-htjFDg.js} +56 -56
- package/dist/index.html +2 -2
- package/package.json +1 -1
- package/src/api-routes.js +9 -1
- package/src/prices.js +43 -0
- package/src/store.js +68 -12
- package/dist/assets/index-B0dy8dWP.css +0 -1
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-B-htjFDg.js"></script>
|
|
8
|
+
<link rel="stylesheet" crossorigin href="/assets/index-52-EmhaW.css">
|
|
9
9
|
</head>
|
|
10
10
|
<body>
|
|
11
11
|
<div id="root"></div>
|
package/package.json
CHANGED
package/src/api-routes.js
CHANGED
|
@@ -14,7 +14,7 @@ import {
|
|
|
14
14
|
getStatus, getSummary, getTimeseries, getProjects,
|
|
15
15
|
getSessions, getUnlabeledSessions, getSessionEvents,
|
|
16
16
|
getEvents, getTools, getAgents, getModels,
|
|
17
|
-
labelSession, ignoreSession, reset
|
|
17
|
+
labelSession, ignoreSession, reset, resetProject
|
|
18
18
|
} from './store.js'
|
|
19
19
|
|
|
20
20
|
// ─── Clientes SSE activos ────────────────────────────────────────────────────
|
|
@@ -194,6 +194,14 @@ export function createRouter({ port = 1337 } = {}) {
|
|
|
194
194
|
res.json({ ok: true })
|
|
195
195
|
})
|
|
196
196
|
|
|
197
|
+
/** POST /api/projects/:project/reset — resetea los datos de un proyecto */
|
|
198
|
+
router.post('/api/projects/:project/reset', requireSafeOrigin, (req, res) => {
|
|
199
|
+
const projectName = decodeURIComponent(req.params.project)
|
|
200
|
+
resetProject(projectName)
|
|
201
|
+
broadcast('project_reset', { project: projectName })
|
|
202
|
+
res.json({ ok: true })
|
|
203
|
+
})
|
|
204
|
+
|
|
197
205
|
return router
|
|
198
206
|
}
|
|
199
207
|
|
package/src/prices.js
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* prices.js — Tabla de precios de modelos Claude (USD por millón de tokens).
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export const MODEL_PRICES = {
|
|
6
|
+
'claude-opus-4-8': { input: 15.00, output: 75.00, cacheWrite: 18.75, cacheRead: 1.50 },
|
|
7
|
+
'claude-sonnet-4-6': { input: 3.00, output: 15.00, cacheWrite: 3.75, cacheRead: 0.30 },
|
|
8
|
+
'claude-haiku-4-5': { input: 0.80, output: 4.00, cacheWrite: 1.00, cacheRead: 0.08 },
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const DEFAULT_PRICES = MODEL_PRICES['claude-sonnet-4-6']
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Devuelve los precios para un nombre de modelo dado.
|
|
15
|
+
* Usa pattern matching por si el nombre incluye fecha (ej: claude-sonnet-4-6-20251022).
|
|
16
|
+
*/
|
|
17
|
+
export function getPrices(modelName) {
|
|
18
|
+
if (!modelName) return DEFAULT_PRICES
|
|
19
|
+
const lower = modelName.toLowerCase()
|
|
20
|
+
if (lower.includes('opus')) return MODEL_PRICES['claude-opus-4-8']
|
|
21
|
+
if (lower.includes('haiku')) return MODEL_PRICES['claude-haiku-4-5']
|
|
22
|
+
if (lower.includes('sonnet')) return MODEL_PRICES['claude-sonnet-4-6']
|
|
23
|
+
return DEFAULT_PRICES
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Estima el coste a partir de tokens para un modelo dado.
|
|
28
|
+
* @param {string|null} model
|
|
29
|
+
* @param {number} tokensInput
|
|
30
|
+
* @param {number} tokensOutput
|
|
31
|
+
* @param {number} tokensCacheRead
|
|
32
|
+
* @param {number} tokensCacheWrite
|
|
33
|
+
* @returns {number} Coste estimado en USD
|
|
34
|
+
*/
|
|
35
|
+
export function estimateCost(model, tokensInput, tokensOutput, tokensCacheRead = 0, tokensCacheWrite = 0) {
|
|
36
|
+
const p = getPrices(model)
|
|
37
|
+
return (
|
|
38
|
+
tokensInput * p.input / 1_000_000 +
|
|
39
|
+
tokensOutput * p.output / 1_000_000 +
|
|
40
|
+
tokensCacheRead * p.cacheRead / 1_000_000 +
|
|
41
|
+
tokensCacheWrite * p.cacheWrite / 1_000_000
|
|
42
|
+
)
|
|
43
|
+
}
|
package/src/store.js
CHANGED
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
import fs from 'node:fs'
|
|
13
13
|
import os from 'node:os'
|
|
14
14
|
import path from 'node:path'
|
|
15
|
+
import { estimateCost } from './prices.js'
|
|
15
16
|
|
|
16
17
|
// ─── Ruta de persistencia ───────────────────────────────────────────────────
|
|
17
18
|
|
|
@@ -477,6 +478,49 @@ export function reset() {
|
|
|
477
478
|
saveSync()
|
|
478
479
|
}
|
|
479
480
|
|
|
481
|
+
/**
|
|
482
|
+
* Resetea los datos de un proyecto concreto eliminando sus sesiones y sus
|
|
483
|
+
* puntos de timeseries, eventos y entradas cumulativas asociadas.
|
|
484
|
+
* @param {string} projectName
|
|
485
|
+
*/
|
|
486
|
+
export function resetProject(projectName) {
|
|
487
|
+
// Recoger los sessionIds del proyecto
|
|
488
|
+
const sessionIds = new Set()
|
|
489
|
+
for (const session of state.sessions.values()) {
|
|
490
|
+
if (resolveProject(session.sessionId, session.project) === projectName) {
|
|
491
|
+
sessionIds.add(session.sessionId)
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
// Eliminar sesiones y sus entradas auxiliares
|
|
496
|
+
for (const sid of sessionIds) {
|
|
497
|
+
state.sessions.delete(sid)
|
|
498
|
+
state.sessionMappings.delete(sid)
|
|
499
|
+
state.ignoredSessions.delete(sid)
|
|
500
|
+
|
|
501
|
+
// Limpiar baselines de métricas cumulativas para esta sesión
|
|
502
|
+
for (const key of state.cumulativeValues.keys()) {
|
|
503
|
+
if (key.includes(`:${sid}:`)) state.cumulativeValues.delete(key)
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
// Filtrar timeseries: eliminar puntos de las sesiones del proyecto
|
|
508
|
+
for (const [metric, points] of state.timeseries.entries()) {
|
|
509
|
+
const filtered = points.filter(p => !sessionIds.has(p.labels['session.id']))
|
|
510
|
+
if (filtered.length !== points.length) state.timeseries.set(metric, filtered)
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
// Filtrar buffer de eventos
|
|
514
|
+
const remaining = state.events.filter(e => !sessionIds.has(e.sessionId))
|
|
515
|
+
state.events.length = 0
|
|
516
|
+
state.events.push(...remaining)
|
|
517
|
+
|
|
518
|
+
// Eliminar el proyecto del mapa
|
|
519
|
+
state.projects.delete(projectName)
|
|
520
|
+
|
|
521
|
+
scheduleSave()
|
|
522
|
+
}
|
|
523
|
+
|
|
480
524
|
// ─── Getters ─────────────────────────────────────────────────────────────────
|
|
481
525
|
|
|
482
526
|
/**
|
|
@@ -516,7 +560,9 @@ export function getSummary(from) {
|
|
|
516
560
|
tokensInput += session.tokensInput
|
|
517
561
|
tokensOutput += session.tokensOutput
|
|
518
562
|
tokensCache += session.tokensCache
|
|
519
|
-
cost += session.cost
|
|
563
|
+
cost += session.cost > 0
|
|
564
|
+
? session.cost
|
|
565
|
+
: estimateCost(session.model, session.tokensInput, session.tokensOutput, session.tokensCache)
|
|
520
566
|
activeTimeMs += session.durationActiveMs
|
|
521
567
|
sessionSet.add(session.sessionId)
|
|
522
568
|
}
|
|
@@ -612,7 +658,9 @@ export function getProjects(from) {
|
|
|
612
658
|
const session = state.sessions.get(sessionId)
|
|
613
659
|
if (!session || session.lastSeen < minTs) continue
|
|
614
660
|
activeSessions.add(sessionId)
|
|
615
|
-
cost += session.cost
|
|
661
|
+
cost += session.cost > 0
|
|
662
|
+
? session.cost
|
|
663
|
+
: estimateCost(session.model, session.tokensInput, session.tokensOutput, session.tokensCache)
|
|
616
664
|
tokensInput += session.tokensInput
|
|
617
665
|
tokensOutput += session.tokensOutput
|
|
618
666
|
tokensCache += session.tokensCache
|
|
@@ -655,7 +703,10 @@ export function getSessions({ limit = 50, project = null } = {}) {
|
|
|
655
703
|
|
|
656
704
|
return sessions.slice(0, limit).map(s => ({
|
|
657
705
|
...s,
|
|
658
|
-
project: resolveProject(s.sessionId, s.project)
|
|
706
|
+
project: resolveProject(s.sessionId, s.project),
|
|
707
|
+
cost: s.cost > 0
|
|
708
|
+
? s.cost
|
|
709
|
+
: estimateCost(s.model, s.tokensInput, s.tokensOutput, s.tokensCache)
|
|
659
710
|
}))
|
|
660
711
|
}
|
|
661
712
|
|
|
@@ -749,15 +800,20 @@ export function getAgents() {
|
|
|
749
800
|
*/
|
|
750
801
|
export function getModels(from) {
|
|
751
802
|
return Array.from(state.models.entries())
|
|
752
|
-
.map(([model, stats]) =>
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
803
|
+
.map(([model, stats]) => {
|
|
804
|
+
const cost = stats.cost > 0
|
|
805
|
+
? stats.cost
|
|
806
|
+
: estimateCost(model, stats.tokensInput, stats.tokensOutput)
|
|
807
|
+
return {
|
|
808
|
+
model,
|
|
809
|
+
requests: stats.requests,
|
|
810
|
+
tokensInput: stats.tokensInput,
|
|
811
|
+
tokensOutput: stats.tokensOutput,
|
|
812
|
+
cost,
|
|
813
|
+
avgLatencyMs: 0,
|
|
814
|
+
avgTtftMs: 0
|
|
815
|
+
}
|
|
816
|
+
})
|
|
761
817
|
.sort((a, b) => b.cost - a.cost)
|
|
762
818
|
}
|
|
763
819
|
|
|
@@ -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}.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}.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-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))}.focus\:border-accent-green:focus{--tw-border-opacity: 1;border-color:rgb(255 107 53 / var(--tw-border-opacity, 1))}@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))}}
|