@oh-my-pi-zen/omp-stats 16.3.6-zen.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (120) hide show
  1. package/CHANGELOG.md +197 -0
  2. package/README.md +82 -0
  3. package/build.ts +95 -0
  4. package/dist/client/index.css +1 -0
  5. package/dist/client/index.html +24 -0
  6. package/dist/client/index.js +257 -0
  7. package/dist/client/styles.css +1656 -0
  8. package/dist/types/aggregator.d.ts +87 -0
  9. package/dist/types/client/App.d.ts +1 -0
  10. package/dist/types/client/api.d.ts +21 -0
  11. package/dist/types/client/app/AppLayout.d.ts +16 -0
  12. package/dist/types/client/app/NavRail.d.ts +7 -0
  13. package/dist/types/client/app/RangeControl.d.ts +7 -0
  14. package/dist/types/client/app/SyncButton.d.ts +14 -0
  15. package/dist/types/client/app/ThemeToggle.d.ts +1 -0
  16. package/dist/types/client/app/TopBar.d.ts +15 -0
  17. package/dist/types/client/app/routes.d.ts +12 -0
  18. package/dist/types/client/components/AgentTokenShare.d.ts +5 -0
  19. package/dist/types/client/components/chart-shared.d.ts +173 -0
  20. package/dist/types/client/components/models-table-shared.d.ts +175 -0
  21. package/dist/types/client/components/range-meta.d.ts +21 -0
  22. package/dist/types/client/data/charts.d.ts +1 -0
  23. package/dist/types/client/data/formatters.d.ts +8 -0
  24. package/dist/types/client/data/useHashRoute.d.ts +8 -0
  25. package/dist/types/client/data/useResource.d.ts +13 -0
  26. package/dist/types/client/data/view-models.d.ts +67 -0
  27. package/dist/types/client/index.d.ts +2 -0
  28. package/dist/types/client/routes/BehaviorRoute.d.ts +7 -0
  29. package/dist/types/client/routes/CostsRoute.d.ts +7 -0
  30. package/dist/types/client/routes/ErrorsRoute.d.ts +8 -0
  31. package/dist/types/client/routes/GainRoute.d.ts +7 -0
  32. package/dist/types/client/routes/ModelsRoute.d.ts +7 -0
  33. package/dist/types/client/routes/OverviewRoute.d.ts +8 -0
  34. package/dist/types/client/routes/ProjectsRoute.d.ts +7 -0
  35. package/dist/types/client/routes/RequestsRoute.d.ts +8 -0
  36. package/dist/types/client/routes/ToolsRoute.d.ts +7 -0
  37. package/dist/types/client/routes/index.d.ts +9 -0
  38. package/dist/types/client/types.d.ts +63 -0
  39. package/dist/types/client/ui/AsyncBoundary.d.ts +12 -0
  40. package/dist/types/client/ui/DataTable.d.ts +17 -0
  41. package/dist/types/client/ui/EmptyState.d.ts +7 -0
  42. package/dist/types/client/ui/ErrorState.d.ts +6 -0
  43. package/dist/types/client/ui/JsonBlock.d.ts +7 -0
  44. package/dist/types/client/ui/MetricCluster.d.ts +5 -0
  45. package/dist/types/client/ui/Panel.d.ts +7 -0
  46. package/dist/types/client/ui/RequestDrawer.d.ts +5 -0
  47. package/dist/types/client/ui/SegmentedControl.d.ts +12 -0
  48. package/dist/types/client/ui/Skeleton.d.ts +8 -0
  49. package/dist/types/client/ui/StatusPill.d.ts +7 -0
  50. package/dist/types/client/ui/index.d.ts +11 -0
  51. package/dist/types/client/useSystemTheme.d.ts +11 -0
  52. package/dist/types/db.d.ts +144 -0
  53. package/dist/types/embedded-client.d.ts +18 -0
  54. package/dist/types/gain-aggregator.d.ts +26 -0
  55. package/dist/types/index.d.ts +7 -0
  56. package/dist/types/parser.d.ts +53 -0
  57. package/dist/types/server.d.ts +7 -0
  58. package/dist/types/shared-types.d.ts +301 -0
  59. package/dist/types/sync-worker.d.ts +31 -0
  60. package/dist/types/types.d.ts +164 -0
  61. package/dist/types/user-metrics.d.ts +72 -0
  62. package/package.json +95 -0
  63. package/src/aggregator.ts +501 -0
  64. package/src/client/App.tsx +109 -0
  65. package/src/client/api.ts +102 -0
  66. package/src/client/app/AppLayout.tsx +93 -0
  67. package/src/client/app/NavRail.tsx +44 -0
  68. package/src/client/app/RangeControl.tsx +39 -0
  69. package/src/client/app/SyncButton.tsx +75 -0
  70. package/src/client/app/ThemeToggle.tsx +37 -0
  71. package/src/client/app/TopBar.tsx +73 -0
  72. package/src/client/app/routes.ts +69 -0
  73. package/src/client/components/AgentTokenShare.tsx +68 -0
  74. package/src/client/components/chart-shared.tsx +257 -0
  75. package/src/client/components/models-table-shared.tsx +255 -0
  76. package/src/client/components/range-meta.ts +73 -0
  77. package/src/client/css.d.ts +1 -0
  78. package/src/client/data/charts.ts +14 -0
  79. package/src/client/data/formatters.ts +45 -0
  80. package/src/client/data/useHashRoute.ts +87 -0
  81. package/src/client/data/useResource.ts +154 -0
  82. package/src/client/data/view-models.ts +251 -0
  83. package/src/client/index.tsx +10 -0
  84. package/src/client/routes/BehaviorRoute.tsx +623 -0
  85. package/src/client/routes/CostsRoute.tsx +234 -0
  86. package/src/client/routes/ErrorsRoute.tsx +118 -0
  87. package/src/client/routes/GainRoute.tsx +226 -0
  88. package/src/client/routes/ModelsRoute.tsx +430 -0
  89. package/src/client/routes/OverviewRoute.tsx +342 -0
  90. package/src/client/routes/ProjectsRoute.tsx +163 -0
  91. package/src/client/routes/RequestsRoute.tsx +123 -0
  92. package/src/client/routes/ToolsRoute.tsx +463 -0
  93. package/src/client/routes/index.ts +9 -0
  94. package/src/client/styles.css +1330 -0
  95. package/src/client/types.ts +80 -0
  96. package/src/client/ui/AsyncBoundary.tsx +54 -0
  97. package/src/client/ui/DataTable.tsx +122 -0
  98. package/src/client/ui/EmptyState.tsx +16 -0
  99. package/src/client/ui/ErrorState.tsx +25 -0
  100. package/src/client/ui/JsonBlock.tsx +75 -0
  101. package/src/client/ui/MetricCluster.tsx +67 -0
  102. package/src/client/ui/Panel.tsx +24 -0
  103. package/src/client/ui/RequestDrawer.tsx +208 -0
  104. package/src/client/ui/SegmentedControl.tsx +36 -0
  105. package/src/client/ui/Skeleton.tsx +17 -0
  106. package/src/client/ui/StatusPill.tsx +15 -0
  107. package/src/client/ui/index.ts +11 -0
  108. package/src/client/useSystemTheme.ts +87 -0
  109. package/src/db.ts +1530 -0
  110. package/src/embedded-client.generated.txt +0 -0
  111. package/src/embedded-client.ts +26 -0
  112. package/src/gain-aggregator.ts +281 -0
  113. package/src/index.ts +194 -0
  114. package/src/parser.ts +450 -0
  115. package/src/server.ts +352 -0
  116. package/src/shared-types.ts +323 -0
  117. package/src/sync-worker.ts +40 -0
  118. package/src/types.ts +171 -0
  119. package/src/user-metrics.ts +685 -0
  120. package/tailwind.config.js +40 -0
