frontend-hamroun 1.2.80 → 1.2.83

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 (128) hide show
  1. package/dist/index.cjs +1 -1
  2. package/dist/index.cjs.map +1 -1
  3. package/dist/index.client.cjs +1 -1
  4. package/dist/index.client.js +2 -2
  5. package/dist/index.js +11 -7
  6. package/dist/index.js.map +1 -1
  7. package/dist/{renderer-Din1y3YM.cjs → renderer-BL3gq8cW.cjs} +2 -2
  8. package/dist/{renderer-Din1y3YM.cjs.map → renderer-BL3gq8cW.cjs.map} +1 -1
  9. package/dist/{renderer-Bo9zkUZ_.js → renderer-Dyy-o05F.js} +2 -2
  10. package/dist/{renderer-Bo9zkUZ_.js.map → renderer-Dyy-o05F.js.map} +1 -1
  11. package/dist/{server-renderer-QHt45Ip2.js → server-renderer-C1WXH-zV.js} +99 -73
  12. package/dist/server-renderer-C1WXH-zV.js.map +1 -0
  13. package/dist/server-renderer-Chs-nmJm.cjs +2 -0
  14. package/dist/server-renderer-Chs-nmJm.cjs.map +1 -0
  15. package/dist/server-renderer.cjs +1 -1
  16. package/dist/server-renderer.js +1 -1
  17. package/package.json +1 -1
  18. package/templates/basic-app/src/App.jsx +16 -0
  19. package/templates/basic-app/src/client.jsx +5 -0
  20. package/templates/basic-app/src/components/Counter.jsx +13 -0
  21. package/templates/basic-app/src/jsx-shim.js +3 -0
  22. package/templates/basic-app/src/jsx-shim.ts +7 -0
  23. package/templates/basic-app/src/main.jsx +98 -0
  24. package/templates/basic-app/src/server.js +47 -0
  25. package/templates/complete-app/api/hello.js +0 -0
  26. package/templates/complete-app/lib/frontend-hamroun.js +182 -0
  27. package/templates/complete-app/package.json +18 -0
  28. package/templates/complete-app/pages/about.js +119 -0
  29. package/templates/complete-app/pages/about.jsx +0 -0
  30. package/templates/complete-app/pages/index.js +157 -0
  31. package/templates/complete-app/pages/index.jsx +0 -0
  32. package/templates/complete-app/pages/wasm-demo.js +290 -0
  33. package/templates/complete-app/pages/wasm-demo.jsx +0 -0
  34. package/templates/complete-app/public/client.js +89 -0
  35. package/templates/complete-app/public/index.html +118 -0
  36. package/templates/complete-app/public/styles.css +76 -0
  37. package/templates/complete-app/server.js +226 -0
  38. package/templates/complete-app/src/App.tsx +59 -0
  39. package/templates/complete-app/src/client.tsx +18 -0
  40. package/templates/complete-app/src/server.ts +218 -0
  41. package/templates/complete-app/tsconfig.json +22 -0
  42. package/templates/complete-app/tsconfig.server.json +19 -0
  43. package/templates/{ssr-template → complete-app}/vite.config.js +16 -5
  44. package/templates/complete-app/vite.config.ts +30 -0
  45. package/templates/complete-app/wasm/build.bat +0 -0
  46. package/templates/complete-app/wasm/build.sh +0 -0
  47. package/templates/complete-app/wasm/example.go +0 -0
  48. package/templates/fullstack-app/build/main.css +874 -874
  49. package/templates/fullstack-app/build/main.css.map +7 -7
  50. package/templates/fullstack-app/build/main.js +996 -967
  51. package/templates/fullstack-app/build/main.js.map +7 -7
  52. package/templates/fullstack-app/package-lock.json +6301 -0
  53. package/templates/fullstack-app/public/styles.css +768 -768
  54. package/templates/go/example.go +154 -99
  55. package/templates/ssr-template/dist/client/assets/main-D-VH3xOb.js +1 -0
  56. package/templates/ssr-template/dist/client/index.html +23 -0
  57. package/templates/ssr-template/dist/client.js +951 -0
  58. package/templates/ssr-template/dist/server.js +739 -0
  59. package/templates/ssr-template/esbuild.config.js +33 -0
  60. package/templates/ssr-template/jsx-shim.js +1 -0
  61. package/templates/ssr-template/package.json +14 -8
  62. package/templates/ssr-template/src/App.tsx +847 -49
  63. package/templates/ssr-template/src/client.tsx +3 -17
  64. package/templates/ssr-template/src/server.ts +21 -204
  65. package/templates/ssr-template/tsconfig.json +9 -8
  66. package/templates/ssr-template/tsconfig.server.json +6 -14
  67. package/templates/ssr-template/vite.config.ts +19 -17
  68. package/templates/wasm/build-wasm.js +228 -0
  69. package/templates/wasm/dist/assets/index-BNqTDBdE.js +295 -0
  70. package/templates/wasm/dist/example.wasm +0 -0
  71. package/templates/wasm/dist/index.html +53 -0
  72. package/templates/{go-wasm-app/public/wasm → wasm/dist}/wasm_exec.js +572 -561
  73. package/templates/wasm/esbuild.config.js +63 -0
  74. package/templates/wasm/go/main.go +256 -0
  75. package/templates/wasm/go/wasm_exec.js +0 -0
  76. package/templates/wasm/index.html +97 -0
  77. package/templates/wasm/jsx-shim.js +9 -0
  78. package/templates/wasm/package-lock.json +4577 -0
  79. package/templates/wasm/package.json +25 -0
  80. package/templates/wasm/public/example.wasm +0 -0
  81. package/templates/wasm/public/wasm_exec.js +572 -0
  82. package/templates/wasm/src/App.tsx +550 -0
  83. package/templates/wasm/src/client.tsx +220 -0
  84. package/templates/wasm/src/index.tsx +21 -0
  85. package/templates/wasm/src/server.ts +145 -0
  86. package/templates/wasm/tsconfig.json +21 -0
  87. package/templates/wasm/tsconfig.node.json +13 -0
  88. package/templates/wasm/tsconfig.server.json +23 -0
  89. package/templates/wasm/vite.config.ts +38 -0
  90. package/templates/wasm/wasm-loader.js +103 -0
  91. package/dist/server-renderer-CqIpQ-od.cjs +0 -2
  92. package/dist/server-renderer-CqIpQ-od.cjs.map +0 -1
  93. package/dist/server-renderer-QHt45Ip2.js.map +0 -1
  94. package/templates/basic-app/bun.lock +0 -196
  95. package/templates/basic-app/docs/rapport_pfe.aux +0 -27
  96. package/templates/basic-app/docs/rapport_pfe.out +0 -10
  97. package/templates/basic-app/docs/rapport_pfe.pdf +0 -0
  98. package/templates/basic-app/docs/rapport_pfe.tex +0 -68
  99. package/templates/basic-app/docs/rapport_pfe.toc +0 -14
  100. package/templates/basic-app/package-lock.json +0 -4185
  101. package/templates/go-wasm-app/README.md +0 -38
  102. package/templates/go-wasm-app/babel.config.js +0 -15
  103. package/templates/go-wasm-app/build-client.js +0 -49
  104. package/templates/go-wasm-app/build-wasm.js +0 -237
  105. package/templates/go-wasm-app/package.json +0 -23
  106. package/templates/go-wasm-app/public/index.html +0 -128
  107. package/templates/go-wasm-app/public/styles.css +0 -197
  108. package/templates/go-wasm-app/public/wasm/example.wasm +0 -0
  109. package/templates/go-wasm-app/public/wasm/wasm_exec_node.js +0 -39
  110. package/templates/go-wasm-app/server.js +0 -521
  111. package/templates/go-wasm-app/src/App.jsx +0 -38
  112. package/templates/go-wasm-app/src/app.js +0 -153
  113. package/templates/go-wasm-app/src/client.js +0 -57
  114. package/templates/go-wasm-app/src/components/Footer.jsx +0 -13
  115. package/templates/go-wasm-app/src/components/Header.jsx +0 -19
  116. package/templates/go-wasm-app/src/components/WasmDemo.jsx +0 -120
  117. package/templates/go-wasm-app/src/main.jsx +0 -12
  118. package/templates/go-wasm-app/src/wasm/example.go +0 -75
  119. package/templates/go-wasm-app/tsconfig.server.json +0 -18
  120. package/templates/go-wasm-app/vite.config.js +0 -34
  121. package/templates/ssr-template/package-lock.json +0 -2478
  122. package/templates/ssr-template/public/index.html +0 -47
  123. package/templates/ssr-template/server.js +0 -369
  124. /package/templates/{ssr-template → complete-app}/client.js +0 -0
  125. /package/templates/{ssr-template → complete-app}/readme.md +0 -0
  126. /package/templates/{ssr-template → complete-app}/server.ts +0 -0
  127. /package/templates/{ssr-template → complete-app}/src/client.ts +0 -0
  128. /package/templates/{ssr-template → complete-app}/src/pages/index.tsx +0 -0
