@leanspec/cli 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 (68) hide show
  1. package/README.md +267 -0
  2. package/bin/lean-spec-rust.js +201 -0
  3. package/bin/lean-spec.js +9 -0
  4. package/bin/leanspec-rust.js +193 -0
  5. package/bin/leanspec.js +9 -0
  6. package/binaries/darwin-arm64/leanspec +0 -0
  7. package/binaries/darwin-arm64/leanspec-http +0 -0
  8. package/binaries/darwin-arm64/leanspec-mcp +0 -0
  9. package/binaries/darwin-arm64/package.json +24 -0
  10. package/binaries/darwin-arm64/postinstall.js +17 -0
  11. package/binaries/darwin-x64/leanspec +0 -0
  12. package/binaries/darwin-x64/leanspec-http +0 -0
  13. package/binaries/darwin-x64/leanspec-mcp +0 -0
  14. package/binaries/darwin-x64/package.json +24 -0
  15. package/binaries/darwin-x64/postinstall.js +17 -0
  16. package/binaries/linux-x64/leanspec +0 -0
  17. package/binaries/linux-x64/leanspec-http +0 -0
  18. package/binaries/linux-x64/leanspec-mcp +0 -0
  19. package/binaries/linux-x64/package.json +24 -0
  20. package/binaries/linux-x64/postinstall.js +17 -0
  21. package/binaries/windows-x64/leanspec-http.exe +0 -0
  22. package/binaries/windows-x64/leanspec-mcp.exe +0 -0
  23. package/binaries/windows-x64/leanspec.exe +0 -0
  24. package/binaries/windows-x64/package.json +24 -0
  25. package/binaries/windows-x64/postinstall.js +6 -0
  26. package/package.json +48 -0
  27. package/templates/detailed/AGENTS-minimal.md +9 -0
  28. package/templates/detailed/AGENTS.md +114 -0
  29. package/templates/detailed/README.md +28 -0
  30. package/templates/detailed/config.json +20 -0
  31. package/templates/detailed/files/DESIGN.md +43 -0
  32. package/templates/detailed/files/PLAN.md +59 -0
  33. package/templates/detailed/files/README.md +30 -0
  34. package/templates/detailed/files/TEST.md +71 -0
  35. package/templates/examples/api-refactor/README.md +81 -0
  36. package/templates/examples/api-refactor/package.json +16 -0
  37. package/templates/examples/api-refactor/src/app.js +40 -0
  38. package/templates/examples/api-refactor/src/services/currencyService.js +43 -0
  39. package/templates/examples/api-refactor/src/services/timezoneService.js +41 -0
  40. package/templates/examples/api-refactor/src/services/weatherService.js +42 -0
  41. package/templates/examples/dark-theme/README.md +66 -0
  42. package/templates/examples/dark-theme/package.json +16 -0
  43. package/templates/examples/dark-theme/src/public/app.js +277 -0
  44. package/templates/examples/dark-theme/src/public/index.html +225 -0
  45. package/templates/examples/dark-theme/src/public/style.css +625 -0
  46. package/templates/examples/dark-theme/src/server.js +18 -0
  47. package/templates/examples/dashboard-widgets/README.md +70 -0
  48. package/templates/examples/dashboard-widgets/index.html +12 -0
  49. package/templates/examples/dashboard-widgets/package.json +22 -0
  50. package/templates/examples/dashboard-widgets/src/App.css +20 -0
  51. package/templates/examples/dashboard-widgets/src/App.jsx +16 -0
  52. package/templates/examples/dashboard-widgets/src/components/Dashboard.css +17 -0
  53. package/templates/examples/dashboard-widgets/src/components/Dashboard.jsx +15 -0
  54. package/templates/examples/dashboard-widgets/src/components/WidgetWrapper.css +23 -0
  55. package/templates/examples/dashboard-widgets/src/components/WidgetWrapper.jsx +16 -0
  56. package/templates/examples/dashboard-widgets/src/components/widgets/ChartWidget.css +33 -0
  57. package/templates/examples/dashboard-widgets/src/components/widgets/ChartWidget.jsx +28 -0
  58. package/templates/examples/dashboard-widgets/src/components/widgets/StatsWidget.css +24 -0
  59. package/templates/examples/dashboard-widgets/src/components/widgets/StatsWidget.jsx +22 -0
  60. package/templates/examples/dashboard-widgets/src/index.css +13 -0
  61. package/templates/examples/dashboard-widgets/src/main.jsx +10 -0
  62. package/templates/examples/dashboard-widgets/src/utils/mockData.js +30 -0
  63. package/templates/examples/dashboard-widgets/vite.config.js +6 -0
  64. package/templates/standard/AGENTS-minimal.md +10 -0
  65. package/templates/standard/AGENTS.md +114 -0
  66. package/templates/standard/README.md +25 -0
  67. package/templates/standard/config.json +18 -0
  68. package/templates/standard/files/README.md +42 -0