package/CHANGELOG.md ADDED
@@ -0,0 +1,197 @@
1
+ # Changelog
2
+
3
+ ## [Unreleased]
4
+
5
+ ## [16.3.1] - 2026-07-02
6
+
7
+ ### Added
8
+
9
+ - Added a Tools tab to the `omp stats` dashboard (`/#/tools`): per-tool call counts, error rates, result/argument payload sizes, per-model breakdown, and a stacked calls-over-time chart. Token and cost columns attribute each invoking turn's real provider usage evenly across that turn's tool calls. Existing databases re-parse sessions once on the next sync to backfill historical tool calls.
10
+
11
+ ## [16.2.7] - 2026-06-30
12
+
13
+ ### Fixed
14
+
15
+ - Improved premium request calculation accuracy by correctly accounting for specific model families.
16
+
17
+ ## [16.2.6] - 2026-06-29
18
+
19
+ ### Fixed
20
+
21
+ - Fixed application crashes and Bun aborts on macOS and when parsing large stats session files, including during `omp --smoke-test` runs, by utilizing a more resilient serial parser and lenient line scanner.
22
+
23
+ ## [16.2.3] - 2026-06-28
24
+
25
+ ### Added
26
+
27
+ - Support for parsing named advisor transcripts using the `__advisor.<slug>.jsonl` naming convention.
28
+
29
+ ## [16.2.0] - 2026-06-27
30
+
31
+ ### Added
32
+
33
+ - Added a Gain tab to the `omp stats` dashboard (`/#/gain`) to display snapcompact token-savings with project scoping from synced session folders.
34
+
35
+ ## [16.1.17] - 2026-06-24
36
+
37
+ ### Fixed
38
+
39
+ - Stats sync counted the same provider request multiple times when a forked or branched session file copied the parent's entries verbatim. Inserts now skip rows whose `(entry_id, timestamp)` already exists under a different `session_file`, and a one-shot migration on the next `omp stats` run collapses any pre-existing duplicates ([#3370](https://github.com/cagedbird043/oh-my-pi-zen/issues/3370)).
40
+
41
+ ## [16.1.15] - 2026-06-22
42
+
43
+ ### Added
44
+
45
+ - Added token usage breakdown by agent type (Main, Subagents, Advisor) to the overview dashboard
46
+
47
+ ## [16.0.10] - 2026-06-18
48
+
49
+ ### Changed
50
+
51
+ - Updated description of moderated content categories to use more inclusive terminology
52
+
53
+ ### Fixed
54
+
55
+ - Wide data tables (Requests, Errors, Overview, Projects) overflowed the page horizontally at narrow-desktop widths (768-1023px): the `.stats-table-desktop-only` wrapper used for mobile-card tables lacked the `overflow-x: auto` containment that `.stats-table-container` already has. They now scroll within their own bounds instead of spilling the page body.
56
+
57
+ ## [16.0.5] - 2026-06-17
58
+
59
+ ### Added
60
+
61
+ - New Projects view summarizing usage, cost, and reliability per project folder (backed by the existing `/api/stats/folders` endpoint).
62
+ - System-aware light/dark theme toggle — follows the OS by default, and an explicit choice persists across reloads.
63
+
64
+ ### Changed
65
+
66
+ - Redesigned the local stats dashboard with an OMP-themed product shell, dedicated per-section views, accessible loading/empty/error states, and flicker-free navigation between screens and time ranges.
67
+
68
+ ### Fixed
69
+
70
+ - The 1h time-range chart rendered an empty/single-point line; it now buckets at 5-minute granularity for a real trend.
71
+
72
+ ## [15.13.3] - 2026-06-15
73
+
74
+ ### Changed
75
+
76
+ - Renamed `__omp_stats_sync_worker` to `__omp_worker_stats_sync`.
77
+
78
+ ## [15.13.1] - 2026-06-15
79
+
80
+ ### Fixed
81
+
82
+ - Dropped `git` from the profanity list so normal repository mentions no longer count as profanity
83
+
84
+ ## [15.12.4] - 2026-06-13
85
+
86
+ ### Fixed
87
+
88
+ - Fixed the stats dashboard's SQLite init never setting `PRAGMA busy_timeout`, so a concurrent `omp` startup hitting WAL recovery could crash `initDb()` with `SQLITE_BUSY` instead of waiting through it. The busy handler is now installed before `PRAGMA journal_mode=WAL` ([#2421](https://github.com/cagedbird043/oh-my-pi-zen/issues/2421)).
89
+
90
+ ## [15.11.0] - 2026-06-10
91
+
92
+ ### Added
93
+
94
+ - Added support for prebuilt npm bundle mode via `PI_BUNDLED`, allowing the stats server to use an embedded dashboard bundle in packaged CLI distributions
95
+
96
+ ### Fixed
97
+
98
+ - Fixed handling of legacy `embedded-client.generated.txt` placeholder content so it is treated as missing archive instead of being decoded into invalid bytes
99
+ - Fixed ENOENT handling while scanning dashboard source/build directories so missing `client/` or `dist/client` trees no longer crash startup
100
+
101
+ ## [15.10.11] - 2026-06-10
102
+
103
+ ### Changed
104
+
105
+ - Bundled-model lookups (`getBundledModel`, `GeneratedProvider`) now import from the new `@oh-my-pi-zen/pi-catalog` package instead of the `@oh-my-pi-zen/pi-ai` barrel, which no longer re-exports catalog values
106
+ - The session-sync worker re-enters the host CLI entry (`workerHostEntry()` + `__omp_stats_sync_worker` argv selector) when running inside omp — source, npm bundle, or compiled binary — and keeps loading its own `sync-worker.ts` module directly for standalone `omp-stats`, bun test, and SDK hosts
107
+
108
+ ## [15.1.6] - 2026-05-19
109
+
110
+ ### Fixed
111
+
112
+ - Fixed `omp stats` crashing on first session sync in published `omp-{linux,darwin,windows}-*` binaries with `BuildMessage: ModuleNotFound resolving "./packages/stats/src/sync-worker.ts"`; the release build script now lists the stats sync, browser tab, and JS eval workers as explicit `--compile` entrypoints so Bun emits them into bunfs, matching the dev build script and the AGENTS.md worker spawn contract. ([#1150](https://github.com/cagedbird043/oh-my-pi-zen/issues/1150))
113
+
114
+ ## [15.1.0] - 2026-05-15
115
+
116
+ ### Fixed
117
+
118
+ - Fixed incremental `parseSessionFile(path, fromOffset)` losing the active service tier when resuming past a `service_tier_change` entry, so priority OpenAI replies appended after the offset are now credited with `premiumRequests: 1` (regression introduced by 13f59162e which stopped folding priority-tier into per-message premium counts)
119
+
120
+ ## [15.0.1] - 2026-05-14
121
+
122
+ ### Breaking Changes
123
+
124
+ - Raised the minimum required Bun version to >=1.3.14 in package metadata
125
+
126
+ ### Changed
127
+
128
+ - Changed the "Premium Reqs" dashboard card to also include OpenAI priority service-tier requests (`serviceTier: "priority"`), counting each as 1 premium request alongside GitHub Copilot premium calls. Pre-existing sessions are backfilled on the next `omp stats` run: a one-shot `premium_requests_priority_v1` sentinel wipes `file_offsets` so every session re-parses, and `insertMessageStats` now `UPSERT`s `premium_requests` (other columns untouched) using the `service_tier_change` entries already in the session log to retroactively credit priority traffic.
129
+
130
+ ## [14.9.9] - 2026-05-12
131
+
132
+ ### Added
133
+
134
+ - Added separate input-token and output-token totals to the overview dashboard cards.
135
+
136
+ ### Fixed
137
+
138
+ - Fixed `omp stats` in compiled binaries by using the serial sync path instead of spawning a raw file-asset worker that cannot import bundled parser code.
139
+ - Fixed behavior backfills after failed compiled-binary sync attempts by marking the backfill sentinel only after a successful full sync.
140
+
141
+ ## [14.9.7] - 2026-05-12
142
+
143
+ ### Breaking Changes
144
+
145
+ - Broke backward compatibility of behavior stats fields by replacing `yellingSentences`/`dramaRuns` with `yelling`/`anguish` and adding `negation`, `repetition`, `blame` in query result types and persisted `user_messages` schema
146
+
147
+ ### Added
148
+
149
+ - Added `SyncOptions` to `syncAllSessions` with `onProgress` and `workers` to optionally show per-file sync progress and tune parser concurrency
150
+ - Added new frustration behavior metrics (`negation`, `repetition`, `blame`) plus a `frustration` aggregate in behavior charts, model tables, and summary cards
151
+
152
+ ### Changed
153
+
154
+ - Changed sync ingestion to parse session files through a worker pool while applying parsed results and database writes on the main thread
155
+ - Changed behavior analysis to strip code blocks, XML/URLs, quoted lines, and placeholders before scoring and to suppress signals on long structured messages
156
+ - Changed dashboard metrics labels and totals to the new signal names, including replacing the old three-signal totals with `yelling`, `profanity`, `anguish`, and `frustration`
157
+ - Changed sync output to print a live terminal progress indicator while processing session files
158
+
159
+ ### Fixed
160
+
161
+ - Fixed user-message attribution so assistant model/provider links are backfilled during incremental sync instead of being left unknown
162
+ - Fixed word-boundary regex handling in profanity detection so matching now works as intended in normal prose
163
+
164
+ ## [14.9.5] - 2026-05-12
165
+
166
+ ### Added
167
+
168
+ - Added time range selection options (1h, 24h, 7d, 30d, 90d, All) to the dashboard header and bound them to reloading statistics for the selected window
169
+ - Added a **Behavior** dashboard page that tracks user yelling (CAPS), profanity, and dramatic punctuation (`!!!` / `???`) per day, with by-model comparisons mirroring the cost page
170
+ - Added a per-model behavior table to the **Behavior** page mirroring the Models table: sortable rows of CAPS / profanity / drama hits per model with sparkline trend and an expandable per-model breakdown chart
171
+ - Added optional `range` query parameter support on stats endpoints to retrieve metrics scoped to a requested time window
172
+
173
+ ### Changed
174
+
175
+ - Changed the Costs dashboard summary to report totals, average per day, and top model for the selected time range instead of a fixed 30-day window and removed the previous-30-day trend comparison
176
+ - Changed behavior metrics ingestion to compute yelling from user message sentence-level uppercase ratios, filtering out short uppercase fragments so the behavior data is attributed to messages more accurately
177
+ - Removed per-chart 14/30/90 day pickers on Costs and Behavior pages so every page obeys the single time-range selector in the header
178
+ - Changed dashboard and stats queries to return data from the selected time window instead of always using all-time aggregates
179
+ - Changed the default displayed range in the UI/API to last 24h
180
+ - Added support for returning all data when `range=all` is requested
181
+
182
+ ### Fixed
183
+
184
+ - Fixed handling of unknown `range` values by falling back to the last 24h instead of returning unscoped data
185
+ - Fixed `omp stats` failing to build the client on globally-installed installs by promoting `tailwindcss` from `devDependencies` to `dependencies` (the client build runs at runtime)
186
+
187
+ ## [14.5.4] - 2026-04-28
188
+
189
+ ### Fixed
190
+
191
+ - Fixed GPT cost reporting by deriving missing OpenAI Codex costs from the model catalog and backfilling existing zero-cost rows.
192
+
193
+ ## [13.6.0] - 2026-03-03
194
+
195
+ ### Fixed
196
+
197
+ - Include subtask session files in usage stats ([#250](https://github.com/cagedbird043/oh-my-pi-zen/issues/250))
package/README.md ADDED
@@ -0,0 +1,82 @@
1
+ # @oh-my-pi-zen/omp-stats
2
+
3
+ Local observability dashboard for AI usage statistics.
4
+
5
+ ## Features
6
+
7
+ - **Session log parsing**: Reads JSONL session logs from `~/.omp/agent/sessions/`
8
+ - **SQLite aggregation**: Efficient stats storage and querying using `bun:sqlite`
9
+ - **Web dashboard**: Real-time metrics visualization with Chart.js
10
+ - **Incremental sync**: Only processes new/modified log entries
11
+
12
+ ## Metrics Tracked
13
+
14
+ | Metric | Calculation |
15
+ |--------|-------------|
16
+ | Tokens/s | `output_tokens / (duration / 1000)` |
17
+ | Cache Rate | `cache_read / (input + cache_read) * 100` |
18
+ | Error Rate | `count(stopReason=error) / total_calls * 100` |
19
+ | Total Cost | Sum of `usage.cost.total` |
20
+ | Avg Latency | Mean of `duration` |
21
+ | TTFT | Mean of `ttft` (time to first token) |
22
+
23
+ ## Usage
24
+
25
+ ### Via CLI
26
+
27
+ ```bash
28
+ # Start dashboard server (default: http://localhost:3847)
29
+ omp stats
30
+
31
+ # Custom port
32
+ omp stats --port 8080
33
+
34
+ # Print summary to console
35
+ omp stats --summary
36
+
37
+ # Output as JSON (for scripting)
38
+ omp stats --json
39
+ ```
40
+
41
+ ### Programmatic
42
+
43
+ ```typescript
44
+ import { getDashboardStats, syncAllSessions } from "@oh-my-pi-zen/omp-stats";
45
+
46
+ // Sync session logs to database
47
+ const { processed, files } = await syncAllSessions();
48
+
49
+ // Get aggregated stats
50
+ const stats = await getDashboardStats();
51
+ console.log(stats.overall.totalCost);
52
+ console.log(stats.byModel[0].avgTokensPerSecond);
53
+ ```
54
+
55
+ ## API Endpoints
56
+
57
+ | Endpoint | Description |
58
+ |----------|-------------|
59
+ | `GET /api/stats` | Overall stats with all breakdowns |
60
+ | `GET /api/stats/models` | Per-model statistics |
61
+ | `GET /api/stats/folders` | Per-folder/project statistics |
62
+ | `GET /api/stats/timeseries` | Hourly time series data |
63
+ | `GET /api/sync` | Trigger sync and return counts |
64
+
65
+ ## Data Storage
66
+
67
+ - **Session logs**: `~/.omp/agent/sessions/` (JSONL files)
68
+ - **Stats database**: `~/.omp/stats.db` (SQLite)
69
+
70
+ ## Dashboard
71
+
72
+ The web dashboard provides:
73
+
74
+ - Overall metrics cards (requests, cost, cache rate, error rate, duration, tokens/s)
75
+ - Time series chart showing requests and errors over time
76
+ - Per-model breakdown table
77
+ - Per-folder breakdown table
78
+ - Auto-refresh every 30 seconds
79
+
80
+ ## License
81
+
82
+ MIT
package/build.ts ADDED
@@ -0,0 +1,95 @@
1
+ import * as fs from "node:fs/promises";
2
+ import * as path from "node:path";
3
+ import { compile } from "@tailwindcss/node";
4
+
5
+ /**
6
+ * Extract Tailwind class names from source files by scanning for className attributes.
7
+ */
8
+ async function extractTailwindClasses(dir: string): Promise<Set<string>> {
9
+ const classes = new Set<string>();
10
+ const classPattern = /className\s*=\s*["'`]([^"'`]+)["'`]/g;
11
+
12
+ async function scanDir(currentDir: string): Promise<void> {
13
+ const entries = await fs.readdir(currentDir, { withFileTypes: true });
14
+ for (const entry of entries) {
15
+ const fullPath = path.join(currentDir, entry.name);
16
+ if (entry.isDirectory()) {
17
+ await scanDir(fullPath);
18
+ } else if (entry.isFile() && /\.(tsx|ts|jsx|js)$/.test(entry.name)) {
19
+ const content = await Bun.file(fullPath).text();
20
+ const matches = content.matchAll(classPattern);
21
+ for (const match of matches) {
22
+ for (const cls of match[1].split(/\s+/)) {
23
+ if (cls) classes.add(cls);
24
+ }
25
+ }
26
+ }
27
+ }
28
+ }
29
+
30
+ await scanDir(dir);
31
+ return classes;
32
+ }
33
+
34
+ // Clean dist
35
+ await fs.rm("./dist/client", { recursive: true, force: true });
36
+
37
+ // Build Tailwind CSS
38
+ console.log("Building Tailwind CSS...");
39
+ const sourceCss = await Bun.file("./src/client/styles.css").text();
40
+ const candidates = await extractTailwindClasses("./src/client");
41
+ const baseDir = path.resolve("./src/client");
42
+
43
+ const compiler = await compile(sourceCss, {
44
+ base: baseDir,
45
+ onDependency: () => {},
46
+ });
47
+ const tailwindOutput = compiler.build([...candidates]);
48
+ await Bun.write("./dist/client/styles.css", tailwindOutput);
49
+
50
+ // Build React app
51
+ console.log("Building React app...");
52
+ const result = await Bun.build({
53
+ entrypoints: ["./src/client/index.tsx"],
54
+ outdir: "./dist/client",
55
+ minify: true,
56
+ naming: "[dir]/[name].[ext]",
57
+ });
58
+
59
+ if (!result.success) {
60
+ console.error("Build failed");
61
+ for (const message of result.logs) {
62
+ console.error(message);
63
+ }
64
+ process.exit(1);
65
+ }
66
+
67
+ // Create index.html
68
+ const indexHtml = `<!DOCTYPE html>
69
+ <html lang="en">
70
+ <head>
71
+ <meta charset="UTF-8">
72
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
73
+ <title>AI Usage Statistics</title>
74
+ <script>
75
+ (function () {
76
+ try {
77
+ var stored = localStorage.getItem("omp-stats-theme");
78
+ var system = matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
79
+ var theme = stored === "light" || stored === "dark" ? stored : system;
80
+ document.documentElement.dataset.theme = theme;
81
+ document.documentElement.style.colorScheme = theme;
82
+ } catch (e) {}
83
+ })();
84
+ </script>
85
+ <link rel="stylesheet" href="styles.css">
86
+ </head>
87
+ <body>
88
+ <div id="root"></div>
89
+ <script src="index.js" type="module"></script>
90
+ </body>
91
+ </html>`;
92
+
93
+ await Bun.write("./dist/client/index.html", indexHtml);
94
+
95
+ console.log("Build complete");
@@ -0,0 +1 @@
1
+ @layer theme,base,components,utilities;@layer theme{@theme default{--font-sans: ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji"; --font-serif: ui-serif,Georgia,Cambria,"Times New Roman",Times,serif; --font-mono: ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace; --color-red-50: oklch(97.1% .013 17.38); --color-red-100: oklch(93.6% .032 17.717); --color-red-200: oklch(88.5% .062 18.334); --color-red-300: oklch(80.8% .114 19.571); --color-red-400: oklch(70.4% .191 22.216); --color-red-500: oklch(63.7% .237 25.331); --color-red-600: oklch(57.7% .245 27.325); --color-red-700: oklch(50.5% .213 27.518); --color-red-800: oklch(44.4% .177 26.899); --color-red-900: oklch(39.6% .141 25.723); --color-red-950: oklch(25.8% .092 26.042); --color-orange-50: oklch(98% .016 73.684); --color-orange-100: oklch(95.4% .038 75.164); --color-orange-200: oklch(90.1% .076 70.697); --color-orange-300: oklch(83.7% .128 66.29); --color-orange-400: oklch(75% .183 55.934); --color-orange-500: oklch(70.5% .213 47.604); --color-orange-600: oklch(64.6% .222 41.116); --color-orange-700: oklch(55.3% .195 38.402); --color-orange-800: oklch(47% .157 37.304); --color-orange-900: oklch(40.8% .123 38.172); --color-orange-950: oklch(26.6% .079 36.259); --color-amber-50: oklch(98.7% .022 95.277); --color-amber-100: oklch(96.2% .059 95.617); --color-amber-200: oklch(92.4% .12 95.746); --color-amber-300: oklch(87.9% .169 91.605); --color-amber-400: oklch(82.8% .189 84.429); --color-amber-500: oklch(76.9% .188 70.08); --color-amber-600: oklch(66.6% .179 58.318); --color-amber-700: oklch(55.5% .163 48.998); --color-amber-800: oklch(47.3% .137 46.201); --color-amber-900: oklch(41.4% .112 45.904); --color-amber-950: oklch(27.9% .077 45.635); --color-yellow-50: oklch(98.7% .026 102.212); --color-yellow-100: oklch(97.3% .071 103.193); --color-yellow-200: oklch(94.5% .129 101.54); --color-yellow-300: oklch(90.5% .182 98.111); --color-yellow-400: oklch(85.2% .199 91.936); --color-yellow-500: oklch(79.5% .184 86.047); --color-yellow-600: oklch(68.1% .162 75.834); --color-yellow-700: oklch(55.4% .135 66.442); --color-yellow-800: oklch(47.6% .114 61.907); --color-yellow-900: oklch(42.1% .095 57.708); --color-yellow-950: oklch(28.6% .066 53.813); --color-lime-50: oklch(98.6% .031 120.757); --color-lime-100: oklch(96.7% .067 122.328); --color-lime-200: oklch(93.8% .127 124.321); --color-lime-300: oklch(89.7% .196 126.665); --color-lime-400: oklch(84.1% .238 128.85); --color-lime-500: oklch(76.8% .233 130.85); --color-lime-600: oklch(64.8% .2 131.684); --color-lime-700: oklch(53.2% .157 131.589); --color-lime-800: oklch(45.3% .124 130.933); --color-lime-900: oklch(40.5% .101 131.063); --color-lime-950: oklch(27.4% .072 132.109); --color-green-50: oklch(98.2% .018 155.826); --color-green-100: oklch(96.2% .044 156.743); --color-green-200: oklch(92.5% .084 155.995); --color-green-300: oklch(87.1% .15 154.449); --color-green-400: oklch(79.2% .209 151.711); --color-green-500: oklch(72.3% .219 149.579); --color-green-600: oklch(62.7% .194 149.214); --color-green-700: oklch(52.7% .154 150.069); --color-green-800: oklch(44.8% .119 151.328); --color-green-900: oklch(39.3% .095 152.535); --color-green-950: oklch(26.6% .065 152.934); --color-emerald-50: oklch(97.9% .021 166.113); --color-emerald-100: oklch(95% .052 163.051); --color-emerald-200: oklch(90.5% .093 164.15); --color-emerald-300: oklch(84.5% .143 164.978); --color-emerald-400: oklch(76.5% .177 163.223); --color-emerald-500: oklch(69.6% .17 162.48); --color-emerald-600: oklch(59.6% .145 163.225); --color-emerald-700: oklch(50.8% .118 165.612); --color-emerald-800: oklch(43.2% .095 166.913); --color-emerald-900: oklch(37.8% .077 168.94); --color-emerald-950: oklch(26.2% .051 172.552); --color-teal-50: oklch(98.4% .014 180.72); --color-teal-100: oklch(95.3% .051 180.801); --color-teal-200: oklch(91% .096 180.426); --color-teal-300: oklch(85.5% .138 181.071); --color-teal-400: oklch(77.7% .152 181.912); --color-teal-500: oklch(70.4% .14 182.503); --color-teal-600: oklch(60% .118 184.704); --color-teal-700: oklch(51.1% .096 186.391); --color-teal-800: oklch(43.7% .078 188.216); --color-teal-900: oklch(38.6% .063 188.416); --color-teal-950: oklch(27.7% .046 192.524); --color-cyan-50: oklch(98.4% .019 200.873); --color-cyan-100: oklch(95.6% .045 203.388); --color-cyan-200: oklch(91.7% .08 205.041); --color-cyan-300: oklch(86.5% .127 207.078); --color-cyan-400: oklch(78.9% .154 211.53); --color-cyan-500: oklch(71.5% .143 215.221); --color-cyan-600: oklch(60.9% .126 221.723); --color-cyan-700: oklch(52% .105 223.128); --color-cyan-800: oklch(45% .085 224.283); --color-cyan-900: oklch(39.8% .07 227.392); --color-cyan-950: oklch(30.2% .056 229.695); --color-sky-50: oklch(97.7% .013 236.62); --color-sky-100: oklch(95.1% .026 236.824); --color-sky-200: oklch(90.1% .058 230.902); --color-sky-300: oklch(82.8% .111 230.318); --color-sky-400: oklch(74.6% .16 232.661); --color-sky-500: oklch(68.5% .169 237.323); --color-sky-600: oklch(58.8% .158 241.966); --color-sky-700: oklch(50% .134 242.749); --color-sky-800: oklch(44.3% .11 240.79); --color-sky-900: oklch(39.1% .09 240.876); --color-sky-950: oklch(29.3% .066 243.157); --color-blue-50: oklch(97% .014 254.604); --color-blue-100: oklch(93.2% .032 255.585); --color-blue-200: oklch(88.2% .059 254.128); --color-blue-300: oklch(80.9% .105 251.813); --color-blue-400: oklch(70.7% .165 254.624); --color-blue-500: oklch(62.3% .214 259.815); --color-blue-600: oklch(54.6% .245 262.881); --color-blue-700: oklch(48.8% .243 264.376); --color-blue-800: oklch(42.4% .199 265.638); --color-blue-900: oklch(37.9% .146 265.522); --color-blue-950: oklch(28.2% .091 267.935); --color-indigo-50: oklch(96.2% .018 272.314); --color-indigo-100: oklch(93% .034 272.788); --color-indigo-200: oklch(87% .065 274.039); --color-indigo-300: oklch(78.5% .115 274.713); --color-indigo-400: oklch(67.3% .182 276.935); --color-indigo-500: oklch(58.5% .233 277.117); --color-indigo-600: oklch(51.1% .262 276.966); --color-indigo-700: oklch(45.7% .24 277.023); --color-indigo-800: oklch(39.8% .195 277.366); --color-indigo-900: oklch(35.9% .144 278.697); --color-indigo-950: oklch(25.7% .09 281.288); --color-violet-50: oklch(96.9% .016 293.756); --color-violet-100: oklch(94.3% .029 294.588); --color-violet-200: oklch(89.4% .057 293.283); --color-violet-300: oklch(81.1% .111 293.571); --color-violet-400: oklch(70.2% .183 293.541); --color-violet-500: oklch(60.6% .25 292.717); --color-violet-600: oklch(54.1% .281 293.009); --color-violet-700: oklch(49.1% .27 292.581); --color-violet-800: oklch(43.2% .232 292.759); --color-violet-900: oklch(38% .189 293.745); --color-violet-950: oklch(28.3% .141 291.089); --color-purple-50: oklch(97.7% .014 308.299); --color-purple-100: oklch(94.6% .033 307.174); --color-purple-200: oklch(90.2% .063 306.703); --color-purple-300: oklch(82.7% .119 306.383); --color-purple-400: oklch(71.4% .203 305.504); --color-purple-500: oklch(62.7% .265 303.9); --color-purple-600: oklch(55.8% .288 302.321); --color-purple-700: oklch(49.6% .265 301.924); --color-purple-800: oklch(43.8% .218 303.724); --color-purple-900: oklch(38.1% .176 304.987); --color-purple-950: oklch(29.1% .149 302.717); --color-fuchsia-50: oklch(97.7% .017 320.058); --color-fuchsia-100: oklch(95.2% .037 318.852); --color-fuchsia-200: oklch(90.3% .076 319.62); --color-fuchsia-300: oklch(83.3% .145 321.434); --color-fuchsia-400: oklch(74% .238 322.16); --color-fuchsia-500: oklch(66.7% .295 322.15); --color-fuchsia-600: oklch(59.1% .293 322.896); --color-fuchsia-700: oklch(51.8% .253 323.949); --color-fuchsia-800: oklch(45.2% .211 324.591); --color-fuchsia-900: oklch(40.1% .17 325.612); --color-fuchsia-950: oklch(29.3% .136 325.661); --color-pink-50: oklch(97.1% .014 343.198); --color-pink-100: oklch(94.8% .028 342.258); --color-pink-200: oklch(89.9% .061 343.231); --color-pink-300: oklch(82.3% .12 346.018); --color-pink-400: oklch(71.8% .202 349.761); --color-pink-500: oklch(65.6% .241 354.308); --color-pink-600: oklch(59.2% .249 .584); --color-pink-700: oklch(52.5% .223 3.958); --color-pink-800: oklch(45.9% .187 3.815); --color-pink-900: oklch(40.8% .153 2.432); --color-pink-950: oklch(28.4% .109 3.907); --color-rose-50: oklch(96.9% .015 12.422); --color-rose-100: oklch(94.1% .03 12.58); --color-rose-200: oklch(89.2% .058 10.001); --color-rose-300: oklch(81% .117 11.638); --color-rose-400: oklch(71.2% .194 13.428); --color-rose-500: oklch(64.5% .246 16.439); --color-rose-600: oklch(58.6% .253 17.585); --color-rose-700: oklch(51.4% .222 16.935); --color-rose-800: oklch(45.5% .188 13.697); --color-rose-900: oklch(41% .159 10.272); --color-rose-950: oklch(27.1% .105 12.094); --color-slate-50: oklch(98.4% .003 247.858); --color-slate-100: oklch(96.8% .007 247.896); --color-slate-200: oklch(92.9% .013 255.508); --color-slate-300: oklch(86.9% .022 252.894); --color-slate-400: oklch(70.4% .04 256.788); --color-slate-500: oklch(55.4% .046 257.417); --color-slate-600: oklch(44.6% .043 257.281); --color-slate-700: oklch(37.2% .044 257.287); --color-slate-800: oklch(27.9% .041 260.031); --color-slate-900: oklch(20.8% .042 265.755); --color-slate-950: oklch(12.9% .042 264.695); --color-gray-50: oklch(98.5% .002 247.839); --color-gray-100: oklch(96.7% .003 264.542); --color-gray-200: oklch(92.8% .006 264.531); --color-gray-300: oklch(87.2% .01 258.338); --color-gray-400: oklch(70.7% .022 261.325); --color-gray-500: oklch(55.1% .027 264.364); --color-gray-600: oklch(44.6% .03 256.802); --color-gray-700: oklch(37.3% .034 259.733); --color-gray-800: oklch(27.8% .033 256.848); --color-gray-900: oklch(21% .034 264.665); --color-gray-950: oklch(13% .028 261.692); --color-zinc-50: oklch(98.5% 0 0); --color-zinc-100: oklch(96.7% .001 286.375); --color-zinc-200: oklch(92% .004 286.32); --color-zinc-300: oklch(87.1% .006 286.286); --color-zinc-400: oklch(70.5% .015 286.067); --color-zinc-500: oklch(55.2% .016 285.938); --color-zinc-600: oklch(44.2% .017 285.786); --color-zinc-700: oklch(37% .013 285.805); --color-zinc-800: oklch(27.4% .006 286.033); --color-zinc-900: oklch(21% .006 285.885); --color-zinc-950: oklch(14.1% .005 285.823); --color-neutral-50: oklch(98.5% 0 0); --color-neutral-100: oklch(97% 0 0); --color-neutral-200: oklch(92.2% 0 0); --color-neutral-300: oklch(87% 0 0); --color-neutral-400: oklch(70.8% 0 0); --color-neutral-500: oklch(55.6% 0 0); --color-neutral-600: oklch(43.9% 0 0); --color-neutral-700: oklch(37.1% 0 0); --color-neutral-800: oklch(26.9% 0 0); --color-neutral-900: oklch(20.5% 0 0); --color-neutral-950: oklch(14.5% 0 0); --color-stone-50: oklch(98.5% .001 106.423); --color-stone-100: oklch(97% .001 106.424); --color-stone-200: oklch(92.3% .003 48.717); --color-stone-300: oklch(86.9% .005 56.366); --color-stone-400: oklch(70.9% .01 56.259); --color-stone-500: oklch(55.3% .013 58.071); --color-stone-600: oklch(44.4% .011 73.639); --color-stone-700: oklch(37.4% .01 67.558); --color-stone-800: oklch(26.8% .007 34.298); --color-stone-900: oklch(21.6% .006 56.043); --color-stone-950: oklch(14.7% .004 49.25); --color-mauve-50: oklch(98.5% 0 0); --color-mauve-100: oklch(96% .003 325.6); --color-mauve-200: oklch(92.2% .005 325.62); --color-mauve-300: oklch(86.5% .012 325.68); --color-mauve-400: oklch(71.1% .019 323.02); --color-mauve-500: oklch(54.2% .034 322.5); --color-mauve-600: oklch(43.5% .029 321.78); --color-mauve-700: oklch(36.4% .029 323.89); --color-mauve-800: oklch(26.3% .024 320.12); --color-mauve-900: oklch(21.2% .019 322.12); --color-mauve-950: oklch(14.5% .008 326); --color-olive-50: oklch(98.8% .003 106.5); --color-olive-100: oklch(96.6% .005 106.5); --color-olive-200: oklch(93% .007 106.5); --color-olive-300: oklch(88% .011 106.6); --color-olive-400: oklch(73.7% .021 106.9); --color-olive-500: oklch(58% .031 107.3); --color-olive-600: oklch(46.6% .025 107.3); --color-olive-700: oklch(39.4% .023 107.4); --color-olive-800: oklch(28.6% .016 107.4); --color-olive-900: oklch(22.8% .013 107.4); --color-olive-950: oklch(15.3% .006 107.1); --color-mist-50: oklch(98.7% .002 197.1); --color-mist-100: oklch(96.3% .002 197.1); --color-mist-200: oklch(92.5% .005 214.3); --color-mist-300: oklch(87.2% .007 219.6); --color-mist-400: oklch(72.3% .014 214.4); --color-mist-500: oklch(56% .021 213.5); --color-mist-600: oklch(45% .017 213.2); --color-mist-700: oklch(37.8% .015 216); --color-mist-800: oklch(27.5% .011 216.9); --color-mist-900: oklch(21.8% .008 223.9); --color-mist-950: oklch(14.8% .004 228.8); --color-taupe-50: oklch(98.6% .002 67.8); --color-taupe-100: oklch(96% .002 17.2); --color-taupe-200: oklch(92.2% .005 34.3); --color-taupe-300: oklch(86.8% .007 39.5); --color-taupe-400: oklch(71.4% .014 41.2); --color-taupe-500: oklch(54.7% .021 43.1); --color-taupe-600: oklch(43.8% .017 39.3); --color-taupe-700: oklch(36.7% .016 35.7); --color-taupe-800: oklch(26.8% .011 36.5); --color-taupe-900: oklch(21.4% .009 43.1); --color-taupe-950: oklch(14.7% .004 49.3); --color-black: #000; --color-white: #fff; --spacing: .25rem; --breakpoint-sm: 40rem; --breakpoint-md: 48rem; --breakpoint-lg: 64rem; --breakpoint-xl: 80rem; --breakpoint-2xl: 96rem; --container-3xs: 16rem; --container-2xs: 18rem; --container-xs: 20rem; --container-sm: 24rem; --container-md: 28rem; --container-lg: 32rem; --container-xl: 36rem; --container-2xl: 42rem; --container-3xl: 48rem; --container-4xl: 56rem; --container-5xl: 64rem; --container-6xl: 72rem; --container-7xl: 80rem; --text-xs: .75rem; --text-xs--line-height: calc(1/.75); --text-sm: .875rem; --text-sm--line-height: calc(1.25/.875); --text-base: 1rem; --text-base--line-height: calc(1.5/1); --text-lg: 1.125rem; --text-lg--line-height: calc(1.75/1.125); --text-xl: 1.25rem; --text-xl--line-height: calc(1.75/1.25); --text-2xl: 1.5rem; --text-2xl--line-height: calc(2/1.5); --text-3xl: 1.875rem; --text-3xl--line-height: calc(2.25/1.875); --text-4xl: 2.25rem; --text-4xl--line-height: calc(2.5/2.25); --text-5xl: 3rem; --text-5xl--line-height: 1; --text-6xl: 3.75rem; --text-6xl--line-height: 1; --text-7xl: 4.5rem; --text-7xl--line-height: 1; --text-8xl: 6rem; --text-8xl--line-height: 1; --text-9xl: 8rem; --text-9xl--line-height: 1; --font-weight-thin: 100; --font-weight-extralight: 200; --font-weight-light: 300; --font-weight-normal: 400; --font-weight-medium: 500; --font-weight-semibold: 600; --font-weight-bold: 700; --font-weight-extrabold: 800; --font-weight-black: 900; --tracking-tighter: -.05em; --tracking-tight: -.025em; --tracking-normal: 0em; --tracking-wide: .025em; --tracking-wider: .05em; --tracking-widest: .1em; --leading-tight: 1.25; --leading-snug: 1.375; --leading-normal: 1.5; --leading-relaxed: 1.625; --leading-loose: 2; --radius-xs: .125rem; --radius-sm: .25rem; --radius-md: .375rem; --radius-lg: .5rem; --radius-xl: .75rem; --radius-2xl: 1rem; --radius-3xl: 1.5rem; --radius-4xl: 2rem; --shadow-2xs: 0 1px #0000000d; --shadow-xs: 0 1px 2px 0 #0000000d; --shadow-sm: 0 1px 3px 0 #0000001a,0 1px 2px -1px #0000001a; --shadow-md: 0 4px 6px -1px #0000001a,0 2px 4px -2px #0000001a; --shadow-lg: 0 10px 15px -3px #0000001a,0 4px 6px -4px #0000001a; --shadow-xl: 0 20px 25px -5px #0000001a,0 8px 10px -6px #0000001a; --shadow-2xl: 0 25px 50px -12px #00000040; --inset-shadow-2xs: inset 0 1px #0000000d; --inset-shadow-xs: inset 0 1px 1px #0000000d; --inset-shadow-sm: inset 0 2px 4px #0000000d; --drop-shadow-xs: 0 1px 1px #0000000d; --drop-shadow-sm: 0 1px 2px #00000026; --drop-shadow-md: 0 3px 3px #0000001f; --drop-shadow-lg: 0 4px 4px #00000026; --drop-shadow-xl: 0 9px 7px #0000001a; --drop-shadow-2xl: 0 25px 25px #00000026; --text-shadow-2xs: 0px 1px 0px #00000026; --text-shadow-xs: 0px 1px 1px #0003; --text-shadow-sm: 0px 1px 0px #00000013,0px 1px 1px #00000013,0px 2px 2px #00000013; --text-shadow-md: 0px 1px 1px #0000001a,0px 1px 2px #0000001a,0px 2px 4px #0000001a; --text-shadow-lg: 0px 1px 2px #0000001a,0px 3px 2px #0000001a,0px 4px 8px #0000001a; --ease-in: cubic-bezier(.4,0,1,1); --ease-out: cubic-bezier(0,0,.2,1); --ease-in-out: cubic-bezier(.4,0,.2,1); --animate-spin: spin 1s linear infinite; --animate-ping: ping 1s cubic-bezier(0,0,.2,1)infinite; --animate-pulse: pulse 2s cubic-bezier(.4,0,.6,1)infinite; --animate-bounce: bounce 1s infinite; @keyframes spin { to { transform: rotate(360deg); }}@keyframes ping { 75%,100% { transform: scale(2); opacity: 0; }}@keyframes pulse { 50% { opacity: .5; }}@keyframes bounce { 0%,100% { transform: translateY(-25%); animation-timing-function: cubic-bezier(.8,0,1,1); }50% { transform: none; animation-timing-function: cubic-bezier(0,0,.2,1); }}--blur-xs: 4px; --blur-sm: 8px; --blur-md: 12px; --blur-lg: 16px; --blur-xl: 24px; --blur-2xl: 40px; --blur-3xl: 64px; --perspective-dramatic: 100px; --perspective-near: 300px; --perspective-normal: 500px; --perspective-midrange: 800px; --perspective-distant: 1200px; --aspect-video: 16/9; --default-transition-duration: .15s; --default-transition-timing-function: cubic-bezier(.4,0,.2,1); --default-font-family: --theme(--font-sans,initial); --default-font-feature-settings: --theme(--font-sans--font-feature-settings,initial); --default-font-variation-settings: --theme(--font-sans--font-variation-settings,initial); --default-mono-font-family: --theme(--font-mono,initial); --default-mono-font-feature-settings: --theme(--font-mono--font-feature-settings,initial); --default-mono-font-variation-settings: --theme(--font-mono--font-variation-settings,initial);}@theme default inline reference{--blur: 8px; --shadow: 0 1px 3px 0 #0000001a,0 1px 2px -1px #0000001a; --shadow-inner: inset 0 2px 4px 0 #0000000d; --drop-shadow: 0 1px 2px #0000001a,0 1px 1px #0000000f; --radius: .25rem; --max-width-prose: 65ch;}}@layer base{*,:after,:before{box-sizing:border-box;border:0 solid;margin:0;padding:0}::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::-webkit-file-upload-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:--theme(--default-font-family,ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji");font-feature-settings:--theme(--default-font-feature-settings,normal);font-variation-settings:--theme(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{color:inherit;border-top-width:1px;height:0}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;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:--theme(--default-mono-font-family,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace);font-feature-settings:--theme(--default-mono-font-feature-settings,normal);font-variation-settings:--theme(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{position:relative;vertical-align:baseline;font-size:75%;line-height:0}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::-webkit-file-upload-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:-webkit-any([multiple],[size])) optgroup{font-weight:bolder}:where(select:-moz-any([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:-webkit-any([multiple],[size])) optgroup option{padding-inline-start:20px}:where(select:-moz-any([multiple],[size])) optgroup option{padding-inline-start:20px}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::-webkit-file-upload-button{margin-inline-end:4px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports ( not (-webkit-appearance: -apple-pay-button)) or (contain-intrinsic-size: 1px){::placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{text-align:inherit;min-height:1lh}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block-start:0;padding-block-end:0}::-webkit-datetime-edit-year-field{padding-block-start:0;padding-block-end:0}::-webkit-datetime-edit-month-field{padding-block-start:0;padding-block-end:0}::-webkit-datetime-edit-day-field{padding-block-start:0;padding-block-end:0}::-webkit-datetime-edit-hour-field{padding-block-start:0;padding-block-end:0}::-webkit-datetime-edit-minute-field{padding-block-start:0;padding-block-end:0}::-webkit-datetime-edit-second-field{padding-block-start:0;padding-block-end:0}::-webkit-datetime-edit-millisecond-field{padding-block-start:0;padding-block-end:0}::-webkit-datetime-edit-meridiem-field{padding-block-start:0;padding-block-end:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button{appearance:button}input:where([type=button],[type=reset],[type=submit]){appearance:button}::-webkit-file-upload-button{appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}}@layer utilities{@tailwind utilities;}:root{--buncss-light: ;--buncss-dark:initial;color-scheme:dark;--page:oklch(.16 .02 307);--surface:oklch(.19 .022 307);--surface-2:oklch(.235 .026 307);--border:oklch(1 0 0/9%);--border-strong:oklch(1 0 0/13%);--text:oklch(.92 .01 307);--muted:oklch(.71 .016 307);--dim:oklch(.53 .018 307);--accent:oklch(.674 .23 341);--accent-fg:oklch(.2 .07 341);--accent-muted:oklch(.674 .23 341/18%);--link:oklch(.817 .112 205);--success:oklch(.76 .14 150);--danger:oklch(.66 .19 25);--warning:oklch(.79 .14 85);--text-primary:var(--text);--text-secondary:var(--muted);--text-muted:var(--dim);--border-subtle:var(--border);--accent-red:var(--danger);--accent-green:var(--success);--accent-cyan:var(--link);--bg-hover:var(--surface-2);--bg-elevated:var(--surface-2);--radius-sm:3px;--radius-md:6px;--radius-lg:8px}@media (prefers-color-scheme:light){:root:not([data-theme]){--buncss-light:initial;--buncss-dark: ;color-scheme:light;--page:oklch(.985 .004 307);--surface:oklch(1 0 0);--surface-2:oklch(.965 .006 307);--border:oklch(0 0 0/10%);--border-strong:oklch(0 0 0/15%);--text:oklch(.26 .03 307);--muted:oklch(.46 .03 307);--dim:oklch(.58 .025 307);--accent:oklch(.62 .23 341);--accent-fg:oklch(.99 .01 341);--accent-muted:oklch(.62 .23 341/14%);--link:oklch(.58 .13 230);--success:oklch(.55 .13 150);--danger:oklch(.55 .19 25);--warning:oklch(.6 .13 85)}}[data-theme=light]{--buncss-light:initial;--buncss-dark: ;color-scheme:light;--page:oklch(.985 .004 307);--surface:oklch(1 0 0);--surface-2:oklch(.965 .006 307);--border:oklch(0 0 0/10%);--border-strong:oklch(0 0 0/15%);--text:oklch(.26 .03 307);--muted:oklch(.46 .03 307);--dim:oklch(.58 .025 307);--accent:oklch(.62 .23 341);--accent-fg:oklch(.99 .01 341);--accent-muted:oklch(.62 .23 341/14%);--link:oklch(.58 .13 230);--success:oklch(.55 .13 150);--danger:oklch(.55 .19 25);--warning:oklch(.6 .13 85)}@layer base{*{box-sizing:border-box}html{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}body{background:var(--page);color:var(--text);min-height:100vh;margin:0;font-family:ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Noto Sans,Ubuntu,Cantarell,Helvetica Neue,sans-serif}button:focus-visible{outline:2px solid var(--link);outline-offset:2px}a:focus-visible{outline:2px solid var(--link);outline-offset:2px}[tabindex]:focus-visible{outline:2px solid var(--link);outline-offset:2px}}.stats-app-container{display:flex;background:var(--page);color:var(--text);min-height:100vh}.stats-nav-rail{background:var(--surface);border-right:1px solid var(--border);display:flex;flex-direction:column;flex-shrink:0;width:240px}.stats-desktop-nav{display:flex}@media (max-width:1023px){.stats-desktop-nav{display:none}}.stats-nav-rail-header{border-bottom:1px solid var(--border);display:flex;align-items: center;height:56px;padding:20px 24px}.stats-logo-container{display:flex;flex-direction:column}.stats-logo-text{letter-spacing:.1em;color:var(--text);font-size:14px;font-weight:700}.stats-logo-subtext{text-transform:uppercase;letter-spacing:.05em;color:var(--muted);font-size:10px}.stats-nav-rail-menu{display:flex;flex-direction:column;flex:1;gap:4px;padding:16px 12px}.stats-nav-rail-item{display:flex;color:var(--muted);border-radius:var(--radius-md);cursor:pointer;text-align:left;background:0 0;border:none;align-items: center;gap:12px;padding:10px 16px;transition:all .15s;font-size:13px;font-weight:500}.stats-nav-rail-item:hover{color:var(--text);background:var(--surface-2)}.stats-nav-rail-item[data-active=true]{color:var(--accent);background:var(--accent-muted);position:relative}.stats-nav-rail-item[data-active=true]:before{content:"";position:absolute;background:var(--accent);border-radius:0 3px 3px 0;width:3px;height:18px;top:50%;left:0;transform:translateY(-50%)}.stats-nav-rail-item-icon{flex-shrink:0}.stats-nav-rail-footer{border-top:1px solid var(--border);padding:16px 24px}.stats-version-tag{color:var(--dim);font-size:11px}.stats-main-pane{display:flex;flex-direction:column;flex:1;min-width:0}.stats-top-bar{background:var(--surface);border-bottom:1px solid var(--border);display:flex;justify-content:space-between;align-items: center;height:56px;padding:0 24px}@media (max-width:767px){.stats-top-bar{flex-wrap:wrap;gap:12px;height:auto;min-height:56px;padding:8px 16px}}.stats-top-bar-left{display:flex;align-items: center;gap:12px}.stats-mobile-menu-btn{display:none;color:var(--text);cursor:pointer;background:0 0;border:none;padding:4px}@media (max-width:1023px){.stats-mobile-menu-btn{display:inline-flex;justify-content:center;align-items: center}}.stats-page-title{color:var(--text);margin:0;font-size:16px;font-weight:600}.stats-top-bar-right{display:flex;align-items: center;gap:16px}@media (max-width:767px){.stats-top-bar-right{justify-content:space-between;gap:8px;width:100%}}.stats-top-bar-meta{display:flex;align-items: center}.stats-last-updated{color:var(--dim);font-variant-numeric:tabular-nums;font-size:11px}.stats-content-area{overflow-y:auto;flex:1}.stats-content-inner{display:flex;flex-direction:column;gap:24px;max-width:1560px;margin:0 auto;padding:24px}@media (max-width:767px){.stats-content-inner{gap:16px;padding:16px}}.stats-mobile-drawer-overlay{position:fixed;backdrop-filter:none;z-index:100;background:#0006;inset:0}.stats-mobile-drawer{position:absolute;background:var(--surface);border-right:1px solid var(--border);display:flex;z-index:101;animation:slideIn .2s ease-out;flex-direction:column;width:260px;top:0;bottom:0;left:0}@keyframes slideIn{0%{transform:translate(-100%)}to{transform:translate(0)}}.stats-mobile-drawer-header{display:flex;border-bottom:1px solid var(--border);justify-content:space-between;align-items: center;height:56px;padding:0 16px 0 24px}.stats-mobile-nav{display:flex;height:calc(100% - 56px)}.stats-button{display:inline-flex;border-radius:var(--radius-md);cursor:pointer;border:1px solid #0000;justify-content:center;align-items: center;gap:8px;padding:8px 14px;transition:all .15s;font-family:inherit;font-size:13px;font-weight:500}.stats-button:active:not(:disabled){transform:translateY(1px)}.stats-button-primary{background:var(--accent);color:var(--accent-fg)}.stats-button-primary:hover:not(:disabled){opacity:.9}.stats-button-primary:disabled{opacity:.5;cursor:not-allowed}.stats-button-secondary{background:var(--surface-2);color:var(--text);border-color:var(--border)}.stats-button-secondary:hover:not(:disabled){background:var(--border)}.stats-button-secondary:disabled{opacity:.5;cursor:not-allowed}.stats-range-control{display:flex;background:var(--surface-2);border:1px solid var(--border);border-radius:var(--radius-md);padding:2px}.stats-range-control-btn{color:var(--muted);border-radius:var(--radius-sm);cursor:pointer;background:0 0;border:none;padding:4px 10px;transition:all .1s;font-size:12px;font-weight:500}.stats-range-control-btn:hover{color:var(--text)}.stats-range-control-btn[data-active=true]{color:var(--text);background:var(--surface);box-shadow:0 1px 2px #0000002e}.stats-sync-container{display:flex;align-items: center;gap:12px}.stats-sync-status-msg{font-variant-numeric:tabular-nums;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:200px;font-size:11px}.stats-sync-status-msg[data-type=success]{color:var(--success)}.stats-sync-status-msg[data-type=error]{color:var(--danger)}.stats-sync-icon{flex-shrink:0}.stats-spin{animation:spin 1s linear infinite}@keyframes spin{to{transform:rotate(360deg)}}.stats-panel{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius-lg);display:flex;flex-direction:column}.stats-panel-header{border-bottom:1px solid var(--border);display:flex;justify-content:space-between;align-items: center;gap:16px;padding:16px 20px}@media (max-width:480px){.stats-panel-header{flex-direction:column;align-items: flex-start;gap:8px}}.stats-panel-header-titles{display:flex;flex-direction:column}.stats-panel-title{color:var(--text);margin:0;font-size:14px;font-weight:600}.stats-panel-subtitle{color:var(--muted);margin:2px 0 0;font-size:12px}.stats-panel-actions{display:flex;align-items: center;gap:12px}.stats-panel-body{padding:20px}.stats-metric-cluster{display:flex;flex-direction:column;gap:16px}.stats-metric-primary-grid{display:grid;grid-template-columns:repeat(4,1fr);gap:16px}@media (max-width:1023px){.stats-metric-primary-grid{grid-template-columns:repeat(2,1fr)}}@media (max-width:480px){.stats-metric-primary-grid{grid-template-columns:1fr}}.stats-metric-secondary-grid{display:grid;grid-template-columns:repeat(6,1fr);gap:12px}@media (max-width:1023px){.stats-metric-secondary-grid{grid-template-columns:repeat(3,1fr)}}@media (max-width:600px){.stats-metric-secondary-grid{grid-template-columns:repeat(2,1fr)}}.stats-metric-card{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius-lg);display:flex;flex-direction:column;justify-content:space-between;padding:16px}.stats-metric-card.primary{padding:20px 18px}.stats-metric-card.primary .stats-metric-label{color:var(--accent)}.stats-metric-card.secondary{padding:12px 14px}.stats-metric-label{text-transform:uppercase;letter-spacing:.05em;color:var(--muted);margin-bottom:8px;font-size:11px;font-weight:600}.stats-metric-value{color:var(--text);font-variant-numeric:tabular-nums;font-size:20px;font-weight:700}.stats-metric-card.secondary .stats-metric-value{font-size:15px;font-weight:600}.stats-table-wrapper{width:100%}.stats-table-container{overflow-x:auto;width:100%}.stats-table{border-collapse:collapse;font-variant-numeric:tabular-nums;width:100%;font-size:13px}.stats-table-th{text-transform:uppercase;letter-spacing:.05em;color:var(--muted);border-bottom:2px solid var(--border);padding:12px 16px;font-size:11px;font-weight:600}.stats-table-td{border-bottom:1px solid var(--border);color:var(--text);padding:10px 16px}.stats-table-tr{background:0 0}.stats-table-tr-clickable{cursor:pointer;transition:background .12s,box-shadow .12s}.stats-table-tr-clickable:hover{background:var(--surface-2);box-shadow:inset 2px 0 0 var(--accent)}.stats-text-left{text-align:left}.stats-text-right{text-align:right}.stats-table-empty{text-align:center;color:var(--muted);border:1px dashed var(--border);border-radius:var(--radius-lg);padding:40px;font-size:13px}.stats-progress-bar-track{background:var(--surface-2);overflow:hidden;border-radius:999px}.stats-progress-bar-fill{border-radius:inherit;height:100%;transition:width .18s}.stats-progress-bar-fill[data-variant=link]{background:var(--link)}.stats-progress-bar-fill[data-variant=success]{background:var(--success)}.stats-table-mobile-only{display:none}.stats-table-desktop-only{overflow-x:auto;width:100%}@media (max-width:767px){.stats-table-desktop-only{display:none}.stats-table-mobile-only{display:block}}.stats-table-mobile-list{display:flex;flex-direction:column;gap:12px}.stats-table-mobile-card-wrapper{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius-lg);padding:14px}.stats-status-pill{display:inline-flex;border-radius:100px;justify-content:center;align-items: center;padding:2px 8px;font-size:11px;font-weight:600;line-height:1.2}.stats-status-pill[data-variant=success]{background:color-mix(in srgb,var(--success)18%,transparent);color:var(--success)}.stats-status-pill[data-variant=danger]{background:color-mix(in srgb,var(--danger)18%,transparent);color:var(--danger)}.stats-status-pill[data-variant=warning]{background:color-mix(in srgb,var(--warning)18%,transparent);color:var(--warning)}.stats-status-pill[data-variant=info]{background:color-mix(in srgb,var(--link)18%,transparent);color:var(--link)}.stats-status-pill[data-variant=default]{background:var(--surface-2);color:var(--muted)}.stats-segmented-control{display:flex;background:var(--surface-2);border:1px solid var(--border);border-radius:var(--radius-md);gap:2px;padding:2px}.stats-segmented-control-btn{color:var(--muted);border-radius:var(--radius-sm);cursor:pointer;white-space:nowrap;background:0 0;border:none;padding:6px 12px;transition:all .1s;font-size:12px;font-weight:500}.stats-segmented-control-btn:hover{color:var(--text)}.stats-segmented-control-btn[data-active=true]{color:var(--text);background:var(--surface);box-shadow:0 1px 2px #0000002e}.stats-drawer-overlay{position:fixed;z-index:100;display:flex;background:#00000080;justify-content:flex-end;inset:0}.stats-drawer{background:var(--surface);border-left:1px solid var(--border);display:flex;animation:drawerSlide .25s cubic-bezier(.16,1,.3,1);flex-direction:column;width:600px;max-width:100%;height:100%;box-shadow:-4px 0 24px #00000040}@keyframes drawerSlide{0%{transform:translate(100%)}to{transform:translate(0)}}.stats-drawer-header{border-bottom:1px solid var(--border);display:flex;background:var(--surface);flex-shrink:0;justify-content:space-between;align-items: center;height:56px;padding:0 24px}.stats-drawer-header-left{display:flex;align-items:baseline;gap:12px}.stats-drawer-title{color:var(--text);margin:0;font-size:15px;font-weight:600}.stats-drawer-id{color:var(--muted);font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;font-size:11px}.stats-drawer-close-btn{color:var(--muted);cursor:pointer;display:inline-flex;border-radius:var(--radius-sm);background:0 0;border:none;justify-content:center;align-items: center;padding:4px;transition:all .1s}.stats-drawer-close-btn:hover{color:var(--text);background:var(--surface-2)}.stats-drawer-body{overflow-y:auto;flex:1;padding:24px}.stats-drawer-content{display:flex;flex-direction:column;gap:24px}.stats-drawer-status-card{background:var(--surface-2);border:1px solid var(--border);border-radius:var(--radius-lg);padding:16px}.stats-drawer-status-row{display:flex;justify-content:space-between;align-items: flex-start;gap:16px}.stats-drawer-model{color:var(--text);font-size:16px;font-weight:700}.stats-drawer-provider{color:var(--muted);margin-top:2px;font-size:12px}.stats-drawer-error-block{border-top:1px solid var(--border);margin-top:14px;padding-top:12px}.stats-drawer-error-label{text-transform:uppercase;letter-spacing:.05em;color:var(--danger);margin-bottom:4px;font-size:11px;font-weight:600}.stats-drawer-error-text{color:var(--text);white-space:pre-wrap;word-break:break-all;font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;font-size:12px}.stats-drawer-metrics-grid{display:grid;grid-template-columns:repeat(2,1fr);gap:12px}.stats-drawer-metric-card{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius-lg);display:flex;flex-direction:column;justify-content:center;padding:14px}.stats-drawer-metric-label{display:flex;text-transform:uppercase;letter-spacing:.05em;color:var(--muted);align-items: center;gap:6px;margin-bottom:6px;font-size:11px;font-weight:600}.stats-drawer-metric-icon{color:var(--dim)}.stats-drawer-metric-value{color:var(--text);font-variant-numeric:tabular-nums;font-size:16px;font-weight:700}.stats-drawer-metric-sub{color:var(--muted);font-variant-numeric:tabular-nums;margin-top:2px;font-size:11px}.stats-drawer-json-blocks{display:flex;flex-direction:column;gap:16px}.stats-json-block{border:1px solid var(--border);border-radius:var(--radius-lg);background:var(--surface);overflow:hidden}.stats-json-block-header{background:var(--surface-2);border-bottom:1px solid var(--border);display:flex;cursor:pointer;user-select:none;justify-content:space-between;align-items: center;padding:10px 16px;font-size:12px;font-weight:600}.stats-json-block-header:hover{background:var(--border)}.stats-json-block-title{color:var(--text)}.stats-json-block-toggle-indicator{color:var(--muted)}.stats-json-block-content-wrapper{background:var(--page);overflow-x:auto;padding:16px}.stats-json-block-content{color:var(--text);margin:0;font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;font-size:12px;line-height:1.5}.stats-skeleton{background:var(--surface-2);position:relative;overflow:hidden;border-radius:var(--radius-sm)}.stats-skeleton[data-variant=text]{height:14px;margin-top:4px;margin-bottom:4px}.stats-skeleton[data-variant=circle]{border-radius:50%}.stats-skeleton:after{content:"";position:absolute;animation:shimmer 1.5s infinite;background:linear-gradient(90deg,#0000 0%,#ffffff0d 50%,#0000 100%);inset:0;transform:translate(-100%)}@media (prefers-color-scheme:light){.stats-skeleton:after{background:linear-gradient(90deg,#0000 0%,#00000008 50%,#0000 100%)}}@keyframes shimmer{to{transform:translate(100%)}}@media (prefers-reduced-motion:reduce){.stats-skeleton:after{animation:none}}.stats-boundary-skeleton{border:1px solid var(--border);border-radius:var(--radius-lg);background:var(--surface);padding:20px}.stats-empty-state,.stats-error-state{display:flex;text-align:center;background:var(--surface);border:1px dashed var(--border);border-radius:var(--radius-lg);flex-direction:column;justify-content:center;align-items: center;padding:48px 24px}.stats-empty-state-message{color:var(--muted);margin:0;font-size:13px}.stats-error-state-content{max-width:360px}.stats-error-state-title{color:var(--text);margin:0 0 8px;font-size:14px;font-weight:600}.stats-error-state-message{color:var(--danger);word-break:break-word;margin:0 0 16px;font-size:12px}.stats-error-state-btn{margin:0 auto}.stats-text-primary{color:var(--text)}.stats-text-secondary{color:var(--muted)}.stats-text-muted{color:var(--dim)}.stats-text-xs{font-size:12px}.stats-font-medium{font-weight:500}.stats-font-semibold{font-weight:600}*{scrollbar-width:thin;scrollbar-color:var(--border-strong)transparent}*::-webkit-scrollbar{width:10px;height:10px}*::-webkit-scrollbar-track{background:0 0}*::-webkit-scrollbar-thumb{background:var(--border-strong);background-clip:padding-box;border:2px solid #0000;border-radius:999px}*::-webkit-scrollbar-thumb:hover{background:var(--dim);background-clip:padding-box;border:2px solid #0000}*::-webkit-scrollbar-corner{background:0 0}@media (prefers-reduced-motion:reduce){.stats-button,.stats-table-tr-clickable,.stats-nav-rail-item,.stats-range-control-btn,.stats-segmented-control-btn{transition:none}.stats-button:active:not(:disabled){transform:none}.stats-mobile-drawer,.stats-drawer{animation:none}}.stats-logo-container{display:grid;grid-template-columns:auto 1fr;align-items: center;column-gap:10px}.stats-logo-container:before{content:"";grid-row:1/3;background:linear-gradient(135deg,#ed4abf 0%,#9b4dff 50%,#5ad8e6 100%);border-radius:4px;width:16px;height:16px}.stats-logo-text,.stats-logo-subtext{grid-column:2}.stats-json-actions{display:flex;align-items: center;gap:12px}.stats-json-copy-btn{display:inline-flex;color:var(--muted);border:1px solid var(--border);border-radius:var(--radius-sm);cursor:pointer;background:0 0;align-items: center;gap:5px;padding:3px 8px;transition:color .12s,background .12s;font-family:inherit;font-size:11px;font-weight:600}.stats-json-copy-btn:hover{color:var(--text);background:var(--surface-2)}.stats-empty-state-icon{color:var(--dim);margin-bottom:12px}@media (prefers-reduced-motion:reduce){.stats-json-copy-btn{transition:none}}.stats-theme-toggle{display:inline-flex;border:1px solid var(--border);border-radius:var(--radius-md);background:var(--surface);color:var(--muted);cursor:pointer;flex-shrink:0;justify-content:center;align-items: center;width:32px;height:32px;transition:color .14s,background .14s,border-color .14s}.stats-theme-toggle:hover{color:var(--text);border-color:var(--border-strong)}@media (prefers-reduced-motion:reduce){.stats-theme-toggle{transition:none}}
@@ -0,0 +1,24 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>AI Usage Statistics</title>
7
+ <script>
8
+ (function () {
9
+ try {
10
+ var stored = localStorage.getItem("omp-stats-theme");
11
+ var system = matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
12
+ var theme = stored === "light" || stored === "dark" ? stored : system;
13
+ document.documentElement.dataset.theme = theme;
14
+ document.documentElement.style.colorScheme = theme;
15
+ } catch (e) {}
16
+ })();
17
+ </script>
18
+ <link rel="stylesheet" href="styles.css">
19
+ </head>
20
+ <body>
21
+ <div id="root"></div>
22
+ <script src="index.js" type="module"></script>
23
+ </body>
24
+ </html>