@@ -1,59 +1,857 @@
1
- import { useState, useEffect, useMemo, useRef, createContext } from 'frontend-hamroun';
1
+ import {
2
+ jsx,
3
+ useState,
4
+ useEffect,
5
+ useMemo,
6
+ useErrorBoundary
7
+ } from 'frontend-hamroun';
2
8
 
3
- // Create a theme context
4
- const ThemeContext = createContext('light');
9
+ // Todo Item Component - simple props-based component
10
+ function TodoItem({ todo, onToggle, onDelete }) {
11
+ return (
12
+ <div className={`todo-item ${todo.completed ? 'completed' : ''}`}>
13
+ <input
14
+ type="checkbox"
15
+ checked={todo.completed}
16
+ onChange={() => onToggle(todo.id)}
17
+ className="todo-checkbox"
18
+ />
19
+ <span className="todo-text">{todo.text}</span>
20
+ <span className={`todo-priority priority-${todo.priority}`}>
21
+ {todo.priority === 'high' && '🔴'}
22
+ {todo.priority === 'medium' && '🟡'}
23
+ {todo.priority === 'low' && '🟢'}
24
+ {todo.priority}
25
+ </span>
26
+ <div className="todo-actions">
27
+ <button
28
+ onClick={() => onDelete(todo.id)}
29
+ className="btn btn-sm btn-danger"
30
+ title="Delete todo"
31
+ >
32
+ 🗑️
33
+ </button>
34
+ </div>
35
+ </div>
36
+ );
37
+ }
38
+
39
+ // Theme Toggle Button Component
40
+ function ThemeToggleButton({ theme, onToggle }) {
41
+ return (
42
+ <button
43
+ onClick={onToggle}
44
+ className="btn btn-secondary theme-toggle"
45
+ title="Toggle theme"
46
+ >
47
+ {theme === 'light' ? '🌙' : '☀️'}
48
+ </button>
49
+ );
50
+ }
51
+
52
+ // App Footer Component
53
+ function AppFooter({ theme, isBrowser }) {
54
+ return (
55
+ <footer className="app-footer">
56
+ <div className="container">
57
+ <p>Built with Frontend Hamroun • Hooks: useState, useEffect, useMemo, useErrorBoundary</p>
58
+ <p>Theme: <strong>{theme}</strong> • Environment: <strong>{isBrowser ? 'Client' : 'Server'}</strong></p>
59
+ </div>
60
+ </footer>
61
+ );
62
+ }
5
63
 
