@wsxjs/wsx-base-components 0.0.8 → 0.0.9

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.
package/dist/style.css ADDED
@@ -0,0 +1 @@
1
+ .todo-list-light{padding:1.5rem;max-width:600px;margin:0 auto;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,sans-serif}.todo-header{margin-bottom:1.5rem;text-align:center}.todo-header h2{margin:0 0 .5rem;color:#2196f3;font-size:1.75rem}.subtitle{margin:0;color:#666;font-size:.9rem}.todo-input-section{display:flex;gap:.5rem;margin-bottom:1rem}.todo-input{flex:1;padding:.75rem;border:2px solid #ddd;border-radius:4px;font-size:1rem;transition:border-color .2s}.todo-input:focus{outline:none;border-color:#2196f3}.todo-filters{display:flex;gap:.5rem;margin-bottom:1rem;justify-content:center}.filter-btn{padding:.5rem 1rem;border:2px solid #ddd;background:#fff;border-radius:4px;cursor:pointer;transition:all .2s;font-size:.9rem}.filter-btn:hover{background:#f5f5f5}.filter-btn.active{background:#2196f3;color:#fff;border-color:#2196f3}.todo-list-container{min-height:200px;margin-bottom:1rem}.empty-state{text-align:center;padding:3rem 1rem;color:#999;font-style:italic}.todo-items{list-style:none;padding:0;margin:0}.todo-item{display:flex;align-items:center;gap:.75rem;padding:.75rem;margin-bottom:.5rem;background:#f0f7ff;border-radius:4px;transition:background .2s}.todo-item:hover{background:#e3f2fd}.todo-item.completed{opacity:.6}.todo-checkbox{width:1.25rem;height:1.25rem;cursor:pointer}.todo-text{flex:1;font-size:1rem}.todo-item.completed .todo-text{text-decoration:line-through;color:#999}.todo-actions{display:flex;gap:.5rem;justify-content:center;margin-top:1rem}.btn{padding:.5rem 1rem;border:none;border-radius:4px;cursor:pointer;font-size:.9rem;transition:all .2s}.btn:disabled{opacity:.5;cursor:not-allowed}.btn-primary{background:#2196f3;color:#fff}.btn-primary:hover:not(:disabled){background:#1976d2}.btn-warning{background:#ff9800;color:#fff}.btn-warning:hover{background:#e68900}.btn-danger{background:#f44336;color:#fff}.btn-danger:hover{background:#da190b}.btn-sm{padding:.25rem .5rem;font-size:.8rem}.debug-info{margin-top:2rem;padding-top:1rem;border-top:1px solid #ddd}.debug-info summary{cursor:pointer;color:#666;font-size:.9rem}.debug-info pre{background:#f5f5f5;padding:1rem;border-radius:4px;overflow-x:auto;font-size:.85rem;margin-top:.5rem}.user-profile{padding:1.5rem;max-width:800px;margin:0 auto;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,sans-serif}.profile-header{margin-bottom:2rem;text-align:center}.profile-header h2{margin:0 0 .5rem;color:#333;font-size:1.75rem}.profile-header .subtitle{color:#666;font-size:.9rem;margin:0}.profile-content{display:flex;flex-direction:column;gap:2rem}.profile-section{background:#f8f9fa;padding:1.5rem;border-radius:8px;border:1px solid #e0e0e0}.profile-section h3{margin:0 0 1rem;color:#333;font-size:1.25rem;border-bottom:2px solid #007bff;padding-bottom:.5rem}.form-group{margin-bottom:1rem}.form-group label{display:block;margin-bottom:.5rem;color:#555;font-weight:500;font-size:.9rem}.form-group input[type=checkbox]{margin-right:.5rem}.input-field{width:100%;padding:.75rem;border:1px solid #ddd;border-radius:4px;font-size:1rem;transition:border-color .2s;box-sizing:border-box}.input-field:focus{outline:none;border-color:#007bff;box-shadow:0 0 0 3px #007bff1a}.profile-actions{display:flex;gap:1rem;flex-wrap:wrap}.btn{padding:.75rem 1.5rem;border:none;border-radius:4px;font-size:1rem;cursor:pointer;transition:all .2s;font-weight:500}.btn-primary{background:#007bff;color:#fff}.btn-primary:hover{background:#0056b3}.btn-secondary{background:#6c757d;color:#fff}.btn-secondary:hover{background:#545b62}.btn-warning{background:#ffc107;color:#212529}.btn-warning:hover{background:#e0a800}.profile-display{background:#f8f9fa;padding:1.5rem;border-radius:8px;border:1px solid #e0e0e0}.profile-display h3{margin:0 0 1rem;color:#333;font-size:1.25rem}.json-display{background:#2d2d2d;color:#f8f8f2;padding:1rem;border-radius:4px;overflow-x:auto;font-family:Courier New,monospace;font-size:.875rem;line-height:1.5;margin:0;white-space:pre-wrap;word-wrap:break-word}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wsxjs/wsx-base-components",
3
- "version": "0.0.8",
3
+ "version": "0.0.9",
4
4
  "description": "Base UI components built with WSX Framework",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",
@@ -18,7 +18,7 @@
18
18
  "!**/test"
19
19
  ],
