vibespot 1.2.0 → 1.3.0

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 (43) hide show
  1. package/README.md +44 -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 +186 -108
  23. package/ui/docs/screenshots/brand-kit-preview.png +0 -0
  24. package/ui/docs/screenshots/content-type-dropdown.png +0 -0
  25. package/ui/docs/screenshots/editor-full-layout.png +0 -0
  26. package/ui/docs/screenshots/inline-wysiwyg-editing.png +0 -0
  27. package/ui/docs/screenshots/multi-page-tree.png +0 -0
  28. package/ui/docs/screenshots/onboarding-walkthrough.png +0 -0
  29. package/ui/docs/screenshots/split-pane-view.png +0 -0
  30. package/ui/docs/screenshots/visual-controls-toolbar.png +0 -0
  31. package/ui/docs/screenshots/workspace-tabs.png +0 -0
  32. package/ui/email-preview.js +109 -0
  33. package/ui/field-editor.js +72 -1
  34. package/ui/icons.js +120 -0
  35. package/ui/index.html +877 -629
  36. package/ui/inline-edit.js +710 -0
  37. package/ui/plan.js +0 -0
  38. package/ui/preview.js +101 -198
  39. package/ui/section-controls.js +628 -0
  40. package/ui/settings.js +58 -16
  41. package/ui/setup.js +750 -140
  42. package/ui/styles.css +3430 -952
  43. 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
+ })();