gardenjs 1.7.2 → 1.7.3

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 (29) hide show
  1. package/README.md +1 -0
  2. package/bin/servegarden.js +22 -18
  3. package/dist/assets/{frame-BnV1YnGW.css → frame-DNQctfZb.css} +1 -1
  4. package/dist/assets/frame-Z1Hax3or.js +9 -0
  5. package/dist/assets/index-6DOENHaB.js +46 -0
  6. package/dist/assets/index-xKFU1UAQ.css +19 -0
  7. package/dist/assets/{props-BQOViwFg.js → props-BlmngvIZ.js} +1 -1
  8. package/dist/frame.html +3 -3
  9. package/dist/index.html +3 -3
  10. package/package.json +1 -1
  11. package/src/client/GardenApp.svelte +7 -4
  12. package/src/client/GardenFrame.svelte +11 -4
  13. package/src/client/components/sidebar/Sidebar.svelte +0 -1
  14. package/src/client/components/stage/BackgroundGrid.svelte +0 -11
  15. package/src/client/components/stage/DistanceMeasure.svelte +137 -0
  16. package/src/client/components/stage/Stage.svelte +15 -4
  17. package/src/client/components/stage/panel/PanelExamplesNav.svelte +10 -2
  18. package/src/client/components/stage/panel/ParamsPane.svelte +251 -124
  19. package/src/client/components/stage/panel/controls/ArrayControl.svelte +202 -124
  20. package/src/client/components/stage/panel/controls/NumberControl.svelte +1 -1
  21. package/src/client/components/stage/panel/controls/ObjectControl.svelte +139 -51
  22. package/src/client/components/stage/panel/controls/button.scss +8 -5
  23. package/src/client/components/stage/panel/controls/button_unset.scss +0 -1
  24. package/src/client/components/topbar/Topbar.svelte +7 -0
  25. package/src/client/logic/stage.js +92 -31
  26. package/src/codegenerator/base_generator.js +13 -20
  27. package/dist/assets/frame-DZrm9TF0.js +0 -9
  28. package/dist/assets/index-D3v3wg7J.js +0 -46
  29. package/dist/assets/index-qFmggI7l.css +0 -19
@@ -49,144 +49,194 @@
49
49
  const getFieldType = (config) => String(config?.type ?? 'text').toLowerCase()
50
50
 
51
51
  let items = $derived(Array.isArray(value) ? value : [])
52
+ let isUnset = $derived(value === undefined || value === null)
52
53
  </script>
53
54
 
