juxscript 1.0.62 → 1.0.64

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 (141) hide show
  1. package/bin/cli.js +161 -293
  2. package/docs/v2comps/HEADLESS.md +83 -0
  3. package/docs/v2comps/ISOMORPHISM.md +10 -0
  4. package/juxconfig.example.js +63 -58
  5. package/lib/componentsv2/base/BaseEngine.js +258 -0
  6. package/lib/componentsv2/base/BaseEngine.js.map +1 -0
  7. package/lib/componentsv2/base/BaseEngine.ts +303 -0
  8. package/lib/componentsv2/base/BaseSkin.js +108 -0
  9. package/lib/componentsv2/base/BaseSkin.js.map +1 -0
  10. package/lib/componentsv2/base/BaseSkin.ts +137 -0
  11. package/lib/componentsv2/base/GlobalBus.js +56 -0
  12. package/lib/componentsv2/base/GlobalBus.js.map +1 -0
  13. package/lib/componentsv2/base/GlobalBus.ts +60 -0
  14. package/lib/componentsv2/base/State.js +68 -0
  15. package/lib/componentsv2/base/State.js.map +1 -0
  16. package/lib/componentsv2/base/State.ts +62 -0
  17. package/lib/componentsv2/grid/component.js +41 -0
  18. package/lib/componentsv2/grid/component.js.map +1 -0
  19. package/lib/componentsv2/grid/component.ts +67 -0
  20. package/lib/componentsv2/grid/engine.js +73 -0
  21. package/lib/componentsv2/grid/engine.js.map +1 -0
  22. package/lib/componentsv2/grid/engine.ts +110 -0
  23. package/lib/componentsv2/grid/skin.js +95 -0
  24. package/lib/componentsv2/grid/skin.js.map +1 -0
  25. package/lib/componentsv2/grid/skin.ts +105 -0
  26. package/lib/componentsv2/grid/structure.css +58 -0
  27. package/lib/componentsv2/index.js +218 -0
  28. package/lib/componentsv2/index.js.map +1 -0
  29. package/lib/componentsv2/index.ts +253 -0
  30. package/lib/componentsv2/input/component.js +21 -0
  31. package/lib/componentsv2/input/component.js.map +1 -0
  32. package/lib/componentsv2/input/component.ts +28 -0
  33. package/lib/componentsv2/input/engine.js +50 -0
  34. package/lib/componentsv2/input/engine.js.map +1 -0
  35. package/lib/componentsv2/input/engine.ts +76 -0
  36. package/lib/componentsv2/input/skin.js +91 -0
  37. package/lib/componentsv2/input/skin.js.map +1 -0
  38. package/lib/componentsv2/input/skin.ts +91 -0
  39. package/lib/componentsv2/input/structure.css +47 -0
  40. package/lib/componentsv2/list/component.js +83 -0
  41. package/lib/componentsv2/list/component.js.map +1 -0
  42. package/lib/componentsv2/list/component.ts +97 -0
  43. package/lib/componentsv2/list/engine.js +261 -0
  44. package/lib/componentsv2/list/engine.js.map +1 -0
  45. package/lib/componentsv2/list/engine.ts +345 -0
  46. package/lib/componentsv2/list/skin.js +343 -0
  47. package/lib/componentsv2/list/skin.js.map +1 -0
  48. package/lib/componentsv2/list/skin.ts +367 -0
  49. package/lib/componentsv2/list/structure.css +359 -0
  50. package/lib/componentsv2/plugins/ClientSQLitePlugin.js +130 -0
  51. package/lib/componentsv2/plugins/ClientSQLitePlugin.js.map +1 -0
  52. package/lib/componentsv2/plugins/ClientSQLitePlugin.ts +154 -0
  53. package/lib/componentsv2/plugins/IndexedDBPlugin.js +75 -0
  54. package/lib/componentsv2/plugins/IndexedDBPlugin.js.map +1 -0
  55. package/lib/componentsv2/plugins/IndexedDBPlugin.ts +96 -0
  56. package/lib/componentsv2/plugins/LocalStoragePlugin.js +65 -0
  57. package/lib/componentsv2/plugins/LocalStoragePlugin.js.map +1 -0
  58. package/lib/componentsv2/plugins/LocalStoragePlugin.ts +86 -0
  59. package/lib/componentsv2/plugins/ServerSQLitePlugin.js +70 -0
  60. package/lib/componentsv2/plugins/ServerSQLitePlugin.js.map +1 -0
  61. package/lib/componentsv2/plugins/ServerSQLitePlugin.ts +99 -0
  62. package/lib/componentsv2/stubs/ComponentComposition.ts.stub +32 -0
  63. package/lib/componentsv2/stubs/ComponentEngine.ts.stub +36 -0
  64. package/lib/componentsv2/stubs/ComponentSkin.ts.stub +34 -0
  65. package/lib/componentsv2/stubs/ComponentStructure.css.stub +13 -0
  66. package/lib/componentsv2/tools/CreateSkin.js +62 -0
  67. package/lib/componentsv2/tools/DocSpam.js +134 -0
  68. package/lib/componentsv2/tools/FluencyAudit.js +141 -0
  69. package/lib/componentsv2/tools/OptionsAudit.js +177 -0
  70. package/lib/componentsv2/tools/Scaffold.js +140 -0
  71. package/lib/utils/fetch.js +428 -0
  72. package/lib/utils/fetch.js.map +1 -0
  73. package/machinery/build.js +2 -1
  74. package/machinery/compiler.js +200 -37
  75. package/machinery/config.js +93 -6
  76. package/machinery/diagnose.js +72 -0
  77. package/machinery/jux-module-pattern.md +118 -0
  78. package/machinery/server.js +23 -7
  79. package/machinery/verifier.js +143 -0
  80. package/machinery/watcher.js +53 -64
  81. package/package.json +11 -2
  82. package/lib/components/alert.ts +0 -200
  83. package/lib/components/app.ts +0 -258
  84. package/lib/components/badge.ts +0 -101
  85. package/lib/components/base/BaseComponent.ts +0 -417
  86. package/lib/components/base/FormInput.ts +0 -227
  87. package/lib/components/button.ts +0 -178
  88. package/lib/components/card.ts +0 -173
  89. package/lib/components/chart.ts +0 -231
  90. package/lib/components/checkbox.ts +0 -242
  91. package/lib/components/code.ts +0 -123
  92. package/lib/components/container.ts +0 -140
  93. package/lib/components/data.ts +0 -135
  94. package/lib/components/datepicker.ts +0 -234
  95. package/lib/components/dialog.ts +0 -172
  96. package/lib/components/divider.ts +0 -100
  97. package/lib/components/dropdown.ts +0 -186
  98. package/lib/components/element.ts +0 -267
  99. package/lib/components/error-handler.ts +0 -285
  100. package/lib/components/fileupload.ts +0 -309
  101. package/lib/components/grid.ts +0 -291
  102. package/lib/components/guard.ts +0 -92
  103. package/lib/components/heading.ts +0 -96
  104. package/lib/components/helpers.ts +0 -41
  105. package/lib/components/hero.ts +0 -224
  106. package/lib/components/icon.ts +0 -160
  107. package/lib/components/icons.ts +0 -175
  108. package/lib/components/include.ts +0 -440
  109. package/lib/components/input.ts +0 -457
  110. package/lib/components/list.ts +0 -419
  111. package/lib/components/loading.ts +0 -100
  112. package/lib/components/menu.ts +0 -260
  113. package/lib/components/modal.ts +0 -239
  114. package/lib/components/nav.ts +0 -257
  115. package/lib/components/paragraph.ts +0 -97
  116. package/lib/components/progress.ts +0 -139
  117. package/lib/components/radio.ts +0 -278
  118. package/lib/components/req.ts +0 -302
  119. package/lib/components/script.ts +0 -43
  120. package/lib/components/select.ts +0 -252
  121. package/lib/components/sidebar.ts +0 -167
  122. package/lib/components/style.ts +0 -43
  123. package/lib/components/switch.ts +0 -246
  124. package/lib/components/table.ts +0 -1249
  125. package/lib/components/tabs.ts +0 -250
  126. package/lib/components/theme-toggle.ts +0 -300
  127. package/lib/components/token-calculator.ts +0 -313
  128. package/lib/components/tooltip.ts +0 -144
  129. package/lib/components/view.ts +0 -190
  130. package/lib/components/write.ts +0 -272
  131. package/lib/jux.ts +0 -365
  132. package/lib/layouts/default.css +0 -260
  133. package/lib/layouts/figma.css +0 -334
  134. package/lib/reactivity/state.ts +0 -78
  135. package/machinery/bundleAssets.js +0 -0
  136. package/machinery/bundleJux.js +0 -0
  137. package/machinery/bundleVendors.js +0 -0
  138. package/presets/default/all.jux +0 -343
  139. package/presets/default/index.jux +0 -90
  140. package/presets/default/layout.jux +0 -57
  141. package/presets/default/style.css +0 -1612
