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
@@ -0,0 +1,739 @@
1
+ // jsx-shim.js
2
+ import { jsx, Fragment } from "frontend-hamroun";
3
+
4
+ // src/server.ts
5
+ import express from "express";
6
+ import path from "path";
7
+ import { fileURLToPath } from "url";
8
+ import { renderToString, jsx as jsx3 } from "frontend-hamroun";
9
+
10
+ // src/App.tsx
11
+ import {
12
+ jsx as jsx2,
13
+ useState,
14
+ useEffect,
15
+ useMemo,
16
+ useErrorBoundary
17
+ } from "frontend-hamroun";
18
+ function TodoItem({ todo, onToggle, onDelete }) {
19
+ return /* @__PURE__ */ jsx2("div", { className: `todo-item ${todo.completed ? "completed" : ""}` }, /* @__PURE__ */ jsx2(
20
+ "input",
21
+ {
22
+ type: "checkbox",
23
+ checked: todo.completed,
24
+ onChange: () => onToggle(todo.id),
25
+ className: "todo-checkbox"
26
+ }
27
+ ), /* @__PURE__ */ jsx2("span", { className: "todo-text" }, todo.text), /* @__PURE__ */ jsx2("span", { className: `todo-priority priority-${todo.priority}` }, todo.priority === "high" && "\u{1F534}", todo.priority === "medium" && "\u{1F7E1}", todo.priority === "low" && "\u{1F7E2}", todo.priority), /* @__PURE__ */ jsx2("div", { className: "todo-actions" }, /* @__PURE__ */ jsx2(
28
+ "button",
29
+ {
30
+ onClick: () => onDelete(todo.id),
31
+ className: "btn btn-sm btn-danger",
32
+ title: "Delete todo"
33
+ },
34
+ "\u{1F5D1}\uFE0F"
35
+ )));
36
+ }
37
+ function ThemeToggleButton({ theme, onToggle }) {
38
+ return /* @__PURE__ */ jsx2(
39
+ "button",
40
+ {
41
+ onClick: onToggle,
42
+ className: "btn btn-secondary theme-toggle",
43
+ title: "Toggle theme"
44
+ },
45
+ theme === "light" ? "\u{1F319}" : "\u2600\uFE0F"
46
+ );
47
+ }
48
+ function AppFooter({ theme, isBrowser }) {
49
+ return /* @__PURE__ */ jsx2("footer", { className: "app-footer" }, /* @__PURE__ */ jsx2("div", { className: "container" }, /* @__PURE__ */ jsx2("p", null, "Built with Frontend Hamroun \u2022 Hooks: useState, useEffect, useMemo, useErrorBoundary"), /* @__PURE__ */ jsx2("p", null, "Theme: ", /* @__PURE__ */ jsx2("strong", null, theme), " \u2022 Environment: ", /* @__PURE__ */ jsx2("strong", null, isBrowser ? "Client" : "Server"))));
50
+ }
51
+ function App() {
52
+ const [todos, setTodos] = useState([
53
+ { id: 1, text: "Learn Frontend Hamroun hooks", completed: false, priority: "high" },
54
+ { id: 2, text: "Build a todo app", completed: false, priority: "medium" },
55
+ { id: 3, text: "Master SSR concepts", completed: true, priority: "low" }
56
+ ]);
57
+ const [newTask, setNewTask] = useState("");
58
+ const [taskFilter, setTaskFilter] = useState("all");
59
+ const [taskPriority, setTaskPriority] = useState("medium");
60
+ const [theme, setTheme] = useState("light");
61
+ const [isLoading, setIsLoading] = useState(false);
62
+ const [error, resetError] = useErrorBoundary();
63
+ const isBrowser = typeof window !== "undefined";
64
+ useEffect(() => {
65
+ if (!isBrowser)
66
+ return;
67
+ console.log("Todo App mounted");
68
+ const savedTodos = localStorage.getItem("todos");
69
+ const savedTheme = localStorage.getItem("theme");
70
+ if (savedTodos) {
71
+ try {
72
+ setTodos(JSON.parse(savedTodos));
73
+ } catch (e) {
74
+ console.error("Failed to load saved todos");
75
+ }
76
+ }
77
+ if (savedTheme) {
78
+ setTheme(savedTheme);
79
+ }
80
+ return () => {
81
+ console.log("Todo App unmounting");
82
+ };
83
+ }, []);
84
+ useEffect(() => {
85
+ if (!isBrowser)
86
+ return;
87
+ document.body.className = `theme-${theme}`;
88
+ document.documentElement.setAttribute("data-theme", theme);
89
+ localStorage.setItem("theme", theme);
90
+ }, [theme, isBrowser]);
91
+ useEffect(() => {
92
+ if (!isBrowser || todos.length === 0)
93
+ return;
94
+ localStorage.setItem("todos", JSON.stringify(todos));
95
+ }, [todos, isBrowser]);
96
+ const filteredTodos = useMemo(() => {
97
+ console.log("Filtering todos - memoized computation");
98
+ return todos.filter((todo) => {
99
+ if (taskFilter === "completed")
100
+ return todo.completed;
101
+ if (taskFilter === "active")
102
+ return !todo.completed;
103
+ if (taskFilter === "high")
104
+ return todo.priority === "high";
105
+ if (taskFilter === "medium")
106
+ return todo.priority === "medium";
107
+ if (taskFilter === "low")
108
+ return todo.priority === "low";
109
+ return true;
110
+ });
111
+ }, [todos, taskFilter]);
112
+ const todoStats = useMemo(() => {
113
+ const total = todos.length;
114
+ const completed = todos.filter((t) => t.completed).length;
115
+ const active = total - completed;
116
+ const highPriority = todos.filter((t) => t.priority === "high" && !t.completed).length;
117
+ return { total, completed, active, highPriority };
118
+ }, [todos]);
119
+ const handleToggleTodo = (id) => {
120
+ console.log("Toggling todo:", id);
121
+ setTodos((prev) => prev.map(
122
+ (todo) => todo.id === id ? { ...todo, completed: !todo.completed } : todo
123
+ ));
124
+ };
125
+ const handleDeleteTodo = (id) => {
126
+ console.log("Deleting todo:", id);
127
+ setTodos((prev) => prev.filter((todo) => todo.id !== id));
128
+ };
129
+ const handleAddTodo = (text, priority) => {
130
+ if (!text || !text.trim())
131
+ return;
132
+ const newTodo = {
133
+ id: Date.now(),
134
+ text: text.trim(),
135
+ completed: false,
136
+ priority: priority || "medium"
137
+ };
138
+ setTodos((prev) => [...prev, newTodo]);
139
+ };
140
+ const addTask = () => {
141
+ if (newTask.trim()) {
142
+ setIsLoading(true);
143
+ setTimeout(() => {
144
+ handleAddTodo(newTask, taskPriority);
145
+ setNewTask("");
146
+ setIsLoading(false);
147
+ }, 300);
148
+ }
149
+ };
150
+ const handleKeyPress = (e) => {
151
+ if (e.key === "Enter") {
152
+ addTask();
153
+ }
154
+ };
155
+ const clearCompleted = () => {
156
+ setTodos((prev) => prev.filter((todo) => !todo.completed));
157
+ };
158
+ const markAllComplete = () => {
159
+ setTodos((prev) => prev.map((todo) => ({ ...todo, completed: true })));
160
+ };
161
+ const toggleTheme = () => {
162
+ setTheme((prev) => prev === "light" ? "dark" : "light");
163
+ };
164
+ const simulateError = () => {
165
+ throw new Error("Simulated error for testing error boundary");
166
+ };
167
+ if (error) {
168
+ return /* @__PURE__ */ jsx2("div", { className: "error-container" }, /* @__PURE__ */ jsx2("h2", null, "Something went wrong!"), /* @__PURE__ */ jsx2("p", null, error.message), /* @__PURE__ */ jsx2("button", { onClick: resetError, className: "btn btn-primary" }, "Try Again"));
169
+ }
170
+ return /* @__PURE__ */ jsx2("div", { className: `app theme-${theme}` }, /* @__PURE__ */ jsx2("header", { className: "app-header" }, /* @__PURE__ */ jsx2("div", { className: "container" }, /* @__PURE__ */ jsx2("h1", { className: "app-title" }, "\u{1F4DD} Todo App", /* @__PURE__ */ jsx2("span", { className: "subtitle" }, "Built with Frontend Hamroun")), /* @__PURE__ */ jsx2("div", { className: "header-controls" }, /* @__PURE__ */ jsx2(ThemeToggleButton, { theme, onToggle: toggleTheme }), /* @__PURE__ */ jsx2("div", { className: "stats" }, /* @__PURE__ */ jsx2("span", { className: "stat" }, "Total: ", /* @__PURE__ */ jsx2("strong", null, todoStats.total)), /* @__PURE__ */ jsx2("span", { className: "stat" }, "Active: ", /* @__PURE__ */ jsx2("strong", null, todoStats.active)), /* @__PURE__ */ jsx2("span", { className: "stat" }, "Done: ", /* @__PURE__ */ jsx2("strong", null, todoStats.completed)))))), /* @__PURE__ */ jsx2("main", { className: "main-content" }, /* @__PURE__ */ jsx2("div", { className: "container" }, /* @__PURE__ */ jsx2("section", { className: "card add-todo-section" }, /* @__PURE__ */ jsx2("h2", null, "\u2795 Add New Todo"), /* @__PURE__ */ jsx2("div", { className: "add-todo-form" }, /* @__PURE__ */ jsx2(
171
+ "input",
172
+ {
173
+ type: "text",
174
+ value: newTask,
175
+ onChange: (e) => setNewTask(e.target.value),
176
+ onKeyPress: handleKeyPress,
177
+ placeholder: "What needs to be done?",
178
+ className: "input todo-input",
179
+ disabled: isLoading
180
+ }
181
+ ), /* @__PURE__ */ jsx2(
182
+ "select",
183
+ {
184
+ value: taskPriority,
185
+ onChange: (e) => setTaskPriority(e.target.value),
186
+ className: "select priority-select",
187
+ disabled: isLoading
188
+ },
189
+ /* @__PURE__ */ jsx2("option", { value: "low" }, "\u{1F7E2} Low Priority"),
190
+ /* @__PURE__ */ jsx2("option", { value: "medium" }, "\u{1F7E1} Medium Priority"),
191
+ /* @__PURE__ */ jsx2("option", { value: "high" }, "\u{1F534} High Priority")
192
+ ), /* @__PURE__ */ jsx2(
193
+ "button",
194
+ {
195
+ onClick: addTask,
196
+ className: `btn btn-primary add-btn ${isLoading ? "loading" : ""}`,
197
+ disabled: isLoading || !newTask.trim()
198
+ },
199
+ isLoading ? "\u23F3 Adding..." : "\u2795 Add Todo"
200
+ ))), /* @__PURE__ */ jsx2("section", { className: "card filters-section" }, /* @__PURE__ */ jsx2("h2", null, "\u{1F50D} Filter Todos"), /* @__PURE__ */ jsx2("div", { className: "filters" }, ["all", "active", "completed", "high", "medium", "low"].map((filterType) => /* @__PURE__ */ jsx2(
201
+ "button",
202
+ {
203
+ key: filterType,
204
+ onClick: () => setTaskFilter(filterType),
205
+ className: `btn btn-sm filter-btn ${taskFilter === filterType ? "btn-primary" : "btn-outline"}`
206
+ },
207
+ filterType === "all" && "\u{1F4CB} All",
208
+ filterType === "active" && "\u23F3 Active",
209
+ filterType === "completed" && "\u2705 Completed",
210
+ filterType === "high" && "\u{1F534} High Priority",
211
+ filterType === "medium" && "\u{1F7E1} Medium Priority",
212
+ filterType === "low" && "\u{1F7E2} Low Priority"
213
+ ))), /* @__PURE__ */ jsx2("div", { className: "bulk-actions" }, /* @__PURE__ */ jsx2(
214
+ "button",
215
+ {
216
+ onClick: markAllComplete,
217
+ className: "btn btn-success btn-sm",
218
+ disabled: todoStats.active === 0
219
+ },
220
+ "\u2705 Mark All Complete"
221
+ ), /* @__PURE__ */ jsx2(
222
+ "button",
223
+ {
224
+ onClick: clearCompleted,
225
+ className: "btn btn-warning btn-sm",
226
+ disabled: todoStats.completed === 0
227
+ },
228
+ "\u{1F5D1}\uFE0F Clear Completed"
229
+ ))), /* @__PURE__ */ jsx2("section", { className: "card todos-section" }, /* @__PURE__ */ jsx2("div", { className: "section-header" }, /* @__PURE__ */ jsx2("h2", null, "\u{1F4CB} Todo List"), /* @__PURE__ */ jsx2("div", { className: "filter-info" }, "Showing ", /* @__PURE__ */ jsx2("strong", null, filteredTodos.length), " of ", /* @__PURE__ */ jsx2("strong", null, todoStats.total), " todos", taskFilter !== "all" && /* @__PURE__ */ jsx2("span", { className: "filter-badge" }, taskFilter))), /* @__PURE__ */ jsx2("div", { className: "todos-list" }, filteredTodos.length > 0 ? filteredTodos.map((todo) => /* @__PURE__ */ jsx2(
230
+ TodoItem,
231
+ {
232
+ key: todo.id,
233
+ todo,
234
+ onToggle: handleToggleTodo,
235
+ onDelete: handleDeleteTodo
236
+ }
237
+ )) : /* @__PURE__ */ jsx2("div", { className: "empty-state" }, /* @__PURE__ */ jsx2("p", null, taskFilter === "all" ? "\u{1F389} No todos yet. Add one above!" : taskFilter === "completed" ? "\u{1F4DD} No completed todos yet." : taskFilter === "active" ? "\u{1F3AF} No active todos. Great job!" : `\u{1F50D} No ${taskFilter} priority todos found.`)))), /* @__PURE__ */ jsx2("section", { className: "card actions-section" }, /* @__PURE__ */ jsx2("h2", null, "\u2699\uFE0F Actions"), /* @__PURE__ */ jsx2("div", { className: "action-buttons" }, /* @__PURE__ */ jsx2("button", { onClick: simulateError, className: "btn btn-danger" }, "\u{1F4A5} Test Error Boundary"))))), /* @__PURE__ */ jsx2(AppFooter, { theme, isBrowser }), /* @__PURE__ */ jsx2("style", null, `
238
+ * {
239
+ margin: 0;
240
+ padding: 0;
241
+ box-sizing: border-box;
242
+ }
243
+
244
+ :root {
245
+ --primary: #3b82f6;
246
+ --primary-dark: #2563eb;
247
+ --secondary: #6b7280;
248
+ --success: #10b981;
249
+ --warning: #f59e0b;
250
+ --danger: #ef4444;
251
+ --background: #ffffff;
252
+ --surface: #f9fafb;
253
+ --text: #111827;
254
+ --text-muted: #6b7280;
255
+ --border: #e5e7eb;
256
+ --shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1);
257
+ --shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 0.1);
258
+ }
259
+
260
+ [data-theme="dark"] {
261
+ --primary: #3b82f6;
262
+ --secondary: #9ca3af;
263
+ --success: #10b981;
264
+ --warning: #f59e0b;
265
+ --danger: #ef4444;
266
+ --background: #111827;
267
+ --surface: #1f2937;
268
+ --text: #f9fafb;
269
+ --text-muted: #9ca3af;
270
+ --border: #374151;
271
+ }
272
+
273
+ body {
274
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
275
+ line-height: 1.6;
276
+ color: var(--text);
277
+ background-color: var(--background);
278
+ transition: all 0.3s ease;
279
+ }
280
+
281
+ .app {
282
+ min-height: 100vh;
283
+ display: flex;
284
+ flex-direction: column;
285
+ }
286
+
287
+ .container {
288
+ max-width: 800px;
289
+ margin: 0 auto;
290
+ padding: 0 1rem;
291
+ }
292
+
293
+ /* Header */
294
+ .app-header {
295
+ background: linear-gradient(135deg, var(--primary) 0%, var(--primary-dark) 100%);
296
+ color: white;
297
+ padding: 2rem 0;
298
+ box-shadow: var(--shadow-lg);
299
+ }
300
+
301
+ .app-header .container {
302
+ display: flex;
303
+ justify-content: space-between;
304
+ align-items: center;
305
+ flex-wrap: wrap;
306
+ gap: 1rem;
307
+ }
308
+
309
+ .app-title {
310
+ font-size: 2rem;
311
+ font-weight: 700;
312
+ margin: 0;
313
+ display: flex;
314
+ flex-direction: column;
315
+ gap: 0.25rem;
316
+ }
317
+
318
+ .subtitle {
319
+ font-size: 1rem;
320
+ font-weight: 400;
321
+ opacity: 0.9;
322
+ }
323
+
324
+ .header-controls {
325
+ display: flex;
326
+ align-items: center;
327
+ gap: 1rem;
328
+ }
329
+
330
+ .stats {
331
+ display: flex;
332
+ gap: 1rem;
333
+ font-size: 0.875rem;
334
+ }
335
+
336
+ .stat {
337
+ background: rgba(255, 255, 255, 0.2);
338
+ padding: 0.5rem 0.75rem;
339
+ border-radius: 0.5rem;
340
+ backdrop-filter: blur(10px);
341
+ }
342
+
343
+ /* Main content */
344
+ .main-content {
345
+ flex: 1;
346
+ padding: 2rem 0;
347
+ }
348
+
349
+ /* Cards */
350
+ .card {
351
+ background: var(--surface);
352
+ border-radius: 1rem;
353
+ padding: 1.5rem;
354
+ margin-bottom: 1.5rem;
355
+ box-shadow: var(--shadow);
356
+ border: 1px solid var(--border);
357
+ transition: transform 0.2s ease;
358
+ }
359
+
360
+ .card:hover {
361
+ transform: translateY(-2px);
362
+ }
363
+
364
+ .card h2 {
365
+ font-size: 1.25rem;
366
+ margin-bottom: 1rem;
367
+ color: var(--text);
368
+ }
369
+
370
+ /* Add Todo Form */
371
+ .add-todo-form {
372
+ display: flex;
373
+ gap: 0.75rem;
374
+ flex-wrap: wrap;
375
+ }
376
+
377
+ .todo-input {
378
+ flex: 1;
379
+ min-width: 250px;
380
+ }
381
+
382
+ .priority-select {
383
+ min-width: 150px;
384
+ }
385
+
386
+ /* Filters */
387
+ .filters {
388
+ display: flex;
389
+ gap: 0.5rem;
390
+ margin-bottom: 1rem;
391
+ flex-wrap: wrap;
392
+ }
393
+
394
+ .filter-btn {
395
+ font-size: 0.875rem;
396
+ }
397
+
398
+ .bulk-actions {
399
+ display: flex;
400
+ gap: 0.5rem;
401
+ flex-wrap: wrap;
402
+ }
403
+
404
+ /* Section Header */
405
+ .section-header {
406
+ display: flex;
407
+ justify-content: space-between;
408
+ align-items: center;
409
+ margin-bottom: 1rem;
410
+ flex-wrap: wrap;
411
+ gap: 1rem;
412
+ }
413
+
414
+ .filter-info {
415
+ font-size: 0.875rem;
416
+ color: var(--text-muted);
417
+ }
418
+
419
+ .filter-badge {
420
+ background: var(--primary);
421
+ color: white;
422
+ padding: 0.25rem 0.5rem;
423
+ border-radius: 0.25rem;
424
+ font-size: 0.75rem;
425
+ margin-left: 0.5rem;
426
+ }
427
+
428
+ /* Todos List */
429
+ .todos-list {
430
+ display: flex;
431
+ flex-direction: column;
432
+ gap: 0.75rem;
433
+ }
434
+
435
+ .todo-item {
436
+ display: flex;
437
+ align-items: center;
438
+ gap: 0.75rem;
439
+ padding: 1rem;
440
+ background: var(--background);
441
+ border-radius: 0.5rem;
442
+ border: 1px solid var(--border);
443
+ transition: all 0.2s ease;
444
+ }
445
+
446
+ .todo-item:hover {
447
+ border-color: var(--primary);
448
+ box-shadow: var(--shadow);
449
+ }
450
+
451
+ .todo-item.completed {
452
+ opacity: 0.7;
453
+ }
454
+
455
+ .todo-item.completed .todo-text {
456
+ text-decoration: line-through;
457
+ }
458
+
459
+ .todo-checkbox {
460
+ width: 1.25rem;
461
+ height: 1.25rem;
462
+ cursor: pointer;
463
+ }
464
+
465
+ .todo-text {
466
+ flex: 1;
467
+ font-size: 1rem;
468
+ }
469
+
470
+ .todo-priority {
471
+ padding: 0.25rem 0.5rem;
472
+ border-radius: 0.25rem;
473
+ font-size: 0.75rem;
474
+ font-weight: 600;
475
+ }
476
+
477
+ .priority-high {
478
+ background: #fee2e2;
479
+ color: #991b1b;
480
+ }
481
+
482
+ .priority-medium {
483
+ background: #fef3c7;
484
+ color: #92400e;
485
+ }
486
+
487
+ .priority-low {
488
+ background: #dcfce7;
489
+ color: #166534;
490
+ }
491
+
492
+ .todo-actions {
493
+ display: flex;
494
+ gap: 0.5rem;
495
+ }
496
+
497
+ /* Form elements */
498
+ .input, .select {
499
+ padding: 0.75rem;
500
+ border: 2px solid var(--border);
501
+ border-radius: 0.5rem;
502
+ font-size: 1rem;
503
+ background: var(--background);
504
+ color: var(--text);
505
+ transition: border-color 0.2s ease;
506
+ }
507
+
508
+ .input:focus, .select:focus {
509
+ outline: none;
510
+ border-color: var(--primary);
511
+ }
512
+
513
+ /* Buttons */
514
+ .btn {
515
+ display: inline-flex;
516
+ align-items: center;
517
+ justify-content: center;
518
+ gap: 0.5rem;
519
+ padding: 0.75rem 1.5rem;
520
+ border: none;
521
+ border-radius: 0.5rem;
522
+ font-size: 0.875rem;
523
+ font-weight: 500;
524
+ cursor: pointer;
525
+ transition: all 0.2s ease;
526
+ text-decoration: none;
527
+ user-select: none;
528
+ }
529
+
530
+ .btn:disabled {
531
+ opacity: 0.5;
532
+ cursor: not-allowed;
533
+ }
534
+
535
+ .btn-sm {
536
+ padding: 0.5rem 1rem;
537
+ font-size: 0.75rem;
538
+ }
539
+
540
+ .btn-primary {
541
+ background: var(--primary);
542
+ color: white;
543
+ }
544
+
545
+ .btn-primary:hover:not(:disabled) {
546
+ background: var(--primary-dark);
547
+ transform: translateY(-1px);
548
+ }
549
+
550
+ .btn-secondary {
551
+ background: var(--secondary);
552
+ color: white;
553
+ }
554
+
555
+ .btn-success {
556
+ background: var(--success);
557
+ color: white;
558
+ }
559
+
560
+ .btn-warning {
561
+ background: var(--warning);
562
+ color: white;
563
+ }
564
+
565
+ .btn-danger {
566
+ background: var(--danger);
567
+ color: white;
568
+ }
569
+
570
+ .btn-outline {
571
+ background: transparent;
572
+ color: var(--text);
573
+ border: 2px solid var(--border);
574
+ }
575
+
576
+ .btn-outline:hover:not(:disabled) {
577
+ background: var(--surface);
578
+ border-color: var(--primary);
579
+ }
580
+
581
+ .btn.loading {
582
+ position: relative;
583
+ color: transparent;
584
+ }
585
+
586
+ .btn.loading::after {
587
+ content: '';
588
+ position: absolute;
589
+ width: 1rem;
590
+ height: 1rem;
591
+ border: 2px solid transparent;
592
+ border-top: 2px solid currentColor;
593
+ border-radius: 50%;
594
+ animation: spin 1s linear infinite;
595
+ color: white;
596
+ }
597
+
598
+ @keyframes spin {
599
+ to { transform: rotate(360deg); }
600
+ }
601
+
602
+ .theme-toggle {
603
+ border-radius: 50%;
604
+ width: 3rem;
605
+ height: 3rem;
606
+ padding: 0;
607
+ font-size: 1.25rem;
608
+ background: rgba(255, 255, 255, 0.2);
609
+ color: white;
610
+ border: 2px solid rgba(255, 255, 255, 0.3);
611
+ }
612
+
613
+ .theme-toggle:hover {
614
+ background: rgba(255, 255, 255, 0.3);
615
+ transform: scale(1.05);
616
+ }
617
+
618
+ /* Empty state */
619
+ .empty-state {
620
+ text-align: center;
621
+ padding: 3rem 1rem;
622
+ color: var(--text-muted);
623
+ }
624
+
625
+ .empty-state p {
626
+ font-size: 1.1rem;
627
+ }
628
+
629
+ /* Actions section */
630
+ .action-buttons {
631
+ display: flex;
632
+ gap: 1rem;
633
+ flex-wrap: wrap;
634
+ }
635
+
636
+ /* Footer */
637
+ .app-footer {
638
+ background: var(--surface);
639
+ border-top: 1px solid var(--border);
640
+ padding: 1.5rem 0;
641
+ text-align: center;
642
+ color: var(--text-muted);
643
+ font-size: 0.875rem;
644
+ }
645
+
646
+ .app-footer p {
647
+ margin: 0.25rem 0;
648
+ }
649
+
650
+ /* Error container */
651
+ .error-container {
652
+ display: flex;
653
+ flex-direction: column;
654
+ align-items: center;
655
+ justify-content: center;
656
+ min-height: 100vh;
657
+ padding: 2rem;
658
+ text-align: center;
659
+ }
660
+
661
+ .error-container h2 {
662
+ color: var(--danger);
663
+ margin-bottom: 1rem;
664
+ }
665
+
666
+ .error-container p {
667
+ color: var(--text-muted);
668
+ margin-bottom: 2rem;
669
+ }
670
+
671
+ /* Responsive design */
672
+ @media (max-width: 768px) {
673
+ .app-header .container {
674
+ flex-direction: column;
675
+ text-align: center;
676
+ }
677
+
678
+ .app-title {
679
+ font-size: 1.75rem;
680
+ }
681
+
682
+ .add-todo-form {
683
+ flex-direction: column;
684
+ }
685
+
686
+ .todo-input, .priority-select {
687
+ min-width: auto;
688
+ width: 100%;
689
+ }
690
+
691
+ .section-header {
692
+ flex-direction: column;
693
+ align-items: stretch;
694
+ }
695
+
696
+ .stats {
697
+ justify-content: center;
698
+ }
699
+
700
+ .filters {
701
+ justify-content: center;
702
+ }
703
+
704
+ .bulk-actions {
705
+ justify-content: center;
706
+ }
707
+ }
708
+
709
+ /* Smooth transitions */
710
+ * {
711
+ transition: background-color 0.3s ease, color 0.3s ease, border-color 0.3s ease;
712
+ }
713
+ `));
714
+ }
715
+
716
+ // src/server.ts
717
+ var __filename = fileURLToPath(import.meta.url);
718
+ var __dirname = path.dirname(__filename);
719
+ var app = express();
720
+ var port = 3e3;
721
+ app.use(express.static(path.join(__dirname)));
722
+ app.get("/", async (req, res) => {
723
+ const html = await renderToString(jsx3(App, {}));
724
+ res.send(`
725
+ <!DOCTYPE html>
726
+ <html>
727
+ <head>
728
+ <title>SSR App</title>
729
+ </head>
730
+ <body>
731
+ <div id="root">${html}</div>
732
+ <script type="module" src="/client.js"></script>
733
+ </body>
734
+ </html>
735
+ `);
736
+ });
737
+ app.listen(port, () => {
738
+ console.log(`Server running at http://localhost:${port}`);
739
+ });