@@ -0,0 +1,81 @@
1
+ # API Refactor Demo
2
+
3
+ > **Tutorial**: [Refactoring with Specs](https://lean-spec.dev/docs/tutorials/refactoring-specs)
4
+
5
+ ## Scenario
6
+
7
+ You're maintaining a Node.js application that started simple but has grown messy. The app integrates with multiple external services (weather API, currency converter, timezone lookup), but all the HTTP logic is tangled together in the main application code.
8
+
9
+ You want to extract a reusable API client module that:
10
+ - Centralizes HTTP request handling
11
+ - Provides a clean interface for service calls
12
+ - Handles errors consistently
13
+ - Makes the code easier to test and maintain
14
+
15
+ ## What's Here
16
+
17
+ A monolithic Node.js app with:
18
+ - Weather lookup feature (calls external API)
19
+ - Currency conversion (calls external API)
20
+ - Timezone lookup (calls external API)
21
+ - All HTTP logic mixed into business logic (tight coupling)
22
+ - No error handling abstraction
23
+ - Hard to test individual parts
24
+
25
+ **Files:**
26
+ - `src/app.js` - Main application with all features
27
+ - `src/services/weatherService.js` - Weather API calls (tightly coupled)
28
+ - `src/services/currencyService.js` - Currency API calls (tightly coupled)
29
+ - `src/services/timezoneService.js` - Timezone API calls (tightly coupled)
30
+
31
+ ## Getting Started
32
+
33
+ ```bash
34
+ # Install dependencies
35
+ npm install
36
+
37
+ # Run the app
38
+ npm start
39
+
40
+ # Try the features:
41
+ # - Weather: Get weather for a city
42
+ # - Currency: Convert between currencies
43
+ # - Timezone: Look up timezone info
44
+ ```
45
+
46
+ ## Your Mission
47
+
48
+ Refactor the HTTP logic into a clean, reusable API client. Follow the tutorial and ask your AI assistant:
49
+
50
+ > "Help me refactor this app using LeanSpec. I want to extract a reusable API client module that centralizes all the HTTP logic."
51
+
52
+ The AI will guide you through:
53
+ 1. Creating a refactoring spec
54
+ 2. Designing the API client interface
55
+ 3. Extracting the HTTP logic step by step
56
+ 4. Updating services to use the new client
57
+ 5. Verifying everything still works
58
+
59
+ ## Current Problems
60
+
61
+ - **Duplicated code**: Each service reimplements HTTP requests
62
+ - **No error handling**: Errors handled inconsistently
63
+ - **Hard to test**: Can't mock HTTP calls easily
64
+ - **Tight coupling**: Business logic mixed with HTTP details
65
+ - **No retry logic**: Network failures aren't handled
66
+
67
+ These are perfect opportunities to practice refactoring with specs!
68
+
69
+ ## Expected Result
70
+
71
+ After refactoring, you should have:
72
+ ```
73
+ src/
74
+ app.js (unchanged interface)
75
+ client/
76
+ apiClient.js (new - centralized HTTP logic)
77
+ services/
78
+ weatherService.js (simplified - uses apiClient)
79
+ currencyService.js (simplified - uses apiClient)
80
+ timezoneService.js (simplified - uses apiClient)
81
+ ```
@@ -0,0 +1,16 @@
1
+ {
2
+ "name": "api-refactor-demo",
3
+ "version": "1.0.0",
4
+ "description": "Monolithic Node.js app for LeanSpec Tutorial 3",
5
+ "type": "module",
6
+ "scripts": {
7
+ "start": "node src/app.js",
8
+ "dev": "node --watch src/app.js"
9
+ },
10
+ "keywords": ["leanspec", "tutorial", "demo"],
11
+ "author": "",
12
+ "license": "MIT",
13
+ "dependencies": {
14
+ "node-fetch": "^3.3.2"
15
+ }
16
+ }
@@ -0,0 +1,40 @@
1
+ import { getWeather } from './services/weatherService.js';
2
+ import { convertCurrency } from './services/currencyService.js';
3
+ import { getTimezone } from './services/timezoneService.js';
4
+
5
+ console.log('=== Multi-Service App Demo ===\n');
6
+
7
+ // Demo 1: Weather lookup
8
+ console.log('1. Weather Lookup:');
9
+ try {
10
+ const weather = await getWeather('London');
11
+ console.log(` ${weather.city}: ${weather.temp}°C, ${weather.condition}`);
12
+ } catch (error) {
13
+ console.log(` Error: ${error.message}`);
14
+ }
15
+
16
+ console.log('');
17
+
18
+ // Demo 2: Currency conversion
19
+ console.log('2. Currency Conversion:');
20
+ try {
21
+ const result = await convertCurrency(100, 'USD', 'EUR');
22
+ console.log(` ${result.amount} ${result.from} = ${result.converted} ${result.to}`);
23
+ } catch (error) {
24
+ console.log(` Error: ${error.message}`);
25
+ }
26
+
27
+ console.log('');
28
+
29
+ // Demo 3: Timezone lookup
30
+ console.log('3. Timezone Lookup:');
31
+ try {
32
+ const timezone = await getTimezone('America/New_York');
33
+ console.log(` ${timezone.name}: ${timezone.offset} (${timezone.abbreviation})`);
34
+ } catch (error) {
35
+ console.log(` Error: ${error.message}`);
36
+ }
37
+
38
+ console.log('');
39
+ console.log('Notice: All services work, but they all duplicate HTTP logic!');
40
+ console.log('Your task: Extract a reusable API client module.');
@@ -0,0 +1,43 @@
1
+ import fetch from 'node-fetch';
2
+
3
+ /**
4
+ * Convert currency
5
+ *
6
+ * PROBLEM: Duplicates HTTP logic from weatherService
7
+ * - Same fetch boilerplate
8
+ * - Same error handling pattern
9
+ * - Same JSON parsing
10
+ * - Should be using a shared HTTP client!
11
+ */
12
+ export async function convertCurrency(amount, from, to) {
13
+ // Mock API endpoint (replace with real API in production)
14
+ const url = `https://api.exchangerate.host/convert?from=${from}&to=${to}&amount=${amount}`;
15
+
16
+ try {
17
+ const response = await fetch(url);
18
+
19
+ if (!response.ok) {
20
+ throw new Error(`Currency API error: ${response.status}`);
21
+ }
22
+
23
+ const data = await response.json();
24
+
25
+ // Business logic: Transform API response to our format
26
+ return {
27
+ amount,
28
+ from,
29
+ to,
30
+ converted: data.result || (amount * 0.85).toFixed(2),
31
+ rate: data.info?.rate || 0.85,
32
+ };
33
+ } catch (error) {
34
+ // For demo, return mock data on error
35
+ return {
36
+ amount,
37
+ from,
38
+ to,
39
+ converted: (amount * 0.85).toFixed(2),
40
+ rate: 0.85,
41
+ };
42
+ }
43
+ }
@@ -0,0 +1,41 @@
1
+ import fetch from 'node-fetch';
2
+
3
+ /**
4
+ * Get timezone information
5
+ *
6
+ * PROBLEM: Yet another copy of the same HTTP logic!
7
+ * - Third time we're writing fetch + error handling + JSON parsing
8
+ * - Violates DRY principle
9
+ * - Makes testing hard (need to mock fetch in 3 places)
10
+ * - Changes to HTTP logic need updates in 3 files
11
+ */
12
+ export async function getTimezone(zone) {
13
+ // Mock API endpoint (replace with real API in production)
14
+ const url = `http://worldtimeapi.org/api/timezone/${zone}`;
15
+
16
+ try {
17
+ const response = await fetch(url);
18
+
19
+ if (!response.ok) {
20
+ throw new Error(`Timezone API error: ${response.status}`);
21
+ }
22
+
23
+ const data = await response.json();
24
+
25
+ // Business logic: Transform API response to our format
26
+ return {
27
+ name: data.timezone || zone,
28
+ offset: data.utc_offset || '-05:00',
29
+ abbreviation: data.abbreviation || 'EST',
30
+ datetime: data.datetime || new Date().toISOString(),
31
+ };
32
+ } catch (error) {
33
+ // For demo, return mock data on error
34
+ return {
35
+ name: zone,
36
+ offset: '-05:00',
37
+ abbreviation: 'EST',
38
+ datetime: new Date().toISOString(),
39
+ };
40
+ }
41
+ }
@@ -0,0 +1,42 @@
1
+ import fetch from 'node-fetch';
2
+
3
+ /**
4
+ * Get weather for a city
5
+ *
6
+ * PROBLEM: This function has HTTP logic mixed with business logic
7
+ * - Manual fetch calls
8
+ * - Manual error handling
9
+ * - Manual JSON parsing
10
+ * - No retry logic
11
+ * - Hard to test (can't mock HTTP)
12
+ */
13
+ export async function getWeather(city) {
14
+ // Mock API endpoint (replace with real API in production)
15
+ const url = `https://api.weatherapi.com/v1/current.json?key=mock&q=${city}`;
16
+
17
+ try {
18
+ const response = await fetch(url);
19
+
20
+ if (!response.ok) {
21
+ throw new Error(`Weather API error: ${response.status}`);
22
+ }
23
+
24
+ const data = await response.json();
25
+
26
+ // Business logic: Transform API response to our format
27
+ return {
28
+ city: data.location?.name || city,
29
+ temp: data.current?.temp_c || Math.floor(Math.random() * 30),
30
+ condition: data.current?.condition?.text || 'Sunny',
31
+ humidity: data.current?.humidity || Math.floor(Math.random() * 100),
32
+ };
33
+ } catch (error) {
34
+ // For demo, return mock data on error
35
+ return {
36
+ city,
37
+ temp: Math.floor(Math.random() * 30),
38
+ condition: 'Sunny (mock data)',
39
+ humidity: Math.floor(Math.random() * 100),
40
+ };
41
+ }
42
+ }
@@ -0,0 +1,66 @@
1
+ # Dark Theme Demo
2
+
3
+ > **Tutorial**: [Adding Dark Theme Support](https://lean-spec.dev/docs/tutorials/adding-dark-theme)
4
+
5
+ ## Scenario
6
+
7
+ You're building a professional admin dashboard for a SaaS product. The dashboard looks modern and works great, but users keep requesting a dark mode option for late-night monitoring sessions and to reduce eye strain during extended use. Currently, the dashboard only has a bright light theme.
8
+
9
+ ## What's Here
10
+
11
+ A professional admin dashboard with:
12
+ - Interactive data visualization with charts (Chart.js)
13
+ - Real-time statistics cards with animations
14
+ - Sidebar navigation and top bar
15
+ - Activity feed with recent events
16
+ - Responsive layout
17
+ - Clean, modern light theme
18
+ - No dark mode support (yet!)
19
+
20
+ **Files:**
21
+ - `src/server.js` - Express server for static files
22
+ - `src/public/index.html` - Dashboard interface with sidebar, charts, and stats
23
+ - `src/public/style.css` - Current light theme styles (CSS custom properties ready)
24
+ - `src/public/app.js` - Dashboard logic, chart initialization, and animations
25
+
26
+ ## Getting Started
27
+
28
+ ```bash
29
+ # Install dependencies
30
+ npm install
31
+
32
+ # Start the server
33
+ npm start
34
+
35
+ # Open in your browser:
36
+ # http://localhost:3000
37
+ ```
38
+
39
+ ## Your Mission
40
+
41
+ Add dark theme support with automatic switching based on system preferences. Follow the tutorial and ask your AI assistant:
42
+
43
+ > "Help me add dark theme support to this admin dashboard using LeanSpec. It should automatically switch based on the user's system theme preference."
44
+
45
+ The AI will guide you through:
46
+ 1. Creating a spec for dark theme support
47
+ 2. Designing CSS custom properties for dark mode colors
48
+ 3. Implementing the `@media (prefers-color-scheme: dark)` query
49
+ 4. Ensuring charts adapt to the theme
50
+ 5. Testing the theme switching
51
+
52
+ ## Current Limitations
53
+
54
+ - Only light theme available
55
+ - No manual theme toggle
56
+ - Chart colors don't adapt to theme
57
+ - No theme persistence across sessions
58
+
59
+ These are perfect opportunities to practice spec-driven development!
60
+
61
+ ## Tech Stack
62
+
63
+ - **Frontend**: Vanilla HTML, CSS, JavaScript
64
+ - **Charts**: Chart.js 4.4.0
65
+ - **Backend**: Express.js (serving static files)
66
+ - **Styling**: CSS Custom Properties (CSS Variables)
@@ -0,0 +1,16 @@
1
+ {
2
+ "name": "dark-theme-demo",
3
+ "version": "1.0.0",
4
+ "description": "Professional admin dashboard for LeanSpec Dark Theme Tutorial",
5
+ "type": "module",
6
+ "scripts": {
7
+ "start": "node src/server.js",
8
+ "dev": "node --watch src/server.js"
9
+ },
10
+ "keywords": ["leanspec", "tutorial", "demo", "dashboard", "admin"],
11
+ "author": "",
12
+ "license": "MIT",
13
+ "dependencies": {
14
+ "express": "^4.18.2"
15
+ }
16
+ }
@@ -0,0 +1,277 @@
1
+ // Admin Dashboard - Interactive data visualization
2
+ // This is a starting point for adding dark theme support
3
+
4
+ // ============================================
5
+ // Initialize Charts
6
+ // ============================================
7
+
8
+ function initializeCharts() {
9
+ // Revenue Chart
10
+ const revenueCtx = document.getElementById('revenueChart');
11
+ if (revenueCtx) {
12
+ new Chart(revenueCtx, {
13
+ type: 'line',
14
+ data: {
15
+ labels: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'],
16
+ datasets: [{
17
+ label: 'Revenue',
18
+ data: [4200, 5100, 4800, 6200, 5800, 7100, 6500],
19
+ borderColor: '#0066cc',
20
+ backgroundColor: 'rgba(0, 102, 204, 0.1)',
21
+ borderWidth: 2,
22
+ tension: 0.4,
23
+ fill: true,
24
+ pointBackgroundColor: '#0066cc',
25
+ pointBorderColor: '#fff',
26
+ pointBorderWidth: 2,
27
+ pointRadius: 4,
28
+ pointHoverRadius: 6
29
+ }]
30
+ },
31
+ options: {
32
+ responsive: true,
33
+ maintainAspectRatio: false,
34
+ plugins: {
35
+ legend: {
36
+ display: false
37
+ },
38
+ tooltip: {
39
+ backgroundColor: 'rgba(0, 0, 0, 0.8)',
40
+ padding: 12,
41
+ borderRadius: 8,
42
+ titleColor: '#fff',
43
+ bodyColor: '#fff',
44
+ callbacks: {
45
+ label: function(context) {
46
+ return '$' + context.parsed.y.toLocaleString();
47
+ }
48
+ }
49
+ }
50
+ },
51
+ scales: {
52
+ y: {
53
+ beginAtZero: true,
54
+ grid: {
55
+ color: 'rgba(0, 0, 0, 0.05)'
56
+ },
57
+ ticks: {
58
+ callback: function(value) {
59
+ return '$' + value.toLocaleString();
60
+ }
61
+ }
62
+ },
63
+ x: {
64
+ grid: {
65
+ display: false
66
+ }
67
+ }
68
+ }
69
+ }
70
+ });
71
+ }
72
+
73
+ // Traffic Chart
74
+ const trafficCtx = document.getElementById('trafficChart');
75
+ if (trafficCtx) {
76
+ new Chart(trafficCtx, {
77
+ type: 'doughnut',
78
+ data: {
79
+ labels: ['Organic', 'Direct', 'Social', 'Referral'],
80
+ datasets: [{
81
+ data: [42, 28, 18, 12],
82
+ backgroundColor: [
83
+ '#0066cc',
84
+ '#00a854',
85
+ '#7c3aed',
86
+ '#ff8c00'
87
+ ],
88
+ borderWidth: 0,
89
+ hoverOffset: 8
90
+ }]
91
+ },
92
+ options: {
93
+ responsive: true,
94
+ maintainAspectRatio: false,
95
+ plugins: {
96
+ legend: {
97
+ position: 'bottom',
98
+ labels: {
99
+ padding: 16,
100
+ usePointStyle: true,
101
+ pointStyle: 'circle',
102
+ font: {
103
+ size: 12
104
+ }
105
+ }
106
+ },
107
+ tooltip: {
108
+ backgroundColor: 'rgba(0, 0, 0, 0.8)',
109
+ padding: 12,
110
+ borderRadius: 8,
111
+ titleColor: '#fff',
112
+ bodyColor: '#fff',
113
+ callbacks: {
114
+ label: function(context) {
115
+ return context.label + ': ' + context.parsed + '%';
116
+ }
117
+ }
118
+ }
119
+ }
120
+ }
121
+ });
122
+ }
123
+ }
124
+
125
+ // ============================================
126
+ // Animate Stats on Load
127
+ // ============================================
128
+
129
+ function animateValue(element, start, end, duration) {
130
+ const range = end - start;
131
+ const increment = range / (duration / 16);
132
+ let current = start;
133
+
134
+ const timer = setInterval(() => {
135
+ current += increment;
136
+ if ((increment > 0 && current >= end) || (increment < 0 && current <= end)) {
137
+ element.textContent = formatStatValue(end);
138
+ clearInterval(timer);
139
+ } else {
140
+ element.textContent = formatStatValue(Math.floor(current));
141
+ }
142
+ }, 16);
143
+ }
144
+
145
+ function formatStatValue(value) {
146
+ if (value >= 1000) {
147
+ return value.toLocaleString();
148
+ }
149
+ return value.toString();
150
+ }
151
+
152
+ function animateStats() {
153
+ const stats = [
154
+ { id: 'totalUsers', value: 2847 },
155
+ { id: 'revenue', value: 45231, prefix: '$' },
156
+ { id: 'completedTasks', value: 892 },
157
+ { id: 'activeProjects', value: 24 }
158
+ ];
159
+
160
+ stats.forEach(stat => {
161
+ const element = document.getElementById(stat.id);
162
+ if (element) {
163
+ if (stat.prefix) {
164
+ const originalText = element.textContent;
165
+ element.textContent = stat.prefix + '0';
166
+ animateValue(element, 0, stat.value, 1000);
167
+ const timer = setInterval(() => {
168
+ if (element.textContent !== stat.prefix + '0') {
169
+ const currentVal = element.textContent.replace(/[^0-9]/g, '');
170
+ element.textContent = stat.prefix + Number(currentVal).toLocaleString();
171
+ }
172
+ }, 16);
173
+ setTimeout(() => clearInterval(timer), 1000);
174
+ } else {
175
+ element.textContent = '0';
176
+ animateValue(element, 0, stat.value, 1000);
177
+ }
178
+ }
179
+ });
180
+ }
181
+
182
+ // ============================================
183
+ // Recent Activity
184
+ // ============================================
185
+
186
+ function loadRecentActivity() {
187
+ const activities = [
188
+ {
189
+ type: 'user',
190
+ title: 'New user registration',
191
+ description: 'Sarah Johnson joined the platform',
192
+ time: '5 minutes ago',
193
+ icon: `<svg width="20" height="20" viewBox="0 0 20 20" fill="none">
194
+ <path d="M10 11C12.2091 11 14 9.20914 14 7C14 4.79086 12.2091 3 10 3C7.79086 3 6 4.79086 6 7C6 9.20914 7.79086 11 10 11Z" stroke="currentColor" stroke-width="2"/>
195
+ <path d="M3 17C3 14.2386 5.23858 12 8 12H12C14.7614 12 17 14.2386 17 17" stroke="currentColor" stroke-width="2"/>
196
+ </svg>`
197
+ },
198
+ {
199
+ type: 'report',
200
+ title: 'Monthly report generated',
201
+ description: 'Q4 2024 performance summary is ready',
202
+ time: '1 hour ago',
203
+ icon: `<svg width="20" height="20" viewBox="0 0 20 20" fill="none">
204
+ <rect x="3" y="3" width="14" height="14" rx="2" stroke="currentColor" stroke-width="2"/>
205
+ <path d="M7 8H13M7 12H10" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
206
+ </svg>`
207
+ },
208
+ {
209
+ type: 'alert',
210
+ title: 'Server capacity warning',
211
+ description: 'CPU usage exceeded 80% threshold',
212
+ time: '3 hours ago',
213
+ icon: `<svg width="20" height="20" viewBox="0 0 20 20" fill="none">
214
+ <path d="M10 6V10M10 14H10.01M10 18C14.4183 18 18 14.4183 18 10C18 5.58172 14.4183 2 10 2C5.58172 2 2 5.58172 2 10C2 14.4183 5.58172 18 10 18Z" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
215
+ </svg>`
216
+ },
217
+ {
218
+ type: 'user',
219
+ title: 'Team member added',
220
+ description: 'Michael Chen joined the Development team',
221
+ time: '5 hours ago',
222
+ icon: `<svg width="20" height="20" viewBox="0 0 20 20" fill="none">
223
+ <path d="M10 11C12.2091 11 14 9.20914 14 7C14 4.79086 12.2091 3 10 3C7.79086 3 6 4.79086 6 7C6 9.20914 7.79086 11 10 11Z" stroke="currentColor" stroke-width="2"/>
224
+ <path d="M3 17C3 14.2386 5.23858 12 8 12H12C14.7614 12 17 14.2386 17 17" stroke="currentColor" stroke-width="2"/>
225
+ </svg>`
226
+ },
227
+ {
228
+ type: 'report',
229
+ title: 'Backup completed',
230
+ description: 'Database backup finished successfully',
231
+ time: '8 hours ago',
232
+ icon: `<svg width="20" height="20" viewBox="0 0 20 20" fill="none">
233
+ <path d="M3 12L9 18L21 6" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
234
+ </svg>`
235
+ }
236
+ ];
237
+
238
+ const activityList = document.getElementById('activityList');
239
+ if (activityList) {
240
+ activityList.innerHTML = activities.map(activity => `
241
+ <div class="activity-item">
242
+ <div class="activity-icon ${activity.type}">
243
+ ${activity.icon}
244
+ </div>
245
+ <div class="activity-content">
246
+ <div class="activity-title">${activity.title}</div>
247
+ <div class="activity-description">${activity.description}</div>
248
+ <div class="activity-time">${activity.time}</div>
249
+ </div>
250
+ </div>
251
+ `).join('');
252
+ }
253
+ }
254
+
255
+ // ============================================
256
+ // Initialize Dashboard
257
+ // ============================================
258
+
259
+ document.addEventListener('DOMContentLoaded', () => {
260
+ // Initialize all components
261
+ animateStats();
262
+ initializeCharts();
263
+ loadRecentActivity();
264
+
265
+ // Add subtle hover effects
266
+ document.querySelectorAll('.stat-card').forEach(card => {
267
+ card.addEventListener('mouseenter', (e) => {
268
+ e.currentTarget.style.transform = 'translateY(-4px)';
269
+ });
270
+ card.addEventListener('mouseleave', (e) => {
271
+ e.currentTarget.style.transform = 'translateY(0)';
272
+ });
273
+ });
274
+
275
+ console.log('✓ Dashboard initialized');
276
+ console.log('💡 Try adding dark theme support with CSS custom properties!');
277
+ });