tokenrace 0.1.21 → 0.1.22

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-CclozGtU.js"></script>
8
- <link rel="stylesheet" crossorigin href="/assets/index-BimQXpAH.css">
7
+ <script type="module" crossorigin src="/assets/index-Dt3hqx3Z.js"></script>
8
+ <link rel="stylesheet" crossorigin href="/assets/index-BD6TWWBL.css">
9
9
  </head>
10
10
  <body>
11
11
  <div id="root"></div>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tokenrace",
3
- "version": "0.1.21",
3
+ "version": "0.1.22",
4
4
  "description": "Monitor en tiempo real para Claude Code",
5
5
  "bin": {
6
6
  "tokenrace": "bin/cli.js"
package/src/server.js CHANGED
@@ -15,7 +15,7 @@ import express from 'express'
15
15
  import path from 'node:path'
16
16
  import { fileURLToPath } from 'node:url'
17
17
  import { parseMetrics, parseEvents, parseTraces } from './otlp-parser.js'
18
- import { processMetric, processEvent, processTrace, loadFromDisk, startAutoSave, saveSync, drainAutoDetectQueue, labelSession } from './store.js'
18
+ import { processMetric, processEvent, loadFromDisk, startAutoSave, saveSync, drainAutoDetectQueue, labelSession } from './store.js'
19
19
  import { detectProjectBySessionId } from './git-detector.js'
20
20
  import { createRouter, broadcast } from './api-routes.js'
21
21
 
@@ -92,9 +92,6 @@ export async function startServer({ port = 1337 } = {}) {
92
92
  */
93
93
  app.post('/v1/traces', (req, res) => {
94
94
  const spans = parseTraces(req.body)
95
- for (const span of spans) {
96
- processTrace(span)
97
- }
98
95
  broadcast('trace', { count: spans.length })
99
96
  res.json({ partialSuccess: {} })
100
97
  })
package/src/store.js CHANGED
@@ -65,7 +65,6 @@ const state = {
65
65
  eventIndex: 0,
66
66
  projects: new Map(), // projectName → ProjectAggregates
67
67
  tools: new Map(), // toolName → ToolStats
68
- agents: new Map(), // agentId → AgentData
69
68
  models: new Map(), // modelName → ModelStats
70
69
  cumulativeValues: new Map(), // clave → último valor acumulativo (no persiste)
71
70
  lastSeen: null,
@@ -97,7 +96,7 @@ function normalizeIncoming(name, value, labels) {
97
96
  switch (name) {
98
97
  case 'claude_code.token.usage': {
99
98
  const type = labels.type
100
- const key = `token:${sid}:${type}:${labels.model ?? ''}:${labels.query_source ?? ''}`
99
+ const key = `token:${sid}:${type}:${labels.model ?? ''}:${labels.query_source ?? ''}:${labels['agent.name'] ?? ''}`
101
100
  const delta = cumulativeDelta(key, value)
102
101
  const nameMap = {
103
102
  'input': 'claude_code.tokens.input',
@@ -110,7 +109,7 @@ function normalizeIncoming(name, value, labels) {
110
109
  }
111
110
 
112
111
  case 'claude_code.cost.usage': {
113
- const key = `cost:${sid}:${labels.model ?? ''}:${labels.query_source ?? ''}:${labels.effort ?? ''}`
112
+ const key = `cost:${sid}:${labels.model ?? ''}:${labels.query_source ?? ''}:${labels.effort ?? ''}:${labels['agent.name'] ?? ''}`
114
113
  const delta = cumulativeDelta(key, value)
115
114
  return { name: 'claude_code.cost', value: delta }
116
115
  }
@@ -432,55 +431,12 @@ export function processEvent({ eventName, timestamp, severity, attributes }) {
432
431
  if (durMs > 0) tool.totalDurationMs += durMs
433
432
  }
434
433
 
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
- })
449
- }
450
-
451
434
  state.lastSeen = timestamp
452
435
  state.totalEvents++
453
436
 
454
437
  return eventObj
455
438
  }
456
439
 
457
- /**
458
- * Procesa un span normalizado.
459
- * Solo actúa si el span tiene agentId.
460
- * @param {Object} span
461
- */
462
- export function processTrace(span) {
463
- const attrs = span.attributes
464
- const agentId = attrs['agent.id'] ?? attrs['gen_ai.agent.id'] ?? null
465
- if (!agentId) return
466
-
467
- if (!state.agents.has(agentId)) {
468
- state.agents.set(agentId, {
469
- agentId,
470
- parentAgentId: attrs['agent.parent_id'] ?? null,
471
- tokensInput: 0,
472
- tokensOutput: 0,
473
- cost: 0,
474
- toolCalls: 0,
475
- durationMs: 0
476
- })
477
- }
478
-
479
- const agent = state.agents.get(agentId)
480
- const duration = span.endTime - span.startTime
481
- if (duration > 0) agent.durationMs += duration
482
- }
483
-
484
440
  /**
485
441
  * Asigna un proyecto a una sesión, con aplicación retroactiva.
486
442
  * @param {string} sessionId
@@ -522,7 +478,6 @@ export function reset() {
522
478
  state.eventIndex = 0
523
479
  state.projects.clear()
524
480
  state.tools.clear()
525
- state.agents.clear()
526
481
  state.models.clear()
527
482
  state.cumulativeValues.clear()
528
483
  state.lastSeen = null
@@ -915,10 +870,37 @@ export function getTools(from) {
915
870
  }
916
871
 
917
872
  /**
918
- * Lista de agentes registrados.
873
+ * Estadísticas de subagentes (Task tool), derivadas de las métricas de tokens
874
+ * y coste cuyas labels traen query_source: "subagent" y agent.name.
875
+ * Claude Code no emite eventos/spans con identidad de agente individual, así
876
+ * que se agrega por nombre de agente (p. ej. "Explore", "general-purpose").
919
877
  */
920
878
  export function getAgents() {
921
- return Array.from(state.agents.values())
879
+ const byAgent = new Map() // agentName → { tokensInput, tokensOutput, cost }
880
+
881
+ const metricField = {
882
+ 'claude_code.tokens.input': 'tokensInput',
883
+ 'claude_code.tokens.output': 'tokensOutput',
884
+ 'claude_code.cost': 'cost'
885
+ }
886
+
887
+ for (const [metricName, field] of Object.entries(metricField)) {
888
+ for (const point of state.timeseries.get(metricName) ?? []) {
889
+ const labels = point.labels
890
+ if (labels.query_source !== 'subagent') continue
891
+ const name = labels['agent.name']
892
+ if (!name) continue
893
+
894
+ if (!byAgent.has(name)) {
895
+ byAgent.set(name, { tokensInput: 0, tokensOutput: 0, cost: 0 })
896
+ }
897
+ byAgent.get(name)[field] += point.value
898
+ }
899
+ }
900
+
901
+ return Array.from(byAgent.entries())
902
+ .map(([name, stats]) => ({ name, ...stats }))
903
+ .sort((a, b) => b.cost - a.cost)
922
904
  }
923
905
 
924
906
  /**
@@ -964,8 +946,7 @@ export function saveSync() {
964
946
  eventIndex: state.eventIndex,
965
947
  totalEvents: state.totalEvents,
966
948
  startTime: state.startTime,
967
- tools: Array.from(state.tools.entries()),
968
- agents: Array.from(state.agents.values())
949
+ tools: Array.from(state.tools.entries())
969
950
  }
970
951
 
971
952
  fs.writeFileSync(dataFile, JSON.stringify(data), { mode: 0o600 })
@@ -1043,13 +1024,6 @@ export function loadFromDisk() {
1043
1024
  }
1044
1025
  }
1045
1026
 
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
-
1053
1027
  // Re-aplicar mappings a sesiones (timeseries no necesitan propagación:
1054
1028
  // resolveProject() consulta sessionMappings primero en todos los lectores)
1055
1029
  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-44{width:11rem}.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}.transition-opacity{transition-property:opacity;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))}.hover\:opacity-80:hover{opacity:.8}.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-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))}}