@codemieai/code 0.2.0 → 0.3.0

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 (258) hide show
  1. package/README.md +38 -7
  2. package/dist/agents/core/session/ensure-session.d.ts.map +1 -1
  3. package/dist/agents/core/session/ensure-session.js +14 -11
  4. package/dist/agents/core/session/ensure-session.js.map +1 -1
  5. package/dist/agents/plugins/claude/plugin/{codemie-statusline.mjs → session-status.mjs} +1 -0
  6. package/dist/agents/plugins/claude/plugin/skills/codemie-analytics/SKILL.md +641 -0
  7. package/dist/agents/plugins/claude/plugin/skills/codemie-analytics/references/leaderboard-dashboard-report.md +225 -0
  8. package/dist/agents/plugins/claude/plugin/skills/codemie-analytics/references/people-spending-dashboard-report.md +746 -0
  9. package/dist/agents/plugins/claude/plugin/skills/codemie-analytics/references/people-spending-dashboard-template.html +3270 -0
  10. package/dist/agents/plugins/claude/plugin/skills/codemie-analytics/scripts/analytics-cli.js +893 -0
  11. package/dist/agents/plugins/claude/plugin/skills/codemie-analytics/scripts/inspect-schema.js +211 -0
  12. package/dist/agents/plugins/claude/plugin/skills/codemie-html-report/README.md +39 -0
  13. package/dist/agents/plugins/claude/plugin/skills/codemie-html-report/SKILL.md +117 -26
  14. package/dist/agents/plugins/claude/plugin/skills/codemie-html-report/scripts/inject-css.js +40 -0
  15. package/dist/agents/plugins/claude/plugin/skills/codemie-html-report/scripts/inject-data.js +68 -0
  16. package/dist/agents/plugins/claude/plugin/skills/codemie-html-report/style-guide/css/bundle.css +1 -0
  17. package/dist/agents/plugins/claude/plugin/skills/codemie-sdk/SKILL.md +240 -0
  18. package/dist/agents/plugins/claude/plugin/skills/codemie-sdk/examples/assistants.md +256 -0
  19. package/dist/agents/plugins/claude/plugin/skills/codemie-sdk/examples/categories.md +101 -0
  20. package/dist/agents/plugins/claude/plugin/skills/codemie-sdk/examples/datasources.md +401 -0
  21. package/dist/agents/plugins/claude/plugin/skills/codemie-sdk/examples/integrations.md +242 -0
  22. package/dist/agents/plugins/claude/plugin/skills/codemie-sdk/examples/skills.md +191 -0
  23. package/dist/agents/plugins/claude/plugin/skills/codemie-sdk/examples/users.md +38 -0
  24. package/dist/agents/plugins/claude/plugin/skills/codemie-sdk/examples/workflows.md +151 -0
  25. package/dist/agents/plugins/claude/plugin/skills/msgraph/scripts/msgraph.js +12 -2
  26. package/dist/agents/plugins/claude/plugin/statusline.mjs +216 -0
  27. package/dist/agents/plugins/claude/statusline-installer.d.ts +8 -0
  28. package/dist/agents/plugins/claude/statusline-installer.d.ts.map +1 -0
  29. package/dist/agents/plugins/claude/statusline-installer.js +127 -0
  30. package/dist/agents/plugins/claude/statusline-installer.js.map +1 -0
  31. package/dist/agents/plugins/codemie-code.plugin.d.ts.map +1 -1
  32. package/dist/agents/plugins/codemie-code.plugin.js +5 -0
  33. package/dist/agents/plugins/codemie-code.plugin.js.map +1 -1
  34. package/dist/cli/commands/assistants/chat/index.d.ts.map +1 -1
  35. package/dist/cli/commands/assistants/chat/index.js +8 -2
  36. package/dist/cli/commands/assistants/chat/index.js.map +1 -1
  37. package/dist/cli/commands/assistants/setup/generators/claude-agent-generator.d.ts +3 -2
  38. package/dist/cli/commands/assistants/setup/generators/claude-agent-generator.d.ts.map +1 -1
  39. package/dist/cli/commands/assistants/setup/generators/claude-agent-generator.js +5 -4
  40. package/dist/cli/commands/assistants/setup/generators/claude-agent-generator.js.map +1 -1
  41. package/dist/cli/commands/assistants/setup/generators/claude-skill-generator.d.ts +3 -2
  42. package/dist/cli/commands/assistants/setup/generators/claude-skill-generator.d.ts.map +1 -1
  43. package/dist/cli/commands/assistants/setup/generators/claude-skill-generator.js +8 -6
  44. package/dist/cli/commands/assistants/setup/generators/claude-skill-generator.js.map +1 -1
  45. package/dist/cli/commands/assistants/setup/helpers.d.ts +3 -2
  46. package/dist/cli/commands/assistants/setup/helpers.d.ts.map +1 -1
  47. package/dist/cli/commands/assistants/setup/helpers.js +3 -2
  48. package/dist/cli/commands/assistants/setup/helpers.js.map +1 -1
  49. package/dist/cli/commands/assistants/setup/index.d.ts.map +1 -1
  50. package/dist/cli/commands/assistants/setup/index.js +7 -13
  51. package/dist/cli/commands/assistants/setup/index.js.map +1 -1
  52. package/dist/cli/commands/assistants/setup/summary/index.d.ts +3 -3
  53. package/dist/cli/commands/assistants/setup/summary/index.d.ts.map +1 -1
  54. package/dist/cli/commands/assistants/setup/summary/index.js +5 -5
  55. package/dist/cli/commands/assistants/setup/summary/index.js.map +1 -1
  56. package/dist/cli/commands/install.d.ts.map +1 -1
  57. package/dist/cli/commands/install.js +40 -0
  58. package/dist/cli/commands/install.js.map +1 -1
  59. package/dist/cli/commands/profile/display.d.ts.map +1 -1
  60. package/dist/cli/commands/profile/display.js +1 -0
  61. package/dist/cli/commands/profile/display.js.map +1 -1
  62. package/dist/cli/commands/proxy/connectors/desktop-managed-mcp-servers.json +0 -6
  63. package/dist/cli/commands/proxy/connectors/desktop.d.ts.map +1 -1
  64. package/dist/cli/commands/proxy/connectors/desktop.js +20 -10
  65. package/dist/cli/commands/proxy/connectors/desktop.js.map +1 -1
  66. package/dist/cli/commands/sdk/assistants.d.ts +3 -0
  67. package/dist/cli/commands/sdk/assistants.d.ts.map +1 -0
  68. package/dist/cli/commands/sdk/assistants.js +211 -0
  69. package/dist/cli/commands/sdk/assistants.js.map +1 -0
  70. package/dist/cli/commands/sdk/categories.d.ts +3 -0
  71. package/dist/cli/commands/sdk/categories.d.ts.map +1 -0
  72. package/dist/cli/commands/sdk/categories.js +186 -0
  73. package/dist/cli/commands/sdk/categories.js.map +1 -0
  74. package/dist/cli/commands/sdk/datasources.d.ts +3 -0
  75. package/dist/cli/commands/sdk/datasources.d.ts.map +1 -0
  76. package/dist/cli/commands/sdk/datasources.js +276 -0
  77. package/dist/cli/commands/sdk/datasources.js.map +1 -0
  78. package/dist/cli/commands/sdk/index.d.ts +3 -0
  79. package/dist/cli/commands/sdk/index.d.ts.map +1 -0
  80. package/dist/cli/commands/sdk/index.js +23 -0
  81. package/dist/cli/commands/sdk/index.js.map +1 -0
  82. package/dist/cli/commands/sdk/integrations.d.ts +3 -0
  83. package/dist/cli/commands/sdk/integrations.d.ts.map +1 -0
  84. package/dist/cli/commands/sdk/integrations.js +220 -0
  85. package/dist/cli/commands/sdk/integrations.js.map +1 -0
  86. package/dist/cli/commands/sdk/llm.d.ts +3 -0
  87. package/dist/cli/commands/sdk/llm.d.ts.map +1 -0
  88. package/dist/cli/commands/sdk/llm.js +48 -0
  89. package/dist/cli/commands/sdk/llm.js.map +1 -0
  90. package/dist/cli/commands/sdk/services/assistants.d.ts +13 -0
  91. package/dist/cli/commands/sdk/services/assistants.d.ts.map +1 -0
  92. package/dist/cli/commands/sdk/services/assistants.js +60 -0
  93. package/dist/cli/commands/sdk/services/assistants.js.map +1 -0
  94. package/dist/cli/commands/sdk/services/categories.d.ts +8 -0
  95. package/dist/cli/commands/sdk/services/categories.d.ts.map +1 -0
  96. package/dist/cli/commands/sdk/services/categories.js +19 -0
  97. package/dist/cli/commands/sdk/services/categories.js.map +1 -0
  98. package/dist/cli/commands/sdk/services/datasources.d.ts +33 -0
  99. package/dist/cli/commands/sdk/services/datasources.d.ts.map +1 -0
  100. package/dist/cli/commands/sdk/services/datasources.js +268 -0
  101. package/dist/cli/commands/sdk/services/datasources.js.map +1 -0
  102. package/dist/cli/commands/sdk/services/index.d.ts +6 -0
  103. package/dist/cli/commands/sdk/services/index.d.ts.map +1 -0
  104. package/dist/cli/commands/sdk/services/index.js +6 -0
  105. package/dist/cli/commands/sdk/services/index.js.map +1 -0
  106. package/dist/cli/commands/sdk/services/integrations.d.ts +27 -0
  107. package/dist/cli/commands/sdk/services/integrations.d.ts.map +1 -0
  108. package/dist/cli/commands/sdk/services/integrations.js +59 -0
  109. package/dist/cli/commands/sdk/services/integrations.js.map +1 -0
  110. package/dist/cli/commands/sdk/services/llm.d.ts +4 -0
  111. package/dist/cli/commands/sdk/services/llm.d.ts.map +1 -0
  112. package/dist/cli/commands/sdk/services/llm.js +7 -0
  113. package/dist/cli/commands/sdk/services/llm.js.map +1 -0
  114. package/dist/cli/commands/sdk/services/skills.d.ts +23 -0
  115. package/dist/cli/commands/sdk/services/skills.d.ts.map +1 -0
  116. package/dist/cli/commands/sdk/services/skills.js +69 -0
  117. package/dist/cli/commands/sdk/services/skills.js.map +1 -0
  118. package/dist/cli/commands/sdk/services/users.d.ts +4 -0
  119. package/dist/cli/commands/sdk/services/users.d.ts.map +1 -0
  120. package/dist/cli/commands/sdk/services/users.js +7 -0
  121. package/dist/cli/commands/sdk/services/users.js.map +1 -0
  122. package/dist/cli/commands/sdk/services/workflows.d.ts +7 -0
  123. package/dist/cli/commands/sdk/services/workflows.d.ts.map +1 -0
  124. package/dist/cli/commands/sdk/services/workflows.js +34 -0
  125. package/dist/cli/commands/sdk/services/workflows.js.map +1 -0
  126. package/dist/cli/commands/sdk/skills.d.ts +3 -0
  127. package/dist/cli/commands/sdk/skills.d.ts.map +1 -0
  128. package/dist/cli/commands/sdk/skills.js +492 -0
  129. package/dist/cli/commands/sdk/skills.js.map +1 -0
  130. package/dist/cli/commands/sdk/users.d.ts +3 -0
  131. package/dist/cli/commands/sdk/users.d.ts.map +1 -0
  132. package/dist/cli/commands/sdk/users.js +81 -0
  133. package/dist/cli/commands/sdk/users.js.map +1 -0
  134. package/dist/cli/commands/sdk/utils/cli-utils.d.ts +35 -0
  135. package/dist/cli/commands/sdk/utils/cli-utils.d.ts.map +1 -0
  136. package/dist/cli/commands/sdk/utils/cli-utils.js +110 -0
  137. package/dist/cli/commands/sdk/utils/cli-utils.js.map +1 -0
  138. package/dist/cli/commands/sdk/utils/datasource-types.d.ts +9 -0
  139. package/dist/cli/commands/sdk/utils/datasource-types.d.ts.map +1 -0
  140. package/dist/cli/commands/sdk/utils/datasource-types.js +61 -0
  141. package/dist/cli/commands/sdk/utils/datasource-types.js.map +1 -0
  142. package/dist/cli/commands/sdk/utils/file-utils.d.ts +8 -0
  143. package/dist/cli/commands/sdk/utils/file-utils.d.ts.map +1 -0
  144. package/dist/cli/commands/sdk/utils/file-utils.js +21 -0
  145. package/dist/cli/commands/sdk/utils/file-utils.js.map +1 -0
  146. package/dist/cli/commands/sdk/utils/render.d.ts +82 -0
  147. package/dist/cli/commands/sdk/utils/render.d.ts.map +1 -0
  148. package/dist/cli/commands/sdk/utils/render.js +149 -0
  149. package/dist/cli/commands/sdk/utils/render.js.map +1 -0
  150. package/dist/cli/commands/sdk/workflows.d.ts +3 -0
  151. package/dist/cli/commands/sdk/workflows.d.ts.map +1 -0
  152. package/dist/cli/commands/sdk/workflows.js +170 -0
  153. package/dist/cli/commands/sdk/workflows.js.map +1 -0
  154. package/dist/cli/commands/setup.js +4 -0
  155. package/dist/cli/commands/setup.js.map +1 -1
  156. package/dist/cli/commands/shared/prompts/storage-scope.d.ts +2 -1
  157. package/dist/cli/commands/shared/prompts/storage-scope.d.ts.map +1 -1
  158. package/dist/cli/commands/shared/prompts/storage-scope.js +4 -3
  159. package/dist/cli/commands/shared/prompts/storage-scope.js.map +1 -1
  160. package/dist/cli/commands/skills/add.d.ts.map +1 -1
  161. package/dist/cli/commands/skills/add.js +28 -9
  162. package/dist/cli/commands/skills/add.js.map +1 -1
  163. package/dist/cli/commands/skills/lib/skills-sh-telemetry.d.ts +5 -0
  164. package/dist/cli/commands/skills/lib/skills-sh-telemetry.d.ts.map +1 -1
  165. package/dist/cli/commands/skills/lib/skills-sh-telemetry.js +22 -4
  166. package/dist/cli/commands/skills/lib/skills-sh-telemetry.js.map +1 -1
  167. package/dist/cli/commands/skills/setup/generators/claude-skill-generator.d.ts +3 -2
  168. package/dist/cli/commands/skills/setup/generators/claude-skill-generator.d.ts.map +1 -1
  169. package/dist/cli/commands/skills/setup/generators/claude-skill-generator.js +29 -14
  170. package/dist/cli/commands/skills/setup/generators/claude-skill-generator.js.map +1 -1
  171. package/dist/cli/commands/skills/setup/helpers.d.ts +3 -2
  172. package/dist/cli/commands/skills/setup/helpers.d.ts.map +1 -1
  173. package/dist/cli/commands/skills/setup/helpers.js +6 -8
  174. package/dist/cli/commands/skills/setup/helpers.js.map +1 -1
  175. package/dist/cli/commands/skills/setup/index.js +8 -16
  176. package/dist/cli/commands/skills/setup/index.js.map +1 -1
  177. package/dist/cli/commands/skills/setup/sync.d.ts.map +1 -1
  178. package/dist/cli/commands/skills/setup/sync.js +6 -5
  179. package/dist/cli/commands/skills/setup/sync.js.map +1 -1
  180. package/dist/cli/commands/uninstall.d.ts.map +1 -1
  181. package/dist/cli/commands/uninstall.js +17 -0
  182. package/dist/cli/commands/uninstall.js.map +1 -1
  183. package/dist/cli/index.js +2 -0
  184. package/dist/cli/index.js.map +1 -1
  185. package/dist/env/types.d.ts +11 -4
  186. package/dist/env/types.d.ts.map +1 -1
  187. package/dist/env/types.js +5 -0
  188. package/dist/env/types.js.map +1 -1
  189. package/dist/migrations/004-skills-assistants-top-level.migration.d.ts +10 -0
  190. package/dist/migrations/004-skills-assistants-top-level.migration.d.ts.map +1 -0
  191. package/dist/migrations/004-skills-assistants-top-level.migration.js +65 -0
  192. package/dist/migrations/004-skills-assistants-top-level.migration.js.map +1 -0
  193. package/dist/migrations/005-skill-slug-format.migration.d.ts +10 -0
  194. package/dist/migrations/005-skill-slug-format.migration.d.ts.map +1 -0
  195. package/dist/migrations/005-skill-slug-format.migration.js +82 -0
  196. package/dist/migrations/005-skill-slug-format.migration.js.map +1 -0
  197. package/dist/migrations/index.d.ts +2 -0
  198. package/dist/migrations/index.d.ts.map +1 -1
  199. package/dist/migrations/index.js +2 -2
  200. package/dist/migrations/index.js.map +1 -1
  201. package/dist/providers/core/codemie-auth-helpers.d.ts +4 -1
  202. package/dist/providers/core/codemie-auth-helpers.d.ts.map +1 -1
  203. package/dist/providers/core/codemie-auth-helpers.js +21 -18
  204. package/dist/providers/core/codemie-auth-helpers.js.map +1 -1
  205. package/dist/providers/plugins/anthropic-subscription/anthropic-subscription.setup-steps.d.ts.map +1 -1
  206. package/dist/providers/plugins/anthropic-subscription/anthropic-subscription.setup-steps.js +4 -2
  207. package/dist/providers/plugins/anthropic-subscription/anthropic-subscription.setup-steps.js.map +1 -1
  208. package/dist/providers/plugins/sso/proxy/plugins/endpoint-blocker.plugin.d.ts.map +1 -1
  209. package/dist/providers/plugins/sso/proxy/plugins/endpoint-blocker.plugin.js +14 -6
  210. package/dist/providers/plugins/sso/proxy/plugins/endpoint-blocker.plugin.js.map +1 -1
  211. package/dist/providers/plugins/sso/proxy/plugins/index.d.ts +1 -0
  212. package/dist/providers/plugins/sso/proxy/plugins/index.d.ts.map +1 -1
  213. package/dist/providers/plugins/sso/proxy/plugins/index.js +3 -0
  214. package/dist/providers/plugins/sso/proxy/plugins/index.js.map +1 -1
  215. package/dist/providers/plugins/sso/proxy/plugins/session-expiry-handler.plugin.d.ts +9 -0
  216. package/dist/providers/plugins/sso/proxy/plugins/session-expiry-handler.plugin.d.ts.map +1 -0
  217. package/dist/providers/plugins/sso/proxy/plugins/session-expiry-handler.plugin.js +23 -0
  218. package/dist/providers/plugins/sso/proxy/plugins/session-expiry-handler.plugin.js.map +1 -0
  219. package/dist/providers/plugins/sso/proxy/proxy-errors.d.ts.map +1 -1
  220. package/dist/providers/plugins/sso/proxy/proxy-errors.js +1 -2
  221. package/dist/providers/plugins/sso/proxy/proxy-errors.js.map +1 -1
  222. package/dist/providers/plugins/sso/proxy/sso.proxy.d.ts +10 -0
  223. package/dist/providers/plugins/sso/proxy/sso.proxy.d.ts.map +1 -1
  224. package/dist/providers/plugins/sso/proxy/sso.proxy.js +77 -2
  225. package/dist/providers/plugins/sso/proxy/sso.proxy.js.map +1 -1
  226. package/dist/providers/plugins/sso/session/processors/metrics/metrics-aggregator.js +1 -1
  227. package/dist/providers/plugins/sso/session/processors/metrics/metrics-aggregator.js.map +1 -1
  228. package/dist/providers/plugins/sso/session/processors/metrics/metrics-api-client.js +2 -2
  229. package/dist/providers/plugins/sso/session/processors/metrics/metrics-api-client.js.map +1 -1
  230. package/dist/providers/plugins/sso/session/processors/metrics/metrics-sync-processor.js +1 -1
  231. package/dist/providers/plugins/sso/session/processors/metrics/metrics-sync-processor.js.map +1 -1
  232. package/dist/providers/plugins/sso/sso.auth.d.ts.map +1 -1
  233. package/dist/providers/plugins/sso/sso.auth.js +10 -2
  234. package/dist/providers/plugins/sso/sso.auth.js.map +1 -1
  235. package/dist/providers/plugins/sso/sso.setup-steps.d.ts.map +1 -1
  236. package/dist/providers/plugins/sso/sso.setup-steps.js +3 -1
  237. package/dist/providers/plugins/sso/sso.setup-steps.js.map +1 -1
  238. package/dist/utils/config.d.ts +16 -11
  239. package/dist/utils/config.d.ts.map +1 -1
  240. package/dist/utils/config.js +87 -82
  241. package/dist/utils/config.js.map +1 -1
  242. package/dist/utils/paths.d.ts +5 -0
  243. package/dist/utils/paths.d.ts.map +1 -1
  244. package/dist/utils/paths.js +14 -0
  245. package/dist/utils/paths.js.map +1 -1
  246. package/dist/utils/profile.d.ts +0 -2
  247. package/dist/utils/profile.d.ts.map +1 -1
  248. package/dist/utils/profile.js +0 -5
  249. package/dist/utils/profile.js.map +1 -1
  250. package/dist/utils/security.d.ts.map +1 -1
  251. package/dist/utils/security.js +18 -8
  252. package/dist/utils/security.js.map +1 -1
  253. package/dist/utils/slug.d.ts +2 -0
  254. package/dist/utils/slug.d.ts.map +1 -0
  255. package/dist/utils/slug.js +6 -0
  256. package/dist/utils/slug.js.map +1 -0
  257. package/package.json +4 -2
  258. package/scripts/postinstall.mjs +52 -0