54
- <div class="items">
55
- {#if items.length > 0}
56
- {#each items as item, index (index)}
57
- <div class="item">
58
- <div class="item_header">
59
- <span class="item_number">#{index + 1}</span>
60
- <button
61
- class="btn btn_remove"
62
- title="Remove item"
63
- aria-label="Remove item"
64
- onclick={() => removeItem(index)}
65
- >
66
- <svg
67
- xmlns="http://www.w3.org/2000/svg"
68
- width="16"
69
- viewBox="0 0 24 24"
70
- height="16"
71
- fill="none"
72
- stroke="currentColor"
73
- stroke-width="1.5"
74
- stroke-linecap="round"
75
- stroke-linejoin="round"
76
- ><path
77
- d="M10 11v6m4-6v6m5-11v14a2 2 0 01-2 2H7a2 2 0 01-2-2V6M3 6h18M8 6V4a2 2 0 012-2h4a2 2 0 012 2v2"
78
- /></svg
79
- >
80
- </button>
81
- </div>
82
- <div class="grid">
83
- {#each Object.entries(schema) as [key, config] (key)}
84
- {@const fieldType = getFieldType(config)}
85
- <div class="label">
86
- <div class="field-label">{config.label || key}</div>
55
+ <div class="row">
56
+ <div class="container">
57
+ <div class="items">
58
+ {#if items.length > 0}
59
+ {#each items as item, index (index)}
60
+ <div class="item">
61
+ <div class="item_header">
62
+ <span class="item_number">#{index + 1}</span>
63
+ <div class="item_header_actions">
64
+ <button
65
+ type="button"
66
+ class="btn btn_remove"
67
+ title="Remove item"
68
+ aria-label="Remove item"
69
+ onclick={() => removeItem(index)}
70
+ >
71
+ <svg
72
+ xmlns="http://www.w3.org/2000/svg"
73
+ width="16"
74
+ viewBox="0 0 24 24"
75
+ height="16"
76
+ fill="none"
77
+ stroke="currentColor"
78
+ stroke-width="1.5"
79
+ stroke-linecap="round"
80
+ stroke-linejoin="round"
81
+ ><path
82
+ d="M10 11v6m4-6v6m5-11v14a2 2 0 01-2 2H7a2 2 0 01-2-2V6M3 6h18M8 6V4a2 2 0 012-2h4a2 2 0 012 2v2"
83
+ /></svg
84
+ >
85
+ </button>
86
+ </div>
87
87
  </div>
88
- <div class="input_wrapper">
89
- {#if fieldType === 'array'}
90
- <ArrayControl
91
- value={item[key] ?? []}
92
- onChange={(v) => updateItemProperty(index, key, v)}
93
- />
94
- {:else if fieldType === 'boolean'}
95
- <BooleanControl
96
- value={item[key] ?? undefined}
97
- onChange={(v) => updateItemProperty(index, key, v)}
98
- />
99
- {:else if fieldType === 'color'}
100
- <ColorPickerControl
101
- value={item[key] ?? undefined}
102
- onChange={(v) => updateItemProperty(index, key, v)}
103
- />
104
- {:else if fieldType === 'date'}
105
- <DateControl
106
- value={item[key] ?? undefined}
107
- onChange={(v) => updateItemProperty(index, key, v)}
108
- />
109
- {:else if fieldType === 'datetime'}
110
- <DatetimeControl
111
- value={item[key] ?? undefined}
112
- onChange={(v) => updateItemProperty(index, key, v)}
113
- />
114
- {:else if fieldType === 'multiselect'}
115
- <MultiselectControl
116
- value={item[key] ?? []}
117
- onChange={(v) => updateItemProperty(index, key, v)}
118
- options={config.options ?? []}
119
- control={config.control ?? 'dropdown'}
120
- />
121
- {:else if fieldType === 'number'}
122
- <NumberControl
123
- value={item[key] ?? null}
124
- onChange={(v) => updateItemProperty(index, key, v)}
125
- />
126
- {:else if fieldType === 'object'}
127
- <ObjectControl
128
- value={item[key] ?? {}}
129
- onChange={(v) => updateItemProperty(index, key, v)}
130
- schema={config.schema ?? {}}
131
- />
132
- {:else if fieldType === 'range'}
133
- <RangeControl
134
- value={item[key] ?? null}
135
- min={config.min}
136
- max={config.max}
137
- step={config.step}
138
- onChange={(v) => updateItemProperty(index, key, v)}
139
- />
140
- {:else if fieldType === 'select'}
141
- <SelectControl
142
- value={item[key] ?? undefined}
143
- onChange={(v) => updateItemProperty(index, key, v)}
144
- options={config.options ?? []}
145
- />
146
- {:else if fieldType === 'time'}
147
- <TimeControl
148
- value={item[key] ?? undefined}
149
- onChange={(v) => updateItemProperty(index, key, v)}
150
- />
151
- {:else}
152
- <TextInputControl
153
- value={item[key] ?? ''}
154
- control={config.control ?? 'text'}
155
- numberOfRows={config.numberOfRows}
156
- onChange={(v) => updateItemProperty(index, key, v)}
157
- />
158
- {/if}
88
+ <div class="grid">
89
+ {#each Object.entries(schema) as [key, config] (key)}
90
+ {@const fieldType = getFieldType(config)}
91
+ <div class="label">
92
+ <div class="field-label">{config.label || key}</div>
93
+ </div>
94
+ <div class="input_wrapper">
95
+ {#if fieldType === 'array'}
96
+ <ArrayControl
97
+ value={item[key] ?? []}
98
+ onChange={(v) => updateItemProperty(index, key, v)}
99
+ />
100
+ {:else if fieldType === 'boolean'}
101
+ <BooleanControl
102
+ value={item[key] ?? undefined}
103
+ onChange={(v) => updateItemProperty(index, key, v)}
104
+ />
105
+ {:else if fieldType === 'color'}
106
+ <ColorPickerControl
107
+ value={item[key] ?? undefined}
108
+ onChange={(v) => updateItemProperty(index, key, v)}
109
+ />
110
+ {:else if fieldType === 'date'}
111
+ <DateControl
112
+ value={item[key] ?? undefined}
113
+ onChange={(v) => updateItemProperty(index, key, v)}
114
+ />
115
+ {:else if fieldType === 'datetime'}
116
+ <DatetimeControl
117
+ value={item[key] ?? undefined}
118
+ onChange={(v) => updateItemProperty(index, key, v)}
119
+ />
120
+ {:else if fieldType === 'multiselect'}
121
+ <MultiselectControl
122
+ value={item[key] ?? []}
123
+ onChange={(v) => updateItemProperty(index, key, v)}
124
+ options={config.options ?? []}
125
+ control={config.control ?? 'dropdown'}
126
+ />
127
+ {:else if fieldType === 'number'}
128
+ <NumberControl
129
+ value={item[key] ?? null}
130
+ onChange={(v) => updateItemProperty(index, key, v)}
131
+ />
132
+ {:else if fieldType === 'object'}
133
+ <ObjectControl
134
+ value={item[key] ?? {}}
135
+ onChange={(v) => updateItemProperty(index, key, v)}
136
+ schema={config.schema ?? {}}
137
+ />
138
+ {:else if fieldType === 'range'}
139
+ <RangeControl
140
+ value={item[key] ?? null}
141
+ min={config.min}
142
+ max={config.max}
143
+ step={config.step}
144
+ onChange={(v) => updateItemProperty(index, key, v)}
145
+ />
146
+ {:else if fieldType === 'select'}
147
+ <SelectControl
148
+ value={item[key] ?? undefined}
149
+ onChange={(v) => updateItemProperty(index, key, v)}
150
+ options={config.options ?? []}
151
+ />
152
+ {:else if fieldType === 'time'}
153
+ <TimeControl
154
+ value={item[key] ?? undefined}
155
+ onChange={(v) => updateItemProperty(index, key, v)}
156
+ />
157
+ {:else}
158
+ <TextInputControl
159
+ value={item[key] ?? ''}
160
+ control={config.control ?? 'text'}
161
+ numberOfRows={config.numberOfRows}
162
+ onChange={(v) => updateItemProperty(index, key, v)}
163
+ />
164
+ {/if}
165
+ </div>
166
+ {/each}
159
167
  </div>
160
- {/each}
168
+ </div>
169
+ {/each}
170
+ {/if}
171
+ <div class="actions">
172
+ <button class="btn btn_add" onclick={addItem}>
173
+ <svg
174
+ class="plus"
175
+ xmlns="http://www.w3.org/2000/svg"
176
+ width="14"
177
+ height="14"
178
+ viewBox="0 0 24 24"
179
+ fill="none"
180
+ stroke="currentColor"
181
+ stroke-width="2"
182
+ stroke-linecap="round"
183
+ stroke-linejoin="round"
184
+ ><path d="M5 12h14" /><path d="M12 5v14" /></svg
185
+ >
186
+ <span class="btn-label">Add Item</span>
187
+ </button>
188
+ <div class="unset-area">
189
+ {#if !isUnset && items.length > 0}
190
+ <button
191
+ type="button"
192
+ class="btn btn_unset_action"
193
+ title="Unset"
194
+ aria-label="Unset"
195
+ onclick={() => onChange?.(undefined)}
196
+ >
197
+ <svg
198
+ class="close"
199
+ xmlns="http://www.w3.org/2000/svg"
200
+ width="12"
201
+ height="12"
202
+ viewBox="0 0 24 24"
203
+ fill="none"
204
+ stroke="currentColor"
205
+ stroke-width="2"
206
+ stroke-linecap="round"
207
+ stroke-linejoin="round"
208
+ >
209
+ <path d="M18 6L6 18M6 6l12 12" />
210
+ </svg>
211
+ <span class="btn-label">Unset</span>
212
+ </button>
213
+ {/if}
161
214
  </div>
162
215
  </div>
163
- {/each}
164
- {/if}
165
- <button class="btn btn_add" onclick={addItem}>
166
- <svg
167
- class="plus"
168
- xmlns="http://www.w3.org/2000/svg"
169
- width="14"
170
- height="14"
171
- viewBox="0 0 24 24"
172
- fill="none"
173
- stroke="currentColor"
174
- stroke-width="2"
175
- stroke-linecap="round"
176
- stroke-linejoin="round"><path d="M5 12h14" /><path d="M12 5v14" /></svg
177
- >
178
- Add Item
179
- </button>
216
+ </div>
217
+ </div>
180
218
  </div>
181
219
 
182
220
  <style>
183
221
  @import './button.scss';
184
222
 
223
+ .row {
224
+ display: flex;
225
+ gap: 0.5rem;
226
+ align-items: flex-start;
227
+ }
228
+
229
+ .container {
230
+ flex: 1;
231
+ min-width: 0;
232
+ }
233
+
185
234
  .items {
186
235
  display: flex;
187
236
  flex-direction: column;
188
237
  gap: 1rem;
189
238
  width: 100%;
239
+ --delete-col-width: 4.75rem;
190
240
  }
191
241
 
192
242
  .item {
@@ -196,12 +246,20 @@
196
246
  }
197
247
 
198
248
  .item_header {
199
- display: flex;
200
- justify-content: space-between;
249
+ display: grid;
250
+ grid-template-columns: 1fr var(--delete-col-width);
201
251
  align-items: center;
252
+ gap: 0.5rem;
202
253
  margin-bottom: 0.75rem;
203
254
  }
204
255
 
256
+ .item_header_actions {
257
+ display: flex;
258
+ justify-content: flex-end;
259
+ align-items: center;
260
+ width: var(--delete-col-width);
261
+ }
262
+
205
263
  .item_number {
206
264
  font-size: 0.875rem;
207
265
  font-weight: 600;
@@ -239,4 +297,24 @@
239
297
  display: block;
240
298
  margin-right: 0.25rem;
241
299
  }
300
+
301
+ .actions {
302
+ display: flex;
303
+ align-items: center;
304
+ gap: 0.5rem;
305
+ }
306
+
307
+ .unset-area {
308
+ display: flex;
309
+ justify-content: flex-end;
310
+ align-items: center;
311
+ }
312
+
313
+ .btn_unset_action {
314
+ max-width: 8.5rem;
315
+ }
316
+
317
+ .btn_unset_action .close {
318
+ margin-right: 0.25rem;
319
+ }
242
320
  </style>
@@ -51,7 +51,7 @@
51
51
 
52
52
  <style lang="scss">
53
53
  @use './button_unset.scss';
54
- @import './input.scss';
54
+ @use './input.scss';
55
55
 
56
56
  .row {
57
57
  display: flex;
@@ -33,83 +33,150 @@
33
33
  }
34
34
 
35
35
  let entries = $derived(Object.entries(value || {}))
36
+ let isUnset = $derived(value === undefined || value === null)
36
37
  </script>
37
38
 
38
- <div class="object-param">
39
- {#if entries.length > 0}
40
- {#each entries as [key, val], index (index)}
41
- <div class="object-item">
42
- <input
43
- class="input input_key"
44
- type="text"
45
- value={key}
46
- placeholder="key"
47
- oninput={(e) => updateKey(key, e.currentTarget.value)}
48
- />
49
- <span class="separator">:</span>
50
- <input
51
- type="text"
52
- class="input input_value"
53
- value={String(val ?? '')}
54
- placeholder="value"
55
- oninput={(e) => updateValue(key, e.currentTarget.value)}
56
- />
57
- <button
58
- class="btn btn_remove"
59
- title="Remove property"
60
- aria-label="Remove property"
61
- onclick={() => removeProperty(key)}
62
- >
39
+ <div class="row">
40
+ <div class="container">
41
+ <div class="object-param">
42
+ {#if entries.length > 0}
43
+ {#each entries as [key, val], index (index)}
44
+ <div class="object-item">
45
+ <div class="object-fields">
46
+ <input
47
+ class="input input_key"
48
+ type="text"
49
+ value={key}
50
+ placeholder="key"
51
+ oninput={(e) => updateKey(key, e.currentTarget.value)}
52
+ />
53
+ <span class="separator">:</span>
54
+ <input
55
+ type="text"
56
+ class="input input_value"
57
+ value={String(val ?? '')}
58
+ placeholder="value"
59
+ oninput={(e) => updateValue(key, e.currentTarget.value)}
60
+ />
61
+ </div>
62
+ <div class="object-actions">
63
+ <button
64
+ type="button"
65
+ class="btn btn_remove"
66
+ title="Remove property"
67
+ aria-label="Remove property"
68
+ onclick={() => removeProperty(key)}
69
+ >
70
+ <svg
71
+ xmlns="http://www.w3.org/2000/svg"
72
+ width="16"
73
+ viewBox="0 0 24 24"
74
+ height="16"
75
+ fill="none"
76
+ stroke="currentColor"
77
+ stroke-width="1.5"
78
+ stroke-linecap="round"
79
+ stroke-linejoin="round"
80
+ ><path
81
+ d="M10 11v6m4-6v6m5-11v14a2 2 0 01-2 2H7a2 2 0 01-2-2V6M3 6h18M8 6V4a2 2 0 012-2h4a2 2 0 012 2v2"
82
+ /></svg
83
+ >
84
+ </button>
85
+ </div>
86
+ </div>
87
+ {/each}
88
+ {/if}
89
+ <div class="actions">
90
+ <button class="btn" onclick={addProperty}>
63
91
  <svg
92
+ class="plus"
64
93
  xmlns="http://www.w3.org/2000/svg"
65
- width="16"
94
+ width="14"
95
+ height="14"
66
96
  viewBox="0 0 24 24"
67
- height="16"
68
97
  fill="none"
69
98
  stroke="currentColor"
70
- stroke-width="1.5"
99
+ stroke-width="2"
71
100
  stroke-linecap="round"
72
101
  stroke-linejoin="round"
73
- ><path
74
- d="M10 11v6m4-6v6m5-11v14a2 2 0 01-2 2H7a2 2 0 01-2-2V6M3 6h18M8 6V4a2 2 0 012-2h4a2 2 0 012 2v2"
75
- /></svg
102
+ ><path d="M5 12h14" /><path d="M12 5v14" /></svg
76
103
  >
104
+ <span class="btn-label">Add Property</span>
77
105
  </button>
106
+ <div class="unset-area">
107
+ {#if !isUnset}
108
+ <button
109
+ type="button"
110
+ class="btn btn_unset_action"
111
+ title="Unset"
112
+ aria-label="Unset"
113
+ onclick={() => onChange?.(undefined)}
114
+ >
115
+ <svg
116
+ class="close"
117
+ xmlns="http://www.w3.org/2000/svg"
118
+ width="12"
119
+ height="12"
120
+ viewBox="0 0 24 24"
121
+ fill="none"
122
+ stroke="currentColor"
123
+ stroke-width="2"
124
+ stroke-linecap="round"
125
+ stroke-linejoin="round"
126
+ >
127
+ <path d="M18 6L6 18M6 6l12 12" />
128
+ </svg>
129
+ <span class="btn-label">Unset</span>
130
+ </button>
131
+ {/if}
132
+ </div>
78
133
  </div>
79
- {/each}
80
- {/if}
81
- <button class="btn" onclick={addProperty}>
82
- <svg
83
- class="plus"
84
- xmlns="http://www.w3.org/2000/svg"
85
- width="14"
86
- height="14"
87
- viewBox="0 0 24 24"
88
- fill="none"
89
- stroke="currentColor"
90
- stroke-width="2"
91
- stroke-linecap="round"
92
- stroke-linejoin="round"><path d="M5 12h14" /><path d="M12 5v14" /></svg
93
- >
94
- Add Property
95
- </button>
134
+ </div>
135
+ </div>
96
136
  </div>
97
137
 
98
- <style>
99
- @import './button.scss';
100
- @import './input.scss';
138
+ <style lang="scss">
139
+ @use './button.scss';
140
+ @use './input.scss';
141
+
142
+ .row {
143
+ display: flex;
144
+ gap: 0.5rem;
145
+ align-items: flex-start;
146
+ }
147
+
148
+ .container {
149
+ flex: 1;
150
+ min-width: 0;
151
+ }
101
152
 
102
153
  .object-param {
103
154
  display: flex;
104
155
  flex-direction: column;
105
156
  gap: 0.375rem;
106
157
  width: 100%;
158
+ --delete-col-width: 4.75rem;
107
159
  }
108
160
 
109
161
  .object-item {
162
+ display: grid;
163
+ grid-template-columns: 1fr var(--delete-col-width);
164
+ gap: 0.5rem;
165
+ align-items: center;
166
+ }
167
+
168
+ .object-fields {
110
169
  display: flex;
111
170
  gap: 0.25rem;
112
171
  align-items: center;
172
+ min-width: 0;
173
+ }
174
+
175
+ .object-actions {
176
+ display: flex;
177
+ justify-content: flex-end;
178
+ align-items: center;
179
+ width: var(--delete-col-width);
113
180
  }
114
181
 
115
182
  .input_key {
@@ -128,6 +195,7 @@
128
195
  }
129
196
 
130
197
  .btn_remove {
198
+ flex-shrink: 0;
131
199
  width: 1.5rem;
132
200
  height: 1.5rem;
133
201
  padding: 0;
@@ -145,4 +213,24 @@
145
213
  display: block;
146
214
  margin-right: 0.25rem;
147
215
  }
216
+
217
+ .actions {
218
+ display: flex;
219
+ align-items: center;
220
+ gap: 0.5rem;
221
+ }
222
+
223
+ .unset-area {
224
+ display: flex;
225
+ justify-content: flex-end;
226
+ align-items: center;
227
+ }
228
+
229
+ .btn_unset_action {
230
+ max-width: 8.5rem;
231
+ }
232
+
233
+ .btn_unset_action .close {
234
+ margin-right: 0.25rem;
235
+ }
148
236
  </style>
@@ -6,9 +6,12 @@
6
6
  background-color: var(--c-basic-100);
7
7
  font-size: 0.875rem;
8
8
  color: var(--c-basic-800);
9
- }
10
- .btn:hover,
11
- .btn:focus-visible {
12
- background-color: var(--c-primary-bg);
13
- color: var(--c-primary);
9
+ .btn-label {
10
+ margin-left: 0.25rem;
11
+ }
12
+ &:hover,
13
+ &:focus-visible {
14
+ background-color: var(--c-primary-bg);
15
+ color: var(--c-primary);
16
+ }
14
17
  }
@@ -15,7 +15,6 @@
15
15
  .btn_unset {
16
16
  display: inline-flex;
17
17
  align-items: center;
18
- justify-content: space-between;
19
18
  margin: 0 0 0 0.5rem;
20
19
  padding: 0.25rem 0.5rem;
21
20
  width: 4.25rem;
@@ -8,6 +8,7 @@
8
8
  nodeVisibleInExplorer,
9
9
  showGrid,
10
10
  showInspector,
11
+ showDistanceMeasure,
11
12
  sidebarExpanded = true,
12
13
  stageMaxHeight,
13
14
  stageMaxWidth,
@@ -26,6 +27,7 @@
26
27
  onToggleOrientation,
27
28
  onToggleShowGrid,
28
29
  onToggleShowInspector,
30
+ onToggleShowDistanceMeasure,
29
31
  onRevealInExplorer,
30
32
  } = $props()
31
33
 
@@ -129,6 +131,11 @@
129
131
  </button>
130
132
  <button class="topbar_btn show-m-p_btn" class:active={showInspector === true} title="Visualise margins and paddings" onclick="{onToggleShowInspector}">
131
133
  <span class="is-hidden">Visualise margins and paddings</span>
134
+ <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M12.034 12.681a.498.498 0 01.647-.647l9 3.5a.5.5 0 01-.033.943l-3.444 1.068a1 1 0 00-.66.66l-1.067 3.443a.5.5 0 01-.943.033zM5 3a2 2 0 00-2 2m16-2a2 2 0 012 2M5 21a2 2 0 01-2-2M9 3h1M9 21h2m3-18h1M3 9v1m18-1v2M3 14v1"/></svg>
135
+ <span class="dot"></span>
136
+ </button>
137
+ <button class="topbar_btn distance-measure-btn" class:active={showDistanceMeasure === true} title="Measure distances between selected points" onclick="{onToggleShowDistanceMeasure}">
138
+ <span class="is-hidden">Measure distances between selected points</span>
132
139
  <svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 0 24 24" width="24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M12 15v-3.014M16 15v-3.014M20 6H4m16 2V4M4 8V4m4 11v-3.014"/><rect x="3" y="12" width="18" height="7" rx="1"/></svg>
133
140
  <span class="dot"></span>
134
141
  </button>