vibespot 1.2.0 → 1.3.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.
Files changed (49) hide show
  1. package/README.md +54 -5
  2. package/assets/blog-rules.md +251 -0
  3. package/assets/email-rules.md +390 -0
  4. package/assets/humanify-guide.md +300 -101
  5. package/assets/plan-templates/blog-content-hub.md +18 -9
  6. package/assets/plan-templates/email-announcement.md +41 -0
  7. package/assets/plan-templates/email-event-invite.md +43 -0
  8. package/assets/plan-templates/email-newsletter.md +41 -0
  9. package/assets/plan-templates/email-re-engagement.md +42 -0
  10. package/assets/plan-templates/email-welcome.md +41 -0
  11. package/dist/index.js +1460 -387
  12. package/dist/index.js.map +1 -1
  13. package/package.json +5 -5
  14. package/starters/06-blog-content-hub.json +75 -0
  15. package/starters/06-email-welcome.json +60 -0
  16. package/starters/07-email-announcement.json +60 -0
  17. package/starters/08-email-newsletter.json +52 -0
  18. package/ui/chat.js +777 -63
  19. package/ui/code-editor.js +49 -7
  20. package/ui/dashboard.js +379 -93
  21. package/ui/docs/docs.css +29 -0
  22. package/ui/docs/index.html +416 -119
  23. package/ui/docs/screenshots/asset-type-cards.png +0 -0
  24. package/ui/docs/screenshots/brand-kit-preview.png +0 -0
  25. package/ui/docs/screenshots/content-type-dropdown.png +0 -0
  26. package/ui/docs/screenshots/deploy-progress.png +0 -0
  27. package/ui/docs/screenshots/editor-full-layout.png +0 -0
  28. package/ui/docs/screenshots/email-client-preview.png +0 -0
  29. package/ui/docs/screenshots/inline-wysiwyg-editing.png +0 -0
  30. package/ui/docs/screenshots/module-overview-slideout.png +0 -0
  31. package/ui/docs/screenshots/multi-page-tree.png +0 -0
  32. package/ui/docs/screenshots/onboarding-walkthrough.png +0 -0
  33. package/ui/docs/screenshots/pipeline-progress.png +0 -0
  34. package/ui/docs/screenshots/project-overview-table.png +0 -0
  35. package/ui/docs/screenshots/split-pane-view.png +0 -0
  36. package/ui/docs/screenshots/visual-controls-toolbar.png +0 -0
  37. package/ui/docs/screenshots/workspace-tabs.png +0 -0
  38. package/ui/email-preview.js +109 -0
  39. package/ui/field-editor.js +72 -1
  40. package/ui/icons.js +120 -0
  41. package/ui/index.html +877 -629
  42. package/ui/inline-edit.js +710 -0
  43. package/ui/plan.js +0 -0
  44. package/ui/preview.js +101 -198
  45. package/ui/section-controls.js +628 -0
  46. package/ui/settings.js +58 -16
  47. package/ui/setup.js +750 -140
  48. package/ui/styles.css +3430 -952
  49. package/ui/upload-panel.js +47 -20