6
64
  export function App() {
7
- // Initialize with a default state that works on both server and client
8
- const [count, setCount] = useState(0);
9
- const [theme, setTheme] = useState<'light' | 'dark'>('light');
10
- const renderCount = useRef(0);
65
+ // State hooks
66
+ const [todos, setTodos] = useState([
67
+ { id: 1, text: 'Learn Frontend Hamroun hooks', completed: false, priority: 'high' },
68
+ { id: 2, text: 'Build a todo app', completed: false, priority: 'medium' },
69
+ { id: 3, text: 'Master SSR concepts', completed: true, priority: 'low' }
70
+ ]);
71
+ const [newTask, setNewTask] = useState('');
72
+ const [taskFilter, setTaskFilter] = useState('all');
73
+ const [taskPriority, setTaskPriority] = useState('medium');
74
+ const [theme, setTheme] = useState('light');
75
+ const [isLoading, setIsLoading] = useState(false);
11
76
 
12
- // Client-side only effect
77
+ // Error boundary hook
78
+ const [error, resetError] = useErrorBoundary();
79
+
80
+ // Check if we're in browser environment
81
+ const isBrowser = typeof window !== 'undefined';
82
+
83
+ // Mount effect
13
84
  useEffect(() => {
14
- if (typeof window !== 'undefined') {
15
- renderCount.current += 1;
16
- console.log('Component rendered', renderCount.current, 'times');
85
+ if (!isBrowser) return;
86
+
87
+ console.log('Todo App mounted');
88
+
89
+ // Load saved todos from localStorage
90
+ const savedTodos = localStorage.getItem('todos');
91
+ const savedTheme = localStorage.getItem('theme');
92
+
93
+ if (savedTodos) {
94
+ try {
95
+ setTodos(JSON.parse(savedTodos));
96
+ } catch (e) {
97
+ console.error('Failed to load saved todos');
98
+ }
17
99
  }
18
- return () => console.log('Component unmounting');
19
- }, [count]);
20
-
21
- // Memoized value
22
- const doubled = useMemo(() => count * 2, [count]);
23
-
24
- return (
25
- <ThemeContext.Provider value={theme}>
26
- <div style={{
27
- padding: '20px',
28
- backgroundColor: theme === 'dark' ? '#333' : '#fff',
29
- color: theme === 'dark' ? '#fff' : '#333'
30
- }}>
31
- <h1>Server-Side Rendered App</h1>
32
- <div>
33
- <button
34
- onClick={() => setCount(count - 1)}
35
- data-action="decrement"
36
- >-</button>
37
- <span style={{ margin: '0 10px' }}>{count}</span>
38
- <button
39
- onClick={() => setCount(count + 1)}
40
- data-action="increment"
41
- >+</button>
42
- </div>
43
- <p>Doubled value: {doubled}</p>
44
- {typeof window !== 'undefined' && (
45
- <p>Render count: {renderCount.current}</p>
46
- )}
47
- <button
48
- onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}
49
- style={{ marginTop: '10px' }}
50
- >
51
- Toggle Theme ({theme})
100
+
101
+ if (savedTheme) {
102
+ setTheme(savedTheme);
103
+ }
104
+
105
+ return () => {
106
+ console.log('Todo App unmounting');
107
+ };
108
+ }, []);
109
+
110
+ // Theme effect
111
+ useEffect(() => {
112
+ if (!isBrowser) return;
113
+
114
+ document.body.className = `theme-${theme}`;
115
+ document.documentElement.setAttribute('data-theme', theme);
116
+ localStorage.setItem('theme', theme);
117
+ }, [theme, isBrowser]);
118
+
119
+ // Save todos effect
120
+ useEffect(() => {
121
+ if (!isBrowser || todos.length === 0) return;
122
+
123
+ localStorage.setItem('todos', JSON.stringify(todos));
124
+ }, [todos, isBrowser]);
125
+
126
+ // Memoized filtered todos
127
+ const filteredTodos = useMemo(() => {
128
+ console.log('Filtering todos - memoized computation');
129
+ return todos.filter(todo => {
130
+ if (taskFilter === 'completed') return todo.completed;
131
+ if (taskFilter === 'active') return !todo.completed;
132
+ if (taskFilter === 'high') return todo.priority === 'high';
133
+ if (taskFilter === 'medium') return todo.priority === 'medium';
134
+ if (taskFilter === 'low') return todo.priority === 'low';
135
+ return true;
136
+ });
137
+ }, [todos, taskFilter]);
138
+
139
+ // Memoized todo stats
140
+ const todoStats = useMemo(() => {
141
+ const total = todos.length;
142
+ const completed = todos.filter(t => t.completed).length;
143
+ const active = total - completed;
144
+ const highPriority = todos.filter(t => t.priority === 'high' && !t.completed).length;
145
+
146
+ return { total, completed, active, highPriority };
147
+ }, [todos]);
148
+
149
+ // Todo action handlers
150
+ const handleToggleTodo = (id) => {
151
+ console.log('Toggling todo:', id);
152
+ setTodos(prev => prev.map(todo =>
153
+ todo.id === id ? { ...todo, completed: !todo.completed } : todo
154
+ ));
155
+ };
156
+
157
+ const handleDeleteTodo = (id) => {
158
+ console.log('Deleting todo:', id);
159
+ setTodos(prev => prev.filter(todo => todo.id !== id));
160
+ };
161
+
162
+ const handleAddTodo = (text, priority) => {
163
+ if (!text || !text.trim()) return;
164
+
165
+ const newTodo = {
166
+ id: Date.now(),
167
+ text: text.trim(),
168
+ completed: false,
169
+ priority: priority || 'medium'
170
+ };
171
+ setTodos(prev => [...prev, newTodo]);
172
+ };
173
+
174
+ // Event handlers
175
+ const addTask = () => {
176
+ if (newTask.trim()) {
177
+ setIsLoading(true);
178
+
179
+ // Simulate async operation
180
+ setTimeout(() => {
181
+ handleAddTodo(newTask, taskPriority);
182
+ setNewTask('');
183
+ setIsLoading(false);
184
+ }, 300);
185
+ }
186
+ };
187
+
188
+ const handleKeyPress = (e) => {
189
+ if (e.key === 'Enter') {
190
+ addTask();
191
+ }
192
+ };
193
+
194
+ const clearCompleted = () => {
195
+ setTodos(prev => prev.filter(todo => !todo.completed));
196
+ };
197
+
198
+ const markAllComplete = () => {
199
+ setTodos(prev => prev.map(todo => ({ ...todo, completed: true })));
200
+ };
201
+
202
+ const toggleTheme = () => {
203
+ setTheme(prev => prev === 'light' ? 'dark' : 'light');
204
+ };
205
+
206
+ const simulateError = () => {
207
+ throw new Error('Simulated error for testing error boundary');
208
+ };
209
+
210
+ if (error) {
211
+ return (
212
+ <div className="error-container">
213
+ <h2>Something went wrong!</h2>
214
+ <p>{error.message}</p>
215
+ <button onClick={resetError} className="btn btn-primary">
216
+ Try Again
52
217
  </button>
53
- <script dangerouslySetInnerHTML={{
54
- __html: `window.__INITIAL_STATE__ = ${JSON.stringify({ count: 0, theme: 'light' })};`
55
- }} />
56
218
  </div>
57
- </ThemeContext.Provider>
219
+ );
220
+ }
221
+
222
+ return (
223
+ <div className={`app theme-${theme}`}>
224
+
225
+ {/* Header */}
226
+ <header className="app-header">
227
+ <div className="container">
228
+ <h1 className="app-title">
229
+ 📝 Todo App
230
+ <span className="subtitle">Built with Frontend Hamroun</span>
231
+ </h1>
232
+
233
+ <div className="header-controls">
234
+ <ThemeToggleButton theme={theme} onToggle={toggleTheme} />
235
+ <div className="stats">
236
+ <span className="stat">
237
+ Total: <strong>{todoStats.total}</strong>
238
+ </span>
239
+ <span className="stat">
240
+ Active: <strong>{todoStats.active}</strong>
241
+ </span>
242
+ <span className="stat">
243
+ Done: <strong>{todoStats.completed}</strong>
244
+ </span>
245
+ </div>
246
+ </div>
247
+ </div>
248
+ </header>
249
+
250
+ <main className="main-content">
251
+ <div className="container">
252
+
253
+ {/* Add Todo Section */}
254
+ <section className="card add-todo-section">
255
+ <h2>➕ Add New Todo</h2>
256
+
257
+ <div className="add-todo-form">
258
+ <input
259
+ type="text"
260
+ value={newTask}
261
+ onChange={(e) => setNewTask(e.target.value)}
262
+ onKeyPress={handleKeyPress}
263
+ placeholder="What needs to be done?"
264
+ className="input todo-input"
265
+ disabled={isLoading}
266
+ />
267
+
268
+ <select
269
+ value={taskPriority}
270
+ onChange={(e) => setTaskPriority(e.target.value)}
271
+ className="select priority-select"
272
+ disabled={isLoading}
273
+ >
274
+ <option value="low">🟢 Low Priority</option>
275
+ <option value="medium">🟡 Medium Priority</option>
276
+ <option value="high">🔴 High Priority</option>
277
+ </select>
278
+
279
+ <button
280
+ onClick={addTask}
281
+ className={`btn btn-primary add-btn ${isLoading ? 'loading' : ''}`}
282
+ disabled={isLoading || !newTask.trim()}
283
+ >
284
+ {isLoading ? '⏳ Adding...' : '➕ Add Todo'}
285
+ </button>
286
+ </div>
287
+ </section>
288
+
289
+ {/* Filters Section */}
290
+ <section className="card filters-section">
291
+ <h2>🔍 Filter Todos</h2>
292
+
293
+ <div className="filters">
294
+ {['all', 'active', 'completed', 'high', 'medium', 'low'].map(filterType => (
295
+ <button
296
+ key={filterType}
297
+ onClick={() => setTaskFilter(filterType)}
298
+ className={`btn btn-sm filter-btn ${taskFilter === filterType ? 'btn-primary' : 'btn-outline'}`}
299
+ >
300
+ {filterType === 'all' && '📋 All'}
301
+ {filterType === 'active' && '⏳ Active'}
302
+ {filterType === 'completed' && '✅ Completed'}
303
+ {filterType === 'high' && '🔴 High Priority'}
304
+ {filterType === 'medium' && '🟡 Medium Priority'}
305
+ {filterType === 'low' && '🟢 Low Priority'}
306
+ </button>
307
+ ))}
308
+ </div>
309
+
310
+ <div className="bulk-actions">
311
+ <button
312
+ onClick={markAllComplete}
313
+ className="btn btn-success btn-sm"
314
+ disabled={todoStats.active === 0}
315
+ >
316
+ ✅ Mark All Complete
317
+ </button>
318
+ <button
319
+ onClick={clearCompleted}
320
+ className="btn btn-warning btn-sm"
321
+ disabled={todoStats.completed === 0}
322
+ >
323
+ 🗑️ Clear Completed
324
+ </button>
325
+ </div>
326
+ </section>
327
+
328
+ {/* Todos List Section */}
329
+ <section className="card todos-section">
330
+ <div className="section-header">
331
+ <h2>📋 Todo List</h2>
332
+ <div className="filter-info">
333
+ Showing <strong>{filteredTodos.length}</strong> of <strong>{todoStats.total}</strong> todos
334
+ {taskFilter !== 'all' && <span className="filter-badge">{taskFilter}</span>}
335
+ </div>
336
+ </div>
337
+
338
+ <div className="todos-list">
339
+ {filteredTodos.length > 0 ? (
340
+ filteredTodos.map(todo => (
341
+ <TodoItem
342
+ key={todo.id}
343
+ todo={todo}
344
+ onToggle={handleToggleTodo}
345
+ onDelete={handleDeleteTodo}
346
+ />
347
+ ))
348
+ ) : (
349
+ <div className="empty-state">
350
+ <p>
351
+ {taskFilter === 'all' ? '🎉 No todos yet. Add one above!' :
352
+ taskFilter === 'completed' ? '📝 No completed todos yet.' :
353
+ taskFilter === 'active' ? '🎯 No active todos. Great job!' :
354
+ `🔍 No ${taskFilter} priority todos found.`}
355
+ </p>
356
+ </div>
357
+ )}
358
+ </div>
359
+ </section>
360
+
361
+ {/* Actions Section */}
362
+ <section className="card actions-section">
363
+ <h2>⚙️ Actions</h2>
364
+ <div className="action-buttons">
365
+ <button onClick={simulateError} className="btn btn-danger">
366
+ 💥 Test Error Boundary
367
+ </button>
368
+ </div>
369
+ </section>
370
+
371
+ </div>
372
+ </main>
373
+
374
+ {/* Footer */}
375
+ <AppFooter theme={theme} isBrowser={isBrowser} />
376
+
377
+ {/* Styles */}
378
+ <style>{`
379
+ * {
380
+ margin: 0;
381
+ padding: 0;
382
+ box-sizing: border-box;
383
+ }
384
+
385
+ :root {
386
+ --primary: #3b82f6;
387
+ --primary-dark: #2563eb;
388
+ --secondary: #6b7280;
389
+ --success: #10b981;
390
+ --warning: #f59e0b;
391
+ --danger: #ef4444;
392
+ --background: #ffffff;
393
+ --surface: #f9fafb;
394
+ --text: #111827;
395
+ --text-muted: #6b7280;
396
+ --border: #e5e7eb;
397
+ --shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1);
398
+ --shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 0.1);
399
+ }
400
+
401
+ [data-theme="dark"] {
402
+ --primary: #3b82f6;
403
+ --secondary: #9ca3af;
404
+ --success: #10b981;
405
+ --warning: #f59e0b;
406
+ --danger: #ef4444;
407
+ --background: #111827;
408
+ --surface: #1f2937;
409
+ --text: #f9fafb;
410
+ --text-muted: #9ca3af;
411
+ --border: #374151;
412
+ }
413
+
414
+ body {
415
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
416
+ line-height: 1.6;
417
+ color: var(--text);
418
+ background-color: var(--background);
419
+ transition: all 0.3s ease;
420
+ }
421
+
422
+ .app {
423
+ min-height: 100vh;
424
+ display: flex;
425
+ flex-direction: column;
426
+ }
427
+
428
+ .container {
429
+ max-width: 800px;
430
+ margin: 0 auto;
431
+ padding: 0 1rem;
432
+ }
433
+
434
+ /* Header */
435
+ .app-header {
436
+ background: linear-gradient(135deg, var(--primary) 0%, var(--primary-dark) 100%);
437
+ color: white;
438
+ padding: 2rem 0;
439
+ box-shadow: var(--shadow-lg);
440
+ }
441
+
442
+ .app-header .container {
443
+ display: flex;
444
+ justify-content: space-between;
445
+ align-items: center;
446
+ flex-wrap: wrap;
447
+ gap: 1rem;
448
+ }
449
+
450
+ .app-title {
451
+ font-size: 2rem;
452
+ font-weight: 700;
453
+ margin: 0;
454
+ display: flex;
455
+ flex-direction: column;
456
+ gap: 0.25rem;
457
+ }
458
+
459
+ .subtitle {
460
+ font-size: 1rem;
461
+ font-weight: 400;
462
+ opacity: 0.9;
463
+ }
464
+
465
+ .header-controls {
466
+ display: flex;
467
+ align-items: center;
468
+ gap: 1rem;
469
+ }
470
+
471
+ .stats {
472
+ display: flex;
473
+ gap: 1rem;
474
+ font-size: 0.875rem;
475
+ }
476
+
477
+ .stat {
478
+ background: rgba(255, 255, 255, 0.2);
479
+ padding: 0.5rem 0.75rem;
480
+ border-radius: 0.5rem;
481
+ backdrop-filter: blur(10px);
482
+ }
483
+
484
+ /* Main content */
485
+ .main-content {
486
+ flex: 1;
487
+ padding: 2rem 0;
488
+ }
489
+
490
+ /* Cards */
491
+ .card {
492
+ background: var(--surface);
493
+ border-radius: 1rem;
494
+ padding: 1.5rem;
495
+ margin-bottom: 1.5rem;
496
+ box-shadow: var(--shadow);
497
+ border: 1px solid var(--border);
498
+ transition: transform 0.2s ease;
499
+ }
500
+
501
+ .card:hover {
502
+ transform: translateY(-2px);
503
+ }
504
+
505
+ .card h2 {
506
+ font-size: 1.25rem;
507
+ margin-bottom: 1rem;
508
+ color: var(--text);
509
+ }
510
+
511
+ /* Add Todo Form */
512
+ .add-todo-form {
513
+ display: flex;
514
+ gap: 0.75rem;
515
+ flex-wrap: wrap;
516
+ }
517
+
518
+ .todo-input {
519
+ flex: 1;
520
+ min-width: 250px;
521
+ }
522
+
523
+ .priority-select {
524
+ min-width: 150px;
525
+ }
526
+
527
+ /* Filters */
528
+ .filters {
529
+ display: flex;
530
+ gap: 0.5rem;
531
+ margin-bottom: 1rem;
532
+ flex-wrap: wrap;
533
+ }
534
+
535
+ .filter-btn {
536
+ font-size: 0.875rem;
537
+ }
538
+
539
+ .bulk-actions {
540
+ display: flex;
541
+ gap: 0.5rem;
542
+ flex-wrap: wrap;
543
+ }
544
+
545
+ /* Section Header */
546
+ .section-header {
547
+ display: flex;
548
+ justify-content: space-between;
549
+ align-items: center;
550
+ margin-bottom: 1rem;
551
+ flex-wrap: wrap;
552
+ gap: 1rem;
553
+ }
554
+
555
+ .filter-info {
556
+ font-size: 0.875rem;
557
+ color: var(--text-muted);
558
+ }
559
+
560
+ .filter-badge {
561
+ background: var(--primary);
562
+ color: white;
563
+ padding: 0.25rem 0.5rem;
564
+ border-radius: 0.25rem;
565
+ font-size: 0.75rem;
566
+ margin-left: 0.5rem;
567
+ }
568
+
569
+ /* Todos List */
570
+ .todos-list {
571
+ display: flex;
572
+ flex-direction: column;
573
+ gap: 0.75rem;
574
+ }
575
+
576
+ .todo-item {
577
+ display: flex;
578
+ align-items: center;
579
+ gap: 0.75rem;
580
+ padding: 1rem;
581
+ background: var(--background);
582
+ border-radius: 0.5rem;
583
+ border: 1px solid var(--border);
584
+ transition: all 0.2s ease;
585
+ }
586
+
587
+ .todo-item:hover {
588
+ border-color: var(--primary);
589
+ box-shadow: var(--shadow);
590
+ }
591
+
592
+ .todo-item.completed {
593
+ opacity: 0.7;
594
+ }
595
+
596
+ .todo-item.completed .todo-text {
597
+ text-decoration: line-through;
598
+ }
599
+
600
+ .todo-checkbox {
601
+ width: 1.25rem;
602
+ height: 1.25rem;
603
+ cursor: pointer;
604
+ }
605
+
606
+ .todo-text {
607
+ flex: 1;
608
+ font-size: 1rem;
609
+ }
610
+
611
+ .todo-priority {
612
+ padding: 0.25rem 0.5rem;
613
+ border-radius: 0.25rem;
614
+ font-size: 0.75rem;
615
+ font-weight: 600;
616
+ }
617
+
618
+ .priority-high {
619
+ background: #fee2e2;
620
+ color: #991b1b;
621
+ }
622
+
623
+ .priority-medium {
624
+ background: #fef3c7;
625
+ color: #92400e;
626
+ }
627
+
628
+ .priority-low {
629
+ background: #dcfce7;
630
+ color: #166534;
631
+ }
632
+
633
+ .todo-actions {
634
+ display: flex;
635
+ gap: 0.5rem;
636
+ }
637
+
638
+ /* Form elements */
639
+ .input, .select {
640
+ padding: 0.75rem;
641
+ border: 2px solid var(--border);
642
+ border-radius: 0.5rem;
643
+ font-size: 1rem;
644
+ background: var(--background);
645
+ color: var(--text);
646
+ transition: border-color 0.2s ease;
647
+ }
648
+
649
+ .input:focus, .select:focus {
650
+ outline: none;
651
+ border-color: var(--primary);
652
+ }
653
+
654
+ /* Buttons */
655
+ .btn {
656
+ display: inline-flex;
657
+ align-items: center;
658
+ justify-content: center;
659
+ gap: 0.5rem;
660
+ padding: 0.75rem 1.5rem;
661
+ border: none;
662
+ border-radius: 0.5rem;
663
+ font-size: 0.875rem;
664
+ font-weight: 500;
665
+ cursor: pointer;
666
+ transition: all 0.2s ease;
667
+ text-decoration: none;
668
+ user-select: none;
669
+ }
670
+
671
+ .btn:disabled {
672
+ opacity: 0.5;
673
+ cursor: not-allowed;
674
+ }
675
+
676
+ .btn-sm {
677
+ padding: 0.5rem 1rem;
678
+ font-size: 0.75rem;
679
+ }
680
+
681
+ .btn-primary {
682
+ background: var(--primary);
683
+ color: white;
684
+ }
685
+
686
+ .btn-primary:hover:not(:disabled) {
687
+ background: var(--primary-dark);
688
+ transform: translateY(-1px);
689
+ }
690
+
691
+ .btn-secondary {
692
+ background: var(--secondary);
693
+ color: white;
694
+ }
695
+
696
+ .btn-success {
697
+ background: var(--success);
698
+ color: white;
699
+ }
700
+
701
+ .btn-warning {
702
+ background: var(--warning);
703
+ color: white;
704
+ }
705
+
706
+ .btn-danger {
707
+ background: var(--danger);
708
+ color: white;
709
+ }
710
+
711
+ .btn-outline {
712
+ background: transparent;
713
+ color: var(--text);
714
+ border: 2px solid var(--border);
715
+ }
716
+
717
+ .btn-outline:hover:not(:disabled) {
718
+ background: var(--surface);
719
+ border-color: var(--primary);
720
+ }
721
+
722
+ .btn.loading {
723
+ position: relative;
724
+ color: transparent;
725
+ }
726
+
727
+ .btn.loading::after {
728
+ content: '';
729
+ position: absolute;
730
+ width: 1rem;
731
+ height: 1rem;
732
+ border: 2px solid transparent;
733
+ border-top: 2px solid currentColor;
734
+ border-radius: 50%;
735
+ animation: spin 1s linear infinite;
736
+ color: white;
737
+ }
738
+
739
+ @keyframes spin {
740
+ to { transform: rotate(360deg); }
741
+ }
742
+
743
+ .theme-toggle {
744
+ border-radius: 50%;
745
+ width: 3rem;
746
+ height: 3rem;
747
+ padding: 0;
748
+ font-size: 1.25rem;
749
+ background: rgba(255, 255, 255, 0.2);
750
+ color: white;
751
+ border: 2px solid rgba(255, 255, 255, 0.3);
752
+ }
753
+
754
+ .theme-toggle:hover {
755
+ background: rgba(255, 255, 255, 0.3);
756
+ transform: scale(1.05);
757
+ }
758
+
759
+ /* Empty state */
760
+ .empty-state {
761
+ text-align: center;
762
+ padding: 3rem 1rem;
763
+ color: var(--text-muted);
764
+ }
765
+
766
+ .empty-state p {
767
+ font-size: 1.1rem;
768
+ }
769
+
770
+ /* Actions section */
771
+ .action-buttons {
772
+ display: flex;
773
+ gap: 1rem;
774
+ flex-wrap: wrap;
775
+ }
776
+
777
+ /* Footer */
778
+ .app-footer {
779
+ background: var(--surface);
780
+ border-top: 1px solid var(--border);
781
+ padding: 1.5rem 0;
782
+ text-align: center;
783
+ color: var(--text-muted);
784
+ font-size: 0.875rem;
785
+ }
786
+
787
+ .app-footer p {
788
+ margin: 0.25rem 0;
789
+ }
790
+
791
+ /* Error container */
792
+ .error-container {
793
+ display: flex;
794
+ flex-direction: column;
795
+ align-items: center;
796
+ justify-content: center;
797
+ min-height: 100vh;
798
+ padding: 2rem;
799
+ text-align: center;
800
+ }
801
+
802
+ .error-container h2 {
803
+ color: var(--danger);
804
+ margin-bottom: 1rem;
805
+ }
806
+
807
+ .error-container p {
808
+ color: var(--text-muted);
809
+ margin-bottom: 2rem;
810
+ }
811
+
812
+ /* Responsive design */
813
+ @media (max-width: 768px) {
814
+ .app-header .container {
815
+ flex-direction: column;
816
+ text-align: center;
817
+ }
818
+
819
+ .app-title {
820
+ font-size: 1.75rem;
821
+ }
822
+
823
+ .add-todo-form {
824
+ flex-direction: column;
825
+ }
826
+
827
+ .todo-input, .priority-select {
828
+ min-width: auto;
829
+ width: 100%;
830
+ }
831
+
832
+ .section-header {
833
+ flex-direction: column;
834
+ align-items: stretch;
835
+ }
836
+
837
+ .stats {
838
+ justify-content: center;
839
+ }
840
+
841
+ .filters {
842
+ justify-content: center;
843
+ }
844
+
845
+ .bulk-actions {
846
+ justify-content: center;
847
+ }
848
+ }
849
+
850
+ /* Smooth transitions */
851
+ * {
852
+ transition: background-color 0.3s ease, color 0.3s ease, border-color 0.3s ease;
853
+ }
854
+ `}</style>
855
+ </div>
58
856
  );
59
857
  }