@@ -0,0 +1,3270 @@
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>__DASHBOARD_TITLE__ — LiteLLM</title>
7
+ <script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.0/dist/chart.umd.min.js"></script>
8
+ <style>
9
+ /**
10
+ * CodeMie Design Tokens
11
+ * CSS Custom Properties for the CodeMie UI design system.
12
+ * Dark theme is default. Add class="light" to <html> for light mode.
13
+ *
14
+ * Source: tailwind.config.ts + component analysis from codemie-ui-next
15
+ */
16
+
17
+ /* ============================================================
18
+ FONT IMPORT
19
+ ============================================================ */
20
+ @import url('https://fonts.googleapis.com/css2?family=Inter:ital,wght@0,400;0,500;0,600;0,700;1,400&family=JetBrains+Mono:wght@400;500&display=swap');
21
+
22
+ /* ============================================================
23
+ GLOBAL SCALE (theme-independent)
24
+ ============================================================ */
25
+ :root {
26
+ /* --- Typography --- */
27
+ --font-sans: 'Inter', 'Geist', -apple-system, BlinkMacSystemFont, 'Segoe UI', Arial, sans-serif;
28
+ --font-mono: 'JetBrains Mono', 'GeistMono', 'Fira Code', 'Courier New', monospace;
29
+
30
+ --text-xs-1: 0.625rem; /* 10px */
31
+ --text-xs: 0.75rem; /* 12px */
32
+ --text-sm-1: 0.813rem; /* 13px */
33
+ --text-sm: 0.875rem; /* 14px */
34
+ --text-base: 1rem; /* 16px */
35
+ --text-h1: 2rem; /* 32px */
36
+ --text-h2: 1.5rem; /* 24px */
37
+ --text-h3: 1rem; /* 16px */
38
+ --text-h4: 0.875rem; /* 14px */
39
+ --text-h5: 0.75rem; /* 12px */
40
+
41
+ --lh-h1: 2rem; /* 32px */
42
+ --lh-h2: 1.5rem; /* 24px */
43
+ --lh-h3: 1.3125rem; /* 21px */
44
+ --lh-h4: 1.125rem; /* 18px */
45
+ --lh-h5: 1rem; /* 16px */
46
+
47
+ /* --- Border Radius --- */
48
+ --radius-sm: 4px;
49
+ --radius-md: 6px;
50
+ --radius-lg: 8px;
51
+ --radius-xl: 12px;
52
+ --radius-2xl: 16px;
53
+ --radius-full: 9999px;
54
+
55
+ /* --- Spacing --- */
56
+ --space-0-5: 0.125rem; /* 2px */
57
+ --space-1: 0.25rem; /* 4px */
58
+ --space-1-5: 0.375rem; /* 6px */
59
+ --space-2: 0.5rem; /* 8px */
60
+ --space-2-5: 0.625rem; /* 10px */
61
+ --space-3: 0.75rem; /* 12px */
62
+ --space-4: 1rem; /* 16px */
63
+ --space-5: 1.25rem; /* 20px */
64
+ --space-6: 1.5rem; /* 24px */
65
+ --space-8: 2rem; /* 32px */
66
+
67
+ /* --- Shadows (dark-friendly defaults) --- */
68
+ --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.4);
69
+ --shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.5), 0 2px 4px -2px rgba(0, 0, 0, 0.3);
70
+ --shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.6), 0 4px 6px -4px rgba(0, 0, 0, 0.4);
71
+ --shadow-sidebar: -1px 0 0 0 rgba(255, 255, 255, 0.1);
72
+
73
+ /* --- Transitions --- */
74
+ --transition-fast: 120ms ease;
75
+ --transition-base: 200ms ease;
76
+ --transition-slow: 300ms ease;
77
+
78
+ /* --- Layout --- */
79
+ --navbar-width: 72px;
80
+ --navbar-width-expanded: 196px;
81
+ --sidebar-width: 308px;
82
+ --layout-header-height: 56px;
83
+ --card-height: 158px;
84
+
85
+ /* --- Z-index --- */
86
+ --z-dropdown: 30;
87
+ --z-sticky: 40;
88
+ --z-modal: 50;
89
+ --z-toast: 60;
90
+ }
91
+
92
+ /* ============================================================
93
+ DARK THEME (default)
94
+ ============================================================ */
95
+ :root,
96
+ :root.dark {
97
+
98
+ /* --- Surface / Background --- */
99
+ --color-bg-page: #1A1A1A; /* Main page background */
100
+ --color-bg-sidebar: #151515; /* Sidebar & card backgrounds */
101
+ --color-bg-card: #151515; /* Card surface */
102
+ --color-bg-nav: #000000; /* Navigation rail */
103
+ --color-bg-elevated: #2E2E2E; /* Modals, dropdowns, popovers */
104
+ --color-bg-secondary: #212224; /* Secondary panels */
105
+ --color-bg-tertiary: #2E3033; /* Tertiary areas */
106
+ --color-bg-quaternary: #333436; /* Table headers, input prefix */
107
+ --color-bg-hover: #212224; /* Interactive hover state */
108
+ --color-bg-hover-strong: #333436; /* Stronger hover */
109
+ --color-bg-input: #1A1A1A; /* Input & textarea background */
110
+ --color-bg-input-prefix: #333436; /* Input side label */
111
+ --color-bg-btn-primary: #20222E; /* Primary button background */
112
+ --color-bg-btn-primary-h: #262941; /* Primary button hover */
113
+ --color-bg-pagination-active: #212224;
114
+
115
+ /* --- Text --- */
116
+ --color-text-primary: #FFFFFF; /* Main body text */
117
+ --color-text-secondary: #F2F0EF; /* Secondary text */
118
+ --color-text-tertiary: #CCCCCC; /* Tertiary / subdued */
119
+ --color-text-muted: #BBBBBB; /* Labels, captions */
120
+ --color-text-placeholder: #CCCCCC; /* Input placeholder */
121
+ --color-text-link: #F2F0EF; /* Links in dark mode */
122
+ --color-text-link-hover: #F2F0EF;
123
+ --color-text-inverse: #FFFFFF; /* Text on colored backgrounds */
124
+ --color-text-heading: #BBBBBB; /* Section headings */
125
+ --color-text-nav: #CCCCCC; /* Navigation labels */
126
+
127
+ /* --- Borders --- */
128
+ --color-border-primary: #333436; /* Default border */
129
+ --color-border-secondary: #47484A; /* Hover/focus border */
130
+ --color-border-structural: #333436; /* Layout dividers */
131
+ --color-border-subtle: #4C4C4C; /* Very subtle border */
132
+ --color-border-accent: #FFFFFF; /* Accent / active border */
133
+ --color-border-focus: #FFFFFF; /* Focus ring */
134
+ --color-border-error: #F9303C; /* Error state border */
135
+ --color-border-panel: #333436; /* Panel outlines */
136
+ --color-border-btn-secondary: #333436;
137
+ --color-border-btn-secondary-h: transparent;
138
+
139
+ /* --- Icons --- */
140
+ --color-icon-primary: #FFFFFF;
141
+ --color-icon-secondary: #CCCCCC;
142
+ --color-icon-tertiary: #999999;
143
+ --color-icon-error: #F9303C;
144
+
145
+ /* --- Semantic Colors --- */
146
+ --color-success: #259F4C;
147
+ --color-success-bg: #1B271F;
148
+ --color-success-border: #259F4C;
149
+ --color-success-text: #259F4C;
150
+
151
+ --color-error: #F9303C;
152
+ --color-error-bg: #262121;
153
+ --color-error-border: #FE3B4C;
154
+ --color-error-text: #FE3B4C;
155
+
156
+ --color-warning: #F5A534;
157
+ --color-warning-bg: #492B00;
158
+ --color-warning-border: #663B00;
159
+ --color-warning-text: #F5A534;
160
+
161
+ --color-info: #2297F6;
162
+ --color-info-bg: #002442;
163
+ --color-info-border: #003A69;
164
+ --color-info-text: #2297F6;
165
+
166
+ --color-purple: #C084FC;
167
+ --color-purple-bg: #2D1B3D;
168
+ --color-purple-border: #2D1B3D;
169
+ --color-purple-text: #F3E8FF;
170
+
171
+ --color-cyan: #06B6D4;
172
+ --color-cyan-bg: #003942;
173
+ --color-cyan-border: #005866;
174
+
175
+ /* --- Status Badges --- */
176
+ --status-not-started-text: #A0A0A0;
177
+ --status-not-started-bg: #333333;
178
+ --status-not-started-border: #4C4C4C;
179
+
180
+ --status-in-progress-text: #2297F6;
181
+ --status-in-progress-bg: #002442;
182
+ --status-in-progress-border: #003A69;
183
+
184
+ --status-pending-text: #06B6D4;
185
+ --status-pending-bg: #003942;
186
+ --status-pending-border: #005866;
187
+
188
+ --status-success-text: #259F4C;
189
+ --status-success-bg: #1B271F;
190
+ --status-success-border: #259F4C;
191
+
192
+ --status-error-text: #FE3B4C;
193
+ --status-error-bg: #262121;
194
+ --status-error-border: #FE3B4C;
195
+
196
+ --status-warning-text: #F5A534;
197
+ --status-warning-bg: #492B00;
198
+ --status-warning-border: #663B00;
199
+
200
+ --status-advanced-text: #C084FC;
201
+ --status-advanced-bg: #2D1B3D;
202
+ --status-advanced-border: #2D1B3D;
203
+
204
+ /* --- Gradients --- */
205
+ --gradient-primary-btn: linear-gradient(90deg, #672D92, #547CCC);
206
+ --gradient-brand: linear-gradient(152deg, #0078C2, #0047FF, #8453D2);
207
+ --gradient-magical: linear-gradient(90deg, #672D92, #5677C8);
208
+ --gradient-purple-radial: radial-gradient(271.77% 163.1% at 50% -10.71%, #200E32 0%, #9E00FF 75.14%, #EC56FF 100%);
209
+ --gradient-switch-off: linear-gradient(to right, #BBB, #666);
210
+ --gradient-switch-on: linear-gradient(to right, #672C92, #547CCC);
211
+
212
+ /* --- Primitive Palette (for swatch display) --- */
213
+ --blue-25: #F1F8FF; --blue-50: #D5E7FC; --blue-100: #B2D7FF;
214
+ --blue-300: #2297F6; --blue-400: #007AFF; --blue-500: #4E32FF;
215
+ --blue-550: #0C4DAF; --blue-600: #003A69; --blue-800: #002442;
216
+ }
217
+
218
+ /* ============================================================
219
+ LIGHT THEME
220
+ Apply class="light" to <html> or <body>
221
+ ============================================================ */
222
+ .light {
223
+ --color-bg-page: #F9F9F9;
224
+ --color-bg-sidebar: #FBFBFB;
225
+ --color-bg-card: #FFFFFF;
226
+ --color-bg-nav: #FBFBFB;
227
+ --color-bg-elevated: #FFFFFF;
228
+ --color-bg-secondary: #FFFFFF;
229
+ --color-bg-tertiary: #FBFBFB;
230
+ --color-bg-quaternary: #B2D7FF;
231
+ --color-bg-hover: #D5E7FC;
232
+ --color-bg-hover-strong: #D5E7FC;
233
+ --color-bg-input: #FFFFFF;
234
+ --color-bg-input-prefix: #EEEEEE;
235
+ --color-bg-btn-primary: #D5E7FC;
236
+ --color-bg-btn-primary-h: #B2D7FF;
237
+ --color-bg-pagination-active: #D5E7FC;
238
+
239
+ --color-text-primary: #333333;
240
+ --color-text-secondary: #333333;
241
+ --color-text-tertiary: #333333;
242
+ --color-text-muted: #666666;
243
+ --color-text-placeholder: #999999;
244
+ --color-text-link: #007AFF;
245
+ --color-text-link-hover: #0C4DAF;
246
+ --color-text-inverse: #FFFFFF;
247
+ --color-text-heading: #007AFF;
248
+ --color-text-nav: #FFFFFF;
249
+
250
+ --color-border-primary: #CCCCCC;
251
+ --color-border-secondary: #BBBBBB;
252
+ --color-border-structural: #E5E5E5;
253
+ --color-border-subtle: #999999;
254
+ --color-border-accent: #007AFF;
255
+ --color-border-focus: #000000;
256
+ --color-border-error: #F9303C;
257
+ --color-border-panel: #E5E5E5;
258
+ --color-border-btn-secondary: transparent;
259
+ --color-border-btn-secondary-h: #007AFF;
260
+
261
+ --color-icon-primary: #666666;
262
+ --color-icon-secondary: #333333;
263
+ --color-icon-tertiary: #707070;
264
+ --color-icon-error: #F9303C;
265
+
266
+ --color-success: #259F4C;
267
+ --color-success-bg: #E6F7E6;
268
+ --color-success-border: #259F4C;
269
+ --color-success-text: #259F4C;
270
+
271
+ --color-error: #F9303C;
272
+ --color-error-bg: #F0E2E3;
273
+ --color-error-border: #FE3B4C;
274
+ --color-error-text: #FE3B4C;
275
+
276
+ --color-warning: #F5A534;
277
+ --color-warning-bg: #FAF2E7;
278
+ --color-warning-border: #F5A534;
279
+ --color-warning-text: #F5A534;
280
+
281
+ --color-info: #2297F6;
282
+ --color-info-bg: #D5E7FC;
283
+ --color-info-border: #2297F6;
284
+ --color-info-text: #2297F6;
285
+
286
+ --color-purple: #8B5CF6;
287
+ --color-purple-bg: #F3E8FF;
288
+ --color-purple-border: #F3E8FF;
289
+ --color-purple-text: #C084FC;
290
+
291
+ --color-cyan: #06B6D4;
292
+ --color-cyan-bg: #DFFAFF;
293
+ --color-cyan-border: #005866;
294
+
295
+ --status-not-started-text: #A0A0A0;
296
+ --status-not-started-bg: #EEEEEE;
297
+ --status-not-started-border: #999999;
298
+
299
+ --status-in-progress-text: #2297F6;
300
+ --status-in-progress-bg: #D5E7FC;
301
+ --status-in-progress-border: #2297F6;
302
+
303
+ --status-pending-text: #06B6D4;
304
+ --status-pending-bg: #DFFAFF;
305
+ --status-pending-border: #06B6D4;
306
+
307
+ --status-success-text: #259F4C;
308
+ --status-success-bg: #E6F7E6;
309
+ --status-success-border: #259F4C;
310
+
311
+ --status-error-text: #FE3B4C;
312
+ --status-error-bg: #F0E2E3;
313
+ --status-error-border: #FE3B4C;
314
+
315
+ --status-warning-text: #F5A534;
316
+ --status-warning-bg: #FAF2E7;
317
+ --status-warning-border: #F5A534;
318
+
319
+ --status-advanced-text: #8B5CF6;
320
+ --status-advanced-bg: #F3E8FF;
321
+ --status-advanced-border: #C084FC;
322
+
323
+ --gradient-primary-btn: linear-gradient(90deg, #3676f7, #cc22f2);
324
+ --gradient-magical: linear-gradient(90deg, #3676f7, #cc22f2);
325
+ --gradient-switch-off: linear-gradient(to right, #fff, #fff);
326
+ --gradient-switch-on: linear-gradient(to right, #007AFF, #007AFF);
327
+
328
+ --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.06);
329
+ --shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.08), 0 2px 4px -2px rgba(0, 0, 0, 0.06);
330
+ --shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -4px rgba(0, 0, 0, 0.08);
331
+ }
332
+ /**
333
+ * CodeMie Base Styles
334
+ * Reset, body, scrollbar, and global base rules.
335
+ */
336
+
337
+ /* ============================================================
338
+ RESET
339
+ ============================================================ */
340
+ *, *::before, *::after {
341
+ box-sizing: border-box;
342
+ margin: 0;
343
+ padding: 0;
344
+ }
345
+
346
+ /* ============================================================
347
+ BODY
348
+ ============================================================ */
349
+ html {
350
+ font-size: 16px;
351
+ -webkit-text-size-adjust: 100%;
352
+ scroll-behavior: smooth;
353
+ }
354
+
355
+ body {
356
+ font-family: var(--font-sans);
357
+ font-size: var(--text-sm);
358
+ line-height: 1.5;
359
+ color: var(--color-text-primary);
360
+ background-color: var(--color-bg-page);
361
+ -webkit-font-smoothing: antialiased;
362
+ -moz-osx-font-smoothing: grayscale;
363
+ }
364
+
365
+ /* ============================================================
366
+ LINKS
367
+ ============================================================ */
368
+ a {
369
+ color: var(--color-text-link);
370
+ text-decoration: none;
371
+ transition: color var(--transition-fast);
372
+ }
373
+
374
+ a:hover {
375
+ color: var(--color-text-link-hover);
376
+ text-decoration: underline;
377
+ }
378
+
379
+ /* ============================================================
380
+ SCROLLBAR (WebKit)
381
+ ============================================================ */
382
+ ::-webkit-scrollbar {
383
+ width: 6px;
384
+ height: 6px;
385
+ }
386
+
387
+ ::-webkit-scrollbar-track {
388
+ background: transparent;
389
+ }
390
+
391
+ ::-webkit-scrollbar-thumb {
392
+ background: var(--color-border-subtle);
393
+ border-radius: var(--radius-full);
394
+ }
395
+
396
+ ::-webkit-scrollbar-thumb:hover {
397
+ background: var(--color-border-secondary);
398
+ }
399
+
400
+ /* ============================================================
401
+ SELECTION
402
+ ============================================================ */
403
+ ::selection {
404
+ background: rgba(34, 151, 246, 0.3);
405
+ color: var(--color-text-primary);
406
+ }
407
+
408
+ /* ============================================================
409
+ FOCUS RING
410
+ ============================================================ */
411
+ :focus-visible {
412
+ outline: 2px solid var(--color-border-focus);
413
+ outline-offset: 2px;
414
+ border-radius: var(--radius-sm);
415
+ }
416
+
417
+ /* ============================================================
418
+ IMAGES & MEDIA
419
+ ============================================================ */
420
+ img, svg, video {
421
+ display: block;
422
+ max-width: 100%;
423
+ }
424
+
425
+ /* ============================================================
426
+ LISTS
427
+ ============================================================ */
428
+ ul, ol {
429
+ list-style: none;
430
+ }
431
+
432
+ /* ============================================================
433
+ CODE
434
+ ============================================================ */
435
+ code, pre, kbd, samp {
436
+ font-family: var(--font-mono);
437
+ }
438
+
439
+ code {
440
+ font-size: 0.875em;
441
+ background: var(--color-bg-quaternary);
442
+ color: var(--color-text-secondary);
443
+ padding: 0.1em 0.4em;
444
+ border-radius: var(--radius-sm);
445
+ border: 1px solid var(--color-border-primary);
446
+ }
447
+
448
+ pre {
449
+ background: var(--color-bg-quaternary);
450
+ border: 1px solid var(--color-border-primary);
451
+ border-radius: var(--radius-lg);
452
+ padding: var(--space-4);
453
+ overflow-x: auto;
454
+ font-size: var(--text-xs);
455
+ line-height: 1.6;
456
+ color: var(--color-text-secondary);
457
+ }
458
+
459
+ pre code {
460
+ background: transparent;
461
+ border: none;
462
+ padding: 0;
463
+ font-size: inherit;
464
+ }
465
+
466
+ /* ============================================================
467
+ HR / DIVIDER
468
+ ============================================================ */
469
+ hr, .divider {
470
+ border: none;
471
+ border-top: 1px solid var(--color-border-structural);
472
+ margin: var(--space-4) 0;
473
+ }
474
+
475
+ /* ============================================================
476
+ LAYOUT HELPERS
477
+ ============================================================ */
478
+ .container {
479
+ width: 100%;
480
+ max-width: 1200px;
481
+ margin: 0 auto;
482
+ padding: 0 var(--space-4);
483
+ }
484
+
485
+ .section {
486
+ padding: var(--space-8) 0;
487
+ }
488
+
489
+ /* ============================================================
490
+ ICON (inline SVG sizing helpers)
491
+ ============================================================ */
492
+ .icon-xs { width: 12px; height: 12px; flex-shrink: 0; }
493
+ .icon-sm { width: 16px; height: 16px; flex-shrink: 0; }
494
+ .icon-md { width: 18px; height: 18px; flex-shrink: 0; }
495
+ .icon-lg { width: 20px; height: 20px; flex-shrink: 0; }
496
+ .icon-xl { width: 24px; height: 24px; flex-shrink: 0; }
497
+ .icon-2xl { width: 32px; height: 32px; flex-shrink: 0; }
498
+ /**
499
+ * CodeMie Typography
500
+ * Headings, text scales, font utilities.
501
+ */
502
+
503
+ /* ============================================================
504
+ HEADINGS
505
+ ============================================================ */
506
+ h1, .h1 {
507
+ font-size: var(--text-h1);
508
+ line-height: var(--lh-h1);
509
+ font-weight: 700;
510
+ color: var(--color-text-primary);
511
+ letter-spacing: -0.02em;
512
+ }
513
+
514
+ h2, .h2 {
515
+ font-size: var(--text-h2);
516
+ line-height: var(--lh-h2);
517
+ font-weight: 600;
518
+ color: var(--color-text-primary);
519
+ }
520
+
521
+ h3, .h3 {
522
+ font-size: var(--text-h3);
523
+ line-height: var(--lh-h3);
524
+ font-weight: 600;
525
+ color: var(--color-text-primary);
526
+ }
527
+
528
+ h4, .h4 {
529
+ font-size: var(--text-h4);
530
+ line-height: var(--lh-h4);
531
+ font-weight: 600;
532
+ color: var(--color-text-primary);
533
+ }
534
+
535
+ h5, .h5 {
536
+ font-size: var(--text-h5);
537
+ line-height: var(--lh-h5);
538
+ font-weight: 600;
539
+ color: var(--color-text-primary);
540
+ }
541
+
542
+ h6, .h6 {
543
+ font-size: var(--text-xs);
544
+ line-height: 1.4;
545
+ font-weight: 600;
546
+ color: var(--color-text-muted);
547
+ text-transform: uppercase;
548
+ letter-spacing: 0.05em;
549
+ }
550
+
551
+ /* Section label (used above section headings) */
552
+ .section-label {
553
+ font-size: var(--text-xs);
554
+ font-weight: 600;
555
+ color: var(--color-text-heading);
556
+ text-transform: uppercase;
557
+ letter-spacing: 0.05em;
558
+ margin-bottom: var(--space-2);
559
+ }
560
+
561
+ /* ============================================================
562
+ TEXT SIZE UTILITIES
563
+ ============================================================ */
564
+ .text-2xl { font-size: 1.5rem; }
565
+ .text-xl { font-size: 1.25rem; }
566
+ .text-lg { font-size: 1.125rem; }
567
+ .text-base { font-size: var(--text-base); }
568
+ .text-sm { font-size: var(--text-sm); }
569
+ .text-sm-1 { font-size: var(--text-sm-1); }
570
+ .text-xs { font-size: var(--text-xs); }
571
+ .text-xs-1 { font-size: var(--text-xs-1); }
572
+
573
+ /* ============================================================
574
+ FONT WEIGHT UTILITIES
575
+ ============================================================ */
576
+ .font-normal { font-weight: 400; }
577
+ .font-medium { font-weight: 500; }
578
+ .font-semibold { font-weight: 600; }
579
+ .font-bold { font-weight: 700; }
580
+
581
+ /* ============================================================
582
+ TEXT COLOR UTILITIES
583
+ ============================================================ */
584
+ .text-primary { color: var(--color-text-primary); }
585
+ .text-secondary { color: var(--color-text-secondary); }
586
+ .text-tertiary { color: var(--color-text-tertiary); }
587
+ .text-muted { color: var(--color-text-muted); }
588
+ .text-link { color: var(--color-text-link); }
589
+ .text-inverse { color: var(--color-text-inverse); }
590
+
591
+ .text-success { color: var(--color-success-text); }
592
+ .text-error { color: var(--color-error-text); }
593
+ .text-warning { color: var(--color-warning-text); }
594
+ .text-info { color: var(--color-info-text); }
595
+
596
+ /* ============================================================
597
+ TEXT STYLE UTILITIES
598
+ ============================================================ */
599
+ .uppercase { text-transform: uppercase; }
600
+ .capitalize { text-transform: capitalize; }
601
+ .lowercase { text-transform: lowercase; }
602
+
603
+ .truncate {
604
+ overflow: hidden;
605
+ text-overflow: ellipsis;
606
+ white-space: nowrap;
607
+ }
608
+
609
+ .line-clamp-2 {
610
+ display: -webkit-box;
611
+ -webkit-line-clamp: 2;
612
+ -webkit-box-orient: vertical;
613
+ overflow: hidden;
614
+ }
615
+
616
+ .line-clamp-3 {
617
+ display: -webkit-box;
618
+ -webkit-line-clamp: 3;
619
+ -webkit-box-orient: vertical;
620
+ overflow: hidden;
621
+ }
622
+
623
+ .nowrap { white-space: nowrap; }
624
+ .text-left { text-align: left; }
625
+ .text-center { text-align: center; }
626
+ .text-right { text-align: right; }
627
+
628
+ /* ============================================================
629
+ PARAGRAPH
630
+ ============================================================ */
631
+ p {
632
+ color: var(--color-text-secondary);
633
+ font-size: var(--text-sm);
634
+ line-height: 1.6;
635
+ }
636
+
637
+ p + p {
638
+ margin-top: var(--space-3);
639
+ }
640
+
641
+ /* Caption / helper text */
642
+ .caption {
643
+ font-size: var(--text-xs);
644
+ color: var(--color-text-muted);
645
+ line-height: 1.4;
646
+ }
647
+
648
+ /* Monospace inline text */
649
+ .mono {
650
+ font-family: var(--font-mono);
651
+ font-size: 0.9em;
652
+ }
653
+ /**
654
+ * CodeMie Button Components
655
+ * All button variants, sizes, and states.
656
+ *
657
+ * Usage: <button class="btn btn-primary btn-md">Label</button>
658
+ */
659
+
660
+ /* ============================================================
661
+ BASE BUTTON
662
+ ============================================================ */
663
+ .btn {
664
+ display: inline-flex;
665
+ align-items: center;
666
+ justify-content: center;
667
+ gap: var(--space-1-5);
668
+ font-family: var(--font-sans);
669
+ font-weight: 600;
670
+ white-space: nowrap;
671
+ border-radius: var(--radius-lg);
672
+ border: 1px solid transparent;
673
+ cursor: pointer;
674
+ transition:
675
+ background-color var(--transition-base),
676
+ border-color var(--transition-base),
677
+ color var(--transition-base),
678
+ opacity var(--transition-base);
679
+ text-decoration: none;
680
+ user-select: none;
681
+ outline: none;
682
+ position: relative;
683
+ overflow: hidden;
684
+ -webkit-font-smoothing: antialiased;
685
+ }
686
+
687
+ .btn:focus-visible {
688
+ outline: 2px solid var(--color-border-focus);
689
+ outline-offset: 2px;
690
+ }
691
+
692
+ .btn:disabled,
693
+ .btn[aria-disabled='true'] {
694
+ cursor: not-allowed;
695
+ opacity: 0.5;
696
+ pointer-events: none;
697
+ }
698
+
699
+ /* ============================================================
700
+ SIZES
701
+ ============================================================ */
702
+ /* Small — 24px height */
703
+ .btn-sm {
704
+ height: 24px;
705
+ padding: 0 var(--space-1-5);
706
+ gap: var(--space-1);
707
+ font-size: var(--text-xs);
708
+ line-height: 20px;
709
+ }
710
+
711
+ /* Medium — 28px height (default) */
712
+ .btn-md {
713
+ height: 28px;
714
+ padding: 0 var(--space-2);
715
+ gap: var(--space-1-5);
716
+ font-size: var(--text-xs);
717
+ line-height: 24px;
718
+ }
719
+
720
+ /* Large — 44px height */
721
+ .btn-lg {
722
+ height: 44px;
723
+ padding: 0 var(--space-4);
724
+ gap: var(--space-2-5);
725
+ font-size: var(--text-sm);
726
+ line-height: 28px;
727
+ }
728
+
729
+ /* ============================================================
730
+ VARIANTS
731
+ ============================================================ */
732
+
733
+ /* Primary — Navy/gradient background with gradient border */
734
+ .btn-primary {
735
+ background-color: var(--color-bg-btn-primary);
736
+ color: var(--color-text-secondary);
737
+ border: 1px solid transparent;
738
+ background-image:
739
+ linear-gradient(var(--color-bg-btn-primary), var(--color-bg-btn-primary)),
740
+ var(--gradient-primary-btn);
741
+ background-origin: border-box;
742
+ background-clip: padding-box, border-box;
743
+ }
744
+
745
+ .btn-primary:hover:not(:disabled) {
746
+ background-image:
747
+ linear-gradient(var(--color-bg-btn-primary-h), var(--color-bg-btn-primary-h)),
748
+ var(--gradient-primary-btn);
749
+ }
750
+
751
+ /* Secondary — Subtle bordered */
752
+ .btn-secondary {
753
+ background-color: var(--color-bg-secondary);
754
+ color: var(--color-text-secondary);
755
+ border-color: var(--color-border-btn-secondary);
756
+ }
757
+
758
+ .btn-secondary:hover:not(:disabled) {
759
+ background-color: var(--color-bg-hover-strong);
760
+ border-color: var(--color-border-btn-secondary-h);
761
+ }
762
+
763
+ /* Base — Standard outlined */
764
+ .btn-base {
765
+ background-color: var(--color-bg-secondary);
766
+ color: var(--color-text-primary);
767
+ border-color: var(--color-border-structural);
768
+ }
769
+
770
+ .btn-base:hover:not(:disabled) {
771
+ background-color: var(--color-border-structural);
772
+ }
773
+
774
+ /* Action — Accent highlighted (blue tint bg in dark) */
775
+ .btn-action {
776
+ background-color: var(--color-bg-btn-primary);
777
+ color: var(--color-text-secondary);
778
+ border-color: var(--color-bg-input-prefix);
779
+ }
780
+
781
+ .btn-action:hover:not(:disabled) {
782
+ border-color: var(--color-border-btn-secondary-h);
783
+ background-color: var(--color-bg-btn-primary-h);
784
+ }
785
+
786
+ /* Delete — Red destructive */
787
+ .btn-delete {
788
+ background-color: rgba(254, 59, 76, 0.1);
789
+ color: var(--color-error-text);
790
+ border-color: var(--color-error-border);
791
+ }
792
+
793
+ .btn-delete:hover:not(:disabled) {
794
+ background-color: rgba(254, 59, 76, 0.15);
795
+ border-color: var(--color-error);
796
+ }
797
+
798
+ /* Tertiary / Ghost — no background */
799
+ .btn-tertiary,
800
+ .btn-ghost {
801
+ background-color: transparent;
802
+ color: var(--color-text-primary);
803
+ border-color: transparent;
804
+ }
805
+
806
+ .btn-tertiary:hover:not(:disabled),
807
+ .btn-ghost:hover:not(:disabled) {
808
+ background-color: var(--color-border-structural);
809
+ }
810
+
811
+ /* Magical — Gradient fill, brand colors */
812
+ .btn-magical {
813
+ background: var(--gradient-magical);
814
+ color: var(--color-text-inverse);
815
+ border-color: var(--color-border-structural);
816
+ }
817
+
818
+ .btn-magical:hover:not(:disabled) {
819
+ filter: brightness(1.1);
820
+ }
821
+
822
+ /* Link — Plain text, no box */
823
+ .btn-link {
824
+ background: transparent;
825
+ border-color: transparent;
826
+ color: var(--color-text-link);
827
+ padding-left: 0;
828
+ padding-right: 0;
829
+ font-weight: 500;
830
+ height: auto;
831
+ }
832
+
833
+ .btn-link:hover:not(:disabled) {
834
+ text-decoration: underline;
835
+ }
836
+
837
+ /* ============================================================
838
+ ICON BUTTON (square)
839
+ ============================================================ */
840
+ .btn-icon {
841
+ padding: 0;
842
+ aspect-ratio: 1;
843
+ }
844
+
845
+ .btn-icon.btn-sm { width: 24px; }
846
+ .btn-icon.btn-md { width: 28px; }
847
+ .btn-icon.btn-lg { width: 44px; }
848
+
849
+ /* ============================================================
850
+ FULL WIDTH
851
+ ============================================================ */
852
+ .btn-full {
853
+ width: 100%;
854
+ }
855
+
856
+ /* ============================================================
857
+ LOADING STATE
858
+ ============================================================ */
859
+ .btn-loading {
860
+ cursor: wait;
861
+ pointer-events: none;
862
+ }
863
+
864
+ .btn-loading::after {
865
+ content: '';
866
+ position: absolute;
867
+ inset: 0;
868
+ background: linear-gradient(
869
+ 90deg,
870
+ transparent 0%,
871
+ rgba(255, 255, 255, 0.12) 50%,
872
+ transparent 100%
873
+ );
874
+ background-size: 200% 100%;
875
+ animation: btn-shimmer 1.5s infinite linear;
876
+ }
877
+
878
+ @keyframes btn-shimmer {
879
+ from { background-position: -200% 0; }
880
+ to { background-position: 200% 0; }
881
+ }
882
+
883
+ /* ============================================================
884
+ BUTTON GROUP
885
+ ============================================================ */
886
+ .btn-group {
887
+ display: inline-flex;
888
+ gap: 0;
889
+ }
890
+
891
+ .btn-group .btn {
892
+ border-radius: 0;
893
+ }
894
+
895
+ .btn-group .btn:first-child {
896
+ border-radius: var(--radius-lg) 0 0 var(--radius-lg);
897
+ }
898
+
899
+ .btn-group .btn:last-child {
900
+ border-radius: 0 var(--radius-lg) var(--radius-lg) 0;
901
+ }
902
+
903
+ .btn-group .btn:not(:first-child) {
904
+ margin-left: -1px;
905
+ }
906
+ /**
907
+ * CodeMie Form Components
908
+ * Input, Textarea, Select, Checkbox, Radio, Switch.
909
+ */
910
+
911
+ /* ============================================================
912
+ FORM GROUP (field wrapper)
913
+ ============================================================ */
914
+ .form-group {
915
+ display: flex;
916
+ flex-direction: column;
917
+ gap: var(--space-1);
918
+ }
919
+
920
+ /* ============================================================
921
+ LABEL
922
+ ============================================================ */
923
+ .form-label {
924
+ display: flex;
925
+ align-items: center;
926
+ gap: var(--space-0-5);
927
+ font-size: var(--text-xs);
928
+ font-weight: 500;
929
+ color: var(--color-text-muted);
930
+ }
931
+
932
+ .form-label .required {
933
+ color: var(--color-error);
934
+ margin-left: 1px;
935
+ }
936
+
937
+ /* ============================================================
938
+ INPUT
939
+ ============================================================ */
940
+ .input-wrapper {
941
+ display: flex;
942
+ align-items: stretch;
943
+ min-height: 32px;
944
+ max-height: 32px;
945
+ border: 1px solid var(--color-border-primary);
946
+ border-radius: var(--radius-lg);
947
+ background-color: var(--color-bg-input);
948
+ transition:
949
+ border-color var(--transition-base),
950
+ box-shadow var(--transition-base);
951
+ overflow: hidden;
952
+ }
953
+
954
+ .input-wrapper:hover:not(.input-disabled) {
955
+ border-color: var(--color-border-secondary);
956
+ }
957
+
958
+ .input-wrapper:focus-within:not(.input-disabled) {
959
+ border-color: var(--color-border-secondary);
960
+ }
961
+
962
+ .input-wrapper.input-error {
963
+ border-color: var(--color-border-error);
964
+ }
965
+
966
+ .input-wrapper.input-disabled {
967
+ opacity: 0.6;
968
+ cursor: not-allowed;
969
+ }
970
+
971
+ /* The actual <input> inside wrapper */
972
+ .form-input {
973
+ flex: 1;
974
+ background: transparent;
975
+ border: none;
976
+ outline: none;
977
+ font-family: var(--font-sans);
978
+ font-size: var(--text-sm);
979
+ color: var(--color-text-primary);
980
+ padding: 0 var(--space-2);
981
+ min-width: 0;
982
+ }
983
+
984
+ .form-input::placeholder {
985
+ color: var(--color-text-placeholder);
986
+ }
987
+
988
+ .form-input:disabled {
989
+ cursor: not-allowed;
990
+ }
991
+
992
+ /* Standalone input (no wrapper needed) */
993
+ input.input,
994
+ .input {
995
+ display: block;
996
+ width: 100%;
997
+ height: 32px;
998
+ padding: 0 var(--space-2);
999
+ background-color: var(--color-bg-input);
1000
+ border: 1px solid var(--color-border-primary);
1001
+ border-radius: var(--radius-lg);
1002
+ font-family: var(--font-sans);
1003
+ font-size: var(--text-sm);
1004
+ color: var(--color-text-primary);
1005
+ outline: none;
1006
+ transition: border-color var(--transition-base);
1007
+ }
1008
+
1009
+ input.input::placeholder,
1010
+ .input::placeholder {
1011
+ color: var(--color-text-placeholder);
1012
+ }
1013
+
1014
+ input.input:hover,
1015
+ .input:hover {
1016
+ border-color: var(--color-border-secondary);
1017
+ }
1018
+
1019
+ input.input:focus,
1020
+ .input:focus {
1021
+ border-color: var(--color-border-secondary);
1022
+ }
1023
+
1024
+ input.input.input-error,
1025
+ .input.input-error {
1026
+ border-color: var(--color-border-error);
1027
+ }
1028
+
1029
+ input.input:disabled,
1030
+ .input:disabled {
1031
+ opacity: 0.6;
1032
+ cursor: not-allowed;
1033
+ }
1034
+
1035
+ /* Sizes */
1036
+ .input-lg {
1037
+ height: 40px;
1038
+ font-size: var(--text-base);
1039
+ padding: 0 var(--space-3);
1040
+ }
1041
+
1042
+ /* Input prefix / suffix labels */
1043
+ .input-prefix,
1044
+ .input-suffix {
1045
+ display: flex;
1046
+ align-items: center;
1047
+ padding: 0 var(--space-2);
1048
+ font-size: var(--text-xs);
1049
+ color: var(--color-text-tertiary);
1050
+ background-color: var(--color-bg-input-prefix);
1051
+ white-space: nowrap;
1052
+ flex-shrink: 0;
1053
+ border-right: 1px solid var(--color-border-subtle);
1054
+ }
1055
+
1056
+ .input-suffix {
1057
+ border-right: none;
1058
+ border-left: 1px solid var(--color-border-subtle);
1059
+ }
1060
+
1061
+ .input-adornment {
1062
+ display: flex;
1063
+ align-items: center;
1064
+ padding: 0 var(--space-2);
1065
+ color: var(--color-icon-tertiary);
1066
+ flex-shrink: 0;
1067
+ }
1068
+
1069
+ /* ============================================================
1070
+ TEXTAREA
1071
+ ============================================================ */
1072
+ textarea.textarea,
1073
+ .textarea {
1074
+ display: block;
1075
+ width: 100%;
1076
+ min-height: 80px;
1077
+ max-height: 384px; /* max-h-96 */
1078
+ padding: var(--space-2-5) var(--space-3);
1079
+ background-color: var(--color-bg-input);
1080
+ border: 1px solid var(--color-border-primary);
1081
+ border-radius: var(--radius-lg);
1082
+ font-family: var(--font-sans);
1083
+ font-size: var(--text-sm);
1084
+ color: var(--color-text-primary);
1085
+ outline: none;
1086
+ resize: vertical;
1087
+ line-height: 1.5;
1088
+ transition: border-color var(--transition-base);
1089
+ }
1090
+
1091
+ textarea.textarea::placeholder,
1092
+ .textarea::placeholder {
1093
+ color: var(--color-text-placeholder);
1094
+ }
1095
+
1096
+ textarea.textarea:hover,
1097
+ .textarea:hover {
1098
+ border-color: var(--color-border-secondary);
1099
+ }
1100
+
1101
+ textarea.textarea:focus,
1102
+ .textarea:focus {
1103
+ border-color: var(--color-border-secondary);
1104
+ }
1105
+
1106
+ textarea.textarea.textarea-error,
1107
+ .textarea.textarea-error {
1108
+ border-color: var(--color-border-error);
1109
+ }
1110
+
1111
+ textarea.textarea:disabled,
1112
+ .textarea:disabled {
1113
+ background-color: var(--color-bg-secondary);
1114
+ color: var(--color-text-tertiary);
1115
+ cursor: not-allowed;
1116
+ opacity: 0.7;
1117
+ }
1118
+
1119
+ /* ============================================================
1120
+ SELECT
1121
+ ============================================================ */
1122
+ .select-trigger {
1123
+ display: flex;
1124
+ align-items: center;
1125
+ justify-content: space-between;
1126
+ height: 32px;
1127
+ padding: 0 var(--space-2);
1128
+ background-color: var(--color-bg-input);
1129
+ border: 1px solid var(--color-border-primary);
1130
+ border-radius: var(--radius-lg);
1131
+ font-family: var(--font-sans);
1132
+ font-size: var(--text-sm);
1133
+ color: var(--color-text-primary);
1134
+ cursor: pointer;
1135
+ user-select: none;
1136
+ transition: border-color var(--transition-base);
1137
+ gap: var(--space-2);
1138
+ }
1139
+
1140
+ .select-trigger:hover {
1141
+ border-color: var(--color-border-secondary);
1142
+ }
1143
+
1144
+ .select-trigger.select-open {
1145
+ border-color: var(--color-border-secondary);
1146
+ }
1147
+
1148
+ .select-trigger .select-arrow {
1149
+ color: var(--color-icon-tertiary);
1150
+ flex-shrink: 0;
1151
+ transition: transform var(--transition-base);
1152
+ }
1153
+
1154
+ .select-trigger.select-open .select-arrow {
1155
+ transform: rotate(180deg);
1156
+ }
1157
+
1158
+ /* Native select fallback */
1159
+ select.select {
1160
+ display: block;
1161
+ width: 100%;
1162
+ height: 32px;
1163
+ padding: 0 var(--space-2);
1164
+ background-color: var(--color-bg-input);
1165
+ border: 1px solid var(--color-border-primary);
1166
+ border-radius: var(--radius-lg);
1167
+ font-family: var(--font-sans);
1168
+ font-size: var(--text-sm);
1169
+ color: var(--color-text-primary);
1170
+ outline: none;
1171
+ cursor: pointer;
1172
+ appearance: none;
1173
+ background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='%23999' stroke-width='2'%3E%3Cpath d='M6 9l6 6 6-6'/%3E%3C/svg%3E");
1174
+ background-repeat: no-repeat;
1175
+ background-position: right 8px center;
1176
+ padding-right: 28px;
1177
+ }
1178
+
1179
+ select.select:hover {
1180
+ border-color: var(--color-border-secondary);
1181
+ }
1182
+
1183
+ select.select:focus {
1184
+ border-color: var(--color-border-secondary);
1185
+ }
1186
+
1187
+ /* Dropdown panel */
1188
+ .select-panel {
1189
+ position: absolute;
1190
+ z-index: var(--z-dropdown);
1191
+ min-width: 160px;
1192
+ max-width: 256px;
1193
+ margin-top: var(--space-2);
1194
+ padding: var(--space-1-5);
1195
+ background-color: var(--color-bg-elevated);
1196
+ border: 1px solid var(--color-border-panel);
1197
+ border-radius: var(--radius-lg);
1198
+ box-shadow: var(--shadow-md);
1199
+ max-height: 240px;
1200
+ overflow-y: auto;
1201
+ }
1202
+
1203
+ .select-option {
1204
+ display: block;
1205
+ padding: var(--space-1-5) var(--space-2-5);
1206
+ font-size: var(--text-sm);
1207
+ color: var(--color-text-primary);
1208
+ border-radius: var(--radius-md);
1209
+ cursor: pointer;
1210
+ transition: background-color var(--transition-fast);
1211
+ white-space: nowrap;
1212
+ overflow: hidden;
1213
+ text-overflow: ellipsis;
1214
+ }
1215
+
1216
+ .select-option:hover {
1217
+ background-color: var(--color-bg-hover-strong);
1218
+ }
1219
+
1220
+ .select-option.selected {
1221
+ background-color: var(--color-bg-hover);
1222
+ font-weight: 500;
1223
+ }
1224
+
1225
+ /* ============================================================
1226
+ CHECKBOX
1227
+ ============================================================ */
1228
+ .checkbox-wrapper {
1229
+ display: inline-flex;
1230
+ align-items: center;
1231
+ gap: var(--space-2);
1232
+ cursor: pointer;
1233
+ user-select: none;
1234
+ }
1235
+
1236
+ .checkbox-wrapper input[type='checkbox'] {
1237
+ position: absolute;
1238
+ opacity: 0;
1239
+ width: 0;
1240
+ height: 0;
1241
+ }
1242
+
1243
+ .checkbox-box {
1244
+ display: flex;
1245
+ align-items: center;
1246
+ justify-content: center;
1247
+ width: 20px;
1248
+ height: 20px;
1249
+ flex-shrink: 0;
1250
+ border-radius: var(--radius-md);
1251
+ border: 2px solid var(--color-border-primary);
1252
+ background-color: var(--color-bg-elevated);
1253
+ transition:
1254
+ border-color var(--transition-fast),
1255
+ background-color var(--transition-fast);
1256
+ }
1257
+
1258
+ .checkbox-wrapper:hover .checkbox-box {
1259
+ border-color: var(--color-border-secondary);
1260
+ }
1261
+
1262
+ .checkbox-wrapper input[type='checkbox']:checked + .checkbox-box {
1263
+ border-color: var(--color-text-primary);
1264
+ background-color: var(--color-text-primary);
1265
+ }
1266
+
1267
+ .checkbox-check {
1268
+ display: none;
1269
+ width: 12px;
1270
+ height: 12px;
1271
+ color: var(--color-bg-page);
1272
+ }
1273
+
1274
+ .checkbox-wrapper input[type='checkbox']:checked + .checkbox-box .checkbox-check {
1275
+ display: block;
1276
+ }
1277
+
1278
+ .checkbox-label {
1279
+ font-size: var(--text-sm);
1280
+ color: var(--color-text-primary);
1281
+ }
1282
+
1283
+ .checkbox-wrapper.disabled {
1284
+ opacity: 0.6;
1285
+ cursor: not-allowed;
1286
+ pointer-events: none;
1287
+ }
1288
+
1289
+ /* ============================================================
1290
+ RADIO
1291
+ ============================================================ */
1292
+ .radio-wrapper {
1293
+ display: inline-flex;
1294
+ align-items: center;
1295
+ gap: var(--space-2);
1296
+ cursor: pointer;
1297
+ user-select: none;
1298
+ }
1299
+
1300
+ .radio-wrapper input[type='radio'] {
1301
+ position: absolute;
1302
+ opacity: 0;
1303
+ width: 0;
1304
+ height: 0;
1305
+ }
1306
+
1307
+ .radio-dot {
1308
+ display: flex;
1309
+ align-items: center;
1310
+ justify-content: center;
1311
+ width: 18px;
1312
+ height: 18px;
1313
+ flex-shrink: 0;
1314
+ border-radius: var(--radius-full);
1315
+ border: 2px solid var(--color-border-primary);
1316
+ background-color: var(--color-bg-elevated);
1317
+ transition:
1318
+ border-color var(--transition-fast),
1319
+ background-color var(--transition-fast);
1320
+ }
1321
+
1322
+ .radio-dot::after {
1323
+ content: '';
1324
+ width: 8px;
1325
+ height: 8px;
1326
+ border-radius: var(--radius-full);
1327
+ background-color: var(--color-bg-page);
1328
+ opacity: 0;
1329
+ transition: opacity var(--transition-fast);
1330
+ }
1331
+
1332
+ .radio-wrapper:hover .radio-dot {
1333
+ border-color: var(--color-border-secondary);
1334
+ }
1335
+
1336
+ .radio-wrapper input[type='radio']:checked + .radio-dot {
1337
+ border-color: var(--color-text-primary);
1338
+ background-color: var(--color-text-primary);
1339
+ }
1340
+
1341
+ .radio-wrapper input[type='radio']:checked + .radio-dot::after {
1342
+ opacity: 1;
1343
+ }
1344
+
1345
+ .radio-label {
1346
+ font-size: var(--text-sm);
1347
+ color: var(--color-text-primary);
1348
+ }
1349
+
1350
+ /* ============================================================
1351
+ SWITCH (Toggle)
1352
+ ============================================================ */
1353
+ .switch-wrapper {
1354
+ display: inline-flex;
1355
+ align-items: center;
1356
+ gap: var(--space-2);
1357
+ cursor: pointer;
1358
+ user-select: none;
1359
+ }
1360
+
1361
+ .switch-wrapper input[type='checkbox'] {
1362
+ position: absolute;
1363
+ opacity: 0;
1364
+ width: 0;
1365
+ height: 0;
1366
+ }
1367
+
1368
+ .switch-track {
1369
+ position: relative;
1370
+ width: 32px;
1371
+ height: 16px;
1372
+ border-radius: var(--radius-full);
1373
+ background: var(--gradient-switch-off);
1374
+ border: 1px solid var(--color-border-primary);
1375
+ transition:
1376
+ background var(--transition-base),
1377
+ border-color var(--transition-base);
1378
+ flex-shrink: 0;
1379
+ }
1380
+
1381
+ .switch-wrapper input[type='checkbox']:checked + .switch-track {
1382
+ background: var(--gradient-switch-on);
1383
+ border-color: var(--color-border-accent);
1384
+ }
1385
+
1386
+ .switch-knob {
1387
+ position: absolute;
1388
+ top: 1px;
1389
+ left: 2px;
1390
+ width: 12px;
1391
+ height: 12px;
1392
+ border-radius: var(--radius-full);
1393
+ background-color: #CCCCCC;
1394
+ transition: transform var(--transition-base), background-color var(--transition-base);
1395
+ pointer-events: none;
1396
+ }
1397
+
1398
+ .switch-wrapper input[type='checkbox']:checked ~ .switch-track .switch-knob {
1399
+ transform: translateX(16px);
1400
+ background-color: #FFFFFF;
1401
+ }
1402
+
1403
+ /* Alternative pure CSS switch using :has() or JS class */
1404
+ .switch-track.switch-on .switch-knob {
1405
+ transform: translateX(16px);
1406
+ background-color: #FFFFFF;
1407
+ }
1408
+
1409
+ .switch-label {
1410
+ font-size: var(--text-xs);
1411
+ color: var(--color-text-muted);
1412
+ transition: color var(--transition-fast);
1413
+ }
1414
+
1415
+ .switch-wrapper:hover .switch-label {
1416
+ color: var(--color-border-accent);
1417
+ }
1418
+
1419
+ /* ============================================================
1420
+ FORM ERROR / HELPER TEXT
1421
+ ============================================================ */
1422
+ .form-error {
1423
+ font-size: var(--text-sm);
1424
+ color: var(--color-error-text);
1425
+ display: flex;
1426
+ align-items: center;
1427
+ gap: var(--space-1);
1428
+ margin-top: var(--space-0-5);
1429
+ }
1430
+
1431
+ .form-helper {
1432
+ font-size: var(--text-xs);
1433
+ color: var(--color-text-muted);
1434
+ margin-top: var(--space-0-5);
1435
+ }
1436
+
1437
+ /* ============================================================
1438
+ SEARCH INPUT
1439
+ ============================================================ */
1440
+ .search-input-wrapper {
1441
+ position: relative;
1442
+ display: flex;
1443
+ align-items: center;
1444
+ }
1445
+
1446
+ .search-input-wrapper .search-icon {
1447
+ position: absolute;
1448
+ left: var(--space-2);
1449
+ color: var(--color-icon-tertiary);
1450
+ pointer-events: none;
1451
+ }
1452
+
1453
+ .search-input-wrapper input {
1454
+ padding-left: 28px;
1455
+ }
1456
+ /**
1457
+ * CodeMie UI Components
1458
+ * Cards, Badges, Status badges, Alerts, Avatars, Tags, Spinner, Tooltip, Progress.
1459
+ */
1460
+
1461
+ /* ============================================================
1462
+ CARD
1463
+ ============================================================ */
1464
+ .card {
1465
+ display: flex;
1466
+ flex-direction: column;
1467
+ background-color: var(--color-bg-card);
1468
+ border: 1px solid var(--color-border-structural);
1469
+ border-radius: var(--radius-xl);
1470
+ overflow: hidden;
1471
+ transition:
1472
+ border-color var(--transition-base),
1473
+ box-shadow var(--transition-base);
1474
+ }
1475
+
1476
+ .card:hover {
1477
+ border-color: var(--color-border-secondary);
1478
+ }
1479
+
1480
+ .card-fixed {
1481
+ height: var(--card-height); /* 158px */
1482
+ }
1483
+
1484
+ .card-header {
1485
+ display: flex;
1486
+ align-items: center;
1487
+ justify-content: space-between;
1488
+ padding: var(--space-4);
1489
+ border-bottom: 1px solid var(--color-border-structural);
1490
+ }
1491
+
1492
+ .card-body {
1493
+ flex: 1;
1494
+ padding: var(--space-4);
1495
+ overflow: hidden;
1496
+ }
1497
+
1498
+ .card-footer {
1499
+ padding: var(--space-3) var(--space-4);
1500
+ border-top: 1px solid var(--color-border-structural);
1501
+ display: flex;
1502
+ align-items: center;
1503
+ gap: var(--space-2);
1504
+ }
1505
+
1506
+ .card-title {
1507
+ font-size: var(--text-base);
1508
+ font-weight: 600;
1509
+ color: var(--color-text-primary);
1510
+ overflow: hidden;
1511
+ text-overflow: ellipsis;
1512
+ white-space: nowrap;
1513
+ }
1514
+
1515
+ .card-subtitle {
1516
+ font-size: var(--text-xs);
1517
+ color: var(--color-text-muted);
1518
+ white-space: nowrap;
1519
+ overflow: hidden;
1520
+ text-overflow: ellipsis;
1521
+ }
1522
+
1523
+ .card-description {
1524
+ font-size: var(--text-xs);
1525
+ color: var(--color-text-tertiary);
1526
+ display: -webkit-box;
1527
+ -webkit-line-clamp: 2;
1528
+ -webkit-box-orient: vertical;
1529
+ overflow: hidden;
1530
+ }
1531
+
1532
+ /* Panel — larger surface for content areas */
1533
+ .panel {
1534
+ background-color: var(--color-bg-secondary);
1535
+ border: 1px solid var(--color-border-panel);
1536
+ border-radius: var(--radius-lg);
1537
+ overflow: hidden;
1538
+ }
1539
+
1540
+ .panel-header {
1541
+ display: flex;
1542
+ align-items: center;
1543
+ justify-content: space-between;
1544
+ padding: var(--space-3) var(--space-4);
1545
+ border-bottom: 1px solid var(--color-border-structural);
1546
+ background-color: var(--color-bg-tertiary);
1547
+ }
1548
+
1549
+ .panel-body {
1550
+ padding: var(--space-4);
1551
+ }
1552
+
1553
+ /* ============================================================
1554
+ STATUS BADGE
1555
+ ============================================================ */
1556
+ .badge {
1557
+ display: inline-flex;
1558
+ align-items: center;
1559
+ gap: var(--space-1-5);
1560
+ padding: 0 var(--space-2);
1561
+ height: 17px;
1562
+ line-height: 17px;
1563
+ border-radius: var(--radius-full);
1564
+ border: 1px solid;
1565
+ font-size: var(--text-xs-1);
1566
+ font-weight: 700;
1567
+ text-transform: uppercase;
1568
+ letter-spacing: 0.04em;
1569
+ white-space: nowrap;
1570
+ user-select: none;
1571
+ flex-shrink: 0;
1572
+ }
1573
+
1574
+ .badge-dot {
1575
+ width: 7px;
1576
+ height: 7px;
1577
+ border-radius: var(--radius-full);
1578
+ flex-shrink: 0;
1579
+ display: inline-block;
1580
+ }
1581
+
1582
+ /* Variants */
1583
+ .badge-not-started {
1584
+ background-color: var(--status-not-started-bg);
1585
+ color: var(--status-not-started-text);
1586
+ border-color: var(--status-not-started-border);
1587
+ }
1588
+ .badge-not-started .badge-dot { background-color: var(--status-not-started-text); }
1589
+
1590
+ .badge-in-progress {
1591
+ background-color: var(--status-in-progress-bg);
1592
+ color: var(--status-in-progress-text);
1593
+ border-color: var(--status-in-progress-border);
1594
+ }
1595
+ .badge-in-progress .badge-dot {
1596
+ background-color: var(--status-in-progress-text);
1597
+ animation: badge-pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;
1598
+ }
1599
+
1600
+ .badge-pending {
1601
+ background-color: var(--status-pending-bg);
1602
+ color: var(--status-pending-text);
1603
+ border-color: var(--status-pending-border);
1604
+ }
1605
+ .badge-pending .badge-dot { background-color: var(--status-pending-text); }
1606
+
1607
+ .badge-success {
1608
+ background-color: var(--status-success-bg);
1609
+ color: var(--status-success-text);
1610
+ border-color: var(--status-success-border);
1611
+ }
1612
+ .badge-success .badge-dot { background-color: var(--status-success-text); }
1613
+
1614
+ .badge-error {
1615
+ background-color: var(--status-error-bg);
1616
+ color: var(--status-error-text);
1617
+ border-color: var(--status-error-border);
1618
+ }
1619
+ .badge-error .badge-dot { background-color: var(--status-error-text); }
1620
+
1621
+ .badge-warning {
1622
+ background-color: var(--status-warning-bg);
1623
+ color: var(--status-warning-text);
1624
+ border-color: var(--status-warning-border);
1625
+ }
1626
+ .badge-warning .badge-dot { background-color: var(--status-warning-text); }
1627
+
1628
+ .badge-advanced {
1629
+ background-color: var(--status-advanced-bg);
1630
+ color: var(--status-advanced-text);
1631
+ border-color: var(--status-advanced-border);
1632
+ }
1633
+ .badge-advanced .badge-dot { background-color: var(--status-advanced-text); }
1634
+
1635
+ @keyframes badge-pulse {
1636
+ 0%, 100% { opacity: 1; }
1637
+ 50% { opacity: 0.4; }
1638
+ }
1639
+
1640
+ /* ============================================================
1641
+ TAG (category / label)
1642
+ ============================================================ */
1643
+ .tag {
1644
+ display: inline-flex;
1645
+ align-items: center;
1646
+ gap: var(--space-1);
1647
+ padding: var(--space-1) var(--space-2);
1648
+ border-radius: var(--radius-lg);
1649
+ border: 1px solid var(--color-border-secondary);
1650
+ background-color: var(--color-bg-secondary);
1651
+ font-size: var(--text-xs);
1652
+ font-weight: 600;
1653
+ color: var(--color-text-tertiary);
1654
+ white-space: nowrap;
1655
+ user-select: none;
1656
+ }
1657
+
1658
+ /* Tag sizes */
1659
+ .tag-sm {
1660
+ padding: 1px var(--space-1-5);
1661
+ font-size: var(--text-xs-1);
1662
+ border-radius: var(--radius-md);
1663
+ }
1664
+
1665
+ /* Tag colors */
1666
+ .tag-blue {
1667
+ background-color: var(--color-info-bg);
1668
+ border-color: var(--color-info-border);
1669
+ color: var(--color-info-text);
1670
+ }
1671
+
1672
+ .tag-green {
1673
+ background-color: var(--color-success-bg);
1674
+ border-color: var(--color-success-border);
1675
+ color: var(--color-success-text);
1676
+ }
1677
+
1678
+ .tag-red {
1679
+ background-color: var(--color-error-bg);
1680
+ border-color: var(--color-error-border);
1681
+ color: var(--color-error-text);
1682
+ }
1683
+
1684
+ .tag-yellow {
1685
+ background-color: var(--color-warning-bg);
1686
+ border-color: var(--color-warning-border);
1687
+ color: var(--color-warning-text);
1688
+ }
1689
+
1690
+ .tag-purple {
1691
+ background-color: var(--color-purple-bg);
1692
+ border-color: var(--color-purple-border);
1693
+ color: var(--color-purple-text);
1694
+ }
1695
+
1696
+ /* ============================================================
1697
+ ALERT / INFO BANNER
1698
+ ============================================================ */
1699
+ .alert {
1700
+ display: flex;
1701
+ align-items: flex-start;
1702
+ gap: var(--space-2);
1703
+ padding: var(--space-2) var(--space-3);
1704
+ border-radius: var(--radius-md);
1705
+ border: 1px solid;
1706
+ font-size: var(--text-xs);
1707
+ line-height: 1.5;
1708
+ }
1709
+
1710
+ .alert-icon {
1711
+ flex-shrink: 0;
1712
+ margin-top: 1px;
1713
+ }
1714
+
1715
+ .alert-info {
1716
+ background-color: var(--color-info-bg);
1717
+ border-color: var(--color-info-border);
1718
+ color: var(--color-info-text);
1719
+ }
1720
+
1721
+ .alert-success {
1722
+ background-color: var(--color-success-bg);
1723
+ border-color: var(--color-success-border);
1724
+ color: var(--color-success-text);
1725
+ }
1726
+
1727
+ .alert-warning {
1728
+ background-color: var(--color-warning-bg);
1729
+ border-color: var(--color-warning-border);
1730
+ color: var(--color-warning-text);
1731
+ }
1732
+
1733
+ .alert-error {
1734
+ background-color: var(--color-error-bg);
1735
+ border-color: var(--color-error-border);
1736
+ color: var(--color-error-text);
1737
+ }
1738
+
1739
+ /* ============================================================
1740
+ AVATAR
1741
+ ============================================================ */
1742
+ .avatar {
1743
+ display: flex;
1744
+ align-items: center;
1745
+ justify-content: center;
1746
+ border-radius: var(--radius-full);
1747
+ border: 1px solid var(--color-border-secondary);
1748
+ background-color: var(--color-bg-elevated);
1749
+ overflow: hidden;
1750
+ flex-shrink: 0;
1751
+ user-select: none;
1752
+ font-weight: 600;
1753
+ color: #FFFFFF;
1754
+ font-size: var(--text-xs);
1755
+ }
1756
+
1757
+ .avatar img {
1758
+ width: 100%;
1759
+ height: 100%;
1760
+ object-fit: cover;
1761
+ border-radius: var(--radius-full);
1762
+ }
1763
+
1764
+ /* Sizes */
1765
+ .avatar-xs { width: 20px; height: 20px; font-size: 8px; border-width: 1px; }
1766
+ .avatar-sm { width: 24px; height: 24px; font-size: 9px; border-width: 1px; }
1767
+ .avatar-md { width: 32px; height: 32px; font-size: var(--text-xs); }
1768
+ .avatar-chat { width: 40px; height: 40px; font-size: var(--text-sm); }
1769
+ .avatar-lg { width: 72px; height: 72px; font-size: var(--text-h3); border-width: 2px; }
1770
+ .avatar-xl { width: 96px; height: 96px; font-size: var(--text-h2); border-width: 2px; }
1771
+ .avatar-modal { width: 176px; height: 176px; font-size: var(--text-h1); border-width: 2px; }
1772
+
1773
+ /* Avatar initials color palette */
1774
+ .avatar-color-0 { background-color: #AA47BC; }
1775
+ .avatar-color-1 { background-color: #7A1FA2; }
1776
+ .avatar-color-2 { background-color: #6B8592; }
1777
+ .avatar-color-3 { background-color: #465A65; }
1778
+ .avatar-color-4 { background-color: #EC407A; }
1779
+ .avatar-color-5 { background-color: #C2175B; }
1780
+ .avatar-color-6 { background-color: #5C6BC0; }
1781
+ .avatar-color-7 { background-color: #0288D1; }
1782
+ .avatar-color-8 { background-color: #00579C; }
1783
+ .avatar-color-9 { background-color: #0098A6; }
1784
+ .avatar-color-10 { background-color: #00887A; }
1785
+ .avatar-color-11 { background-color: #004C3F; }
1786
+ .avatar-color-12 { background-color: #689F39; }
1787
+ .avatar-color-13 { background-color: #33691E; }
1788
+ .avatar-color-14 { background-color: #8C6E63; }
1789
+ .avatar-color-15 { background-color: #5D4038; }
1790
+ .avatar-color-16 { background-color: #7E57C2; }
1791
+ .avatar-color-17 { background-color: #512DA7; }
1792
+ .avatar-color-18 { background-color: #EF6C00; }
1793
+ .avatar-color-19 { background-color: #F5511E; }
1794
+ .avatar-color-20 { background-color: #AA3410; }
1795
+
1796
+ /* Avatar group (stacked) */
1797
+ .avatar-group {
1798
+ display: flex;
1799
+ align-items: center;
1800
+ }
1801
+
1802
+ .avatar-group .avatar {
1803
+ margin-left: -8px;
1804
+ }
1805
+
1806
+ .avatar-group .avatar:first-child {
1807
+ margin-left: 0;
1808
+ }
1809
+
1810
+ /* ============================================================
1811
+ SPINNER
1812
+ ============================================================ */
1813
+ .spinner {
1814
+ display: inline-block;
1815
+ border-radius: var(--radius-full);
1816
+ border-style: solid;
1817
+ border-color: var(--color-border-secondary);
1818
+ border-top-color: var(--color-text-primary);
1819
+ animation: spinner-spin 0.8s linear infinite;
1820
+ flex-shrink: 0;
1821
+ }
1822
+
1823
+ .spinner-xs { width: 12px; height: 12px; border-width: 1.5px; }
1824
+ .spinner-sm { width: 16px; height: 16px; border-width: 2px; }
1825
+ .spinner-md { width: 24px; height: 24px; border-width: 2px; }
1826
+ .spinner-lg { width: 32px; height: 32px; border-width: 3px; }
1827
+ .spinner-xl { width: 48px; height: 48px; border-width: 3px; }
1828
+
1829
+ .spinner-page {
1830
+ display: flex;
1831
+ justify-content: center;
1832
+ align-items: center;
1833
+ min-height: 200px;
1834
+ }
1835
+
1836
+ @keyframes spinner-spin {
1837
+ to { transform: rotate(360deg); }
1838
+ }
1839
+
1840
+ /* ============================================================
1841
+ PROGRESS BAR
1842
+ ============================================================ */
1843
+ .progress-track {
1844
+ position: relative;
1845
+ overflow: hidden;
1846
+ background-color: var(--color-bg-tertiary);
1847
+ border-radius: 68px;
1848
+ border: 1px solid var(--color-border-secondary);
1849
+ width: 85px;
1850
+ height: 14px;
1851
+ }
1852
+
1853
+ .progress-fill {
1854
+ height: 100%;
1855
+ background: var(--gradient-primary-btn);
1856
+ border-radius: 68px;
1857
+ transition: width 200ms ease-out;
1858
+ }
1859
+
1860
+ .progress-label {
1861
+ position: absolute;
1862
+ left: 50%;
1863
+ top: 50%;
1864
+ transform: translate(-50%, -50%);
1865
+ font-size: 9px;
1866
+ line-height: 10px;
1867
+ font-weight: 600;
1868
+ color: var(--color-text-primary);
1869
+ white-space: nowrap;
1870
+ }
1871
+
1872
+ /* Full-width progress bar */
1873
+ .progress-bar {
1874
+ width: 100%;
1875
+ height: 4px;
1876
+ background-color: var(--color-bg-tertiary);
1877
+ border-radius: var(--radius-full);
1878
+ overflow: hidden;
1879
+ }
1880
+
1881
+ .progress-bar .progress-fill {
1882
+ height: 4px;
1883
+ border-radius: var(--radius-full);
1884
+ }
1885
+
1886
+ /* ============================================================
1887
+ TOOLTIP
1888
+ ============================================================ */
1889
+ .tooltip-wrapper {
1890
+ position: relative;
1891
+ display: inline-flex;
1892
+ }
1893
+
1894
+ .tooltip {
1895
+ position: absolute;
1896
+ z-index: var(--z-toast);
1897
+ bottom: calc(100% + 8px);
1898
+ left: 50%;
1899
+ transform: translateX(-50%);
1900
+ background-color: var(--color-bg-elevated);
1901
+ color: var(--color-text-primary);
1902
+ padding: var(--space-2) var(--space-3);
1903
+ border-radius: var(--radius-md);
1904
+ font-size: var(--text-xs);
1905
+ line-height: 1;
1906
+ white-space: nowrap;
1907
+ box-shadow: var(--shadow-md);
1908
+ pointer-events: none;
1909
+ opacity: 0;
1910
+ transition: opacity var(--transition-fast);
1911
+ }
1912
+
1913
+ .tooltip-wrapper:hover .tooltip {
1914
+ opacity: 1;
1915
+ }
1916
+
1917
+ /* Tooltip positions */
1918
+ .tooltip-right {
1919
+ bottom: auto;
1920
+ top: 50%;
1921
+ left: calc(100% + 8px);
1922
+ transform: translateY(-50%);
1923
+ }
1924
+
1925
+ .tooltip-bottom {
1926
+ bottom: auto;
1927
+ top: calc(100% + 8px);
1928
+ }
1929
+
1930
+ /* ============================================================
1931
+ EMPTY STATE
1932
+ ============================================================ */
1933
+ .empty-state {
1934
+ display: flex;
1935
+ flex-direction: column;
1936
+ align-items: center;
1937
+ justify-content: center;
1938
+ padding: var(--space-8) var(--space-4);
1939
+ text-align: center;
1940
+ gap: var(--space-3);
1941
+ }
1942
+
1943
+ .empty-state-icon {
1944
+ color: var(--color-icon-tertiary);
1945
+ margin-bottom: var(--space-1);
1946
+ }
1947
+
1948
+ .empty-state-title {
1949
+ font-size: var(--text-h4);
1950
+ font-weight: 600;
1951
+ color: var(--color-text-primary);
1952
+ }
1953
+
1954
+ .empty-state-description {
1955
+ font-size: var(--text-sm);
1956
+ color: var(--color-text-muted);
1957
+ max-width: 320px;
1958
+ }
1959
+
1960
+ /* ============================================================
1961
+ CHIP / CLOSE TAG
1962
+ ============================================================ */
1963
+ .chip {
1964
+ display: inline-flex;
1965
+ align-items: center;
1966
+ gap: var(--space-1);
1967
+ padding: 2px var(--space-1-5);
1968
+ border-radius: var(--radius-full);
1969
+ background-color: var(--color-bg-quaternary);
1970
+ border: 1px solid var(--color-border-primary);
1971
+ font-size: var(--text-xs);
1972
+ color: var(--color-text-secondary);
1973
+ }
1974
+
1975
+ .chip-close {
1976
+ display: flex;
1977
+ align-items: center;
1978
+ justify-content: center;
1979
+ width: 14px;
1980
+ height: 14px;
1981
+ border-radius: var(--radius-full);
1982
+ background: transparent;
1983
+ border: none;
1984
+ color: var(--color-icon-tertiary);
1985
+ cursor: pointer;
1986
+ padding: 0;
1987
+ transition: color var(--transition-fast), background-color var(--transition-fast);
1988
+ }
1989
+
1990
+ .chip-close:hover {
1991
+ color: var(--color-text-primary);
1992
+ background-color: var(--color-bg-hover);
1993
+ }
1994
+
1995
+ /* ============================================================
1996
+ STAT CARD (metric KPI tile)
1997
+ ============================================================ */
1998
+ .stat-card {
1999
+ display: flex;
2000
+ flex-direction: column;
2001
+ gap: var(--space-1);
2002
+ padding: var(--space-3) var(--space-4);
2003
+ background-color: var(--color-bg-card);
2004
+ border: 1px solid var(--color-border-structural);
2005
+ border-radius: var(--radius-xl);
2006
+ min-width: 0;
2007
+ }
2008
+
2009
+ .stat-card-label {
2010
+ font-size: var(--text-xs-1);
2011
+ font-weight: 700;
2012
+ text-transform: uppercase;
2013
+ letter-spacing: 0.04em;
2014
+ color: var(--color-text-muted);
2015
+ white-space: nowrap;
2016
+ overflow: hidden;
2017
+ text-overflow: ellipsis;
2018
+ }
2019
+
2020
+ .stat-card-value {
2021
+ font-size: var(--text-h2);
2022
+ font-weight: 700;
2023
+ line-height: 1.2;
2024
+ color: var(--color-text-primary);
2025
+ }
2026
+
2027
+ .stat-card-desc {
2028
+ font-size: var(--text-xs);
2029
+ color: var(--color-text-muted);
2030
+ white-space: nowrap;
2031
+ overflow: hidden;
2032
+ text-overflow: ellipsis;
2033
+ }
2034
+
2035
+ /* Stat card inside a grid */
2036
+ .stat-grid {
2037
+ display: grid;
2038
+ grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
2039
+ gap: var(--space-3);
2040
+ }
2041
+
2042
+ /* ============================================================
2043
+ DESCRIPTION LIST (key-value pairs)
2044
+ ============================================================ */
2045
+ .dl-grid {
2046
+ display: grid;
2047
+ grid-template-columns: max-content 1fr;
2048
+ gap: var(--space-2) var(--space-4);
2049
+ font-size: var(--text-sm);
2050
+ }
2051
+
2052
+ .dl-grid dt {
2053
+ color: var(--color-text-muted);
2054
+ font-weight: 500;
2055
+ white-space: nowrap;
2056
+ }
2057
+
2058
+ .dl-grid dd {
2059
+ color: var(--color-text-primary);
2060
+ }
2061
+ /**
2062
+ * CodeMie Layout & Navigation
2063
+ * Tables, Tabs, Pagination, Modals, Navigation.
2064
+ */
2065
+
2066
+ /* ============================================================
2067
+ TABLE
2068
+ ============================================================ */
2069
+ .table-wrapper {
2070
+ width: 100%;
2071
+ overflow-x: auto;
2072
+ }
2073
+
2074
+ table.table,
2075
+ .table {
2076
+ width: 100%;
2077
+ border-collapse: separate;
2078
+ border-spacing: 0;
2079
+ font-size: var(--text-xs);
2080
+ line-height: 1.3;
2081
+ }
2082
+
2083
+ .table thead tr th {
2084
+ text-align: left;
2085
+ padding: var(--space-2-5) var(--space-4);
2086
+ font-weight: 600;
2087
+ color: var(--color-text-primary);
2088
+ background-color: var(--color-bg-quaternary);
2089
+ border-top: 1px solid var(--color-border-structural);
2090
+ border-bottom: 1px solid var(--color-border-structural);
2091
+ white-space: nowrap;
2092
+ position: sticky;
2093
+ top: 0;
2094
+ z-index: 1;
2095
+ }
2096
+
2097
+ .table thead tr th:first-child {
2098
+ border-left: 1px solid var(--color-border-structural);
2099
+ border-radius: var(--radius-lg) 0 0 0;
2100
+ }
2101
+
2102
+ .table thead tr th:last-child {
2103
+ border-right: 1px solid var(--color-border-structural);
2104
+ border-radius: 0 var(--radius-lg) 0 0;
2105
+ }
2106
+
2107
+ .table tbody tr td {
2108
+ padding: var(--space-3) var(--space-4);
2109
+ color: var(--color-text-secondary);
2110
+ border-bottom: 1px solid var(--color-border-structural);
2111
+ vertical-align: middle;
2112
+ }
2113
+
2114
+ .table tbody tr:hover td {
2115
+ background-color: var(--color-bg-hover);
2116
+ }
2117
+
2118
+ .table tbody tr:last-child td {
2119
+ border-bottom: none;
2120
+ }
2121
+
2122
+ /* Sortable header */
2123
+ .th-sortable {
2124
+ cursor: pointer;
2125
+ user-select: none;
2126
+ gap: var(--space-1);
2127
+ }
2128
+
2129
+ .th-sortable:hover {
2130
+ color: var(--color-text-link);
2131
+ }
2132
+
2133
+ .sort-icon {
2134
+ display: inline-flex;
2135
+ flex-direction: column;
2136
+ gap: 1px;
2137
+ color: var(--color-icon-tertiary);
2138
+ flex-shrink: 0;
2139
+ }
2140
+
2141
+ .th-sortable.sort-asc .sort-icon,
2142
+ .th-sortable.sort-desc .sort-icon {
2143
+ color: var(--color-text-primary);
2144
+ }
2145
+
2146
+ /* Numeric / right-aligned columns */
2147
+ .td-number {
2148
+ text-align: right;
2149
+ font-variant-numeric: tabular-nums;
2150
+ }
2151
+
2152
+ /* ============================================================
2153
+ TABS
2154
+ ============================================================ */
2155
+ .tabs {
2156
+ display: flex;
2157
+ flex-direction: column;
2158
+ }
2159
+
2160
+ .tabs-list {
2161
+ display: flex;
2162
+ align-items: stretch;
2163
+ border-bottom: 1px solid var(--color-border-panel);
2164
+ flex-shrink: 0;
2165
+ }
2166
+
2167
+ .tab-item {
2168
+ display: inline-flex;
2169
+ align-items: center;
2170
+ justify-content: center;
2171
+ gap: var(--space-2);
2172
+ padding: var(--space-2) var(--space-2);
2173
+ padding-bottom: var(--space-4);
2174
+ font-size: var(--text-sm);
2175
+ color: var(--color-text-primary);
2176
+ cursor: pointer;
2177
+ border-bottom: 2px solid transparent;
2178
+ transition:
2179
+ border-color var(--transition-base),
2180
+ color var(--transition-base);
2181
+ text-decoration: none;
2182
+ white-space: nowrap;
2183
+ user-select: none;
2184
+ background: transparent;
2185
+ border-top: none;
2186
+ border-left: none;
2187
+ border-right: none;
2188
+ font-family: var(--font-sans);
2189
+ }
2190
+
2191
+ .tab-item:hover {
2192
+ border-bottom-color: var(--color-text-tertiary);
2193
+ }
2194
+
2195
+ .tab-item.active,
2196
+ .tab-item[aria-selected='true'] {
2197
+ border-bottom-color: var(--color-text-primary);
2198
+ font-weight: 600;
2199
+ cursor: default;
2200
+ }
2201
+
2202
+ /* Small tab variant */
2203
+ .tabs-sm .tab-item {
2204
+ font-size: var(--text-xs);
2205
+ padding: var(--space-2-5);
2206
+ padding-bottom: var(--space-2-5);
2207
+ }
2208
+
2209
+ .tabs-panel {
2210
+ flex: 1;
2211
+ padding-top: var(--space-4);
2212
+ }
2213
+
2214
+ /* ============================================================
2215
+ PAGINATION
2216
+ ============================================================ */
2217
+ .pagination {
2218
+ display: flex;
2219
+ align-items: center;
2220
+ gap: var(--space-2);
2221
+ font-size: var(--text-sm);
2222
+ padding: var(--space-3) 0;
2223
+ border-top: 1px solid var(--color-border-structural);
2224
+ }
2225
+
2226
+ .pagination-info {
2227
+ color: var(--color-text-muted);
2228
+ font-size: var(--text-h5);
2229
+ margin-right: auto;
2230
+ }
2231
+
2232
+ .page-btn {
2233
+ display: flex;
2234
+ align-items: center;
2235
+ justify-content: center;
2236
+ height: 32px;
2237
+ min-width: 32px;
2238
+ padding: 0 var(--space-2);
2239
+ background-color: var(--color-bg-secondary);
2240
+ border: 1px solid var(--color-border-structural);
2241
+ border-radius: var(--radius-lg);
2242
+ color: var(--color-text-link);
2243
+ font-size: var(--text-h5);
2244
+ cursor: pointer;
2245
+ transition:
2246
+ border-color var(--transition-fast),
2247
+ background-color var(--transition-fast);
2248
+ user-select: none;
2249
+ text-decoration: none;
2250
+ font-family: var(--font-sans);
2251
+ font-weight: 400;
2252
+ }
2253
+
2254
+ .page-btn:hover:not(.disabled) {
2255
+ border-color: var(--color-border-accent);
2256
+ }
2257
+
2258
+ .page-btn.active {
2259
+ border-color: var(--color-border-accent);
2260
+ background-color: var(--color-bg-pagination-active);
2261
+ font-weight: 600;
2262
+ }
2263
+
2264
+ .page-btn.disabled {
2265
+ opacity: 0.4;
2266
+ cursor: not-allowed;
2267
+ pointer-events: none;
2268
+ }
2269
+
2270
+ /* ============================================================
2271
+ MODAL / DIALOG
2272
+ ============================================================ */
2273
+ .modal-overlay {
2274
+ position: fixed;
2275
+ inset: 0;
2276
+ background-color: rgba(0, 0, 0, 0.5);
2277
+ display: flex;
2278
+ align-items: center;
2279
+ justify-content: center;
2280
+ z-index: var(--z-modal);
2281
+ padding: var(--space-4);
2282
+ }
2283
+
2284
+ .modal {
2285
+ display: flex;
2286
+ flex-direction: column;
2287
+ background-color: var(--color-bg-elevated);
2288
+ border: 1px solid var(--color-border-panel);
2289
+ border-radius: var(--radius-lg);
2290
+ box-shadow: var(--shadow-lg);
2291
+ max-height: 95vh;
2292
+ width: 100%;
2293
+ overflow: hidden;
2294
+ }
2295
+
2296
+ .modal-sm { max-width: 400px; }
2297
+ .modal-md { max-width: 512px; } /* default */
2298
+ .modal-lg { max-width: 720px; }
2299
+ .modal-xl { max-width: 1024px; }
2300
+ .modal-full { max-width: 90vw; }
2301
+
2302
+ .modal-header {
2303
+ display: flex;
2304
+ align-items: center;
2305
+ justify-content: space-between;
2306
+ gap: var(--space-4);
2307
+ padding: var(--space-3) var(--space-4);
2308
+ border-bottom: 1px solid var(--color-border-structural);
2309
+ flex-shrink: 0;
2310
+ }
2311
+
2312
+ .modal-title {
2313
+ font-size: var(--text-base);
2314
+ font-weight: 600;
2315
+ color: var(--color-text-primary);
2316
+ }
2317
+
2318
+ .modal-close {
2319
+ display: flex;
2320
+ align-items: center;
2321
+ justify-content: center;
2322
+ width: 28px;
2323
+ height: 28px;
2324
+ border-radius: var(--radius-lg);
2325
+ border: none;
2326
+ background: transparent;
2327
+ color: var(--color-icon-tertiary);
2328
+ cursor: pointer;
2329
+ transition:
2330
+ color var(--transition-fast),
2331
+ background-color var(--transition-fast);
2332
+ flex-shrink: 0;
2333
+ }
2334
+
2335
+ .modal-close:hover {
2336
+ color: var(--color-text-primary);
2337
+ background-color: var(--color-bg-quaternary);
2338
+ }
2339
+
2340
+ .modal-body {
2341
+ flex: 1;
2342
+ padding: var(--space-4);
2343
+ overflow-y: auto;
2344
+ }
2345
+
2346
+ .modal-footer {
2347
+ display: flex;
2348
+ align-items: center;
2349
+ justify-content: flex-end;
2350
+ gap: var(--space-3);
2351
+ padding: var(--space-4);
2352
+ border-top: 1px solid var(--color-border-structural);
2353
+ flex-shrink: 0;
2354
+ background-color: var(--color-bg-elevated);
2355
+ border-radius: 0 0 var(--radius-lg) var(--radius-lg);
2356
+ }
2357
+
2358
+ /* ============================================================
2359
+ SIDEBAR NAVIGATION
2360
+ ============================================================ */
2361
+ .nav-sidebar {
2362
+ display: flex;
2363
+ flex-direction: column;
2364
+ gap: var(--space-0-5);
2365
+ padding: var(--space-2);
2366
+ }
2367
+
2368
+ .nav-item {
2369
+ display: flex;
2370
+ align-items: center;
2371
+ gap: var(--space-2);
2372
+ padding: var(--space-2) var(--space-3);
2373
+ border-radius: var(--radius-lg);
2374
+ font-size: var(--text-sm);
2375
+ color: var(--color-text-nav);
2376
+ cursor: pointer;
2377
+ transition:
2378
+ background-color var(--transition-fast),
2379
+ color var(--transition-fast);
2380
+ text-decoration: none;
2381
+ user-select: none;
2382
+ white-space: nowrap;
2383
+ overflow: hidden;
2384
+ }
2385
+
2386
+ .nav-item:hover {
2387
+ background-color: var(--color-bg-hover);
2388
+ color: var(--color-text-primary);
2389
+ }
2390
+
2391
+ .nav-item.active {
2392
+ background-color: var(--color-bg-secondary);
2393
+ color: var(--color-text-primary);
2394
+ font-weight: 500;
2395
+ }
2396
+
2397
+ .nav-item .nav-icon {
2398
+ flex-shrink: 0;
2399
+ color: var(--color-icon-tertiary);
2400
+ transition: color var(--transition-fast);
2401
+ }
2402
+
2403
+ .nav-item:hover .nav-icon,
2404
+ .nav-item.active .nav-icon {
2405
+ color: var(--color-icon-primary);
2406
+ }
2407
+
2408
+ .nav-group-label {
2409
+ padding: var(--space-2) var(--space-3) var(--space-1);
2410
+ font-size: var(--text-xs);
2411
+ font-weight: 600;
2412
+ color: var(--color-text-muted);
2413
+ text-transform: uppercase;
2414
+ letter-spacing: 0.05em;
2415
+ }
2416
+
2417
+ /* ============================================================
2418
+ LAYOUT SHELL (app chrome)
2419
+ ============================================================ */
2420
+ .app-shell {
2421
+ display: flex;
2422
+ min-height: 100vh;
2423
+ }
2424
+
2425
+ .app-navbar {
2426
+ width: var(--navbar-width);
2427
+ min-height: 100vh;
2428
+ background-color: var(--color-bg-nav);
2429
+ display: flex;
2430
+ flex-direction: column;
2431
+ align-items: center;
2432
+ padding: var(--space-3) 0;
2433
+ flex-shrink: 0;
2434
+ border-right: 1px solid var(--color-border-structural);
2435
+ }
2436
+
2437
+ .app-sidebar {
2438
+ width: var(--sidebar-width);
2439
+ min-height: 100vh;
2440
+ background-color: var(--color-bg-sidebar);
2441
+ display: flex;
2442
+ flex-direction: column;
2443
+ border-right: 1px solid var(--color-border-structural);
2444
+ flex-shrink: 0;
2445
+ }
2446
+
2447
+ .app-content {
2448
+ flex: 1;
2449
+ display: flex;
2450
+ flex-direction: column;
2451
+ min-width: 0;
2452
+ overflow: hidden;
2453
+ }
2454
+
2455
+ .app-header {
2456
+ height: var(--layout-header-height);
2457
+ display: flex;
2458
+ align-items: center;
2459
+ padding: 0 var(--space-4);
2460
+ border-bottom: 1px solid var(--color-border-structural);
2461
+ background-color: var(--color-bg-page);
2462
+ flex-shrink: 0;
2463
+ gap: var(--space-4);
2464
+ }
2465
+
2466
+ .app-main {
2467
+ flex: 1;
2468
+ padding: var(--space-4);
2469
+ overflow-y: auto;
2470
+ }
2471
+ /**
2472
+ * CodeMie Utility Classes
2473
+ * Flexbox, spacing, display, overflow, z-index, and misc helpers.
2474
+ */
2475
+
2476
+ /* ============================================================
2477
+ DISPLAY
2478
+ ============================================================ */
2479
+ .hidden { display: none !important; }
2480
+ .block { display: block; }
2481
+ .inline { display: inline; }
2482
+ .inline-block { display: inline-block; }
2483
+ .flex { display: flex; }
2484
+ .inline-flex { display: inline-flex; }
2485
+ .grid { display: grid; }
2486
+
2487
+ /* ============================================================
2488
+ FLEX
2489
+ ============================================================ */
2490
+ .flex-col { flex-direction: column; }
2491
+ .flex-row { flex-direction: row; }
2492
+ .flex-wrap { flex-wrap: wrap; }
2493
+ .flex-nowrap { flex-wrap: nowrap; }
2494
+ .flex-1 { flex: 1; }
2495
+ .flex-auto { flex: auto; }
2496
+ .flex-none { flex: none; }
2497
+ .flex-shrink-0 { flex-shrink: 0; }
2498
+ .grow { flex-grow: 1; }
2499
+
2500
+ /* Align */
2501
+ .items-start { align-items: flex-start; }
2502
+ .items-center { align-items: center; }
2503
+ .items-end { align-items: flex-end; }
2504
+ .items-stretch { align-items: stretch; }
2505
+
2506
+ /* Justify */
2507
+ .justify-start { justify-content: flex-start; }
2508
+ .justify-center { justify-content: center; }
2509
+ .justify-end { justify-content: flex-end; }
2510
+ .justify-between { justify-content: space-between; }
2511
+
2512
+ /* ============================================================
2513
+ GRID
2514
+ ============================================================ */
2515
+ .grid-2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
2516
+ .grid-3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
2517
+ .grid-4 { grid-template-columns: repeat(4, minmax(0, 1fr)); }
2518
+ .grid-auto-fill-sm { grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); }
2519
+ .grid-auto-fill-md { grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); }
2520
+ .grid-auto-fill-lg { grid-template-columns: repeat(auto-fill, minmax(320px, 1fr)); }
2521
+
2522
+ /* ============================================================
2523
+ GAP
2524
+ ============================================================ */
2525
+ .gap-1 { gap: var(--space-1); }
2526
+ .gap-2 { gap: var(--space-2); }
2527
+ .gap-3 { gap: var(--space-3); }
2528
+ .gap-4 { gap: var(--space-4); }
2529
+ .gap-5 { gap: var(--space-5); }
2530
+ .gap-6 { gap: var(--space-6); }
2531
+ .gap-8 { gap: var(--space-8); }
2532
+
2533
+ .gap-x-2 { column-gap: var(--space-2); }
2534
+ .gap-x-3 { column-gap: var(--space-3); }
2535
+ .gap-x-4 { column-gap: var(--space-4); }
2536
+ .gap-y-2 { row-gap: var(--space-2); }
2537
+ .gap-y-3 { row-gap: var(--space-3); }
2538
+ .gap-y-4 { row-gap: var(--space-4); }
2539
+
2540
+ /* ============================================================
2541
+ PADDING
2542
+ ============================================================ */
2543
+ .p-1 { padding: var(--space-1); }
2544
+ .p-2 { padding: var(--space-2); }
2545
+ .p-3 { padding: var(--space-3); }
2546
+ .p-4 { padding: var(--space-4); }
2547
+ .p-6 { padding: var(--space-6); }
2548
+ .p-8 { padding: var(--space-8); }
2549
+
2550
+ .px-2 { padding-left: var(--space-2); padding-right: var(--space-2); }
2551
+ .px-3 { padding-left: var(--space-3); padding-right: var(--space-3); }
2552
+ .px-4 { padding-left: var(--space-4); padding-right: var(--space-4); }
2553
+
2554
+ .py-1 { padding-top: var(--space-1); padding-bottom: var(--space-1); }
2555
+ .py-2 { padding-top: var(--space-2); padding-bottom: var(--space-2); }
2556
+ .py-3 { padding-top: var(--space-3); padding-bottom: var(--space-3); }
2557
+ .py-4 { padding-top: var(--space-4); padding-bottom: var(--space-4); }
2558
+
2559
+ .pt-2 { padding-top: var(--space-2); }
2560
+ .pt-4 { padding-top: var(--space-4); }
2561
+ .pt-8 { padding-top: var(--space-8); }
2562
+ .pb-2 { padding-bottom: var(--space-2); }
2563
+ .pb-4 { padding-bottom: var(--space-4); }
2564
+
2565
+ /* ============================================================
2566
+ MARGIN
2567
+ ============================================================ */
2568
+ .m-auto { margin: auto; }
2569
+ .mx-auto { margin-left: auto; margin-right: auto; }
2570
+ .ml-auto { margin-left: auto; }
2571
+ .mr-auto { margin-right: auto; }
2572
+
2573
+ .mt-1 { margin-top: var(--space-1); }
2574
+ .mt-2 { margin-top: var(--space-2); }
2575
+ .mt-3 { margin-top: var(--space-3); }
2576
+ .mt-4 { margin-top: var(--space-4); }
2577
+ .mt-6 { margin-top: var(--space-6); }
2578
+ .mt-8 { margin-top: var(--space-8); }
2579
+
2580
+ .mb-1 { margin-bottom: var(--space-1); }
2581
+ .mb-2 { margin-bottom: var(--space-2); }
2582
+ .mb-3 { margin-bottom: var(--space-3); }
2583
+ .mb-4 { margin-bottom: var(--space-4); }
2584
+ .mb-6 { margin-bottom: var(--space-6); }
2585
+
2586
+ /* ============================================================
2587
+ WIDTH / HEIGHT
2588
+ ============================================================ */
2589
+ .w-full { width: 100%; }
2590
+ .w-auto { width: auto; }
2591
+ .h-full { height: 100%; }
2592
+ .min-h-screen { min-height: 100vh; }
2593
+ .min-w-0 { min-width: 0; }
2594
+ .max-w-sm { max-width: 384px; }
2595
+ .max-w-md { max-width: 512px; }
2596
+ .max-w-lg { max-width: 720px; }
2597
+ .max-w-xl { max-width: 960px; }
2598
+ .max-w-full { max-width: 100%; }
2599
+
2600
+ /* ============================================================
2601
+ OVERFLOW
2602
+ ============================================================ */
2603
+ .overflow-hidden { overflow: hidden; }
2604
+ .overflow-auto { overflow: auto; }
2605
+ .overflow-x-auto { overflow-x: auto; }
2606
+ .overflow-y-auto { overflow-y: auto; }
2607
+ .overflow-visible { overflow: visible; }
2608
+
2609
+ /* ============================================================
2610
+ POSITION
2611
+ ============================================================ */
2612
+ .relative { position: relative; }
2613
+ .absolute { position: absolute; }
2614
+ .fixed { position: fixed; }
2615
+ .sticky { position: sticky; }
2616
+
2617
+ .inset-0 { inset: 0; }
2618
+ .top-0 { top: 0; }
2619
+ .bottom-0 { bottom: 0; }
2620
+ .left-0 { left: 0; }
2621
+ .right-0 { right: 0; }
2622
+
2623
+ /* ============================================================
2624
+ Z-INDEX
2625
+ ============================================================ */
2626
+ .z-10 { z-index: 10; }
2627
+ .z-20 { z-index: 20; }
2628
+ .z-30 { z-index: var(--z-dropdown); }
2629
+ .z-50 { z-index: var(--z-modal); }
2630
+ .z-60 { z-index: var(--z-toast); }
2631
+
2632
+ /* ============================================================
2633
+ BORDER RADIUS
2634
+ ============================================================ */
2635
+ .rounded-sm { border-radius: var(--radius-sm); }
2636
+ .rounded-md { border-radius: var(--radius-md); }
2637
+ .rounded-lg { border-radius: var(--radius-lg); }
2638
+ .rounded-xl { border-radius: var(--radius-xl); }
2639
+ .rounded-2xl { border-radius: var(--radius-2xl); }
2640
+ .rounded-full { border-radius: var(--radius-full); }
2641
+
2642
+ /* ============================================================
2643
+ BACKGROUND COLORS
2644
+ ============================================================ */
2645
+ .bg-page { background-color: var(--color-bg-page); }
2646
+ .bg-card { background-color: var(--color-bg-card); }
2647
+ .bg-elevated { background-color: var(--color-bg-elevated); }
2648
+ .bg-secondary { background-color: var(--color-bg-secondary); }
2649
+ .bg-tertiary { background-color: var(--color-bg-tertiary); }
2650
+ .bg-success { background-color: var(--color-success-bg); }
2651
+ .bg-error { background-color: var(--color-error-bg); }
2652
+ .bg-warning { background-color: var(--color-warning-bg); }
2653
+ .bg-info { background-color: var(--color-info-bg); }
2654
+
2655
+ /* Gradient backgrounds */
2656
+ .bg-brand { background: var(--gradient-brand); }
2657
+ .bg-magical { background: var(--gradient-magical); }
2658
+ .bg-purple-radial { background: var(--gradient-purple-radial); }
2659
+
2660
+ /* ============================================================
2661
+ BORDER
2662
+ ============================================================ */
2663
+ .border { border: 1px solid var(--color-border-structural); }
2664
+ .border-top { border-top: 1px solid var(--color-border-structural); }
2665
+ .border-bottom { border-bottom: 1px solid var(--color-border-structural); }
2666
+ .border-left { border-left: 1px solid var(--color-border-structural); }
2667
+ .border-right { border-right: 1px solid var(--color-border-structural); }
2668
+ .border-none { border: none; }
2669
+
2670
+ /* ============================================================
2671
+ CURSOR
2672
+ ============================================================ */
2673
+ .cursor-pointer { cursor: pointer; }
2674
+ .cursor-default { cursor: default; }
2675
+ .cursor-not-allowed { cursor: not-allowed; }
2676
+ .cursor-wait { cursor: wait; }
2677
+
2678
+ /* ============================================================
2679
+ POINTER EVENTS
2680
+ ============================================================ */
2681
+ .pointer-none { pointer-events: none; }
2682
+
2683
+ /* ============================================================
2684
+ OPACITY
2685
+ ============================================================ */
2686
+ .opacity-0 { opacity: 0; }
2687
+ .opacity-50 { opacity: 0.5; }
2688
+ .opacity-60 { opacity: 0.6; }
2689
+ .opacity-100 { opacity: 1; }
2690
+
2691
+ /* ============================================================
2692
+ SELECTION
2693
+ ============================================================ */
2694
+ .select-none { user-select: none; }
2695
+ .select-text { user-select: text; }
2696
+
2697
+ /* ============================================================
2698
+ SHADOWS
2699
+ ============================================================ */
2700
+ .shadow-sm { box-shadow: var(--shadow-sm); }
2701
+ .shadow-md { box-shadow: var(--shadow-md); }
2702
+ .shadow-lg { box-shadow: var(--shadow-lg); }
2703
+
2704
+ /* ============================================================
2705
+ TRANSITIONS
2706
+ ============================================================ */
2707
+ .transition { transition: all var(--transition-base); }
2708
+ .transition-colors { transition: color var(--transition-base), background-color var(--transition-base), border-color var(--transition-base); }
2709
+
2710
+
2711
+ .stat-grid {
2712
+ display: grid;
2713
+ grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
2714
+ gap: var(--space-4);
2715
+ margin-bottom: var(--space-6);
2716
+ }
2717
+ .stat-grid-top {
2718
+ display: grid;
2719
+ grid-template-columns: repeat(4, 1fr);
2720
+ gap: var(--space-4);
2721
+ margin-bottom: var(--space-4);
2722
+ }
2723
+ @media (max-width: 960px) { .stat-grid-top { grid-template-columns: repeat(2,1fr); } }
2724
+ .stat-card {
2725
+ background: var(--color-bg-card);
2726
+ border: 1px solid var(--color-border-structural);
2727
+ border-radius: var(--radius-xl);
2728
+ padding: var(--space-4) var(--space-5);
2729
+ display: flex; flex-direction: column; gap: var(--space-1);
2730
+ }
2731
+ .stat-card-label {
2732
+ font-size: var(--text-xs); font-weight: 600;
2733
+ color: var(--color-text-muted);
2734
+ text-transform: uppercase; letter-spacing: 0.05em;
2735
+ }
2736
+ .stat-card-value {
2737
+ font-size: 1.75rem; font-weight: 700;
2738
+ color: var(--color-text-primary); line-height: 1.2;
2739
+ }
2740
+ .stat-card-desc { font-size: var(--text-xs); color: var(--color-text-muted); }
2741
+ .stat-card-grand { border-top: 3px solid #2297F6; }
2742
+ .stat-card-web { border-top: 3px solid #259F4C; }
2743
+ .stat-card-cli { border-top: 3px solid #C084FC; }
2744
+ .stat-card-premium { border-top: 3px solid #F5A534; }
2745
+ .stat-card-muted { border-top: 3px solid var(--color-border-structural); }
2746
+
2747
+ .charts-row {
2748
+ display: grid; grid-template-columns: 1fr 1fr;
2749
+ gap: var(--space-4); margin-bottom: var(--space-4);
2750
+ }
2751
+ @media (max-width: 900px) { .charts-row { grid-template-columns: 1fr; } }
2752
+
2753
+ .progress-bar-wrap { background: var(--color-bg-quaternary); border-radius: var(--radius-full); height: 6px; overflow: hidden; }
2754
+ .progress-bar-fill { height: 100%; border-radius: var(--radius-full); transition: width 0.3s ease; }
2755
+ .fill-ok { background: var(--color-success); }
2756
+ .fill-warn { background: var(--color-warning); }
2757
+ .fill-over { background: var(--color-error); }
2758
+
2759
+ tbody tr { cursor: pointer; transition: background 0.12s; }
2760
+ tbody tr:hover td { background: var(--color-bg-hover) !important; }
2761
+
2762
+ /* spend type mini pill */
2763
+ .spend-pill {
2764
+ display: inline-flex; align-items: center; gap: 3px;
2765
+ font-size: 11px; font-weight: 600;
2766
+ padding: 1px 6px; border-radius: var(--radius-full);
2767
+ }
2768
+ .pill-web { background: rgba(37,159,76,0.15); color: var(--color-success-text); }
2769
+ .pill-cli { background: rgba(192,132,252,0.15); color: var(--color-purple); }
2770
+ .pill-premium { background: rgba(245,165,52,0.15); color: var(--color-warning-text); }
2771
+
2772
+
2773
+ /* === Analytics section styles === */
2774
+ .analytics-section { margin-top: var(--space-4); padding-top: var(--space-4); border-top: 1px solid var(--color-border-structural); }
2775
+ .dim-bars { display: flex; flex-direction: column; gap: 6px; margin-top: var(--space-3); }
2776
+ .dim-bar-row { display: grid; grid-template-columns: 130px 1fr 36px; align-items: center; gap: 8px; }
2777
+ .dim-bar-label { font-size: 11px; color: var(--color-text-muted); }
2778
+ .dim-bar-track { background: var(--color-bg-secondary); border-radius: var(--radius-full); height: 6px; overflow: hidden; }
2779
+ .dim-bar-fill { height: 100%; border-radius: var(--radius-full); transition: width 0.3s; }
2780
+ .dim-bar-score { font-size: 11px; font-weight: 600; color: var(--color-text-primary); text-align: right; }
2781
+ .champion-header { display: flex; align-items: center; gap: var(--space-3); flex-wrap: wrap; margin-bottom: var(--space-3); }
2782
+ .champion-score { font-size: 1.6rem; font-weight: 700; color: var(--color-text-primary); }
2783
+ .champion-rank { font-size: 11px; color: var(--color-text-muted); }
2784
+ .cli-stats-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: var(--space-2); margin-top: var(--space-3); }
2785
+ .cli-stat-cell { background: var(--color-bg-secondary); border-radius: var(--radius-md); padding: var(--space-2) var(--space-3); }
2786
+ .cli-stat-val { font-size: 1rem; font-weight: 700; color: var(--color-text-primary); }
2787
+ .cli-stat-lbl { font-size: 10px; color: var(--color-text-muted); margin-top: 2px; }
2788
+
2789
+ .modal-backdrop {
2790
+ display: none; position: fixed; inset: 0;
2791
+ background: rgba(0,0,0,0.65); z-index: 50;
2792
+ align-items: center; justify-content: center;
2793
+ }
2794
+ .modal-backdrop.open { display: flex; }
2795
+ .modal-box {
2796
+ background: var(--color-bg-elevated);
2797
+ border: 1px solid var(--color-border-structural);
2798
+ border-radius: var(--radius-2xl);
2799
+ padding: var(--space-6);
2800
+ width: 100%; max-width: 580px;
2801
+ max-height: 90vh; overflow-y: auto; position: relative;
2802
+ }
2803
+ .modal-close {
2804
+ position: absolute; top: var(--space-4); right: var(--space-4);
2805
+ background: var(--color-bg-hover);
2806
+ border: 1px solid var(--color-border-structural);
2807
+ border-radius: var(--radius-full);
2808
+ color: var(--color-text-primary);
2809
+ width: 28px; height: 28px; cursor: pointer; font-size: 14px;
2810
+ display: flex; align-items: center; justify-content: center;
2811
+ }
2812
+ .modal-close:hover { background: var(--color-bg-hover-strong); }
2813
+ .modal-email { font-size: var(--text-sm); color: var(--color-text-muted); margin-bottom: var(--space-4); word-break: break-all; }
2814
+ .modal-section { margin-top: var(--space-4); padding-top: var(--space-4); border-top: 1px solid var(--color-border-structural); }
2815
+ .modal-section-title { font-size: var(--text-xs); font-weight: 600; color: var(--color-text-muted); text-transform: uppercase; letter-spacing: 0.05em; margin-bottom: var(--space-3); }
2816
+ .dl-grid { display: grid; grid-template-columns: max-content 1fr; gap: var(--space-2) var(--space-4); font-size: var(--text-sm); }
2817
+ .dl-grid dt { color: var(--color-text-muted); white-space: nowrap; }
2818
+ .dl-grid dd { color: var(--color-text-primary); font-weight: 500; }
2819
+ .budget-bar-label { display: flex; justify-content: space-between; font-size: var(--text-xs); color: var(--color-text-muted); margin-bottom: var(--space-1); }
2820
+ .big-spend { font-size: 2.2rem; font-weight: 700; color: #2297F6; margin-bottom: 4px; }
2821
+ .spend-breakdown-row {
2822
+ display: grid; grid-template-columns: repeat(3, 1fr);
2823
+ gap: var(--space-3); margin-bottom: var(--space-4);
2824
+ }
2825
+ .spend-breakdown-cell {
2826
+ background: var(--color-bg-secondary);
2827
+ border: 1px solid var(--color-border-structural);
2828
+ border-radius: var(--radius-lg);
2829
+ padding: var(--space-2) var(--space-3);
2830
+ text-align: center;
2831
+ }
2832
+ .spend-breakdown-cell .val { font-size: 1.15rem; font-weight: 700; }
2833
+ .spend-breakdown-cell .lbl { font-size: 10px; color: var(--color-text-muted); margin-top: 2px; }
2834
+ .search-bar { display: flex; gap: var(--space-2); align-items: center; flex-wrap: wrap; }
2835
+ .search-bar .input-wrapper { flex: 1; min-width: 200px; }
2836
+ .page-header { display: flex; align-items: flex-start; justify-content: space-between; gap: var(--space-4); margin-bottom: var(--space-6); }
2837
+ .mini-budget-block { margin-top: 8px; }
2838
+ .mini-budget-label { font-size: 10px; color: var(--color-text-muted); margin-bottom: 2px; }
2839
+ </style>
2840
+ </head>
2841
+ <body class="p-6">
2842
+ <div class="container" style="max-width:1400px;">
2843
+
2844
+ <div class="page-header">
2845
+ <div>
2846
+ <h1>__DASHBOARD_TITLE__</h1>
2847
+ <p class="text-muted">LiteLLM direct customer spend &mdash; __TOTAL_USERS__ participants &nbsp;&middot;&nbsp; Generated __REPORT_DATE__ __SUBTITLE_NOTES__</p>
2848
+ </div>
2849
+ <span class="badge badge-in-progress"><span class="badge-dot"></span>Live LiteLLM Data</span>
2850
+ </div>
2851
+
2852
+ <!-- Row 1: Grand total + 3 type totals -->
2853
+ <div class="stat-grid-top">
2854
+ <div class="stat-card stat-card-grand">
2855
+ <span class="stat-card-label">&#x1F4B0; Grand Total</span>
2856
+ <span class="stat-card-value">__GRAND_TOTAL__</span>
2857
+ <span class="stat-card-desc">All 3 spend types combined</span>
2858
+ </div>
2859
+ <div class="stat-card stat-card-web">
2860
+ <span class="stat-card-label">&#x1F310; Web / Platform</span>
2861
+ <span class="stat-card-value">__WEB_TOTAL__</span>
2862
+ <span class="stat-card-desc">email@domain (UI spend)</span>
2863
+ </div>
2864
+ <div class="stat-card stat-card-cli">
2865
+ <span class="stat-card-label">&#x1F4BB; CLI</span>
2866
+ <span class="stat-card-value">__CLI_TOTAL__</span>
2867
+ <span class="stat-card-desc">email_codemie_cli</span>
2868
+ </div>
2869
+ <div class="stat-card stat-card-premium">
2870
+ <span class="stat-card-label">&#x2728; Premium Models</span>
2871
+ <span class="stat-card-value">__PREMIUM_TOTAL__</span>
2872
+ <span class="stat-card-desc">email_codemie_premium_models</span>
2873
+ </div>
2874
+ </div>
2875
+
2876
+ <!-- Row 2: User stats -->
2877
+ <div class="stat-grid" style="grid-template-columns:repeat(auto-fit,minmax(160px,1fr));margin-bottom:var(--space-6);">
2878
+ <div class="stat-card stat-card-muted">
2879
+ <span class="stat-card-label">Participants</span>
2880
+ <span class="stat-card-value">__TOTAL_USERS__</span>
2881
+ <span class="stat-card-desc">From bootcamp.xlsx</span>
2882
+ </div>
2883
+ <div class="stat-card stat-card-muted">
2884
+ <span class="stat-card-label">In LiteLLM</span>
2885
+ <span class="stat-card-value">__IN_LITELLM__</span>
2886
+ <span class="stat-card-desc">Registered as customers</span>
2887
+ </div>
2888
+ <div class="stat-card stat-card-muted">
2889
+ <span class="stat-card-label">Active Spenders</span>
2890
+ <span class="stat-card-value">__ACTIVE_USERS__</span>
2891
+ <span class="stat-card-desc">Any spend &gt; $0</span>
2892
+ </div>
2893
+ <div class="stat-card stat-card-muted">
2894
+ <span class="stat-card-label">Avg Spend (active)</span>
2895
+ <span class="stat-card-value">__AVG_SPEND__</span>
2896
+ <span class="stat-card-desc">Mean — active users only</span>
2897
+ </div>
2898
+
2899
+ </div>
2900
+
2901
+ <!-- Budget estimate widget -->
2902
+ <div class="card" style="margin-bottom:var(--space-4);border-color:var(--color-info-border);background:linear-gradient(135deg,var(--color-bg-card) 80%,var(--color-info-bg) 100%);">
2903
+ <div class="card-body" style="display:grid;grid-template-columns:1fr auto;align-items:center;gap:var(--space-6);padding:var(--space-5) var(--space-6);">
2904
+ <div>
2905
+ <div style="display:flex;align-items:center;gap:var(--space-2);margin-bottom:var(--space-2);">
2906
+ <span style="font-size:18px;">&#x1F4B3;</span>
2907
+ <h3 style="margin:0;">Estimated Monthly Program Budget</h3>
2908
+ <span class="badge badge-in-progress">Projection</span>
2909
+ </div>
2910
+ <p class="text-muted" style="margin:0;font-size:var(--text-sm);">
2911
+ Projected cost for the full cohort based on the
2912
+ <strong style="color:var(--color-info-text);">current average spend per active user (__AVG_SPEND__)</strong>
2913
+ scaled to all <strong style="color:var(--color-text-primary);">656 participants</strong>,
2914
+ with a <strong style="color:var(--color-text-primary);">+20% overhead buffer</strong> for spikes, onboarding, and premium model usage.
2915
+ </p>
2916
+ <div style="margin-top:var(--space-3);display:flex;gap:var(--space-6);flex-wrap:wrap;">
2917
+ <div>
2918
+ <div style="font-size:var(--text-xs);color:var(--color-text-muted);margin-bottom:2px;">Base estimate</div>
2919
+ <div style="font-size:var(--text-sm);font-weight:600;color:var(--color-text-primary);">__BASE_ESTIMATE__ &nbsp;<span style="font-weight:400;color:var(--color-text-muted);">(__AVG_SPEND__ &times; __TOTAL_USERS__)</span></div>
2920
+ </div>
2921
+ <div>
2922
+ <div style="font-size:var(--text-xs);color:var(--color-text-muted);margin-bottom:2px;">+20% buffer</div>
2923
+ <div style="font-size:var(--text-sm);font-weight:600;color:var(--color-warning-text);">__BUFFER_AMOUNT__</div>
2924
+ </div>
2925
+ </div>
2926
+ </div>
2927
+ <div style="text-align:right;flex-shrink:0;">
2928
+ <div style="font-size:var(--text-xs);color:var(--color-text-muted);margin-bottom:4px;">RECOMMENDED BUDGET</div>
2929
+ <div style="font-size:2.8rem;font-weight:700;color:#2297F6;line-height:1;">__BUDGET_PROJECTION__</div>
2930
+ <div style="font-size:var(--text-sm);color:var(--color-text-muted);margin-top:4px;">per month / full cohort</div>
2931
+ <div style="font-size:var(--text-xs);color:var(--color-text-muted);margin-top:2px;">avg __AVG_SPEND__ &times; __TOTAL_USERS__ &times; 1.20</div>
2932
+ </div>
2933
+ </div>
2934
+ </div>
2935
+
2936
+ <!-- Charts -->
2937
+ <div class="charts-row" style="align-items:start;">
2938
+ <div class="card">
2939
+ <div class="card-header"><div class="card-title">Spend Distribution (Total per User)</div></div>
2940
+ <div class="card-body" style="height:250px;"><canvas id="rangeChart"></canvas></div>
2941
+ </div>
2942
+ <div class="card">
2943
+ <div class="card-header"><div class="card-title">Top 10 by Total Spend</div></div>
2944
+ <div class="card-body" style="height:460px;"><canvas id="topChart"></canvas></div>
2945
+ </div>
2946
+ </div>
2947
+ <div class="card" style="margin-bottom:var(--space-4);">
2948
+ <div class="card-header"><div class="card-title">Spend Breakdown by Type</div></div>
2949
+ <div class="card-body" style="height:240px;"><canvas id="breakdownChart"></canvas></div>
2950
+ </div>
2951
+
2952
+ <!-- Table -->
2953
+ <div class="card">
2954
+ <div class="card-header">
2955
+ <div class="card-title">All Participants</div>
2956
+ <div class="search-bar">
2957
+ <div class="input-wrapper">
2958
+ <input class="form-input" id="searchInput" type="text" placeholder="Search by email..." oninput="filterTable()">
2959
+ </div>
2960
+ <select class="input" id="sortSelect" onchange="applySort()" style="width:180px;height:32px;">
2961
+ <option value="total_desc">Total &#x2193; (highest)</option>
2962
+ <option value="web_desc">Web &#x2193;</option>
2963
+ <option value="cli_desc">CLI &#x2193;</option>
2964
+ <option value="premium_desc">Premium &#x2193;</option>
2965
+ <option value="total_asc">Total &#x2191; (lowest)</option>
2966
+ <option value="email_asc">Email A&#x2192;Z</option>
2967
+ </select>
2968
+ <span class="badge" id="countBadge">656 users</span>
2969
+ </div>
2970
+ </div>
2971
+ <div class="card-body" style="padding:0;">
2972
+ <div class="table-wrapper">
2973
+ <table class="table" id="usersTable">
2974
+ <thead>
2975
+ <tr>
2976
+ <th style="width:44px;">#</th>
2977
+ <th>Email</th>
2978
+ <th class="td-number" style="color:var(--color-success-text);">Web ($)</th>
2979
+ <th class="td-number" style="color:var(--color-purple);">CLI ($)</th>
2980
+ <th class="td-number" style="color:var(--color-warning-text);">Premium ($)</th>
2981
+ <th class="td-number">Total ($)</th>
2982
+ <th style="width:110px;">Status</th>
2983
+ </tr>
2984
+ </thead>
2985
+ <tbody id="tableBody"></tbody>
2986
+ </table>
2987
+ </div>
2988
+ <div class="pagination" id="paginationBar" style="padding:12px 16px;"></div>
2989
+ </div>
2990
+ </div>
2991
+ </div>
2992
+
2993
+ <!-- Modal -->
2994
+ <div class="modal-backdrop" id="modalBackdrop" onclick="backdropClose(event)">
2995
+ <div class="modal-box">
2996
+ <button class="modal-close" onclick="closeModal()">&#x2715;</button>
2997
+ <h3 id="modalTitle"></h3>
2998
+ <p class="modal-email" id="modalEmail"></p>
2999
+ <div id="modalContent"></div>
3000
+ </div>
3001
+ </div>
3002
+
3003
+ <script>
3004
+ const ALL_USERS = __ALL_USERS_JSON__;
3005
+ const STATS = __STATS_JSON__;
3006
+ const ANALYTICS_DATA = __ANALYTICS_JSON__;
3007
+ const RANGES = __RANGES_JSON__;
3008
+
3009
+ let filtered = ALL_USERS.slice();
3010
+ let currentPage = 1;
3011
+ const PAGE_SIZE = 50;
3012
+
3013
+ // ── CHARTS ──
3014
+ function initCharts() {
3015
+ const rangeLabels = Object.keys(RANGES);
3016
+ const rangeValues = Object.values(RANGES);
3017
+ new Chart(document.getElementById('rangeChart'), {
3018
+ type: 'bar',
3019
+ data: { labels: rangeLabels, datasets: [{ label: 'Users', data: rangeValues,
3020
+ backgroundColor: ['#4C4C4C','#2297F6','#259F4C','#F5A534','#C084FC','#F9303C'], borderRadius: 6 }] },
3021
+ options: { responsive: true, maintainAspectRatio: false,
3022
+ plugins: { legend: { display: false }, tooltip: { callbacks: { label: c => ' ' + c.raw + ' users' } } },
3023
+ scales: { x: { grid: { color: '#333436' }, ticks: { color: '#BBBBBB', font: { size: 11 } } },
3024
+ y: { grid: { color: '#333436' }, ticks: { color: '#BBBBBB', font: { size: 11 } } } } }
3025
+ });
3026
+
3027
+ const top10 = ALL_USERS.filter(u => u.total_spend > 0).slice(0, 10);
3028
+ new Chart(document.getElementById('topChart'), {
3029
+ type: 'bar',
3030
+ data: { labels: top10.map(u => u.email.split('@')[0].replace(/_/g,' ')),
3031
+ datasets: [
3032
+ { label: 'Web', data: top10.map(u => u.web_spend), backgroundColor: '#259F4C', borderRadius: 0, barThickness: 28 },
3033
+ { label: 'CLI', data: top10.map(u => u.cli_spend), backgroundColor: '#C084FC', borderRadius: 0, barThickness: 28 },
3034
+ { label: 'Premium', data: top10.map(u => u.premium_spend), backgroundColor: '#F5A534', borderRadius: 4, barThickness: 28 }
3035
+ ] },
3036
+ options: { indexAxis: 'y', responsive: true, maintainAspectRatio: false,
3037
+ plugins: { legend: { position: 'bottom', labels: { color: '#BBBBBB', font: { size: 12 }, boxWidth: 14, padding: 16 } },
3038
+ tooltip: { callbacks: { label: c => ' ' + c.dataset.label + ': $' + c.raw.toFixed(2) } } },
3039
+ scales: { x: { stacked: true, grid: { color: '#333436' }, ticks: { color: '#BBBBBB', font: { size: 11 }, callback: v => '$' + v } },
3040
+ y: { stacked: true, grid: { color: 'transparent' }, ticks: { color: '#CCCCCC', font: { size: 12 } } } } }
3041
+ });
3042
+
3043
+ // Stacked breakdown for ALL users (aggregated)
3044
+ new Chart(document.getElementById('breakdownChart'), {
3045
+ type: 'bar',
3046
+ data: { labels: ['Total Spend'],
3047
+ datasets: [
3048
+ { label: 'Web / Platform', data: [STATS.web_total], backgroundColor: '#259F4C', borderRadius: 0 },
3049
+ { label: 'CLI', data: [STATS.cli_total], backgroundColor: '#C084FC', borderRadius: 0 },
3050
+ { label: 'Premium Models', data: [STATS.premium_total], backgroundColor: '#F5A534', borderRadius: 6 }
3051
+ ] },
3052
+ options: { indexAxis: 'y', responsive: true, maintainAspectRatio: false,
3053
+ plugins: { legend: { position: 'right', labels: { color: '#BBBBBB', font: { size: 12 }, boxWidth: 14 } },
3054
+ tooltip: { callbacks: { label: c => ' ' + c.dataset.label + ': $' + c.raw.toFixed(2) } } },
3055
+ scales: { x: { stacked: true, grid: { color: '#333436' }, ticks: { color: '#BBBBBB', callback: v => '$' + v } },
3056
+ y: { stacked: true, grid: { color: 'transparent' }, ticks: { color: '#BBBBBB' } } } }
3057
+ });
3058
+ }
3059
+
3060
+ function spendBadge(total) {
3061
+ if (total === 0) return '<span class="badge badge-not-started">No spend</span>';
3062
+ if (total <= 5) return '<span class="badge badge-in-progress">Low</span>';
3063
+ if (total <= 20) return '<span class="badge badge-success">Moderate</span>';
3064
+ if (total <= 50) return '<span class="badge badge-warning">High</span>';
3065
+ if (total <= 150) return '<span class="badge badge-error">Very High</span>';
3066
+ return '<span class="badge badge-error" style="background:rgba(249,48,60,0.25);">Top</span>';
3067
+ }
3068
+
3069
+ function fmt(v) { return v > 0 ? '$' + v.toFixed(2) : '—'; }
3070
+
3071
+ function renderTable() {
3072
+ const tbody = document.getElementById('tableBody');
3073
+ const start = (currentPage - 1) * PAGE_SIZE;
3074
+ const page = filtered.slice(start, start + PAGE_SIZE);
3075
+ if (!page.length) {
3076
+ tbody.innerHTML = '<tr><td colspan="7" style="text-align:center;color:var(--color-text-muted);padding:48px;">No results</td></tr>';
3077
+ return;
3078
+ }
3079
+ tbody.innerHTML = page.map(function(u, i) {
3080
+ const rank = start + i + 1;
3081
+ const tc = u.total_spend > 50 ? 'var(--color-warning-text)' : u.total_spend > 0 ? 'var(--color-text-primary)' : 'var(--color-text-muted)';
3082
+ return '<tr data-email="' + encodeURIComponent(u.email) + '" style="cursor:pointer;">'
3083
+ + '<td style="color:var(--color-text-muted);font-weight:600;font-size:var(--text-xs);">' + rank + '</td>'
3084
+ + '<td style="font-family:var(--font-mono);font-size:var(--text-xs);">' + u.email + '</td>'
3085
+ + '<td class="td-number" style="color:var(--color-success-text);">' + fmt(u.web_spend) + '</td>'
3086
+ + '<td class="td-number" style="color:var(--color-purple);">' + fmt(u.cli_spend) + '</td>'
3087
+ + '<td class="td-number" style="color:var(--color-warning-text);">' + fmt(u.premium_spend) + '</td>'
3088
+ + '<td class="td-number" style="font-weight:700;color:' + tc + ';">' + fmt(u.total_spend) + '</td>'
3089
+ + '<td>' + spendBadge(u.total_spend) + '</td>'
3090
+ + '</tr>';
3091
+ }).join('');
3092
+ }
3093
+
3094
+ function renderPagination() {
3095
+ const total = filtered.length;
3096
+ const pages = Math.ceil(total / PAGE_SIZE);
3097
+ document.getElementById('countBadge').textContent = total + ' users';
3098
+ const bar = document.getElementById('paginationBar');
3099
+ if (pages <= 1) { bar.innerHTML = '<span class="pagination-info">Showing ' + total + ' of ' + total + ' users</span>'; return; }
3100
+ const s = (currentPage-1)*PAGE_SIZE+1, e = Math.min(currentPage*PAGE_SIZE, total);
3101
+ let btns = '';
3102
+ const lo = Math.max(1, Math.min(currentPage-3, pages-6)), hi = Math.min(pages, lo+6);
3103
+ if (lo > 1) btns += '<button class="page-btn" onclick="goPage(1)">1</button><span style="color:var(--color-text-muted);padding:0 4px">&hellip;</span>';
3104
+ for (let p=lo; p<=hi; p++) btns += '<button class="page-btn' + (p===currentPage?' active':'') + '" onclick="goPage('+p+')">'+p+'</button>';
3105
+ if (hi < pages) btns += '<span style="color:var(--color-text-muted);padding:0 4px">&hellip;</span><button class="page-btn" onclick="goPage('+pages+')">'+pages+'</button>';
3106
+ bar.innerHTML = '<span class="pagination-info">Showing '+s+'&ndash;'+e+' of '+total+'</span>'
3107
+ + '<button class="page-btn'+(currentPage===1?' disabled':'')+'" onclick="goPage('+(currentPage-1)+')" '+(currentPage===1?'disabled':'')+'>&#8249;</button>'
3108
+ + btns
3109
+ + '<button class="page-btn'+(currentPage===pages?' disabled':'')+'" onclick="goPage('+(currentPage+1)+')" '+(currentPage===pages?'disabled':'')+'>&#8250;</button>';
3110
+ }
3111
+
3112
+ function goPage(p) {
3113
+ const pages = Math.ceil(filtered.length/PAGE_SIZE);
3114
+ if (p<1||p>pages) return;
3115
+ currentPage = p; renderTable(); renderPagination();
3116
+ window.scrollTo({top:0,behavior:'smooth'});
3117
+ }
3118
+
3119
+ function filterTable() {
3120
+ const q = document.getElementById('searchInput').value.toLowerCase();
3121
+ filtered = q ? ALL_USERS.filter(function(u){ return u.email.toLowerCase().indexOf(q)!==-1; }) : ALL_USERS.slice();
3122
+ doSort(); currentPage=1; renderTable(); renderPagination();
3123
+ }
3124
+
3125
+ function applySort() { doSort(); currentPage=1; renderTable(); renderPagination(); }
3126
+
3127
+ function doSort() {
3128
+ const s = document.getElementById('sortSelect').value;
3129
+ const fns = {
3130
+ total_desc: function(a,b){ return b.total_spend-a.total_spend; },
3131
+ web_desc: function(a,b){ return b.web_spend-a.web_spend; },
3132
+ cli_desc: function(a,b){ return b.cli_spend-a.cli_spend; },
3133
+ premium_desc: function(a,b){ return b.premium_spend-a.premium_spend; },
3134
+ total_asc: function(a,b){ return a.total_spend-b.total_spend; },
3135
+ email_asc: function(a,b){ return a.email.localeCompare(b.email); }
3136
+ };
3137
+ filtered.sort(fns[s] || fns.total_desc);
3138
+ }
3139
+
3140
+ function budgetBlock(label, spend, bud, color) {
3141
+ if (!bud) return '';
3142
+ const pct = bud.soft ? Math.min((spend/bud.soft)*100, 100) : null;
3143
+ const cls = pct!==null ? (pct>=100?'fill-over':pct>=75?'fill-warn':'fill-ok') : 'fill-ok';
3144
+ return '<div class="mini-budget-block">'
3145
+ + '<div style="font-size:10px;font-weight:600;color:'+color+';margin-bottom:3px;">'+label+'</div>'
3146
+ + '<dl class="dl-grid" style="gap:4px 12px;font-size:12px;">'
3147
+ + '<dt>Spend</dt><dd>$' + spend.toFixed(2) + '</dd>'
3148
+ + (bud.soft ? '<dt>Soft</dt><dd>$'+bud.soft+'</dd>' : '')
3149
+ + (bud.max ? '<dt>Hard</dt><dd>$'+bud.max+'</dd>' : '')
3150
+ + (bud.duration ? '<dt>Cycle</dt><dd>'+bud.duration+'</dd>' : '')
3151
+ + '</dl>'
3152
+ + (pct!==null ? '<div style="margin-top:6px;"><div class="budget-bar-label"><span style="font-size:10px;">Budget used</span><span style="font-size:10px;font-weight:700;color:'+color+';">'+pct.toFixed(0)+'%</span></div><div class="progress-bar-wrap" style="height:8px;"><div class="progress-bar-fill '+cls+'" style="width:'+pct.toFixed(1)+'%"></div></div></div>' : '')
3153
+ + '</div>';
3154
+ }
3155
+
3156
+ function tierBadge(tier) {
3157
+ var cls = {'pioneer':'badge-advanced','expert':'badge-in-progress','advanced':'badge-success','practitioner':'badge-warning','newcomer':'badge-not-started'}[tier] || 'badge-not-started';
3158
+ var icon = {'pioneer':'🏆','expert':'⭐','advanced':'🔥','practitioner':'📈','newcomer':'🌱'}[tier] || '•';
3159
+ return '<span class="badge ' + cls + '">' + icon + ' ' + (tier||'unknown') + '</span>';
3160
+ }
3161
+
3162
+ function dimBar(label, score, color) {
3163
+ var pct = Math.min(Math.max(score||0, 0), 100);
3164
+ return '<div class="dim-bar-row">'
3165
+ + '<div class="dim-bar-label">' + label + '</div>'
3166
+ + '<div class="dim-bar-track"><div class="dim-bar-fill" style="width:' + pct + '%;background:' + color + ';"></div></div>'
3167
+ + '<div class="dim-bar-score">' + (score||0).toFixed(0) + '</div>'
3168
+ + '</div>';
3169
+ }
3170
+
3171
+ function openModal(enc) {
3172
+ const email = decodeURIComponent(enc);
3173
+ const u = ALL_USERS.find(function(x){ return x.email===email; });
3174
+ if (!u) return;
3175
+
3176
+ document.getElementById('modalTitle').textContent = u.email.split('@')[0].replace(/_/g,' ');
3177
+ document.getElementById('modalEmail').textContent = u.email;
3178
+
3179
+ const statusBadge = u.total_spend===0 ? '<span class="badge badge-not-started">No spend</span>'
3180
+ : '<span class="badge badge-success"><span class="badge-dot"></span>Active</span>';
3181
+
3182
+ // Analytics data for this user
3183
+ const an = ANALYTICS_DATA[email] || {};
3184
+ const lb = an.lb || null;
3185
+ const cli = an.cli || null;
3186
+
3187
+ // Champion section
3188
+ var championHtml = '';
3189
+ if (lb) {
3190
+ var dimColors = ['#2297F6','#C084FC','#259F4C','#F5A534','#06B6D4','#F9303C'];
3191
+ var dimLabels = ['D1 · Platform Usage','D2 · Platform Creation','D3 · Workflow Usage','D4 · Workflow Creation','D5 · CLI & Agentic','D6 · Impact & Knowledge'];
3192
+ var dimScores = [lb.d1, lb.d2, lb.d3, lb.d4, lb.d5, lb.d6];
3193
+ championHtml = '<div class="analytics-section">'
3194
+ + '<div class="modal-section-title">AI Champion Profile</div>'
3195
+ + '<div class="champion-header">'
3196
+ + tierBadge(lb.tier)
3197
+ + (lb.intent ? '<span style="font-size:12px;color:var(--color-text-secondary);">' + lb.intent + '</span>' : '')
3198
+ + '</div>'
3199
+ + '<div style="display:flex;align-items:baseline;gap:8px;margin-bottom:12px;">'
3200
+ + '<span class="champion-score">' + (lb.score||0).toFixed(1) + '</span>'
3201
+ + '<span style="font-size:12px;color:var(--color-text-muted);">/ 100 score</span>'
3202
+ + (lb.rank ? '<span class="champion-rank">&nbsp;· Rank #' + lb.rank + '</span>' : '')
3203
+ + '</div>'
3204
+ + '<div class="dim-bars">';
3205
+ for (var i=0; i<6; i++) {
3206
+ championHtml += dimBar(dimLabels[i], dimScores[i], dimColors[i]);
3207
+ }
3208
+ championHtml += '</div></div>';
3209
+ }
3210
+
3211
+ // CLI section
3212
+ var cliHtml = '';
3213
+ if (cli) {
3214
+ var clsColor = {'production':'var(--color-success-text)','staging':'var(--color-warning-text)','development':'var(--color-info-text)'}[cli.classification] || 'var(--color-text-muted)';
3215
+ var clsBadge = {'production':'badge-success','staging':'badge-warning','development':'badge-in-progress'}[cli.classification] || 'badge-not-started';
3216
+ cliHtml = '<div class="analytics-section">'
3217
+ + '<div class="modal-section-title">CLI Activity (Last 30 Days)</div>'
3218
+ + '<div style="margin-bottom:8px;">'
3219
+ + (cli.classification ? '<span class="badge ' + clsBadge + '">' + cli.classification + '</span>' : '')
3220
+ + '</div>'
3221
+ + '<div class="cli-stats-grid">'
3222
+ + '<div class="cli-stat-cell"><div class="cli-stat-val">' + (cli.sessions||0) + '</div><div class="cli-stat-lbl">Sessions</div></div>'
3223
+ + '<div class="cli-stat-cell"><div class="cli-stat-val" style="color:var(--color-success-text);">+' + ((cli.lines_added||0)).toLocaleString() + '</div><div class="cli-stat-lbl">Lines Added</div></div>'
3224
+ + '<div class="cli-stat-cell"><div class="cli-stat-val" style="color:var(--color-error-text);">-' + ((cli.lines_removed||0)).toLocaleString() + '</div><div class="cli-stat-lbl">Lines Removed</div></div>'
3225
+ + '</div>'
3226
+ + (cli.net_lines !== null && cli.net_lines !== undefined ? '<div style="margin-top:8px;font-size:12px;color:var(--color-text-muted);">Net lines: <strong style="color:var(--color-text-primary);">' + (cli.net_lines>=0?'+':'') + (cli.net_lines||0).toLocaleString() + '</strong></div>' : '')
3227
+ + '</div>';
3228
+ }
3229
+
3230
+ document.getElementById('modalContent').innerHTML =
3231
+ '<div style="font-size:2.2rem;font-weight:700;color:#2297F6;margin-bottom:4px;">$' + u.total_spend.toFixed(2) + '</div>'
3232
+ + '<div style="margin-bottom:16px;">' + statusBadge + ' &nbsp;<span class="text-muted" style="font-size:11px;">Combined LiteLLM spend</span></div>'
3233
+ + '<div class="spend-breakdown-row">'
3234
+ + '<div class="spend-breakdown-cell">'
3235
+ + '<div class="val" style="color:var(--color-success-text);">$' + u.web_spend.toFixed(2) + '</div>'
3236
+ + '<div class="lbl">&#x1F310; Web / Platform</div>'
3237
+ + '</div>'
3238
+ + '<div class="spend-breakdown-cell">'
3239
+ + '<div class="val" style="color:var(--color-purple);">$' + u.cli_spend.toFixed(2) + '</div>'
3240
+ + '<div class="lbl">&#x1F4BB; CLI</div>'
3241
+ + '</div>'
3242
+ + '<div class="spend-breakdown-cell">'
3243
+ + '<div class="val" style="color:var(--color-warning-text);">$' + u.premium_spend.toFixed(2) + '</div>'
3244
+ + '<div class="lbl">&#x2728; Premium Models</div>'
3245
+ + '</div>'
3246
+ + '</div>'
3247
+ + '<div class="modal-section">'
3248
+ + '<div class="modal-section-title">Budget Details per Account</div>'
3249
+ + budgetBlock('Web / Platform (email@domain)', u.web_spend, u.web_budget, 'var(--color-success-text)')
3250
+ + (u.cli_spend > 0 || u.cli_budget ? '<hr style="border-color:var(--color-border-structural);margin:10px 0;">' + budgetBlock('CLI (email_codemie_cli)', u.cli_spend, u.cli_budget, 'var(--color-purple)') : '')
3251
+ + (u.premium_spend > 0 || u.premium_budget ? '<hr style="border-color:var(--color-border-structural);margin:10px 0;">' + budgetBlock('Premium Models (email_codemie_premium_models)', u.premium_spend, u.premium_budget, 'var(--color-warning-text)') : '')
3252
+ + '</div>'
3253
+ + championHtml
3254
+ + cliHtml;
3255
+
3256
+ document.getElementById('modalBackdrop').classList.add('open');
3257
+ }
3258
+
3259
+ function closeModal() { document.getElementById('modalBackdrop').classList.remove('open'); }
3260
+ function backdropClose(e) { if (e.target===document.getElementById('modalBackdrop')) closeModal(); }
3261
+ document.addEventListener('keydown', function(e){ if(e.key==='Escape') closeModal(); });
3262
+
3263
+ document.getElementById('tableBody').addEventListener('click', function(e) {
3264
+ const row = e.target.closest('tr[data-email]');
3265
+ if (row) openModal(row.getAttribute('data-email'));
3266
+ });
3267
+ doSort(); renderTable(); renderPagination(); initCharts();
3268
+ </script>
3269
+ </body>
3270
+ </html>