@@ -0,0 +1,628 @@
1
+ /**
2
+ * Section visual controls — hover-activated toolbar for module-level styling.
3
+ * Shows color picker, padding/margin sliders, image swap, and font size controls
4
+ * per module section. Persists changes via /api/field endpoint.
5
+ */
6
+
7
+ (() => {
8
+ const previewFrame = document.getElementById("preview-frame");
9
+ const fieldCache = new Map();
10
+ let activeToolbar = null;
11
+ let activeModule = null;
12
+ let activePopover = null;
13
+ let hideTimer = null;
14
+ let updateTimer = null;
15
+ let interacting = false;
16
+
17
+ previewFrame.addEventListener("load", () => {
18
+ fieldCache.clear();
19
+ activeToolbar = null;
20
+ activeModule = null;
21
+ activePopover = null;
22
+ attachSectionControls();
23
+ });
24
+
25
+ function attachSectionControls() {
26
+ let doc;
27
+ try {
28
+ doc = previewFrame.contentDocument || previewFrame.contentWindow?.document;
29
+ } catch { return; }
30
+ if (!doc || !doc.body) return;
31
+
32
+ ensureStyles(doc);
33
+
34
+ doc.addEventListener("mouseover", (e) => {
35
+ if (typeof selectModeActive !== "undefined" && selectModeActive) return;
36
+ if (interacting) return;
37
+
38
+ const moduleEl = e.target.closest("[data-module]");
39
+ if (!moduleEl) return;
40
+ if (moduleEl === activeModule) {
41
+ clearTimeout(hideTimer);
42
+ return;
43
+ }
44
+
45
+ clearTimeout(hideTimer);
46
+ showToolbar(doc, moduleEl);
47
+ }, true);
48
+
49
+ doc.addEventListener("mouseout", (e) => {
50
+ if (!activeToolbar || interacting) return;
51
+ const related = e.relatedTarget;
52
+ if (related) {
53
+ if (activeToolbar.contains(related)) return;
54
+ if (activeModule && activeModule.contains(related)) return;
55
+ if (activePopover && activePopover.contains(related)) return;
56
+ }
57
+ hideTimer = setTimeout(() => hideToolbar(doc), 250);
58
+ }, true);
59
+
60
+ doc.addEventListener("keydown", (e) => {
61
+ if (e.key === "Escape" && activePopover) {
62
+ closePopover(doc);
63
+ }
64
+ }, true);
65
+ }
66
+
67
+ async function showToolbar(doc, moduleEl) {
68
+ hideToolbar(doc);
69
+
70
+ const moduleName = moduleEl.getAttribute("data-module");
71
+ if (!moduleName) return;
72
+
73
+ activeModule = moduleEl;
74
+
75
+ let fields = fieldCache.get(moduleName);
76
+ if (!fields) {
77
+ try {
78
+ const res = await fetch("/api/modules");
79
+ const data = await res.json();
80
+ const mod = data.modules.find((m) => m.moduleName === moduleName);
81
+ if (!mod) return;
82
+ fields = JSON.parse(mod.fieldsJson);
83
+ fieldCache.set(moduleName, fields);
84
+ } catch {
85
+ return;
86
+ }
87
+ }
88
+
89
+ if (activeModule !== moduleEl) return;
90
+
91
+ const controls = categorizeFields(fields);
92
+ if (!controls.length) return;
93
+
94
+ const toolbar = doc.createElement("div");
95
+ toolbar.className = "vs-sc-toolbar";
96
+
97
+ for (const ctrl of controls) {
98
+ toolbar.appendChild(createControl(doc, ctrl, moduleName));
99
+ }
100
+
101
+ const computed = doc.defaultView.getComputedStyle(moduleEl);
102
+ if (computed.position === "static") {
103
+ moduleEl.style.position = "relative";
104
+ }
105
+ moduleEl.appendChild(toolbar);
106
+ activeToolbar = toolbar;
107
+
108
+ toolbar.addEventListener("mouseenter", () => clearTimeout(hideTimer));
109
+ toolbar.addEventListener("mouseleave", (e) => {
110
+ if (interacting) return;
111
+ if (activeModule && activeModule.contains(e.relatedTarget)) return;
112
+ if (activePopover && activePopover.contains(e.relatedTarget)) return;
113
+ hideTimer = setTimeout(() => hideToolbar(doc), 250);
114
+ });
115
+ }
116
+
117
+ function hideToolbar(doc) {
118
+ if (interacting) return;
119
+ closePopover(doc);
120
+ if (activeToolbar && activeToolbar.parentNode) {
121
+ activeToolbar.parentNode.removeChild(activeToolbar);
122
+ }
123
+ activeToolbar = null;
124
+ activeModule = null;
125
+ }
126
+
127
+ function categorizeFields(fields, prefix) {
128
+ const controls = [];
129
+
130
+ for (const field of fields) {
131
+ const path = prefix ? `${prefix}.${field.name}` : field.name;
132
+
133
+ if (field.type === "group" && field.children) {
134
+ controls.push(...categorizeFields(field.children, path));
135
+ continue;
136
+ }
137
+
138
+ const n = field.name.toLowerCase();
139
+
140
+ if (field.type === "color") {
141
+ controls.push({ kind: "color", field, path, label: field.label || field.name });
142
+ } else if (field.type === "image") {
143
+ controls.push({ kind: "image", field, path, label: field.label || field.name });
144
+ } else if (field.type === "number" && /padding|margin|spacing/i.test(n)) {
145
+ controls.push({ kind: "spacing", field, path, label: field.label || field.name });
146
+ } else if (field.type === "number" && /font.?size|text.?size/i.test(n)) {
147
+ controls.push({ kind: "fontsize", field, path, label: field.label || field.name });
148
+ }
149
+ }
150
+
151
+ return controls;
152
+ }
153
+
154
+ function createControl(doc, ctrl, moduleName) {
155
+ const btn = doc.createElement("button");
156
+ btn.className = "vs-sc-btn";
157
+ btn.setAttribute("data-kind", ctrl.kind);
158
+ btn.title = ctrl.label;
159
+
160
+ switch (ctrl.kind) {
161
+ case "color": {
162
+ const swatch = doc.createElement("span");
163
+ swatch.className = "vs-sc-swatch";
164
+ swatch.style.background = ctrl.field.default?.color || "#888";
165
+ btn.appendChild(swatch);
166
+ const lbl = doc.createElement("span");
167
+ lbl.className = "vs-sc-label";
168
+ lbl.textContent = shortLabel(ctrl.label);
169
+ btn.appendChild(lbl);
170
+ break;
171
+ }
172
+ case "spacing": {
173
+ btn.innerHTML =
174
+ '<svg class="vs-sc-icon" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 3v18M3 12h18M7 8l5-5 5 5M7 16l5 5 5-5"/></svg>' +
175
+ `<span class="vs-sc-label">${ctrl.field.default ?? 0}px</span>`;
176
+ break;
177
+ }
178
+ case "image": {
179
+ btn.innerHTML =
180
+ '<svg class="vs-sc-icon" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="3" width="18" height="18" rx="2"/><circle cx="8.5" cy="8.5" r="1.5"/><path d="M21 15l-5-5L5 21"/></svg>' +
181
+ `<span class="vs-sc-label">${shortLabel(ctrl.label)}</span>`;
182
+ break;
183
+ }
184
+ case "fontsize": {
185
+ btn.innerHTML =
186
+ '<span class="vs-sc-icon" style="font-weight:600;font-size:11px">Aa</span>' +
187
+ `<span class="vs-sc-label">${ctrl.field.default ?? 16}px</span>`;
188
+ break;
189
+ }
190
+ }
191
+
192
+ btn.addEventListener("click", (e) => {
193
+ e.preventDefault();
194
+ e.stopPropagation();
195
+ openPopover(doc, btn, ctrl, moduleName);
196
+ });
197
+
198
+ return btn;
199
+ }
200
+
201
+ function shortLabel(label) {
202
+ if (label.length <= 12) return label;
203
+ return label.slice(0, 10) + "…";
204
+ }
205
+
206
+ // ---- Popovers ----
207
+
208
+ function openPopover(doc, anchor, ctrl, moduleName) {
209
+ closePopover(doc);
210
+ interacting = true;
211
+
212
+ const pop = doc.createElement("div");
213
+ pop.className = "vs-sc-popover";
214
+
215
+ switch (ctrl.kind) {
216
+ case "color":
217
+ buildColorPopover(doc, pop, anchor, ctrl, moduleName);
218
+ break;
219
+ case "spacing":
220
+ buildSpacingPopover(doc, pop, anchor, ctrl, moduleName);
221
+ break;
222
+ case "image":
223
+ buildImagePopover(doc, pop, anchor, ctrl, moduleName);
224
+ break;
225
+ case "fontsize":
226
+ buildFontSizePopover(doc, pop, anchor, ctrl, moduleName);
227
+ break;
228
+ }
229
+
230
+ const rect = anchor.getBoundingClientRect();
231
+ pop.style.top = rect.bottom + 6 + "px";
232
+ pop.style.left = Math.max(4, rect.left) + "px";
233
+ doc.body.appendChild(pop);
234
+ activePopover = pop;
235
+
236
+ pop.addEventListener("mouseenter", () => clearTimeout(hideTimer));
237
+ pop.addEventListener("mouseleave", (e) => {
238
+ if (activeToolbar && activeToolbar.contains(e.relatedTarget)) return;
239
+ if (activeModule && activeModule.contains(e.relatedTarget)) return;
240
+ closePopover(doc);
241
+ });
242
+ }
243
+
244
+ function closePopover(doc) {
245
+ if (activePopover && activePopover.parentNode) {
246
+ activePopover.parentNode.removeChild(activePopover);
247
+ }
248
+ activePopover = null;
249
+ if (interacting) {
250
+ interacting = false;
251
+ flushUpdate();
252
+ }
253
+ }
254
+
255
+ function buildColorPopover(doc, pop, anchor, ctrl, moduleName) {
256
+ const picker = doc.createElement("input");
257
+ picker.type = "color";
258
+ picker.className = "vs-sc-color-picker";
259
+ picker.value = ctrl.field.default?.color || "#000000";
260
+
261
+ const hex = doc.createElement("input");
262
+ hex.type = "text";
263
+ hex.className = "vs-sc-hex";
264
+ hex.value = picker.value;
265
+
266
+ const row = doc.createElement("div");
267
+ row.className = "vs-sc-color-row";
268
+ row.appendChild(picker);
269
+ row.appendChild(hex);
270
+ pop.appendChild(row);
271
+
272
+ picker.addEventListener("input", () => {
273
+ hex.value = picker.value;
274
+ const swatch = anchor.querySelector(".vs-sc-swatch");
275
+ if (swatch) swatch.style.background = picker.value;
276
+ queueUpdate(moduleName, ctrl.path, {
277
+ color: picker.value,
278
+ opacity: ctrl.field.default?.opacity ?? 100,
279
+ });
280
+ });
281
+
282
+ hex.addEventListener("change", () => {
283
+ if (/^#[0-9a-f]{3,8}$/i.test(hex.value)) {
284
+ picker.value = hex.value.length === 4
285
+ ? "#" + hex.value[1] + hex.value[1] + hex.value[2] + hex.value[2] + hex.value[3] + hex.value[3]
286
+ : hex.value;
287
+ const swatch = anchor.querySelector(".vs-sc-swatch");
288
+ if (swatch) swatch.style.background = hex.value;
289
+ queueUpdate(moduleName, ctrl.path, {
290
+ color: hex.value,
291
+ opacity: ctrl.field.default?.opacity ?? 100,
292
+ });
293
+ }
294
+ });
295
+ }
296
+
297
+ function buildSpacingPopover(doc, pop, anchor, ctrl, moduleName) {
298
+ const lbl = doc.createElement("div");
299
+ lbl.className = "vs-sc-pop-label";
300
+ lbl.textContent = ctrl.label;
301
+
302
+ const row = doc.createElement("div");
303
+ row.className = "vs-sc-slider-row";
304
+
305
+ const slider = doc.createElement("input");
306
+ slider.type = "range";
307
+ slider.className = "vs-sc-slider";
308
+ slider.min = "0";
309
+ slider.max = "120";
310
+ slider.step = "1";
311
+ slider.value = ctrl.field.default ?? 0;
312
+
313
+ const val = doc.createElement("span");
314
+ val.className = "vs-sc-slider-val";
315
+ val.textContent = slider.value + "px";
316
+
317
+ slider.addEventListener("input", () => {
318
+ val.textContent = slider.value + "px";
319
+ const btnLabel = anchor.querySelector(".vs-sc-label");
320
+ if (btnLabel) btnLabel.textContent = slider.value + "px";
321
+ queueUpdate(moduleName, ctrl.path, Number(slider.value));
322
+ });
323
+
324
+ row.appendChild(slider);
325
+ row.appendChild(val);
326
+ pop.appendChild(lbl);
327
+ pop.appendChild(row);
328
+ }
329
+
330
+ function buildImagePopover(doc, pop, anchor, ctrl, moduleName) {
331
+ const lbl = doc.createElement("div");
332
+ lbl.className = "vs-sc-pop-label";
333
+ lbl.textContent = ctrl.label;
334
+
335
+ const input = doc.createElement("input");
336
+ input.type = "text";
337
+ input.className = "vs-sc-url-input";
338
+ input.placeholder = "Enter image URL…";
339
+ input.value = ctrl.field.default?.src || "";
340
+
341
+ const btn = doc.createElement("button");
342
+ btn.className = "vs-sc-apply-btn";
343
+ btn.textContent = "Apply";
344
+
345
+ btn.addEventListener("click", () => {
346
+ const src = input.value.trim();
347
+ if (src) {
348
+ queueUpdate(moduleName, ctrl.path, {
349
+ src,
350
+ alt: ctrl.field.default?.alt || "",
351
+ });
352
+ closePopover(doc);
353
+ }
354
+ });
355
+
356
+ input.addEventListener("keydown", (e) => {
357
+ if (e.key === "Enter") {
358
+ e.preventDefault();
359
+ btn.click();
360
+ }
361
+ if (e.key === "Escape") closePopover(doc);
362
+ });
363
+
364
+ pop.appendChild(lbl);
365
+ pop.appendChild(input);
366
+ pop.appendChild(btn);
367
+ input.focus();
368
+ }
369
+
370
+ function buildFontSizePopover(doc, pop, anchor, ctrl, moduleName) {
371
+ const row = doc.createElement("div");
372
+ row.className = "vs-sc-slider-row";
373
+
374
+ const slider = doc.createElement("input");
375
+ slider.type = "range";
376
+ slider.className = "vs-sc-slider";
377
+ slider.min = "8";
378
+ slider.max = "96";
379
+ slider.step = "1";
380
+ slider.value = ctrl.field.default ?? 16;
381
+
382
+ const val = doc.createElement("span");
383
+ val.className = "vs-sc-slider-val";
384
+ val.textContent = slider.value + "px";
385
+
386
+ slider.addEventListener("input", () => {
387
+ val.textContent = slider.value + "px";
388
+ const btnLabel = anchor.querySelector(".vs-sc-label");
389
+ if (btnLabel) btnLabel.textContent = slider.value + "px";
390
+ queueUpdate(moduleName, ctrl.path, Number(slider.value));
391
+ });
392
+
393
+ row.appendChild(slider);
394
+ row.appendChild(val);
395
+ pop.appendChild(row);
396
+ }
397
+
398
+ // ---- Save helpers ----
399
+
400
+ let pendingUpdate = null;
401
+
402
+ function queueUpdate(moduleName, fieldPath, value) {
403
+ pendingUpdate = { moduleName, fieldPath, value };
404
+ clearTimeout(updateTimer);
405
+ updateTimer = setTimeout(() => flushUpdate(), 300);
406
+ }
407
+
408
+ function flushUpdate() {
409
+ clearTimeout(updateTimer);
410
+ const upd = pendingUpdate;
411
+ if (!upd) return;
412
+ pendingUpdate = null;
413
+ fieldCache.delete(upd.moduleName);
414
+
415
+ fetch("/api/field", {
416
+ method: "POST",
417
+ headers: { "Content-Type": "application/json" },
418
+ body: JSON.stringify({
419
+ moduleName: upd.moduleName,
420
+ fieldPath: upd.fieldPath,
421
+ value: upd.value,
422
+ }),
423
+ }).then(() => {
424
+ if (!interacting) refreshPreview();
425
+ });
426
+ }
427
+
428
+ // ---- Styles (injected into iframe) ----
429
+
430
+ function ensureStyles(doc) {
431
+ if (doc.getElementById("vs-section-controls-css")) return;
432
+ const s = doc.createElement("style");
433
+ s.id = "vs-section-controls-css";
434
+ s.textContent = `
435
+ .vs-sc-toolbar {
436
+ position: absolute;
437
+ top: 8px;
438
+ right: 8px;
439
+ z-index: 10000;
440
+ display: flex;
441
+ align-items: center;
442
+ gap: 2px;
443
+ background: rgba(15, 13, 12, 0.88);
444
+ backdrop-filter: blur(12px);
445
+ -webkit-backdrop-filter: blur(12px);
446
+ border: 1px solid rgba(255, 255, 255, 0.12);
447
+ border-radius: 8px;
448
+ padding: 3px;
449
+ box-shadow: 0 4px 16px rgba(0, 0, 0, 0.35);
450
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
451
+ animation: vs-sc-fadein 0.15s ease;
452
+ pointer-events: auto;
453
+ }
454
+ @keyframes vs-sc-fadein {
455
+ from { opacity: 0; transform: translateY(-4px); }
456
+ to { opacity: 1; transform: translateY(0); }
457
+ }
458
+ .vs-sc-btn {
459
+ display: flex;
460
+ align-items: center;
461
+ gap: 4px;
462
+ padding: 4px 8px;
463
+ background: transparent;
464
+ border: none;
465
+ border-radius: 5px;
466
+ color: rgba(255, 255, 255, 0.8);
467
+ font: 500 11px/1.3 -apple-system, BlinkMacSystemFont, sans-serif;
468
+ cursor: pointer;
469
+ white-space: nowrap;
470
+ transition: background 0.15s;
471
+ }
472
+ .vs-sc-btn:hover {
473
+ background: rgba(255, 255, 255, 0.1);
474
+ color: #fff;
475
+ }
476
+ .vs-sc-swatch {
477
+ display: inline-block;
478
+ width: 14px;
479
+ height: 14px;
480
+ border-radius: 3px;
481
+ border: 1px solid rgba(255, 255, 255, 0.25);
482
+ flex-shrink: 0;
483
+ }
484
+ .vs-sc-icon {
485
+ flex-shrink: 0;
486
+ opacity: 0.7;
487
+ }
488
+ .vs-sc-label {
489
+ max-width: 72px;
490
+ overflow: hidden;
491
+ text-overflow: ellipsis;
492
+ }
493
+
494
+ /* Popover */
495
+ .vs-sc-popover {
496
+ position: fixed;
497
+ z-index: 10001;
498
+ background: rgba(15, 13, 12, 0.95);
499
+ backdrop-filter: blur(16px);
500
+ -webkit-backdrop-filter: blur(16px);
501
+ border: 1px solid rgba(255, 255, 255, 0.12);
502
+ border-radius: 8px;
503
+ padding: 10px;
504
+ box-shadow: 0 6px 24px rgba(0, 0, 0, 0.45);
505
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
506
+ animation: vs-sc-fadein 0.12s ease;
507
+ min-width: 180px;
508
+ }
509
+ .vs-sc-pop-label {
510
+ font: 500 11px/1.4 -apple-system, sans-serif;
511
+ color: rgba(255, 255, 255, 0.5);
512
+ margin-bottom: 6px;
513
+ text-transform: uppercase;
514
+ letter-spacing: 0.4px;
515
+ }
516
+
517
+ /* Color popover */
518
+ .vs-sc-color-row {
519
+ display: flex;
520
+ align-items: center;
521
+ gap: 8px;
522
+ }
523
+ .vs-sc-color-picker {
524
+ width: 36px;
525
+ height: 28px;
526
+ border: none;
527
+ border-radius: 4px;
528
+ padding: 0;
529
+ cursor: pointer;
530
+ background: transparent;
531
+ }
532
+ .vs-sc-color-picker::-webkit-color-swatch-wrapper { padding: 0; }
533
+ .vs-sc-color-picker::-webkit-color-swatch {
534
+ border: 1px solid rgba(255,255,255,0.2);
535
+ border-radius: 4px;
536
+ }
537
+ .vs-sc-color-picker::-moz-color-swatch {
538
+ border: 1px solid rgba(255,255,255,0.2);
539
+ border-radius: 4px;
540
+ }
541
+ .vs-sc-hex {
542
+ flex: 1;
543
+ font: 12px/1.4 -apple-system, sans-serif;
544
+ padding: 4px 6px;
545
+ border: 1px solid rgba(255,255,255,0.15);
546
+ border-radius: 4px;
547
+ background: rgba(0,0,0,0.3);
548
+ color: #fff;
549
+ outline: none;
550
+ width: 80px;
551
+ }
552
+ .vs-sc-hex:focus { border-color: #e8613a; }
553
+
554
+ /* Slider controls */
555
+ .vs-sc-slider-row {
556
+ display: flex;
557
+ align-items: center;
558
+ gap: 8px;
559
+ }
560
+ .vs-sc-slider {
561
+ flex: 1;
562
+ height: 4px;
563
+ -webkit-appearance: none;
564
+ appearance: none;
565
+ background: rgba(255,255,255,0.15);
566
+ border-radius: 2px;
567
+ outline: none;
568
+ cursor: pointer;
569
+ }
570
+ .vs-sc-slider::-webkit-slider-thumb {
571
+ -webkit-appearance: none;
572
+ width: 14px;
573
+ height: 14px;
574
+ border-radius: 50%;
575
+ background: #e8613a;
576
+ border: 2px solid #fff;
577
+ cursor: pointer;
578
+ }
579
+ .vs-sc-slider::-moz-range-thumb {
580
+ width: 14px;
581
+ height: 14px;
582
+ border-radius: 50%;
583
+ background: #e8613a;
584
+ border: 2px solid #fff;
585
+ cursor: pointer;
586
+ }
587
+ .vs-sc-slider-val {
588
+ font: 500 12px/1 -apple-system, sans-serif;
589
+ color: rgba(255,255,255,0.7);
590
+ min-width: 36px;
591
+ text-align: right;
592
+ }
593
+
594
+ /* Image / URL input */
595
+ .vs-sc-url-input {
596
+ width: 100%;
597
+ font: 12px/1.4 -apple-system, sans-serif;
598
+ padding: 6px 8px;
599
+ border: 1px solid rgba(255,255,255,0.15);
600
+ border-radius: 5px;
601
+ background: rgba(0,0,0,0.3);
602
+ color: #fff;
603
+ outline: none;
604
+ margin-bottom: 6px;
605
+ box-sizing: border-box;
606
+ }
607
+ .vs-sc-url-input:focus { border-color: #e8613a; }
608
+ .vs-sc-apply-btn {
609
+ display: block;
610
+ width: 100%;
611
+ font: 500 12px/1 -apple-system, sans-serif;
612
+ padding: 6px 12px;
613
+ border: none;
614
+ border-radius: 5px;
615
+ background: #e8613a;
616
+ color: #fff;
617
+ cursor: pointer;
618
+ transition: background 0.15s;
619
+ }
620
+ .vs-sc-apply-btn:hover { background: #d4522e; }
621
+
622
+ @media (prefers-reduced-motion: reduce) {
623
+ .vs-sc-toolbar, .vs-sc-popover { animation: none; }
624
+ }
625
+ `;
626
+ doc.head.appendChild(s);
627
+ }
628
+ })();