nuxt-devtools-observatory 0.1.26 → 0.1.30

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.
@@ -1,8 +1,8 @@
1
1
  import { defineNuxtPlugin, useNuxtApp, useRuntimeConfig, useRouter } from "#app";
2
2
  import { nextTick } from "vue";
3
+ import { setupComposableRegistry } from "./composables/composable-registry.js";
3
4
  import { setupFetchRegistry } from "./composables/fetch-registry.js";
4
5
  import { setupProvideInjectRegistry } from "./composables/provide-inject-registry.js";
5
- import { setupComposableRegistry } from "./composables/composable-registry.js";
6
6
  import { setupRenderRegistry } from "./composables/render-registry.js";
7
7
  import { setupTransitionRegistry } from "./composables/transition-registry.js";
8
8
  export default defineNuxtPlugin(() => {
@@ -11,7 +11,15 @@ export default defineNuxtPlugin(() => {
11
11
  }
12
12
  const nuxtApp = useNuxtApp();
13
13
  const config = useRuntimeConfig().public.observatory;
14
+ const debugRpc = config.debugRpc === true;
15
+ const debugLog = (...args) => {
16
+ if (debugRpc) {
17
+ console.info("[observatory][rpc][host]", ...args);
18
+ }
19
+ };
14
20
  let composableNavigationMode = config.composableNavigationMode === "session" ? "session" : "route";
21
+ let heartbeatId = null;
22
+ let lastSnapshotSignature = "";
15
23
  if (config.renderHeatmap) {
16
24
  nuxtApp.vueApp.config.performance = true;
17
25
  }
@@ -36,21 +44,24 @@ export default defineNuxtPlugin(() => {
36
44
  if (import.meta.client) {
37
45
  delete window.__observatory__;
38
46
  window.__observatory__ = registries;
39
- let lastMessageSourceRef = null;
40
- let lastMessageOrigin = "";
41
- const messageHandler = (event) => {
42
- if (config.clientOrigin && event.origin !== config.clientOrigin) {
47
+ const composableRegistry = registries.composable;
48
+ if (composableRegistry && composableRegistry.onComposableChange) {
49
+ composableRegistry.onComposableChange(() => {
50
+ broadcastAll("composable:onChange");
51
+ });
52
+ }
53
+ import.meta.hot?.on("observatory:command", (rawPayload) => {
54
+ if (!rawPayload || typeof rawPayload !== "object") {
43
55
  return;
44
56
  }
45
- const type = event.data?.type;
46
- if (type === "observatory:request") {
47
- lastMessageSourceRef = event.source ? new WeakRef(event.source) : null;
48
- lastMessageOrigin = event.origin;
49
- const source = event.source;
50
- source?.postMessage({ type: "observatory:snapshot", data: buildSnapshot() }, event.origin);
57
+ const payload = rawPayload;
58
+ if (payload.cmd === "request-snapshot") {
59
+ debugLog("received command: request-snapshot");
60
+ broadcastAll("command:request-snapshot");
51
61
  return;
52
62
  }
53
- if (type === "observatory:clear-composables") {
63
+ if (payload.cmd === "clear-composables") {
64
+ debugLog("received command: clear-composables");
54
65
  if (composableRegistry) {
55
66
  if (composableNavigationMode === "session") {
56
67
  composableRegistry.clearNonLayout();
@@ -58,57 +69,55 @@ export default defineNuxtPlugin(() => {
58
69
  composableRegistry.clear();
59
70
  }
60
71
  }
61
- const source = event.source;
62
- source?.postMessage({ type: "observatory:snapshot", data: buildSnapshot() }, event.origin);
72
+ broadcastAll("command:clear-composables");
63
73
  return;
64
74
  }
65
- if (type === "observatory:set-composable-mode") {
66
- const mode = event.data?.mode;
67
- if (mode === "route" || mode === "session") {
68
- composableNavigationMode = mode;
69
- }
70
- const source = event.source;
71
- source?.postMessage({ type: "observatory:snapshot", data: buildSnapshot() }, event.origin);
72
- }
73
- if (type === "observatory:edit-composable") {
74
- const { id, key, value } = event.data;
75
- if (composableRegistry) {
76
- composableRegistry.editValue(id, key, value);
75
+ if (payload.cmd === "set-mode") {
76
+ debugLog("received command: set-mode", payload.mode);
77
+ if (payload.mode === "route" || payload.mode === "session") {
78
+ composableNavigationMode = payload.mode;
77
79
  }
80
+ broadcastAll("command:set-mode");
81
+ return;
78
82
  }
79
- if (type === "observatory:open-in-editor") {
80
- const { file } = event.data;
81
- if (file && file !== "unknown") {
82
- const cleaned = file.replace(/^\/@fs/, "").replace(/\?.*$/, "");
83
- fetch(`/__open-in-editor?file=${encodeURIComponent(cleaned)}`).catch(() => {
84
- });
85
- }
83
+ if (payload.cmd === "edit-composable") {
84
+ debugLog("received command: edit-composable", { id: payload.id, key: payload.key });
85
+ composableRegistry?.editValue(payload.id, payload.key, payload.value);
86
86
  }
87
- };
88
- window.addEventListener("message", messageHandler);
87
+ });
89
88
  nuxtApp.hook("app:beforeUnmount", () => {
90
- window.removeEventListener("message", messageHandler);
91
- lastMessageSourceRef = null;
89
+ import.meta.hot?.off("observatory:command");
90
+ if (heartbeatId !== null) {
91
+ window.clearInterval(heartbeatId);
92
+ heartbeatId = null;
93
+ }
92
94
  });
93
- const composableRegistry = registries.composable;
94
- if (composableRegistry && composableRegistry.onComposableChange) {
95
- composableRegistry.onComposableChange(() => {
96
- const lastMessageSource = lastMessageSourceRef?.deref();
97
- if (!lastMessageSource || !lastMessageOrigin) {
98
- return;
99
- }
100
- lastMessageSource.postMessage(
101
- {
102
- type: "observatory:snapshot",
103
- data: buildSnapshot()
104
- },
105
- lastMessageOrigin
106
- );
107
- });
108
- }
109
95
  }
110
96
  nuxtApp.hook("app:mounted", () => {
111
- broadcastAll();
97
+ broadcastAll("app:mounted");
98
+ nextTick(() => {
99
+ broadcastAll("app:mounted:nextTick");
100
+ });
101
+ setTimeout(() => {
102
+ broadcastAll("app:mounted:50ms");
103
+ }, 50);
104
+ setTimeout(() => {
105
+ broadcastAll("app:mounted:250ms");
106
+ }, 250);
107
+ if (import.meta.client && heartbeatId === null) {
108
+ heartbeatId = window.setInterval(() => {
109
+ const snapshot = buildSnapshot();
110
+ const signature = JSON.stringify(snapshot);
111
+ if (signature !== lastSnapshotSignature) {
112
+ lastSnapshotSignature = signature;
113
+ debugLog("heartbeat detected snapshot change");
114
+ import.meta.hot?.send("observatory:snapshot", snapshot);
115
+ }
116
+ }, 400);
117
+ }
118
+ });
119
+ nuxtApp.hook("page:finish", () => {
120
+ broadcastAll("page:finish");
112
121
  });
113
122
  if (import.meta.client) {
114
123
  const router = useRouter();
@@ -118,16 +127,25 @@ export default defineNuxtPlugin(() => {
118
127
  return;
119
128
  }
120
129
  const render = registries.render;
121
- if (render && typeof render.reset === "function") render.reset();
130
+ if (render && typeof render.reset === "function") {
131
+ ;
132
+ render.reset();
133
+ }
122
134
  const provideInject = registries.provideInject;
123
- if (provideInject && typeof provideInject.clear === "function")
135
+ if (provideInject && typeof provideInject.clear === "function") {
136
+ ;
124
137
  provideInject.clear();
138
+ }
125
139
  const composable = registries.composable;
126
- if (composableNavigationMode === "route" && composable && typeof composable.clearNonLayout === "function")
140
+ if (composableNavigationMode === "route" && composable && typeof composable.clearNonLayout === "function") {
141
+ ;
127
142
  composable.clearNonLayout();
143
+ }
128
144
  const transition = registries.transition;
129
- if (transition && typeof transition.clear === "function")
145
+ if (transition && typeof transition.clear === "function") {
146
+ ;
130
147
  transition.clear();
148
+ }
131
149
  }
132
150
  );
133
151
  router.afterEach((to) => {
@@ -141,18 +159,26 @@ export default defineNuxtPlugin(() => {
141
159
  ;
142
160
  render.setRoute(to.path ?? "/");
143
161
  }
144
- nextTick(() => broadcastAll());
162
+ nextTick(() => broadcastAll("router:afterEach"));
145
163
  });
146
164
  }
147
- function broadcastAll() {
165
+ function broadcastAll(reason = "unknown") {
148
166
  if (!import.meta.client) {
149
167
  return;
150
168
  }
151
- const channel = getDevtoolsChannel();
152
- if (!channel) {
169
+ if (!import.meta.hot) {
153
170
  return;
154
171
  }
155
- channel.send("observatory:snapshot", buildSnapshot());
172
+ const snapshot = buildSnapshot();
173
+ debugLog("push snapshot", {
174
+ reason,
175
+ fetch: Array.isArray(snapshot.fetch) ? snapshot.fetch.length : 0,
176
+ composables: Array.isArray(snapshot.composables) ? snapshot.composables.length : 0,
177
+ renders: Array.isArray(snapshot.renders) ? snapshot.renders.length : 0,
178
+ transitions: Array.isArray(snapshot.transitions) ? snapshot.transitions.length : 0
179
+ });
180
+ lastSnapshotSignature = JSON.stringify(snapshot);
181
+ import.meta.hot.send("observatory:snapshot", snapshot);
156
182
  }
157
183
  function buildSnapshot() {
158
184
  function safeParse(val, fallback) {
@@ -191,7 +217,4 @@ export default defineNuxtPlugin(() => {
191
217
  };
192
218
  return snapshot;
193
219
  }
194
- function getDevtoolsChannel() {
195
- return window.__nuxt_devtools__?.channel ?? null;
196
- }
197
220
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nuxt-devtools-observatory",
3
- "version": "0.1.26",
3
+ "version": "0.1.30",
4
4
  "description": "Nuxt DevTools: useFetch Dashboard, provide/inject Graph, Composable Tracker, Render Heatmap",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -34,12 +34,18 @@
34
34
  "client"
35
35
  ],
36
36
  "scripts": {
37
- "dev": "nuxi dev playground",
37
+ "dev": "concurrently -r -n client,nuxt \"pnpm dev:client\" \"nuxi dev playground\"",
38
+ "dev:client": "vite build --watch --config client/vite.config.ts",
39
+ "docs:clean": "rm -rf docs/.nuxt docs/.output",
40
+ "docs:dev": "pnpm docs:clean && pnpm --dir docs dev",
41
+ "docs:build": "pnpm docs:clean && pnpm --dir docs build",
42
+ "docs:generate": "pnpm docs:clean && pnpm --dir docs generate",
43
+ "docs:preview": "pnpm --dir docs preview",
38
44
  "build:client": "vite build --config client/vite.config.ts",
39
45
  "build": "npm run build:client && nuxt-module-build build",
40
46
  "prepack": "npm run build",
41
47
  "lint": "eslint .",
42
- "format": "prettier --write '**/*.{ts,vue,js,json}' --ignore-path .prettierignore && stylelint '**/*.{css,vue}' --ignore-pattern '.nuxt/**' --ignore-pattern '.output/**' --ignore-pattern 'coverage/**' --ignore-pattern 'client/dist/**' --fix && eslint --fix '**/*.{ts,vue,js}' --ignore-pattern 'coverage/**' --ignore-pattern 'client/dist/**'",
48
+ "format": "prettier --write '**/*.{ts,vue,js,json}' --ignore-path .prettierignore && stylelint '**/*.{css,vue}' --ignore-pattern '**/.nuxt/**' --ignore-pattern '**/.output/**' --ignore-pattern 'coverage/**' --ignore-pattern 'client/dist/**' --ignore-pattern 'scripts/**' --fix && eslint --fix '**/*.{ts,vue,js}' --ignore-pattern '**/.nuxt/**' --ignore-pattern '**/.output/**' --ignore-pattern 'coverage/**' --ignore-pattern 'client/dist/**' --ignore-pattern 'scripts/**' --ignore-pattern 'docs/dist/**'",
43
49
  "typecheck": "vue-tsc --noEmit",
44
50
  "test": "vitest run",
45
51
  "test:watch": "vitest",
@@ -56,6 +62,7 @@
56
62
  },
57
63
  "devDependencies": {
58
64
  "@eslint/js": "^9.39.2",
65
+ "@nuxt/devtools-kit": "4.0.0-alpha.3",
59
66
  "@nuxt/eslint": "^1.13.0",
60
67
  "@nuxt/kit": "^3.0.0",
61
68
  "@nuxt/module-builder": "^1.0.2",
@@ -67,6 +74,7 @@
67
74
  "@types/node": "^25.5.0",
68
75
  "@vitejs/plugin-vue": "^6.0.0",
69
76
  "@vitest/coverage-v8": "^4.1.0",
77
+ "concurrently": "^9.2.1",
70
78
  "eslint": "^9.39.2",
71
79
  "eslint-config-prettier": "^10.1.8",
72
80
  "eslint-plugin-jsdoc": "^62.5.0",
@@ -80,6 +88,7 @@
80
88
  "playwright": "^1.58.2",
81
89
  "postcss-html": "^1.8.1",
82
90
  "prettier": "^3.8.1",
91
+ "sirv": "^3.0.2",
83
92
  "stylelint": "^17.4.0",
84
93
  "stylelint-config-standard": "^40.0.0",
85
94
  "stylelint-config-standard-vue": "^1.0.0",
@@ -1 +0,0 @@
1
- .view[data-v-4f9bcc52]{display:flex;flex-direction:column;height:100%;overflow:hidden;padding:12px;gap:10px}.stats-row[data-v-4f9bcc52]{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:8px;flex-shrink:0}.toolbar[data-v-4f9bcc52]{display:flex;align-items:center;gap:6px;flex-shrink:0;flex-wrap:wrap}.split[data-v-4f9bcc52]{display:flex;gap:12px;flex:1;overflow:hidden;min-height:0}.table-wrap[data-v-4f9bcc52]{flex:1;overflow:auto;border:.5px solid var(--border);border-radius:var(--radius-lg)}.detail-panel[data-v-4f9bcc52]{width:280px;flex-shrink:0;display:flex;flex-direction:column;gap:8px;overflow:auto;border:.5px solid var(--border);border-radius:var(--radius-lg);padding:12px;background:var(--bg3)}.detail-empty[data-v-4f9bcc52]{width:280px;flex-shrink:0;display:flex;align-items:center;justify-content:center;color:var(--text3);font-size:12px;border:.5px dashed var(--border);border-radius:var(--radius-lg)}.detail-header[data-v-4f9bcc52]{display:flex;align-items:center;justify-content:space-between}.meta-grid[data-v-4f9bcc52]{display:grid;grid-template-columns:auto 1fr;gap:4px 12px;font-size:11px}.section-label[data-v-4f9bcc52]{font-size:10px;font-weight:500;text-transform:uppercase;letter-spacing:.4px;color:var(--text3);margin-top:6px;min-height:fit-content}.payload-box[data-v-4f9bcc52]{font-family:var(--mono);font-size:11px;color:var(--text2);background:var(--bg2);border-radius:var(--radius);padding:8px 10px;overflow:auto;white-space:pre;max-height:160px}.waterfall[data-v-4f9bcc52]{flex-shrink:0;background:var(--bg3);border:.5px solid var(--border);border-radius:var(--radius-lg);padding:10px 12px}.waterfall-header[data-v-4f9bcc52]{display:flex;align-items:center;justify-content:space-between;gap:8px}.waterfall-body[data-v-4f9bcc52]{margin-top:6px}.wf-row[data-v-4f9bcc52]{display:flex;align-items:center;gap:8px;margin-bottom:4px}.wf-track[data-v-4f9bcc52]{flex:1;position:relative;height:8px;background:var(--bg2);border-radius:2px;overflow:hidden}.wf-bar[data-v-4f9bcc52]{position:absolute;top:0;height:100%;border-radius:2px;opacity:.8}.view[data-v-0c07531b]{display:flex;flex-direction:column;height:100%;overflow:hidden;padding:12px;gap:10px}.toolbar[data-v-0c07531b]{display:flex;align-items:center;gap:6px;flex-shrink:0;flex-wrap:wrap}.split[data-v-0c07531b]{display:flex;gap:12px;flex:1;overflow:hidden;min-height:0}.graph-area[data-v-0c07531b]{flex:1;overflow:auto;border:.5px solid var(--border);border-radius:var(--radius-lg);padding:12px;background:var(--bg3)}.legend[data-v-0c07531b]{display:flex;align-items:center;gap:12px;font-size:11px;color:var(--text2);margin-bottom:12px}.canvas-stage[data-v-0c07531b]{display:flex;justify-content:center;align-items:flex-start;min-width:100%}.dot[data-v-0c07531b]{width:8px;height:8px;border-radius:50%;display:inline-block;margin-right:2px}.canvas-wrap[data-v-0c07531b]{position:relative}.edges-svg[data-v-0c07531b]{position:absolute;top:0;left:0;pointer-events:none}.edge[data-v-0c07531b]{stroke:var(--border);stroke-width:1.5}.graph-node[data-v-0c07531b]{position:absolute;display:flex;align-items:center;gap:7px;padding:0 10px;height:32px;border-radius:var(--radius);border:.5px solid var(--border);background:var(--bg3);cursor:pointer;transition:border-color .12s,background .12s;overflow:hidden;box-sizing:border-box;white-space:nowrap}.graph-node[data-v-0c07531b]:hover{border-color:var(--text3)}.graph-node.is-selected[data-v-0c07531b]{border-color:var(--node-color);background:color-mix(in srgb,var(--node-color) 8%,transparent)}.node-dot[data-v-0c07531b]{width:7px;height:7px;border-radius:50%;flex-shrink:0}.node-label[data-v-0c07531b]{font-size:11px;flex:1;overflow:hidden;text-overflow:ellipsis}.badge-xs[data-v-0c07531b]{font-size:9px;padding:1px 4px}.detail-panel[data-v-0c07531b]{width:280px;flex-shrink:0;overflow:auto;border:.5px solid var(--border);border-radius:var(--radius-lg);padding:12px;background:var(--bg3);display:flex;flex-direction:column;gap:4px;min-height:0}.detail-empty[data-v-0c07531b]{width:280px;display:flex;align-items:center;justify-content:center;color:var(--text3);font-size:12px;border:.5px dashed var(--border);border-radius:var(--radius-lg);flex-shrink:0}.graph-empty[data-v-0c07531b]{display:flex;align-items:center;justify-content:center;min-height:180px;color:var(--text3);font-size:12px}.detail-header[data-v-0c07531b]{display:flex;align-items:center;justify-content:space-between;margin-bottom:6px}.section-label[data-v-0c07531b]{font-size:10px;font-weight:500;text-transform:uppercase;letter-spacing:.4px;color:var(--text3);margin:8px 0 5px}.detail-section[data-v-0c07531b]{display:flex;flex-direction:column;min-height:0}.detail-list[data-v-0c07531b]{display:flex;flex-direction:column;gap:3px;overflow:auto;max-height:220px;padding-right:2px}.provide-row[data-v-0c07531b]{display:flex;flex-direction:column;gap:4px;padding:5px 8px;background:var(--bg2);border-radius:var(--radius);margin-bottom:3px}.row-warning[data-v-0c07531b]{font-size:11px;color:var(--amber);padding:2px 0}.row-consumers[data-v-0c07531b]{display:flex;flex-wrap:wrap;align-items:center;gap:4px;padding:2px 0}.consumer-chip[data-v-0c07531b]{font-size:10px;padding:1px 6px;border-radius:4px;background:color-mix(in srgb,var(--blue) 10%,var(--bg3));border:.5px solid color-mix(in srgb,var(--blue) 30%,var(--border));color:var(--text2)}.scope-badge[data-v-0c07531b]{font-size:10px;padding:1px 6px;border-radius:4px}.scope-global[data-v-0c07531b]{background:color-mix(in srgb,var(--amber) 15%,transparent);border:.5px solid color-mix(in srgb,var(--amber) 40%,var(--border));color:color-mix(in srgb,var(--amber) 80%,var(--text))}.scope-layout[data-v-0c07531b]{background:color-mix(in srgb,var(--purple) 15%,transparent);border:.5px solid color-mix(in srgb,var(--purple) 40%,var(--border));color:color-mix(in srgb,var(--purple) 80%,var(--text))}.scope-component[data-v-0c07531b]{background:var(--bg3);border:.5px solid var(--border);color:var(--text3)}.row-main[data-v-0c07531b]{display:flex;align-items:center;gap:8px;min-width:0}.row-key[data-v-0c07531b]{min-width:100px;color:var(--text2);flex-shrink:0}.row-value-preview[data-v-0c07531b]{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.row-toggle[data-v-0c07531b]{padding:2px 8px;font-size:10px}.value-box[data-v-0c07531b]{font-family:var(--mono);font-size:11px;color:var(--text2);background:#0000001a;border-radius:var(--radius);padding:8px 10px;white-space:pre-wrap;word-break:break-word;overflow:auto;max-height:180px}.inject-row[data-v-0c07531b]{display:flex;align-items:center;gap:8px;padding:5px 8px;background:var(--bg2);border-radius:var(--radius);margin-bottom:3px}.row-from[data-v-0c07531b]{margin-left:auto;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.inject-miss[data-v-0c07531b]{background:#e24b4a14}.jump-btn[data-v-0c07531b]{font-size:10px;padding:1px 6px;border:.5px solid var(--border);border-radius:var(--radius);background:transparent;color:var(--text3);cursor:pointer;flex-shrink:0;font-family:var(--mono)}.jump-btn[data-v-0c07531b]:hover{border-color:var(--teal);color:var(--teal);background:color-mix(in srgb,var(--teal) 8%,transparent)}.view[data-v-28f502da]{display:flex;flex-direction:column;height:100%;overflow:hidden;padding:12px;gap:10px}.stats-row[data-v-28f502da]{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:8px;flex-shrink:0}.toolbar[data-v-28f502da]{display:flex;align-items:center;gap:6px;flex-shrink:0;flex-wrap:wrap}.clear-btn[data-v-28f502da]{color:var(--text3);border-color:var(--border);flex-shrink:0}.clear-btn[data-v-28f502da]:hover{color:var(--red);border-color:var(--red);background:transparent}.mode-btn[data-v-28f502da]{border-color:color-mix(in srgb,var(--blue) 40%,var(--border));color:var(--blue)}.mode-btn[data-v-28f502da]:hover{border-color:var(--blue);background:color-mix(in srgb,var(--blue) 12%,transparent)}.list[data-v-28f502da]{flex:1;overflow:auto;display:flex;flex-direction:column;gap:5px;min-height:0}.comp-card[data-v-28f502da]{background:var(--bg3);border:.5px solid var(--border);border-radius:var(--radius-lg);overflow:hidden;cursor:pointer;flex-shrink:0}.comp-card[data-v-28f502da]:hover{border-color:var(--text3)}.comp-card.leak[data-v-28f502da]{border-left:2px solid var(--red);border-radius:0 var(--radius-lg) var(--radius-lg) 0}.comp-card.expanded[data-v-28f502da]{border-color:var(--purple)}.comp-header[data-v-28f502da]{display:flex;align-items:center;justify-content:space-between;padding:8px 12px;gap:8px}.comp-identity[data-v-28f502da]{display:flex;align-items:baseline;gap:6px;min-width:0;flex:1}.comp-name[data-v-28f502da]{font-size:12px;font-weight:500;color:var(--text);flex-shrink:0}.comp-file[data-v-28f502da]{font-size:11px;color:var(--text3);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.comp-meta[data-v-28f502da]{display:flex;align-items:center;gap:5px;flex-shrink:0}.refs-preview[data-v-28f502da]{display:flex;flex-wrap:wrap;gap:4px;padding:0 12px 8px;align-items:center}.ref-chip[data-v-28f502da]{display:inline-flex;align-items:center;gap:4px;padding:2px 7px;border-radius:4px;background:var(--bg2);border:.5px solid var(--border);font-size:11px;font-family:var(--mono);max-width:220px;overflow:hidden}.ref-chip--reactive[data-v-28f502da]{border-color:color-mix(in srgb,var(--purple) 40%,var(--border));background:color-mix(in srgb,var(--purple) 8%,var(--bg2))}.ref-chip--computed[data-v-28f502da]{border-color:color-mix(in srgb,var(--blue) 40%,var(--border));background:color-mix(in srgb,var(--blue) 8%,var(--bg2))}.ref-chip-key[data-v-28f502da]{color:var(--text2);flex-shrink:0}.ref-chip-val[data-v-28f502da]{color:var(--teal);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.comp-detail[data-v-28f502da]{padding:4px 12px 12px;border-top:.5px solid var(--border);display:flex;flex-direction:column;gap:3px}.leak-banner[data-v-28f502da]{background:color-mix(in srgb,var(--red) 12%,transparent);border:.5px solid color-mix(in srgb,var(--red) 40%,var(--border));border-radius:var(--radius);padding:6px 10px;font-size:11px;color:var(--red);margin-bottom:6px;font-family:var(--mono)}.section-label[data-v-28f502da]{font-size:10px;font-weight:500;text-transform:uppercase;letter-spacing:.4px;color:var(--text3);margin-top:6px;margin-bottom:3px}.ref-row[data-v-28f502da]{display:flex;align-items:flex-start;gap:8px;padding:3px 0}.ref-key[data-v-28f502da]{min-width:90px;color:var(--text2);flex-shrink:0}.ref-val[data-v-28f502da]{flex:1;color:var(--teal);min-width:0}.ref-val--collapsed[data-v-28f502da]{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.ref-val--full[data-v-28f502da]{white-space:pre-wrap;word-break:break-all;line-height:1.5}.ref-row-actions[data-v-28f502da]{display:flex;align-items:center;gap:4px;flex-shrink:0}.expand-btn[data-v-28f502da]{font-size:9px;padding:1px 5px;border-radius:4px;border:.5px solid var(--border);background:var(--bg2);color:var(--text3);cursor:pointer;line-height:1.4;flex-shrink:0}.expand-btn[data-v-28f502da]:hover{border-color:var(--text3);color:var(--text)}.lc-row[data-v-28f502da]{display:flex;align-items:center;gap:8px;padding:2px 0}.lc-dot[data-v-28f502da]{width:6px;height:6px;border-radius:50%;flex-shrink:0}.ref-chip--shared[data-v-28f502da]{border-color:color-mix(in srgb,var(--amber) 50%,var(--border));background:color-mix(in srgb,var(--amber) 10%,var(--bg2))}.ref-chip-shared-dot[data-v-28f502da]{display:inline-block;width:5px;height:5px;border-radius:50%;background:var(--amber);flex-shrink:0;margin-left:1px}.global-banner[data-v-28f502da]{display:flex;align-items:flex-start;gap:8px;background:color-mix(in srgb,var(--amber) 10%,transparent);border:.5px solid color-mix(in srgb,var(--amber) 40%,var(--border));border-radius:var(--radius);padding:7px 10px;font-size:11px;color:var(--text2);margin-bottom:6px}.global-dot[data-v-28f502da]{display:inline-block;width:6px;height:6px;border-radius:50%;background:var(--amber);flex-shrink:0;margin-top:3px}.badge-amber[data-v-28f502da]{background:color-mix(in srgb,var(--amber) 15%,transparent);color:color-mix(in srgb,var(--amber) 80%,var(--text));border:.5px solid color-mix(in srgb,var(--amber) 40%,var(--border))}.history-list[data-v-28f502da]{display:flex;flex-direction:column;gap:1px;background:var(--bg2);border-radius:var(--radius);padding:4px 8px;max-height:180px;overflow-y:auto}.history-row[data-v-28f502da]{display:flex;align-items:center;gap:8px;padding:2px 0;font-size:11px;font-family:var(--mono);border-bottom:.5px solid var(--border)}.history-row[data-v-28f502da]:last-child{border-bottom:none}.history-time[data-v-28f502da]{min-width:52px;color:var(--text3);flex-shrink:0}.history-key[data-v-28f502da]{min-width:80px;color:var(--text2);flex-shrink:0}.history-val[data-v-28f502da]{color:var(--amber);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;flex:1}.stat-card[data-v-28f502da]{background:var(--bg3);border:.5px solid var(--border);border-radius:var(--radius-lg);padding:10px 14px}.stat-label[data-v-28f502da]{font-size:10px;font-weight:500;text-transform:uppercase;letter-spacing:.4px;color:var(--text3);margin-bottom:4px}.stat-val[data-v-28f502da]{font-size:22px;font-weight:500;line-height:1;color:var(--text)}.ref-key--clickable[data-v-28f502da]{cursor:pointer;text-decoration:underline dotted var(--text3);text-underline-offset:2px}.ref-key--clickable[data-v-28f502da]:hover{color:var(--purple);text-decoration-color:var(--purple)}.edit-btn[data-v-28f502da]{font-size:10px;padding:1px 6px;border-radius:var(--radius);border:.5px solid var(--border);background:transparent;color:var(--text3);cursor:pointer;margin-left:auto;flex-shrink:0;font-family:var(--mono)}.edit-btn[data-v-28f502da]:hover{border-color:var(--purple);color:var(--purple);background:color-mix(in srgb,var(--purple) 8%,transparent)}.lookup-panel[data-v-28f502da]{flex-shrink:0;border:.5px solid var(--border);border-radius:var(--radius-lg);background:var(--bg3);overflow:hidden}.lookup-header[data-v-28f502da]{display:flex;align-items:center;gap:6px;padding:7px 12px;border-bottom:.5px solid var(--border);background:var(--bg2)}.lookup-row[data-v-28f502da]{display:flex;align-items:center;gap:8px;padding:5px 12px;border-bottom:.5px solid var(--border)}.lookup-row[data-v-28f502da]:last-child{border-bottom:none}.edit-overlay[data-v-28f502da]{position:fixed;top:0;right:0;bottom:0;left:0;background:#0006;z-index:100;display:flex;align-items:center;justify-content:center}.edit-dialog[data-v-28f502da]{background:var(--bg1, var(--bg2));border:.5px solid var(--border);border-radius:var(--radius-lg);padding:14px 16px;width:380px;max-width:92vw;display:flex;flex-direction:column;gap:6px;box-shadow:0 8px 32px #0000004d}.edit-dialog-header[data-v-28f502da]{display:flex;align-items:center;gap:6px;font-size:12px;color:var(--text2);margin-bottom:2px}.edit-textarea[data-v-28f502da]{width:100%;font-family:var(--mono);font-size:12px;padding:8px 10px;background:var(--bg2);border:.5px solid var(--border);border-radius:var(--radius);color:var(--text);resize:vertical;outline:none}.edit-textarea[data-v-28f502da]:focus{border-color:var(--purple)}.edit-error[data-v-28f502da]{color:var(--red);font-family:var(--mono)}.edit-actions[data-v-28f502da]{display:flex;gap:6px;padding-top:4px}.slide-enter-active[data-v-28f502da],.slide-leave-active[data-v-28f502da]{transition:opacity .15s,transform .15s}.slide-enter-from[data-v-28f502da],.slide-leave-to[data-v-28f502da]{opacity:0;transform:translateY(6px)}.fade-enter-active[data-v-28f502da],.fade-leave-active[data-v-28f502da]{transition:opacity .15s}.fade-enter-from[data-v-28f502da],.fade-leave-to[data-v-28f502da]{opacity:0}.jump-btn[data-v-28f502da]{font-size:10px;padding:1px 6px;border:.5px solid var(--border);border-radius:var(--radius);background:transparent;color:var(--text3);cursor:pointer;flex-shrink:0;font-family:var(--mono)}.jump-btn[data-v-28f502da]:hover{border-color:var(--teal);color:var(--teal);background:color-mix(in srgb,var(--teal) 8%,transparent)}.view[data-v-a5a04da1]{display:flex;flex-direction:column;height:100%;overflow:hidden;padding:12px;gap:10px}.controls[data-v-a5a04da1]{display:flex;align-items:center;gap:8px;flex-shrink:0;flex-wrap:wrap}.mode-group[data-v-a5a04da1]{display:flex;gap:2px}.threshold-group[data-v-a5a04da1]{display:flex;align-items:center;gap:6px}.stats-row[data-v-a5a04da1]{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:8px;flex-shrink:0}.stat-sub[data-v-a5a04da1]{margin-top:4px;font-size:11px;color:var(--text3)}.inspector[data-v-a5a04da1]{display:grid;grid-template-columns:minmax(220px,280px) minmax(0,1fr) minmax(260px,320px);gap:12px;flex:1;min-height:0}.roots-panel[data-v-a5a04da1],.tree-panel[data-v-a5a04da1],.detail-panel[data-v-a5a04da1]{border:.5px solid var(--border);border-radius:var(--radius-lg);background:var(--bg3);min-height:0}.roots-panel[data-v-a5a04da1],.detail-panel[data-v-a5a04da1]{display:flex;flex-direction:column;overflow:auto;padding:12px;gap:8px}.panel-title[data-v-a5a04da1]{font-size:10px;font-weight:500;text-transform:uppercase;letter-spacing:.4px;color:var(--text3)}.root-item[data-v-a5a04da1]{display:flex;align-items:center;justify-content:space-between;gap:8px;width:100%;padding:10px 12px;border:1px solid var(--border);border-radius:var(--radius);background:var(--bg2);color:var(--text);text-align:left}.root-item.active[data-v-a5a04da1]{border-color:var(--teal);background:color-mix(in srgb,var(--teal) 16%,var(--bg2))}.root-label[data-v-a5a04da1]{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.root-copy[data-v-a5a04da1]{display:flex;flex-direction:column;min-width:0}.root-sub[data-v-a5a04da1]{font-size:11px}.root-meta[data-v-a5a04da1]{color:var(--text3);font-size:11px}.tree-panel[data-v-a5a04da1]{display:flex;flex-direction:column;overflow:hidden}.tree-toolbar[data-v-a5a04da1]{padding:12px;border-bottom:.5px solid var(--border)}.search-input[data-v-a5a04da1]{width:100%;padding:10px 12px;border:1px solid var(--border);border-radius:var(--radius);background:var(--bg2);color:var(--text)}.tree-frame[data-v-a5a04da1]{flex:1;min-height:0;overflow:auto;padding:12px}[data-v-a5a04da1] .tree-canvas{display:inline-block;min-width:100%;width:max-content}[data-v-a5a04da1] .tree-node{margin-bottom:4px}[data-v-a5a04da1] .tree-row{display:grid;grid-template-columns:8px 18px minmax(0,1fr) auto;align-items:center;gap:6px;min-width:0;width:100%;padding:4px 8px;padding-left:calc(8px + (var(--tree-depth, 0) * 16px));border:1px solid transparent;border-radius:var(--radius);cursor:pointer;white-space:nowrap}[data-v-a5a04da1] .tree-row:hover{background:var(--bg2)}[data-v-a5a04da1] .tree-row.selected{background:color-mix(in srgb,var(--teal) 12%,var(--bg2));border-color:var(--teal)}[data-v-a5a04da1] .tree-row.hot{box-shadow:inset 2px 0 0 var(--red)}[data-v-a5a04da1] .tree-toggle{width:16px;height:16px;border:none;background:transparent;color:var(--text3);padding:0;font-size:14px;display:inline-flex;align-items:center;justify-content:center}[data-v-a5a04da1] .tree-toggle:disabled{cursor:default}[data-v-a5a04da1] .tree-toggle.empty{opacity:0}[data-v-a5a04da1] .tree-rail{display:block;width:2px;height:14px;border-radius:999px;background:color-mix(in srgb,var(--border) 75%,transparent)}[data-v-a5a04da1] .tree-copy{display:flex;align-items:center;min-width:0;gap:6px;overflow:hidden}[data-v-a5a04da1] .tree-name{font-size:12px;color:var(--text);min-width:0;overflow:hidden;text-overflow:ellipsis}[data-v-a5a04da1] .tree-badges{display:flex;gap:6px;flex-shrink:0;overflow:hidden}[data-v-a5a04da1] .tree-badge{border:1px solid var(--border);border-radius:999px;padding:2px 7px;font-size:10px;color:var(--text3);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:160px}[data-v-a5a04da1] .tree-metrics{display:flex;align-items:center;min-width:92px;justify-content:flex-end;flex-shrink:0;gap:6px}[data-v-a5a04da1] .tree-metric-pill{display:inline-flex;align-items:center;justify-content:center;min-width:78px;padding:2px 8px;border:1px solid var(--border);border-radius:999px;background:var(--bg2);font-size:10px;color:var(--text3)}[data-v-a5a04da1] .tree-persistent-pill{display:inline-flex;align-items:center;padding:2px 8px;border:1px solid color-mix(in srgb,var(--amber) 55%,var(--border));border-radius:999px;background:color-mix(in srgb,var(--amber) 10%,var(--bg2));font-size:10px;color:color-mix(in srgb,var(--amber) 80%,var(--text))}[data-v-a5a04da1] .tree-hydration-pill{display:inline-flex;align-items:center;padding:2px 8px;border:1px solid color-mix(in srgb,var(--teal) 55%,var(--border));border-radius:999px;background:color-mix(in srgb,var(--teal) 10%,var(--bg2));font-size:10px;color:color-mix(in srgb,var(--teal) 80%,var(--text))}[data-v-a5a04da1] .tree-children{margin-left:7px;padding-left:11px;border-left:1px solid color-mix(in srgb,var(--border) 72%,transparent)}.detail-empty[data-v-a5a04da1]{display:flex;align-items:center;justify-content:center;height:100%;color:var(--text3);font-size:12px}.detail-header[data-v-a5a04da1]{display:flex;align-items:center;justify-content:space-between}.meta-grid[data-v-a5a04da1]{display:grid;grid-template-columns:auto 1fr;gap:4px 12px}.detail-pill-row[data-v-a5a04da1]{display:flex;flex-wrap:wrap;gap:6px}.detail-pill[data-v-a5a04da1]{border:1px solid var(--border);border-radius:999px;padding:4px 8px;background:var(--bg2);font-size:11px}.detail-pill.hot[data-v-a5a04da1]{border-color:color-mix(in srgb,var(--red) 50%,var(--border));color:var(--red)}.detail-pill.persistent[data-v-a5a04da1]{border-color:color-mix(in srgb,var(--amber) 55%,var(--border));color:color-mix(in srgb,var(--amber) 80%,var(--text))}.detail-pill.hydrated[data-v-a5a04da1]{border-color:color-mix(in srgb,var(--teal) 55%,var(--border));color:color-mix(in srgb,var(--teal) 80%,var(--text))}.detail-pill.muted[data-v-a5a04da1]{color:var(--text3);border-color:var(--border)}.section-label[data-v-a5a04da1]{font-size:10px;font-weight:500;text-transform:uppercase;letter-spacing:.4px;color:var(--text3);margin-top:8px;margin-bottom:4px}.trigger-item[data-v-a5a04da1]{background:var(--bg2);border-radius:var(--radius);padding:4px 8px;margin-bottom:3px;color:var(--text2)}[data-v-a5a04da1] .tree-jump-btn{display:none;padding:0 4px;border:none;background:transparent;color:var(--text3);font-size:11px;cursor:pointer;line-height:1;flex-shrink:0}[data-v-a5a04da1] .tree-row:hover .tree-jump-btn,[data-v-a5a04da1] .tree-row.selected .tree-jump-btn{display:inline-flex}[data-v-a5a04da1] .tree-jump-btn:hover{color:var(--teal)}.jump-btn[data-v-a5a04da1]{font-size:10px;padding:1px 6px;border:.5px solid var(--border);border-radius:var(--radius);background:transparent;color:var(--text3);cursor:pointer;flex-shrink:0;font-family:var(--mono)}.jump-btn[data-v-a5a04da1]:hover{border-color:var(--teal);color:var(--teal);background:color-mix(in srgb,var(--teal) 8%,transparent)}.route-select[data-v-a5a04da1]{padding:3px 7px;border:.5px solid var(--border);border-radius:var(--radius);background:var(--bg2);color:var(--text);font-size:11px;cursor:pointer;max-width:140px}.timeline-list[data-v-a5a04da1]{display:flex;flex-direction:column;gap:1px;background:var(--bg2);border-radius:var(--radius);padding:4px 8px;max-height:200px;overflow-y:auto;min-height:fit-content}.timeline-row[data-v-a5a04da1]{display:flex;align-items:center;gap:6px;padding:2px 0;font-size:11px;border-bottom:.5px solid var(--border);min-width:0;min-height:fit-content}.timeline-row[data-v-a5a04da1]:last-child{border-bottom:none}.timeline-kind[data-v-a5a04da1]{flex-shrink:0;font-size:10px;font-weight:500;min-width:40px}.timeline-kind.mount[data-v-a5a04da1]{color:var(--teal)}.timeline-kind.update[data-v-a5a04da1]{color:var(--amber)}.timeline-time[data-v-a5a04da1]{flex-shrink:0;min-width:52px;color:var(--text3)}.timeline-dur[data-v-a5a04da1]{flex-shrink:0;min-width:38px;color:var(--text2)}.timeline-trigger[data-v-a5a04da1]{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--text3);flex:1;min-width:0}.timeline-route[data-v-a5a04da1]{flex-shrink:0;color:var(--text3);font-size:10px}@media(max-width:1180px){.inspector[data-v-a5a04da1]{grid-template-columns:minmax(200px,240px) minmax(0,1fr)}.detail-panel[data-v-a5a04da1]{grid-column:1 / -1;max-height:220px}}.timeline-root[data-v-da869dac]{display:flex;flex-direction:column;height:100%;overflow:hidden}.stats-row[data-v-da869dac]{display:flex;gap:10px;padding:12px 14px 0;flex-shrink:0}.stat-card[data-v-da869dac]{background:var(--bg2);border:.5px solid var(--border);border-radius:var(--radius);padding:8px 14px;min-width:72px;text-align:center}.stat-val[data-v-da869dac]{font-size:20px;font-weight:600;font-family:var(--mono);line-height:1.1}.stat-unit[data-v-da869dac]{font-size:12px;opacity:.6;margin-left:1px}.stat-label[data-v-da869dac]{font-size:10px;color:var(--text3);margin-top:2px;text-transform:uppercase;letter-spacing:.4px}.toolbar[data-v-da869dac]{display:flex;align-items:center;gap:8px;padding:10px 14px;flex-shrink:0;border-bottom:.5px solid var(--border)}.search-input[data-v-da869dac]{flex:1;max-width:260px}.filter-group[data-v-da869dac]{display:flex;gap:4px}.content-area[data-v-da869dac]{display:flex;flex:1;overflow:hidden;min-height:0}.table-pane[data-v-da869dac]{flex:1;overflow:hidden auto;min-width:0}.bar-cell[data-v-da869dac]{width:200px;padding:4px 8px}.bar-track[data-v-da869dac]{position:relative;height:8px;background:var(--bg2);border-radius:4px;overflow:hidden}.bar-fill[data-v-da869dac]{position:absolute;top:0;height:100%;min-width:3px;border-radius:4px;transition:width .15s}.detail-panel[data-v-da869dac]{width:260px;flex-shrink:0;border-left:.5px solid var(--border);overflow-y:auto;background:var(--bg3);padding:0 0 16px}.panel-header[data-v-da869dac]{display:flex;align-items:center;justify-content:space-between;padding:10px 14px 8px;border-bottom:.5px solid var(--border);position:sticky;top:0;background:var(--bg3);z-index:1}.panel-title[data-v-da869dac]{font-family:var(--mono);font-size:13px;font-weight:500}.close-btn[data-v-da869dac]{border:none;background:transparent;color:var(--text3);font-size:11px;padding:2px 6px;cursor:pointer}.panel-section[data-v-da869dac]{padding:10px 14px 6px;border-bottom:.5px solid var(--border)}.panel-section-title[data-v-da869dac]{font-size:10px;font-weight:500;color:var(--text3);text-transform:uppercase;letter-spacing:.4px;margin-bottom:8px}.panel-row[data-v-da869dac]{display:flex;justify-content:space-between;align-items:center;gap:8px;padding:3px 0;font-size:12px}.panel-key[data-v-da869dac]{color:var(--text3);flex-shrink:0}.panel-val[data-v-da869dac]{color:var(--text);text-align:right;word-break:break-all}.cancel-notice[data-v-da869dac],.active-notice[data-v-da869dac]{margin:10px 14px 0;font-size:11px;line-height:1.6;padding:8px 10px;border-radius:var(--radius)}.cancel-notice[data-v-da869dac]{background:#e24b4a1a;color:var(--red);border:.5px solid rgb(226 75 74 / 30%)}.active-notice[data-v-da869dac]{background:#7f77dd1a;color:var(--purple);border:.5px solid rgb(127 119 221 / 30%)}code[data-v-da869dac]{font-family:var(--mono);font-size:10px;background:#00000026;padding:1px 4px;border-radius:3px}.panel-slide-enter-active[data-v-da869dac],.panel-slide-leave-active[data-v-da869dac]{transition:transform .18s ease,opacity .18s ease}.panel-slide-enter-from[data-v-da869dac],.panel-slide-leave-to[data-v-da869dac]{transform:translate(12px);opacity:0}#app-root[data-v-12fe13bc]{display:flex;flex-direction:column;height:100vh;overflow:hidden}.tabbar[data-v-12fe13bc]{display:flex;align-items:center;gap:2px;padding:8px 12px 0;border-bottom:.5px solid var(--border);background:var(--bg3);flex-shrink:0}.tabbar-brand[data-v-12fe13bc]{font-size:11px;font-weight:500;color:var(--purple);letter-spacing:.5px;margin-right:12px;padding-bottom:8px}.tab-btn[data-v-12fe13bc]{border:none;border-bottom:2px solid transparent;border-radius:0;background:transparent;color:var(--text3);font-size:12px;padding:6px 12px 8px;cursor:pointer;transition:color .12s,border-color .12s;display:flex;align-items:center;gap:5px}.tab-btn[data-v-12fe13bc]:hover{color:var(--text);background:transparent}.tab-btn.active[data-v-12fe13bc]{color:var(--purple);border-bottom-color:var(--purple)}.tab-icon[data-v-12fe13bc]{font-size:10px;opacity:.6}.tab-content[data-v-12fe13bc]{flex:1;overflow:hidden;display:flex;flex-direction:column}*{box-sizing:border-box;margin:0;padding:0}body{font-family:var(--font);background:var(--bg);color:var(--text);font-size:13px;line-height:1.5}::-webkit-scrollbar{width:6px;height:6px}::-webkit-scrollbar-track{background:transparent}::-webkit-scrollbar-thumb{background:var(--border);border-radius:3px}button{font-family:var(--font);font-size:12px;cursor:pointer;border:.5px solid var(--border);background:transparent;color:var(--text2);padding:4px 10px;border-radius:var(--radius);transition:background .12s}button:hover{background:var(--bg2)}button:active{transform:scale(.98)}button.active{background:#7f77dd26;color:var(--purple);border-color:var(--purple)}button.danger-active{background:#e24b4a1f;color:var(--red);border-color:var(--red)}button.success-active{background:#1d9e751f;color:var(--teal);border-color:var(--teal)}input[type=text],input[type=search]{font-family:var(--font);font-size:12px;border:.5px solid var(--border);background:var(--bg2);color:var(--text);padding:5px 10px;border-radius:var(--radius);outline:none;width:100%}input[type=text]:focus,input[type=search]:focus{border-color:var(--purple);box-shadow:0 0 0 2px #7f77dd33}input[type=range]{accent-color:var(--purple);cursor:pointer}.mono{font-family:var(--mono)}.muted{color:var(--text3)}.text-sm{font-size:11px}.text-xs{font-size:10px}.bold{font-weight:500}.badge{display:inline-block;font-size:10px;font-weight:500;padding:2px 7px;border-radius:99px;white-space:nowrap}.badge-ok{background:#1d9e7526;color:var(--teal)}.badge-err{background:#e24b4a1f;color:var(--red)}.badge-warn{background:#ef9f2726;color:var(--amber)}.badge-info{background:#378add1f;color:var(--blue)}.badge-gray{background:var(--bg2);color:var(--text3);border:.5px solid var(--border)}.badge-purple{background:#7f77dd26;color:var(--purple)}.card{background:var(--bg3);border:.5px solid var(--border);border-radius:var(--radius-lg);padding:12px 14px}.data-table{width:100%;border-collapse:collapse;font-size:12px}.data-table th{text-align:left;font-size:10px;font-weight:500;color:var(--text3);padding:6px 8px;border-bottom:.5px solid var(--border);text-transform:uppercase;letter-spacing:.4px;white-space:nowrap}.data-table td{padding:8px;border-bottom:.5px solid var(--border);color:var(--text);vertical-align:middle}.data-table tr:hover td{background:var(--bg2);cursor:pointer}.data-table tr.selected td{background:#7f77dd14}.stat-card{background:var(--bg2);border-radius:var(--radius);padding:10px 12px}.stat-label{font-size:10px;color:var(--text3);text-transform:uppercase;letter-spacing:.4px;margin-bottom:3px}.stat-val{font-size:20px;font-weight:500}.flex{display:flex}.items-center{align-items:center}.gap-2{gap:8px}.gap-3{gap:12px}.flex-1{flex:1}.overflow-auto{overflow:auto}.p-3{padding:12px}.p-4{padding:16px}.mb-2{margin-bottom:8px}.mb-3{margin-bottom:12px}.mt-2{margin-top:8px}