20
20
  "dependencies": {
21
- "@wsxjs/wsx-core": "0.0.8"
21
+ "@wsxjs/wsx-core": "0.0.9"
22
22
  },
23
23
  "devDependencies": {
24
24
  "@typescript-eslint/eslint-plugin": "^8.37.0",
@@ -28,8 +28,8 @@
28
28
  "tsup": "^8.0.0",
29
29
  "typescript": "^5.0.0",
30
30
  "vite": "^5.4.19",
31
- "@wsxjs/wsx-vite-plugin": "0.0.8",
32
- "@wsxjs/eslint-plugin-wsx": "0.0.8"
31
+ "@wsxjs/eslint-plugin-wsx": "0.0.9",
32
+ "@wsxjs/wsx-vite-plugin": "0.0.9"
33
33
  },
34
34
  "keywords": [
35
35
  "wsx",
@@ -0,0 +1,197 @@
1
+ .todo-list {
2
+ padding: 1.5rem;
3
+ max-width: 600px;
4
+ margin: 0 auto;
5
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
6
+ }
7
+
8
+ .todo-header {
9
+ margin-bottom: 1.5rem;
10
+ text-align: center;
11
+ }
12
+
13
+ .todo-header h2 {
14
+ margin: 0 0 0.5rem 0;
15
+ color: #333;
16
+ font-size: 1.75rem;
17
+ }
18
+
19
+ .subtitle {
20
+ margin: 0;
21
+ color: #666;
22
+ font-size: 0.9rem;
23
+ }
24
+
25
+ .todo-input-section {
26
+ display: flex;
27
+ gap: 0.5rem;
28
+ margin-bottom: 1rem;
29
+ }
30
+
31
+ .todo-input {
32
+ flex: 1;
33
+ padding: 0.75rem;
34
+ border: 2px solid #ddd;
35
+ border-radius: 4px;
36
+ font-size: 1rem;
37
+ transition: border-color 0.2s;
38
+ }
39
+
40
+ .todo-input:focus {
41
+ outline: none;
42
+ border-color: #4caf50;
43
+ }
44
+
45
+ .todo-filters {
46
+ display: flex;
47
+ gap: 0.5rem;
48
+ margin-bottom: 1rem;
49
+ justify-content: center;
50
+ }
51
+
52
+ .filter-btn {
53
+ padding: 0.5rem 1rem;
54
+ border: 2px solid #ddd;
55
+ background: white;
56
+ border-radius: 4px;
57
+ cursor: pointer;
58
+ transition: all 0.2s;
59
+ font-size: 0.9rem;
60
+ }
61
+
62
+ .filter-btn:hover {
63
+ background: #f5f5f5;
64
+ }
65
+
66
+ .filter-btn.active {
67
+ background: #4caf50;
68
+ color: white;
69
+ border-color: #4caf50;
70
+ }
71
+
72
+ .todo-list-container {
73
+ min-height: 200px;
74
+ margin-bottom: 1rem;
75
+ }
76
+
77
+ .empty-state {
78
+ text-align: center;
79
+ padding: 3rem 1rem;
80
+ color: #999;
81
+ font-style: italic;
82
+ }
83
+
84
+ .todo-items {
85
+ list-style: none;
86
+ padding: 0;
87
+ margin: 0;
88
+ }
89
+
90
+ .todo-item {
91
+ display: flex;
92
+ align-items: center;
93
+ gap: 0.75rem;
94
+ padding: 0.75rem;
95
+ margin-bottom: 0.5rem;
96
+ background: #f9f9f9;
97
+ border-radius: 4px;
98
+ transition: background 0.2s;
99
+ }
100
+
101
+ .todo-item:hover {
102
+ background: #f0f0f0;
103
+ }
104
+
105
+ .todo-item.completed {
106
+ opacity: 0.6;
107
+ }
108
+
109
+ .todo-checkbox {
110
+ width: 1.25rem;
111
+ height: 1.25rem;
112
+ cursor: pointer;
113
+ }
114
+
115
+ .todo-text {
116
+ flex: 1;
117
+ font-size: 1rem;
118
+ }
119
+
120
+ .todo-item.completed .todo-text {
121
+ text-decoration: line-through;
122
+ color: #999;
123
+ }
124
+
125
+ .todo-actions {
126
+ display: flex;
127
+ gap: 0.5rem;
128
+ justify-content: center;
129
+ margin-top: 1rem;
130
+ }
131
+
132
+ .btn {
133
+ padding: 0.5rem 1rem;
134
+ border: none;
135
+ border-radius: 4px;
136
+ cursor: pointer;
137
+ font-size: 0.9rem;
138
+ transition: all 0.2s;
139
+ }
140
+
141
+ .btn:disabled {
142
+ opacity: 0.5;
143
+ cursor: not-allowed;
144
+ }
145
+
146
+ .btn-primary {
147
+ background: #4caf50;
148
+ color: white;
149
+ }
150
+
151
+ .btn-primary:hover:not(:disabled) {
152
+ background: #45a049;
153
+ }
154
+
155
+ .btn-warning {
156
+ background: #ff9800;
157
+ color: white;
158
+ }
159
+
160
+ .btn-warning:hover {
161
+ background: #e68900;
162
+ }
163
+
164
+ .btn-danger {
165
+ background: #f44336;
166
+ color: white;
167
+ }
168
+
169
+ .btn-danger:hover {
170
+ background: #da190b;
171
+ }
172
+
173
+ .btn-sm {
174
+ padding: 0.25rem 0.5rem;
175
+ font-size: 0.8rem;
176
+ }
177
+
178
+ .debug-info {
179
+ margin-top: 2rem;
180
+ padding-top: 1rem;
181
+ border-top: 1px solid #ddd;
182
+ }
183
+
184
+ .debug-info summary {
185
+ cursor: pointer;
186
+ color: #666;
187
+ font-size: 0.9rem;
188
+ }
189
+
190
+ .debug-info pre {
191
+ background: #f5f5f5;
192
+ padding: 1rem;
193
+ border-radius: 4px;
194
+ overflow-x: auto;
195
+ font-size: 0.85rem;
196
+ margin-top: 0.5rem;
197
+ }
@@ -0,0 +1,264 @@
1
+ /** @jsxImportSource @wsxjs/wsx-core */
2
+ /**
3
+ * TodoList Component - WebComponent with @state array support
4
+ *
5
+ * Demonstrates:
6
+ * - Using @state decorator with arrays in WebComponent
7
+ * - Array operations: add, remove, update, toggle
8
+ * - Reactive array updates trigger automatic rerender
9
+ * - Shadow DOM styling
10
+ */
11
+
12
+ import { WebComponent, state, autoRegister, createLogger } from "@wsxjs/wsx-core";
13
+
14
+ const logger = createLogger("TodoList");
15
+
16
+ interface TodoItem {
17
+ id: number;
18
+ text: string;
19
+ completed: boolean;
20
+ createdAt: number;
21
+ }
22
+
23
+ @autoRegister({ tagName: "todo-list" })
24
+ export default class TodoList extends WebComponent {
25
+ // @state decorator with array - automatically reactive
26
+ @state private todos: TodoItem[] = [];
27
+
28
+ // Non-reactive input value - prevents rerender on every keystroke
29
+ private _newTodoText = "";
30
+
31
+ // @state decorator with filter state
32
+ @state private filter: "all" | "active" | "completed" = "all";
33
+
34
+ private nextId = 1;
35
+
36
+ constructor() {
37
+ super();
38
+ logger.info("TodoList initialized");
39
+ }
40
+
41
+ render() {
42
+ const filteredTodos = this.getFilteredTodos();
43
+
44
+ return (
45
+ <div class="todo-list">
46
+ <div class="todo-header">
47
+ <h2>📝 Todo List (WebComponent)</h2>
48
+ <p class="subtitle">
49
+ {this.todos.length} total, {this.getActiveCount()} active,{" "}
50
+ {this.getCompletedCount()} completed
51
+ </p>
52
+ </div>
53
+
54
+ <div class="todo-input-section">
55
+ <input
56
+ type="text"
57
+ class="todo-input"
58
+ placeholder="Add a new todo..."
59
+ value={this._newTodoText}
60
+ onInput={this.handleInputChange}
61
+ onKeyDown={this.handleKeyDown}
62
+ />
63
+ <button
64
+ class="btn btn-primary"
65
+ onClick={this.addTodo}
66
+ disabled={!this._newTodoText.trim()}
67
+ >
68
+ Add
69
+ </button>
70
+ </div>
71
+
72
+ <div class="todo-filters">
73
+ <button
74
+ class={`filter-btn ${this.filter === "all" ? "active" : ""}`}
75
+ onClick={() => (this.filter = "all")}
76
+ >
77
+ All ({this.todos.length})
78
+ </button>
79
+ <button
80
+ class={`filter-btn ${this.filter === "active" ? "active" : ""}`}
81
+ onClick={() => (this.filter = "active")}
82
+ >
83
+ Active ({this.getActiveCount()})
84
+ </button>
85
+ <button
86
+ class={`filter-btn ${this.filter === "completed" ? "active" : ""}`}
87
+ onClick={() => (this.filter = "completed")}
88
+ >
89
+ Completed ({this.getCompletedCount()})
90
+ </button>
91
+ </div>
92
+
93
+ <div class="todo-list-container">
94
+ {filteredTodos.length === 0 ? (
95
+ <div class="empty-state">
96
+ {this.filter === "all"
97
+ ? "No todos yet. Add one above! 🎉"
98
+ : `No ${this.filter} todos.`}
99
+ </div>
100
+ ) : (
101
+ <ul class="todo-items">
102
+ {filteredTodos.map((todo) => (
103
+ <li
104
+ key={todo.id}
105
+ class={`todo-item ${todo.completed ? "completed" : ""}`}
106
+ >
107
+ <input
108
+ type="checkbox"
109
+ checked={todo.completed}
110
+ onChange={() => this.toggleTodo(todo.id)}
111
+ class="todo-checkbox"
112
+ />
113
+ <span class="todo-text">{todo.text}</span>
114
+ <button
115
+ class="btn btn-sm btn-danger"
116
+ onClick={() => this.removeTodo(todo.id)}
117
+ >
118
+ Delete
119
+ </button>
120
+ </li>
121
+ ))}
122
+ </ul>
123
+ )}
124
+ </div>
125
+
126
+ {this.todos.length > 0 && (
127
+ <div class="todo-actions">
128
+ <button class="btn btn-warning" onClick={this.clearCompleted}>
129
+ Clear Completed
130
+ </button>
131
+ <button class="btn btn-danger" onClick={this.clearAll}>
132
+ Clear All
133
+ </button>
134
+ </div>
135
+ )}
136
+
137
+ <div class="debug-info">
138
+ <details>
139
+ <summary>Debug Info</summary>
140
+ <pre>
141
+ {JSON.stringify(
142
+ {
143
+ todosCount: this.todos.length,
144
+ filter: this.filter,
145
+ newTodoText: this._newTodoText,
146
+ todos: this.todos,
147
+ },
148
+ null,
149
+ 2
150
+ )}
151
+ </pre>
152
+ </details>
153
+ </div>
154
+ </div>
155
+ );
156
+ }
157
+
158
+ private handleInputChange = (event: Event) => {
159
+ const input = event.target as HTMLInputElement;
160
+ // Update non-reactive state without triggering rerender
161
+ this._newTodoText = input.value;
162
+ // Update button disabled state directly without rerender
163
+ const button = this.shadowRoot?.querySelector(
164
+ ".todo-input-section .btn-primary"
165
+ ) as HTMLButtonElement;
166
+ if (button) {
167
+ button.disabled = !this._newTodoText.trim();
168
+ }
169
+ };
170
+
171
+ private handleKeyDown = (event: KeyboardEvent) => {
172
+ if (event.key === "Enter" && this._newTodoText.trim()) {
173
+ this.addTodo();
174
+ }
175
+ };
176
+
177
+ private addTodo = () => {
178
+ // Get input value directly from DOM to avoid state sync issues
179
+ const input = this.shadowRoot?.querySelector(".todo-input") as HTMLInputElement;
180
+ const text = input?.value.trim() || this._newTodoText.trim();
181
+
182
+ if (!text) {
183
+ return;
184
+ }
185
+
186
+ // Array mutation - automatically reactive (triggers rerender)
187
+ this.todos = [
188
+ ...this.todos,
189
+ {
190
+ id: this.nextId++,
191
+ text,
192
+ completed: false,
193
+ createdAt: Date.now(),
194
+ },
195
+ ];
196
+
197
+ // Clear input - update both state and DOM
198
+ this._newTodoText = "";
199
+ if (input) {
200
+ input.value = "";
201
+ // Update button state
202
+ const button = this.shadowRoot?.querySelector(
203
+ ".todo-input-section .btn-primary"
204
+ ) as HTMLButtonElement;
205
+ if (button) {
206
+ button.disabled = true;
207
+ }
208
+ }
209
+ logger.debug("Todo added", { count: this.todos.length });
210
+ };
211
+
212
+ private removeTodo = (id: number) => {
213
+ // Array filter - creates new array, automatically reactive
214
+ this.todos = this.todos.filter((todo) => todo.id !== id);
215
+ logger.debug("Todo removed", { id, remaining: this.todos.length });
216
+ };
217
+
218
+ private toggleTodo = (id: number) => {
219
+ // Array map - creates new array with updated item, automatically reactive
220
+ this.todos = this.todos.map((todo) =>
221
+ todo.id === id ? { ...todo, completed: !todo.completed } : todo
222
+ );
223
+ logger.debug("Todo toggled", { id });
224
+ };
225
+
226
+ private clearCompleted = () => {
227
+ // Array filter - removes all completed items
228
+ this.todos = this.todos.filter((todo) => !todo.completed);
229
+ logger.debug("Completed todos cleared");
230
+ };
231
+
232
+ private clearAll = () => {
233
+ // Array assignment - clears all items
234
+ this.todos = [];
235
+ logger.debug("All todos cleared");
236
+ };
237
+
238
+ private getFilteredTodos(): TodoItem[] {
239
+ switch (this.filter) {
240
+ case "active":
241
+ return this.todos.filter((todo) => !todo.completed);
242
+ case "completed":
243
+ return this.todos.filter((todo) => todo.completed);
244
+ default:
245
+ return this.todos;
246
+ }
247
+ }
248
+
249
+ private getActiveCount(): number {
250
+ return this.todos.filter((todo) => !todo.completed).length;
251
+ }
252
+
253
+ private getCompletedCount(): number {
254
+ return this.todos.filter((todo) => todo.completed).length;
255
+ }
256
+
257
+ protected onConnected(): void {
258
+ logger.info("TodoList connected to DOM");
259
+ }
260
+
261
+ protected onDisconnected(): void {
262
+ logger.info("TodoList disconnected from DOM");
263
+ }
264
+ }
@@ -0,0 +1,198 @@
1
+ .todo-list-light {
2
+ padding: 1.5rem;
3
+ max-width: 600px;
4
+ margin: 0 auto;
5
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
6
+ /* Light DOM - styles can be inherited from parent */
7
+ }
8
+
9
+ .todo-header {
10
+ margin-bottom: 1.5rem;
11
+ text-align: center;
12
+ }
13
+
14
+ .todo-header h2 {
15
+ margin: 0 0 0.5rem 0;
16
+ color: #2196f3;
17
+ font-size: 1.75rem;
18
+ }
19
+
20
+ .subtitle {
21
+ margin: 0;
22
+ color: #666;
23
+ font-size: 0.9rem;
24
+ }
25
+
26
+ .todo-input-section {
27
+ display: flex;
28
+ gap: 0.5rem;
29
+ margin-bottom: 1rem;
30
+ }
31
+
32
+ .todo-input {
33
+ flex: 1;
34
+ padding: 0.75rem;
35
+ border: 2px solid #ddd;
36
+ border-radius: 4px;
37
+ font-size: 1rem;
38
+ transition: border-color 0.2s;
39
+ }
40
+
41
+ .todo-input:focus {
42
+ outline: none;
43
+ border-color: #2196f3;
44
+ }
45
+
46
+ .todo-filters {
47
+ display: flex;
48
+ gap: 0.5rem;
49
+ margin-bottom: 1rem;
50
+ justify-content: center;
51
+ }
52
+
53
+ .filter-btn {
54
+ padding: 0.5rem 1rem;
55
+ border: 2px solid #ddd;
56
+ background: white;
57
+ border-radius: 4px;
58
+ cursor: pointer;
59
+ transition: all 0.2s;
60
+ font-size: 0.9rem;
61
+ }
62
+
63
+ .filter-btn:hover {
64
+ background: #f5f5f5;
65
+ }
66
+
67
+ .filter-btn.active {
68
+ background: #2196f3;
69
+ color: white;
70
+ border-color: #2196f3;
71
+ }
72
+
73
+ .todo-list-container {
74
+ min-height: 200px;
75
+ margin-bottom: 1rem;
76
+ }
77
+
78
+ .empty-state {
79
+ text-align: center;
80
+ padding: 3rem 1rem;
81
+ color: #999;
82
+ font-style: italic;
83
+ }
84
+
85
+ .todo-items {
86
+ list-style: none;
87
+ padding: 0;
88
+ margin: 0;
89
+ }
90
+
91
+ .todo-item {
92
+ display: flex;
93
+ align-items: center;
94
+ gap: 0.75rem;
95
+ padding: 0.75rem;
96
+ margin-bottom: 0.5rem;
97
+ background: #f0f7ff;
98
+ border-radius: 4px;
99
+ transition: background 0.2s;
100
+ }
101
+
102
+ .todo-item:hover {
103
+ background: #e3f2fd;
104
+ }
105
+
106
+ .todo-item.completed {
107
+ opacity: 0.6;
108
+ }
109
+
110
+ .todo-checkbox {
111
+ width: 1.25rem;
112
+ height: 1.25rem;
113
+ cursor: pointer;
114
+ }
115
+
116
+ .todo-text {
117
+ flex: 1;
118
+ font-size: 1rem;
119
+ }
120
+
121
+ .todo-item.completed .todo-text {
122
+ text-decoration: line-through;
123
+ color: #999;
124
+ }
125
+
126
+ .todo-actions {
127
+ display: flex;
128
+ gap: 0.5rem;
129
+ justify-content: center;
130
+ margin-top: 1rem;
131
+ }
132
+
133
+ .btn {
134
+ padding: 0.5rem 1rem;
135
+ border: none;
136
+ border-radius: 4px;
137
+ cursor: pointer;
138
+ font-size: 0.9rem;
139
+ transition: all 0.2s;
140
+ }
141
+
142
+ .btn:disabled {
143
+ opacity: 0.5;
144
+ cursor: not-allowed;
145
+ }
146
+
147
+ .btn-primary {
148
+ background: #2196f3;
149
+ color: white;
150
+ }
151
+
152
+ .btn-primary:hover:not(:disabled) {
153
+ background: #1976d2;
154
+ }
155
+
156
+ .btn-warning {
157
+ background: #ff9800;
158
+ color: white;
159
+ }
160
+
161
+ .btn-warning:hover {
162
+ background: #e68900;
163
+ }
164
+
165
+ .btn-danger {
166
+ background: #f44336;
167
+ color: white;
168
+ }
169
+
170
+ .btn-danger:hover {
171
+ background: #da190b;
172
+ }
173
+
174
+ .btn-sm {
175
+ padding: 0.25rem 0.5rem;
176
+ font-size: 0.8rem;
177
+ }
178
+
179
+ .debug-info {
180
+ margin-top: 2rem;
181
+ padding-top: 1rem;
182
+ border-top: 1px solid #ddd;
183
+ }
184
+
185
+ .debug-info summary {
186
+ cursor: pointer;
187
+ color: #666;
188
+ font-size: 0.9rem;
189
+ }
190
+
191
+ .debug-info pre {
192
+ background: #f5f5f5;
193
+ padding: 1rem;
194
+ border-radius: 4px;
195
+ overflow-x: auto;
196
+ font-size: 0.85rem;
197
+ margin-top: 0.5rem;
198
+ }