@@ -0,0 +1,367 @@
1
+ import { BaseSkin } from '../base/BaseSkin.js';
2
+ import { ListEngine, ListState, ListItem } from './engine.js';
3
+
4
+ export class ListSkin extends BaseSkin<ListState, ListEngine> {
5
+ // UI Feature Flags
6
+ #showSearch = false;
7
+ #showAdd = false;
8
+ #showDelete = false;
9
+ #showEdit = false;
10
+ #showMove = false;
11
+ #showSort = false;
12
+
13
+ // Internal Drag State
14
+ #dragSourceIndex: number | null = null;
15
+
16
+ // DOM References (Stable elements to prevent re-render thrashing)
17
+ #toolbar: HTMLElement | null = null;
18
+ #listContainer: HTMLElement | null = null;
19
+
20
+ // Toolbar Componenets
21
+ #searchInput: HTMLInputElement | null = null;
22
+ #addBtn: HTMLButtonElement | null = null;
23
+ #sortBtn: HTMLButtonElement | null = null;
24
+
25
+ constructor(engine: ListEngine) {
26
+ super(engine);
27
+ }
28
+
29
+ // ✅ Implements Contract: Points to ./structure.css at runtime
30
+ protected get structureCss(): string {
31
+ return new URL('./structure.css', import.meta.url).href;
32
+ }
33
+
34
+ // --- Public UI API ---
35
+ enableSearch(enabled: boolean): void { this.#showSearch = enabled; this.#renderToolbar(); }
36
+ enableAdd(enabled: boolean): void { this.#showAdd = enabled; this.#renderToolbar(); }
37
+ enableDelete(enabled: boolean): void { this.#showDelete = enabled; this.updateSkin(this.engine.state); }
38
+ enableMove(enabled: boolean): void { this.#showMove = enabled; this.updateSkin(this.engine.state); }
39
+ enableSort(enabled: boolean): void { this.#showSort = enabled; this.#renderToolbar(); }
40
+ enableNoItems(message: string = 'No items found.'): void { this.engine.enableNoItems(message); }
41
+
42
+ enableEdit(enabled: boolean): void {
43
+ this.#showEdit = enabled;
44
+ this.updateSkin(this.engine.state);
45
+ }
46
+
47
+ // --- Template Skin Implementation ---
48
+
49
+ protected bindEvents(root: HTMLElement): void {
50
+ // 1. Delegation: Toolbar Events (Handled via direct element ref where possible now, or delegation fallback)
51
+
52
+ // We attach the input listener globally on root for delegation,
53
+ // but now that we hold the #searchInput ref, we could bind directly.
54
+ // Delegation is still safer if elements are recreated.
55
+ root.addEventListener('input', (e: Event) => {
56
+ const target = e.target as HTMLInputElement;
57
+ if (target.matches('.jux-search-input')) {
58
+ this.engine.filter(target.value);
59
+ }
60
+ });
61
+
62
+ root.addEventListener('click', (e: MouseEvent) => {
63
+ const target = e.target as HTMLElement;
64
+
65
+ // Toolbar: Add
66
+ if (target.closest('.jux-action-add')) {
67
+ this.engine.addItem('New Item');
68
+ const newIndex = this.engine.state.items.length - 1;
69
+ if (newIndex >= 0) this.#openEditModal(newIndex);
70
+ return;
71
+ }
72
+
73
+ // Toolbar: Sort
74
+ if (target.closest('.jux-action-sort')) {
75
+ this.engine.toggleSort();
76
+ return;
77
+ }
78
+
79
+ // List Item Actions
80
+ const li = target.closest('li');
81
+ if (!li) return;
82
+
83
+ // ✅ USE ID for robust targeting
84
+ const id = li.dataset.id;
85
+ const indexStr = li.dataset.index;
86
+ if (!indexStr || !id) return;
87
+
88
+ const index = parseInt(indexStr, 10);
89
+
90
+ // Action: Delete
91
+ if (target.closest('.jux-action-delete')) {
92
+ this.engine.removeItem(id); // ✅ Valid per new engine signature
93
+ e.stopImmediatePropagation();
94
+ return;
95
+ }
96
+
97
+ // Action: Edit
98
+ if (target.closest('.jux-action-edit')) {
99
+ this.#openEditModal(index);
100
+ e.stopImmediatePropagation();
101
+ return;
102
+ }
103
+
104
+ // List Item: Selection
105
+ if (!target.closest('.jux-control')) {
106
+ this.engine.toggleSelection(index);
107
+ }
108
+ });
109
+
110
+ // 2. Drag & Drop Logic
111
+ root.addEventListener('dragstart', (e: DragEvent) => {
112
+ if (!this.#showMove) return;
113
+ const li = (e.target as HTMLElement).closest('li');
114
+ if (li) {
115
+ this.#dragSourceIndex = parseInt(li.dataset.index || '-1', 10);
116
+ e.dataTransfer!.effectAllowed = 'move';
117
+ e.dataTransfer!.setData('text/plain', li.dataset.index || '');
118
+ requestAnimationFrame(() => li.classList.add('jux-dragging'));
119
+ }
120
+ });
121
+
122
+ root.addEventListener('dragend', (e: DragEvent) => {
123
+ const li = (e.target as HTMLElement).closest('li');
124
+ if (li) li.classList.remove('jux-dragging');
125
+ root.querySelectorAll('.jux-item-drop-target').forEach(el => el.classList.remove('jux-item-drop-target'));
126
+ this.#dragSourceIndex = null;
127
+ });
128
+
129
+ root.addEventListener('dragover', (e: DragEvent) => {
130
+ if (!this.#showMove) return;
131
+ e.preventDefault();
132
+ const li = (e.target as HTMLElement).closest('li');
133
+ if (li && !li.classList.contains('jux-dragging')) {
134
+ e.dataTransfer!.dropEffect = 'move';
135
+ root.querySelectorAll('.jux-item-drop-target').forEach(el => el.classList.remove('jux-item-drop-target'));
136
+ li.classList.add('jux-item-drop-target');
137
+ }
138
+ });
139
+
140
+ root.addEventListener('drop', (e: DragEvent) => {
141
+ if (!this.#showMove) return;
142
+ e.preventDefault();
143
+ const li = (e.target as HTMLElement).closest('li');
144
+ if (li && this.#dragSourceIndex !== null) {
145
+ const toIndex = parseInt(li.dataset.index || '-1', 10);
146
+ if (toIndex !== -1 && this.#dragSourceIndex !== toIndex) {
147
+ this.engine.moveItem(this.#dragSourceIndex, toIndex);
148
+ }
149
+ }
150
+ });
151
+ }
152
+
153
+ // --- Internals: Modal Editing ---
154
+ #openEditModal(index: number): void {
155
+ const item = this.engine.state.items[index];
156
+ if (!item) return;
157
+
158
+ const overlay = document.createElement('div');
159
+ overlay.className = 'jux-modal-overlay';
160
+ overlay.innerHTML = `
161
+ <div class="jux-modal">
162
+ <h3>Edit Item</h3>
163
+ <input type="text" id="jux-edit-input" value="${item.text.replace(/"/g, '&quot;')}" />
164
+ <div class="jux-modal-actions">
165
+ <button class="jux-modal-btn jux-btn-cancel" id="jux-edit-cancel">Cancel</button>
166
+ <button class="jux-modal-btn jux-btn-save" id="jux-edit-save">Save</button>
167
+ </div>
168
+ </div>
169
+ `;
170
+ document.body.appendChild(overlay);
171
+ const input = overlay.querySelector('#jux-edit-input') as HTMLInputElement;
172
+ const close = () => document.body.removeChild(overlay);
173
+ const save = () => {
174
+ if (input.value.trim()) this.engine.updateItem(index, input.value);
175
+ close();
176
+ };
177
+
178
+ overlay.querySelector('#jux-edit-cancel')?.addEventListener('click', close);
179
+ overlay.querySelector('#jux-edit-save')?.addEventListener('click', save);
180
+ overlay.addEventListener('click', (e) => { if (e.target === overlay) close(); });
181
+ input.addEventListener('keydown', (e) => {
182
+ if (e.key === 'Enter') save();
183
+ if (e.key === 'Escape') close();
184
+ });
185
+ setTimeout(() => { input.focus(); input.select(); }, 0);
186
+ }
187
+
188
+ /**
189
+ * Skin Contract: Update/Changes
190
+ */
191
+ protected updateSkin(state: ListState): void {
192
+ if (!this.root) return;
193
+
194
+ // Perform the DOM Update
195
+ const performUpdate = () => {
196
+ this.applySkinAttributes(this.root!, state);
197
+
198
+ // Lazy Creation
199
+ if (!this.#listContainer) {
200
+ this.root!.innerHTML = '';
201
+ this.#toolbar = document.createElement('div');
202
+ this.#toolbar.className = 'jux-list-toolbar';
203
+ this.root!.appendChild(this.#toolbar);
204
+
205
+ this.#listContainer = document.createElement('div');
206
+ this.root!.appendChild(this.#listContainer);
207
+ this.#renderToolbar(); // Initial render
208
+ }
209
+
210
+ // Always update toolbar state (e.g. icon direction) without destroying inputs
211
+ this.#renderToolbar();
212
+
213
+ // Container Classes
214
+ const classes = [...state.classes];
215
+ if (state.selectionMode !== 'none') classes.push('jux-list-selectable');
216
+ this.#listContainer!.className = classes.join(' ');
217
+
218
+ // Filtering
219
+ const visibleItems = state.items
220
+ .map((item, originalIndex) => ({ item, originalIndex }))
221
+ .filter(({ item }) =>
222
+ state.filterText
223
+ ? item.text.toLowerCase().includes(state.filterText.toLowerCase())
224
+ : true
225
+ );
226
+
227
+ // Rendering Content
228
+ if (visibleItems.length === 0 && state.noItemsMessage) {
229
+ this.#listContainer!.innerHTML = `<div class="jux-list-empty-state">${state.noItemsMessage}</div>`;
230
+ } else {
231
+ const tag = state.listType === 'ordered' ? 'ol' : 'ul';
232
+ const html = visibleItems
233
+ .map(({ item, originalIndex }) => this.#renderListItem(item, originalIndex, state))
234
+ .join('');
235
+
236
+ this.#listContainer!.innerHTML = `<${tag} class="jux-list-element jux-list-type-${tag}">${html}</${tag}>`;
237
+ }
238
+ };
239
+
240
+ // ✨ View Transitions Logic
241
+ // We SKIP transition if the search input is focused, otherwise typing janks the UI.
242
+ const isSearching = document.activeElement &&
243
+ (document.activeElement === this.#searchInput);
244
+
245
+ if (!isSearching && typeof document !== 'undefined' && 'startViewTransition' in document) {
246
+ // @ts-ignore
247
+ document.startViewTransition(performUpdate);
248
+ } else {
249
+ performUpdate();
250
+ }
251
+ }
252
+
253
+ // --- DOM-Based Toolbar Rendering ---
254
+ #renderToolbar(): void {
255
+ if (!this.#toolbar) return;
256
+
257
+ // 1. Search Logic
258
+ if (this.#showSearch) {
259
+ if (!this.#searchInput) {
260
+ this.#searchInput = document.createElement('input');
261
+ this.#searchInput.type = 'text';
262
+ this.#searchInput.className = 'jux-search-input';
263
+ this.#searchInput.placeholder = 'Filter items...';
264
+ // Insert at start
265
+ this.#toolbar.prepend(this.#searchInput);
266
+ }
267
+ } else if (this.#searchInput) {
268
+ this.#searchInput.remove();
269
+ this.#searchInput = null;
270
+ }
271
+
272
+ // 2. Sort Logic
273
+ if (this.#showSort) {
274
+ if (!this.#sortBtn) {
275
+ this.#sortBtn = document.createElement('button');
276
+ this.#sortBtn.className = 'jux-action-sort';
277
+ this.#sortBtn.type = 'button';
278
+ this.#sortBtn.ariaLabel = 'Sort';
279
+ this.#toolbar.appendChild(this.#sortBtn);
280
+ }
281
+ // Update Icon state
282
+ const s = this.engine.state.sorted;
283
+ const icon = s === 'asc' ? '↑' : (s === 'desc' ? '↓' : '↕');
284
+ if (this.#sortBtn.textContent !== icon) {
285
+ this.#sortBtn.textContent = icon;
286
+ }
287
+ } else if (this.#sortBtn) {
288
+ this.#sortBtn.remove();
289
+ this.#sortBtn = null;
290
+ }
291
+
292
+ // 3. Add Logic
293
+ if (this.#showAdd) {
294
+ if (!this.#addBtn) {
295
+ this.#addBtn = document.createElement('button');
296
+ this.#addBtn.className = 'jux-action-add';
297
+ this.#addBtn.type = 'button';
298
+ this.#addBtn.ariaLabel = 'Add Item';
299
+ this.#addBtn.textContent = '+';
300
+ this.#toolbar.appendChild(this.#addBtn);
301
+ }
302
+ } else if (this.#addBtn) {
303
+ this.#addBtn.remove();
304
+ this.#addBtn = null;
305
+ }
306
+
307
+ // Parent Visibility
308
+ const hasChildren = this.#showSearch || this.#showSort || this.#showAdd;
309
+ this.#toolbar.style.display = hasChildren ? 'flex' : 'none';
310
+
311
+ // ensure Search input is first by re-prepending if it exists
312
+ if (this.#searchInput && this.#toolbar.firstChild !== this.#searchInput) {
313
+ this.#toolbar.prepend(this.#searchInput);
314
+ }
315
+ }
316
+
317
+ #renderListItem(item: ListItem, index: number, state: ListState): string {
318
+ const css = [
319
+ 'jux-list-item',
320
+ item.selected ? 'jux-selected' : '',
321
+ ...(item.classes || [])
322
+ ].filter(Boolean).join(' ');
323
+
324
+ // ✅ Determine Content based on Columns configuration
325
+ let content = item.text;
326
+
327
+ if (state.columns && state.columns.length > 0) {
328
+ content = state.columns.map(col => {
329
+ const key = typeof col === 'string' ? col : col.key;
330
+ const cssClass = (typeof col === 'object' && col.label) ? `jux-col-${key}` : ''; // Basic class hook
331
+ const value = item[key] ?? '';
332
+ // If complex object, stringify or fallback
333
+ const displayVal = (typeof value === 'object') ? JSON.stringify(value) : String(value);
334
+
335
+ return `<span class="jux-col ${cssClass}">${displayVal}</span>`;
336
+ }).join(' <span class="jux-col-sep"> </span> ');
337
+ } else {
338
+ // Default Fallback logic: If no columns but item.text is boring ("[object Object]"), try to find keys
339
+ if (content === '[object Object]') {
340
+ const keys = Object.keys(item).filter(k =>
341
+ k !== 'id' && k !== 'selected' && k !== 'classes' && k !== 'text' && k !== 'value'
342
+ );
343
+ if (keys.length > 0) {
344
+ content = `${item[keys[0]]}`;
345
+ }
346
+ }
347
+ }
348
+
349
+ const moveBtn = this.#showMove ? `<span class="jux-action-drag jux-control" title="Drag to reorder">≡</span>` : '';
350
+ const editBtn = this.#showEdit ? `<button class="jux-action-edit jux-control" aria-label="Edit">✎</button>` : '';
351
+ const deleteBtn = this.#showDelete ? `<button class="jux-action-delete jux-control" aria-label="Delete">×</button>` : '';
352
+ const draggableAttr = this.#showMove ? 'draggable="true"' : '';
353
+ const transitionName = `jux-item-${item.id.replace(/[^a-zA-Z0-9-_]/g, '-')}`;
354
+
355
+ return `
356
+ <li data-id="${item.id}" data-index="${index}" class="${css}" ${draggableAttr} style="view-transition-name: ${transitionName}">
357
+ <div class="jux-list-item-content">
358
+ ${content}
359
+ </div>
360
+ <div class="jux-item-controls">
361
+ ${moveBtn}
362
+ ${editBtn}
363
+ ${deleteBtn}
364
+ </div>
365
+ </li>`;
366
+ }
367
+ }