clawfire 0.1.0 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -3,363 +3,27 @@
3
3
  <head>
4
4
  <meta charset="UTF-8" />
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
- <title>Clawfire Todo App</title>
6
+ <title>Clawfire App</title>
7
7
  <style>
8
- *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
9
-
10
8
  body {
11
9
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
12
10
  background: #0f0f13;
13
11
  color: #e4e4e7;
14
- min-height: 100vh;
15
- display: flex;
16
- justify-content: center;
17
- padding: 48px 16px;
18
- }
19
-
20
- .app {
21
- width: 100%;
22
- max-width: 560px;
23
- }
24
-
25
- /* Header */
26
- .header {
27
- text-align: center;
28
- margin-bottom: 32px;
29
- }
30
- .header h1 {
31
- font-size: 28px;
32
- font-weight: 700;
33
- background: linear-gradient(135deg, #f97316, #fb923c);
34
- -webkit-background-clip: text;
35
- -webkit-text-fill-color: transparent;
36
- margin-bottom: 4px;
37
- }
38
- .header p {
39
- color: #71717a;
40
- font-size: 14px;
41
- }
42
-
43
- /* Input area */
44
- .input-row {
45
- display: flex;
46
- gap: 8px;
47
- margin-bottom: 24px;
48
- }
49
- .input-row input {
50
- flex: 1;
51
- padding: 12px 16px;
52
- background: #1a1a23;
53
- border: 1px solid #27272a;
54
- border-radius: 10px;
55
- color: #e4e4e7;
56
- font-size: 15px;
57
- outline: none;
58
- transition: border-color 0.15s;
59
- }
60
- .input-row input:focus {
61
- border-color: #f97316;
62
- }
63
- .input-row input::placeholder { color: #52525b; }
64
- .input-row button {
65
- padding: 12px 20px;
66
- background: #f97316;
67
- color: #fff;
68
- border: none;
69
- border-radius: 10px;
70
- font-size: 15px;
71
- font-weight: 600;
72
- cursor: pointer;
73
- white-space: nowrap;
74
- transition: background 0.15s;
75
- }
76
- .input-row button:hover { background: #ea580c; }
77
- .input-row button:disabled { opacity: 0.5; cursor: not-allowed; }
78
-
79
- /* Stats bar */
80
- .stats {
81
- display: flex;
82
- justify-content: space-between;
83
- align-items: center;
84
- padding: 8px 0;
85
- margin-bottom: 12px;
86
- font-size: 13px;
87
- color: #71717a;
88
- }
89
- .stats .count { font-weight: 600; color: #a1a1aa; }
90
-
91
- /* Todo list */
92
- .todo-list {
93
- display: flex;
94
- flex-direction: column;
95
- gap: 6px;
96
- }
97
-
98
- .todo-item {
99
- display: flex;
100
- align-items: center;
101
- gap: 12px;
102
- padding: 12px 16px;
103
- background: #1a1a23;
104
- border: 1px solid #27272a;
105
- border-radius: 10px;
106
- transition: border-color 0.15s, opacity 0.2s;
107
- }
108
- .todo-item:hover { border-color: #3f3f46; }
109
- .todo-item.completed { opacity: 0.5; }
110
-
111
- /* Checkbox */
112
- .todo-check {
113
- width: 22px;
114
- height: 22px;
115
- border: 2px solid #3f3f46;
116
- border-radius: 6px;
117
- cursor: pointer;
118
12
  display: flex;
119
- align-items: center;
120
13
  justify-content: center;
121
- flex-shrink: 0;
122
- transition: all 0.15s;
123
- }
124
- .todo-check:hover { border-color: #f97316; }
125
- .todo-item.completed .todo-check {
126
- background: #f97316;
127
- border-color: #f97316;
128
- }
129
- .todo-check svg { display: none; }
130
- .todo-item.completed .todo-check svg { display: block; }
131
-
132
- /* Title */
133
- .todo-title {
134
- flex: 1;
135
- font-size: 15px;
136
- line-height: 1.4;
137
- word-break: break-word;
138
- }
139
- .todo-item.completed .todo-title {
140
- text-decoration: line-through;
141
- color: #71717a;
142
- }
143
-
144
- /* Delete button */
145
- .todo-delete {
146
- background: none;
147
- border: none;
148
- color: #52525b;
149
- cursor: pointer;
150
- padding: 4px;
151
- border-radius: 6px;
152
- display: flex;
153
14
  align-items: center;
154
- justify-content: center;
155
- opacity: 0;
156
- transition: all 0.15s;
157
- }
158
- .todo-item:hover .todo-delete { opacity: 1; }
159
- .todo-delete:hover { color: #ef4444; background: rgba(239, 68, 68, 0.1); }
160
-
161
- /* Empty state */
162
- .empty {
163
- text-align: center;
164
- padding: 48px 0;
165
- color: #52525b;
166
- }
167
- .empty svg { margin-bottom: 12px; }
168
- .empty p { font-size: 14px; }
169
-
170
- /* Loading */
171
- .loading {
172
- text-align: center;
173
- padding: 48px 0;
174
- color: #71717a;
175
- font-size: 14px;
176
- }
177
-
178
- /* Error */
179
- .error-toast {
180
- position: fixed;
181
- bottom: 24px;
182
- left: 50%;
183
- transform: translateX(-50%);
184
- background: #7f1d1d;
185
- color: #fca5a5;
186
- padding: 10px 20px;
187
- border-radius: 8px;
188
- font-size: 13px;
189
- display: none;
190
- z-index: 100;
15
+ min-height: 100vh;
16
+ margin: 0;
191
17
  }
192
- .error-toast.show { display: block; }
18
+ .msg { text-align: center; }
19
+ .msg h1 { color: #f97316; font-size: 24px; margin-bottom: 8px; }
20
+ .msg p { color: #71717a; font-size: 14px; }
193
21
  </style>
194
22
  </head>
195
23
  <body>
196
- <div class="app">
197
- <div class="header">
198
- <h1>Clawfire Todos</h1>
199
- <p>Your first Clawfire app — add, complete, and delete todos</p>
200
- </div>
201
-
202
- <div class="input-row">
203
- <input type="text" id="todo-input" placeholder="What needs to be done?" maxlength="200" />
204
- <button id="add-btn" onclick="addTodo()">Add</button>
205
- </div>
206
-
207
- <div class="stats" id="stats" style="display:none">
208
- <span><span class="count" id="total-count">0</span> total</span>
209
- <span><span class="count" id="done-count">0</span> completed</span>
210
- </div>
211
-
212
- <div id="todo-list" class="todo-list">
213
- <div class="loading">Loading todos...</div>
214
- </div>
215
-
216
- <div class="error-toast" id="error-toast"></div>
24
+ <div class="msg">
25
+ <h1>Clawfire</h1>
26
+ <p>This is the public/ fallback. Your pages are in app/pages/.</p>
217
27
  </div>
218
-
219
- <script>
220
- const API = '/api/todos';
221
- let todos = [];
222
-
223
- // ─── API Calls ──────────────────────────────────
224
- async function api(path, body = {}) {
225
- const res = await fetch(`${API}/${path}`, {
226
- method: 'POST',
227
- headers: { 'Content-Type': 'application/json' },
228
- body: JSON.stringify(body),
229
- });
230
- const json = await res.json();
231
- if (!res.ok) throw new Error(json.error || 'Request failed');
232
- return json.data;
233
- }
234
-
235
- async function loadTodos() {
236
- try {
237
- const data = await api('list');
238
- todos = data.todos;
239
- render();
240
- } catch (e) {
241
- showError('Failed to load todos: ' + e.message);
242
- }
243
- }
244
-
245
- async function addTodo() {
246
- const input = document.getElementById('todo-input');
247
- const title = input.value.trim();
248
- if (!title) return;
249
-
250
- const btn = document.getElementById('add-btn');
251
- btn.disabled = true;
252
- try {
253
- const data = await api('create', { title });
254
- todos.unshift(data.todo);
255
- input.value = '';
256
- render();
257
- } catch (e) {
258
- showError('Failed to add todo: ' + e.message);
259
- } finally {
260
- btn.disabled = false;
261
- input.focus();
262
- }
263
- }
264
-
265
- async function toggleTodo(id) {
266
- const todo = todos.find(t => t.id === id);
267
- if (!todo) return;
268
- try {
269
- const data = await api('update', { id, completed: !todo.completed });
270
- if (data.todo) {
271
- const idx = todos.findIndex(t => t.id === id);
272
- if (idx !== -1) todos[idx] = data.todo;
273
- render();
274
- }
275
- } catch (e) {
276
- showError('Failed to update todo: ' + e.message);
277
- }
278
- }
279
-
280
- async function deleteTodo(id) {
281
- try {
282
- await api('delete', { id });
283
- todos = todos.filter(t => t.id !== id);
284
- render();
285
- } catch (e) {
286
- showError('Failed to delete todo: ' + e.message);
287
- }
288
- }
289
-
290
- // ─── Render ─────────────────────────────────────
291
- function render() {
292
- const list = document.getElementById('todo-list');
293
- const stats = document.getElementById('stats');
294
-
295
- if (todos.length === 0) {
296
- stats.style.display = 'none';
297
- list.innerHTML = `
298
- <div class="empty">
299
- <svg width="48" height="48" fill="none" stroke="#3f3f46" stroke-width="1.5" viewBox="0 0 24 24">
300
- <path d="M9 5H7a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V7a2 2 0 0 0-2-2h-2M9 5a2 2 0 0 1 2-2h2a2 2 0 0 1 2 2M9 5h6"/>
301
- </svg>
302
- <p>No todos yet. Add one above!</p>
303
- </div>`;
304
- return;
305
- }
306
-
307
- const done = todos.filter(t => t.completed).length;
308
- stats.style.display = 'flex';
309
- document.getElementById('total-count').textContent = todos.length;
310
- document.getElementById('done-count').textContent = done;
311
-
312
- list.innerHTML = todos.map(todo => `
313
- <div class="todo-item ${todo.completed ? 'completed' : ''}" data-id="${todo.id}">
314
- <div class="todo-check" onclick="toggleTodo('${todo.id}')">
315
- <svg width="14" height="14" fill="none" stroke="#fff" stroke-width="2.5" viewBox="0 0 24 24">
316
- <path d="M5 13l4 4L19 7"/>
317
- </svg>
318
- </div>
319
- <span class="todo-title">${escapeHtml(todo.title)}</span>
320
- <button class="todo-delete" onclick="deleteTodo('${todo.id}')" title="Delete">
321
- <svg width="16" height="16" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24">
322
- <path d="M18 6L6 18M6 6l12 12"/>
323
- </svg>
324
- </button>
325
- </div>
326
- `).join('');
327
- }
328
-
329
- function escapeHtml(text) {
330
- const d = document.createElement('div');
331
- d.textContent = text;
332
- return d.innerHTML;
333
- }
334
-
335
- function showError(msg) {
336
- const toast = document.getElementById('error-toast');
337
- toast.textContent = msg;
338
- toast.classList.add('show');
339
- setTimeout(() => toast.classList.remove('show'), 3000);
340
- }
341
-
342
- // ─── Init ───────────────────────────────────────
343
- document.getElementById('todo-input').addEventListener('keydown', (e) => {
344
- if (e.key === 'Enter') addTodo();
345
- });
346
-
347
- loadTodos();
348
-
349
- // ─── Live Reload (dev server) ───────────────────
350
- if (location.hostname === 'localhost') {
351
- try {
352
- const es = new EventSource('/__dev/events');
353
- es.onmessage = (e) => {
354
- try {
355
- const data = JSON.parse(e.data);
356
- if (data.type !== 'connected') {
357
- setTimeout(() => location.reload(), 300);
358
- }
359
- } catch {}
360
- };
361
- } catch {}
362
- }
363
- </script>
364
28
  </body>
365
29
  </html>