@explorer02/cfm-survey-sdk 0.2.9 → 0.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 (47) hide show
  1. package/dist/cli/index.js +41 -38
  2. package/dist/cli/index.mjs +39 -36
  3. package/package.json +1 -1
  4. package/templates/docs/00-integration/agent-execution-flow.md +6 -6
  5. package/templates/docs/00-integration/agent-operating-contract.md +1 -2
  6. package/templates/docs/00-integration/client-lib-folder.md +1 -0
  7. package/templates/docs/00-integration/component-checklist.md +0 -1
  8. package/templates/docs/00-integration/ui-customization-wizard.md +38 -29
  9. package/templates/docs/00-integration/wizard-chrome-contract.md +3 -12
  10. package/templates/docs/00-integration/wizard-config-handoff.md +19 -11
  11. package/templates/docs/00-integration/wizard-post-customize-build.md +0 -6
  12. package/templates/docs/00-integration/wizard-preview-build-guide.md +17 -0
  13. package/templates/docs/00-integration/wizard-question-type-styling.md +3 -3
  14. package/templates/docs/00-integration/wizard-troubleshooting.md +4 -20
  15. package/templates/docs/01-components/03-rating-scale.md +8 -3
  16. package/templates/docs/02-reference/value-derivation.md +2 -0
  17. package/templates/docs/03-ui-specs/01-rating.md +15 -1
  18. package/templates/docs/index.md +1 -1
  19. package/templates/docs/templates/CsatMatrixScale.tsx +59 -53
  20. package/templates/docs/templates/CustomSliderTrack.tsx +8 -17
  21. package/templates/docs/templates/FileUploadScale.tsx +4 -12
  22. package/templates/docs/templates/Header.tsx +49 -32
  23. package/templates/docs/templates/HeatmapScale.tsx +2 -5
  24. package/templates/docs/templates/LikertMatrixScale.tsx +45 -57
  25. package/templates/docs/templates/MatrixDropdown.tsx +11 -12
  26. package/templates/docs/templates/Question.tsx +24 -4
  27. package/templates/docs/templates/RankOrderScale.tsx +9 -42
  28. package/templates/docs/templates/RatingScale.tsx +73 -75
  29. package/templates/docs/templates/SliderMatrixScale.tsx +3 -4
  30. package/templates/docs/templates/selectionStyles.ts +3 -2
  31. package/templates/docs/templates/surveyUiScaleUtils.ts +15 -1
  32. package/templates/docs/templates/verify-agent-build.sh +0 -17
  33. package/templates/preview-harness/preview-bridge.inline.js +61 -14
  34. package/templates/preview-harness/previewPages.js +84 -48
  35. package/templates/preview-harness/previewPages.ts +7 -5
  36. package/templates/preview-harness/vite-app/src/QuestionPreview.tsx +22 -14
  37. package/templates/preview-harness/vite-app/src/fixtures/questions.ts +13 -2
  38. package/templates/preview-harness/vite-app/src/preview-live-overrides.css +6 -11
  39. package/templates/previewBridge.ts +38 -5
  40. package/templates/survey-theme.css +17 -17
  41. package/templates/wizard-dist/assets/{PreviewMock-CysRws9G.js → PreviewMock-Bax7oRAL.js} +1 -1
  42. package/templates/wizard-dist/assets/{TypePanel-DWX1LL3q.js → TypePanel-D2t4FPSd.js} +1 -1
  43. package/templates/wizard-dist/assets/index-BhWM50Yu.css +1 -0
  44. package/templates/wizard-dist/assets/index-c5lka74l.js +34 -0
  45. package/templates/wizard-dist/index.html +2 -2
  46. package/templates/wizard-dist/assets/index-DIuc7AU2.js +0 -34
  47. package/templates/wizard-dist/assets/index-pCM8n0FT.css +0 -1
@@ -8,8 +8,9 @@
8
8
  */
9
9
  import type { CSSProperties } from 'react';
10
10
 
11
- export const cellSelectedVar = 'var(--cfm-matrix-selected, var(--cfm-primary))';
12
- export const focusRingVar = 'var(--cfm-input-focus-ring, var(--cfm-primary))';
11
+ /** Terminal hex fallbacks match apps/client RatingScale — visible before survey-theme.css loads. */
12
+ export const cellSelectedVar = 'var(--cfm-matrix-selected, var(--cfm-primary, #fbe8f3))';
13
+ export const focusRingVar = 'var(--cfm-input-focus-ring, var(--cfm-primary, #e20074))';
13
14
 
14
15
  const unselectedRing = '#d1d5db';
15
16
  const unselectedDot = '#d1d5db';
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Portable scale utilities — copy to src/lib/surveyUiScaleUtils.ts or co-locate with components.
3
3
  */
4
- import type { NpsScalePoint, ScaleColumn } from '@explorer02/cfm-survey-sdk';
4
+ import type { AnswerValue, NpsScalePoint, ScaleColumn } from '@explorer02/cfm-survey-sdk';
5
5
 
6
6
  export const SCALE_COLORS: readonly string[] = [
7
7
  '#e2001a', '#e4251b', '#ec610a', '#f18b00', '#f7b200', '#fcd900',
@@ -32,6 +32,20 @@ export function npsValueFromOption(option: NpsScalePoint, index: number): number
32
32
  return Number.isFinite(numeric) && option.optionLabel.trim() !== '' ? numeric : index;
33
33
  }
34
34
 
35
+ /** Loose match for NPS/star stored answers (number) vs derived option values. */
36
+ export function isScaleAnswerSelected(
37
+ selectedValue: AnswerValue | undefined,
38
+ optionValue: number,
39
+ ): boolean {
40
+ if (selectedValue === undefined || selectedValue === null) return false;
41
+ if (typeof selectedValue === 'number') return selectedValue === optionValue;
42
+ if (typeof selectedValue === 'string') {
43
+ const parsed = Number(selectedValue);
44
+ return Number.isFinite(parsed) && parsed === optionValue;
45
+ }
46
+ return false;
47
+ }
48
+
35
49
  /** Matches SDK matrixColumnStoredValue — scale-matrix answers store column ids. */
36
50
  export function matrixColumnStoredValue(column: { id: string; label: string }): string | null {
37
51
  return column.id === 'na' ? null : column.id;
@@ -102,23 +102,6 @@ if ! grep -q "layoutFlags" "$PREVIEW_BRIDGE"; then
102
102
  fi
103
103
  echo "OK: previewBridge.ts present with layoutFlags"
104
104
 
105
- # Scale templates must consume wizard CSS vars (not only RatingScale)
106
- SCALE_CFM_COUNT=0
107
- for candidate in src/components/LikertMatrixScale.tsx components/LikertMatrixScale.tsx app/components/LikertMatrixScale.tsx; do
108
- if [ -f "$candidate" ] && grep -q -- '--cfm-' "$candidate"; then SCALE_CFM_COUNT=1; break; fi
109
- done
110
- if [ "$SCALE_CFM_COUNT" -eq 0 ]; then
111
- echo "WARN: LikertMatrixScale.tsx missing --cfm-* vars — copy from docs/templates"
112
- fi
113
-
114
- SCALE_CSAT_COUNT=0
115
- for candidate in src/components/CsatMatrixScale.tsx components/CsatMatrixScale.tsx app/components/CsatMatrixScale.tsx; do
116
- if [ -f "$candidate" ] && grep -q -- '--cfm-' "$candidate"; then SCALE_CSAT_COUNT=1; break; fi
117
- done
118
- if [ "$SCALE_CSAT_COUNT" -eq 0 ]; then
119
- echo "WARN: CsatMatrixScale.tsx missing --cfm-* vars — copy from docs/templates"
120
- fi
121
-
122
105
  REQUIRED_TYPES=(MCQ TEXTFIELD NPS_SCALE CFM_MATRIX CSAT_MATRIX RATING_MATRIX SLIDER_MATRIX FILE_UPLOAD TEXT_AND_MEDIA HEATMAP RANK_ORDER)
123
106
  QUESTION_FILE=""
124
107
  for candidate in src/components/Question.tsx components/Question.tsx app/components/Question.tsx; do
@@ -17,6 +17,7 @@
17
17
  delete pending[k];
18
18
  }
19
19
  }
20
+ applyThemeSideEffects();
20
21
  }
21
22
 
22
23
  function queue(vars) {
@@ -33,6 +34,7 @@
33
34
  root.style.setProperty(k, vars[k]);
34
35
  }
35
36
  }
37
+ applyThemeSideEffects();
36
38
  }
37
39
 
38
40
  function setDisplay(selector, visible) {
@@ -41,6 +43,44 @@
41
43
  });
42
44
  }
43
45
 
46
+ var TRAFFIC = ['#ef4444', '#f97316', '#eab308', '#84cc16', '#22c55e', '#14b8a6', '#06b6d4', '#3b82f6', '#6366f1', '#8b5cf6', '#a855f7'];
47
+
48
+ function readVar(name, fallback) {
49
+ return getComputedStyle(document.documentElement).getPropertyValue(name).trim() || fallback;
50
+ }
51
+
52
+ function applyNumberBadgeColors() {
53
+ var mode = readVar('--cfm-number-label-mode', 'traffic');
54
+ var mono = readVar('--cfm-number-mono-color', '#9ca3af');
55
+ document.querySelectorAll('[data-cfm-number-badge]').forEach(function (el) {
56
+ if (el.classList.contains('selected')) return;
57
+ var i = parseInt(el.getAttribute('data-cfm-number-badge'), 10);
58
+ if (isNaN(i)) return;
59
+ var bg = TRAFFIC[i] || '#9ca3af';
60
+ if (mode === 'monochrome') bg = mono;
61
+ if (mode === 'individual') bg = readVar('--cfm-number-color-' + i, '#9ca3af');
62
+ el.style.backgroundColor = bg;
63
+ });
64
+ }
65
+
66
+ function applyMultiStatement(show) {
67
+ document.querySelectorAll('[data-cfm-matrix-row-extra]').forEach(function (el) {
68
+ el.style.display = show ? '' : 'none';
69
+ });
70
+ }
71
+
72
+ function applyDropzoneStyle() {
73
+ var style = readVar('--cfm-upload-border-style', 'dashed');
74
+ document.querySelectorAll('[data-cfm-dropzone], .cfm-dropzone').forEach(function (el) {
75
+ el.style.borderStyle = style;
76
+ });
77
+ }
78
+
79
+ function applyThemeSideEffects() {
80
+ applyNumberBadgeColors();
81
+ applyDropzoneStyle();
82
+ }
83
+
44
84
  function applyContent(patch) {
45
85
  if (!patch) return;
46
86
  if (patch.logoUrl !== undefined) {
@@ -49,6 +89,7 @@
49
89
  if (patch.logoUrl) {
50
90
  img.src = patch.logoUrl;
51
91
  img.style.display = '';
92
+ img.style.background = 'transparent';
52
93
  } else if (el.hasAttribute('data-cfm-logo')) {
53
94
  img.style.display = 'none';
54
95
  img.removeAttribute('src');
@@ -171,21 +212,27 @@
171
212
  }
172
213
  }
173
214
  }
174
- if (patch.previewState && patch.previewState.textfieldPlaceholder !== undefined) {
175
- document.querySelectorAll('[data-cfm-textfield]').forEach(function (el) {
176
- el.setAttribute('placeholder', patch.previewState.textfieldPlaceholder || '');
177
- });
178
- }
179
- if (patch.previewState && patch.previewState.hintMinText !== undefined) {
180
- document.querySelectorAll('[data-cfm-hint-min]').forEach(function (el) {
181
- el.textContent = patch.previewState.hintMinText || '';
182
- });
183
- }
184
- if (patch.previewState && patch.previewState.hintMaxText !== undefined) {
185
- document.querySelectorAll('[data-cfm-hint-max]').forEach(function (el) {
186
- el.textContent = patch.previewState.hintMaxText || '';
187
- });
215
+ if (patch.previewState) {
216
+ if (patch.previewState.textfieldPlaceholder !== undefined) {
217
+ document.querySelectorAll('[data-cfm-textfield]').forEach(function (el) {
218
+ el.setAttribute('placeholder', patch.previewState.textfieldPlaceholder || '');
219
+ });
220
+ }
221
+ if (patch.previewState.hintMinText !== undefined) {
222
+ document.querySelectorAll('[data-cfm-hint-min], .cfm-hint-min').forEach(function (el) {
223
+ el.textContent = patch.previewState.hintMinText || '';
224
+ });
225
+ }
226
+ if (patch.previewState.hintMaxText !== undefined) {
227
+ document.querySelectorAll('[data-cfm-hint-max], .cfm-hint-max').forEach(function (el) {
228
+ el.textContent = patch.previewState.hintMaxText || '';
229
+ });
230
+ }
231
+ if (patch.previewState.multiStatement !== undefined) {
232
+ applyMultiStatement(!!patch.previewState.multiStatement);
233
+ }
188
234
  }
235
+ applyThemeSideEffects();
189
236
  }
190
237
 
191
238
  function handleMessage(event) {
@@ -1,8 +1,9 @@
1
1
  'use strict';
2
2
 
3
+ /** Static wizard preview pages — layout matches previewPages.ts; tokens via --cfm-* + preview-bridge. */
3
4
  const sharedStyles = `
4
5
  .cfm-header { height:var(--cfm-header-height); background:var(--cfm-header-bg); border-bottom:1px solid var(--cfm-header-border); display:flex; align-items:center; padding:0 var(--cfm-header-padding-x); box-shadow:0 2px 8px rgba(0,0,0,0.06); }
5
- .cfm-header-logo { width:var(--cfm-header-logo-width); height:var(--cfm-header-logo-height); object-fit:contain; padding:var(--cfm-header-logo-padding); background:var(--cfm-primary); }
6
+ .cfm-header-logo { width:var(--cfm-header-logo-width); height:var(--cfm-header-logo-height); max-height:calc(var(--cfm-header-height, 120px) - 8px); object-fit:contain; padding:var(--cfm-header-logo-padding); background:transparent; }
6
7
  .cfm-header-company { font-size:var(--cfm-header-company-size); font-weight:var(--cfm-header-company-weight); color:var(--cfm-header-company-color); margin-left:16px; }
7
8
  .cfm-footer { background:var(--cfm-footer-bg); color:var(--cfm-footer-text); padding:var(--cfm-footer-padding-y) var(--cfm-footer-padding-x); display:flex; flex-wrap:wrap; gap:16px; justify-content:space-between; align-items:flex-end; }
8
9
  .cfm-footer-links { display:flex; gap:24px; flex-wrap:wrap; }
@@ -11,82 +12,117 @@ const sharedStyles = `
11
12
  .cfm-progress-fill { height:100%; width:45%; background:var(--cfm-progress-fill); border-radius:999px; }
12
13
  .cfm-mcq-option { border:2px solid #e5e7eb; border-radius:var(--cfm-mcq-border-radius); padding:var(--cfm-mcq-card-padding); margin-bottom:var(--cfm-mcq-option-gap); display:flex; align-items:center; gap:8px; }
13
14
  .cfm-mcq-option.selected { border-color:var(--cfm-mcq-selected-accent); background:var(--cfm-mcq-selected-bg); }
14
- .cfm-nps-row { display:flex; gap:var(--cfm-nps-cell-gap); flex-wrap:wrap; }
15
- .cfm-nps-cell { width:var(--cfm-nps-cell-size); height:var(--cfm-nps-cell-size); border-radius:var(--cfm-border-radius); background:var(--cfm-nps-track-bar); display:flex; align-items:center; justify-content:center; font-size:14px; }
16
- .cfm-nps-cell.selected { background:var(--cfm-nps-selected-fill); border:2px solid var(--cfm-accent); }
15
+ .cfm-hint-row { display:flex; justify-content:space-between; font-size:12px; font-weight:600; margin-bottom:8px; }
16
+ .cfm-hint-min, .cfm-hint-max { color:var(--cfm-hint-label-color,#6b7280); background:var(--cfm-hint-label-bg,transparent); padding:2px 4px; border-radius:4px; }
17
+ .cfm-nps-badges { display:grid; grid-template-columns:repeat(11,minmax(0,1fr)); gap:var(--cfm-nps-cell-gap,2px); margin-bottom:8px; }
18
+ .cfm-nps-badge { width:var(--cfm-nps-cell-size,24px); height:var(--cfm-nps-cell-size,24px); margin:0 auto; border-radius:3px; font-size:10px; font-weight:700; color:#fff; display:flex; align-items:center; justify-content:center; box-sizing:border-box; }
19
+ .cfm-nps-badge.selected { border:2px solid var(--cfm-input-focus-ring); background:var(--cfm-matrix-selected); }
20
+ .cfm-nps-track { background:var(--cfm-nps-track-bar,#f2f2f2); border-radius:6px; padding:8px 4px; }
21
+ .cfm-nps-row { display:grid; grid-template-columns:repeat(11,minmax(0,1fr)); gap:var(--cfm-nps-cell-gap,2px); }
22
+ .cfm-nps-radio { width:var(--cfm-nps-cell-size,32px); height:var(--cfm-nps-cell-size,32px); margin:0 auto; border-radius:50%; border:2px solid #d1d5db; background:#fff; box-sizing:border-box; display:flex; align-items:center; justify-content:center; }
23
+ .cfm-nps-radio.selected { border-color:var(--cfm-input-focus-ring); background:var(--cfm-matrix-selected); }
24
+ .cfm-nps-radio.selected::after { content:''; width:33%; height:33%; border-radius:50%; background:var(--cfm-input-focus-ring); }
17
25
  .cfm-matrix-row { display:flex; align-items:center; padding:8px 0; background:var(--cfm-row-band); margin-bottom:4px; }
18
- .cfm-matrix-label { width:var(--cfm-matrix-row-width); flex-shrink:0; padding:0 16px; font-size:14px; font-weight:500; }
19
- .cfm-matrix-cells { display:flex; gap:8px; flex:1; justify-content:center; }
20
- .cfm-matrix-cell { width:var(--cfm-csat-cell-size); height:var(--cfm-csat-cell-size); border-radius:50%; border:2px solid #d1d5db; }
21
- .cfm-matrix-cell.selected { border-color:var(--cfm-csat-ring-color); background:var(--cfm-selected-bg); }
22
- .cfm-emoji { font-size:var(--cfm-csat-emoji-size); opacity:var(--cfm-csat-unselected-opacity); }
23
- .cfm-emoji.selected { opacity:1; outline:2px solid var(--cfm-csat-ring-color); border-radius:8px; }
24
- .cfm-star { color:var(--cfm-star-color); font-size:24px; opacity:var(--cfm-star-unselected-opacity); }
25
- .cfm-star.selected { opacity:1; }
26
- .cfm-badge { width:40px; height:40px; border-radius:8px; background:var(--cfm-badge-color); color:white; display:flex; align-items:center; justify-content:center; font-weight:600; }
27
- .cfm-slider-track { height:8px; background:var(--cfm-slider-track); border-radius:4px; position:relative; margin:24px 0; }
28
- .cfm-slider-thumb { width:20px; height:20px; background:var(--cfm-slider-thumb); border-radius:50%; position:absolute; top:-6px; left:60%; }
29
- .cfm-tick { width:var(--cfm-slider-tick-size); height:var(--cfm-slider-tick-size); background:var(--cfm-slider-tick); border-radius:4px; font-size:11px; display:flex; align-items:center; justify-content:center; color:white; }
30
- .cfm-dropzone { border:2px dashed var(--cfm-upload-accent); border-radius:var(--cfm-border-radius); padding:var(--cfm-upload-padding); text-align:center; color:var(--cfm-text); }
26
+ .cfm-matrix-label { width:var(--cfm-matrix-row-width,180px); flex-shrink:0; padding:0 16px; font-size:14px; font-weight:500; box-sizing:border-box; }
27
+ .cfm-matrix-cells { display:flex; gap:8px; flex:1; justify-content:center; align-items:center; }
28
+ .cfm-matrix-col-label { font-size:13px; font-weight:500; text-align:center; color:var(--cfm-csat-column-label-color,var(--cfm-matrix-column-label-color,#4b5563)); background:var(--cfm-csat-column-label-bg,var(--cfm-matrix-column-label-bg,transparent)); padding:2px 6px; border-radius:4px; }
29
+ .cfm-matrix-cell { width:var(--cfm-csat-cell-size,18px); height:var(--cfm-csat-cell-size,18px); border-radius:50%; border:2px solid #d1d5db; box-sizing:border-box; }
30
+ .cfm-matrix-cell.selected { border-color:var(--cfm-input-focus-ring); background:var(--cfm-matrix-selected); }
31
+ .cfm-emoji { font-size:var(--cfm-csat-emoji-size,28px); opacity:var(--cfm-csat-unselected-opacity,0.5); line-height:1; }
32
+ .cfm-emoji.selected { opacity:1; outline:2px solid var(--cfm-input-focus-ring); border-radius:8px; }
33
+ .cfm-star { color:var(--cfm-input-focus-ring); font-size:var(--cfm-csat-emoji-size,24px); opacity:var(--cfm-csat-unselected-opacity,0.5); }
34
+ .cfm-star.selected { opacity:1; color:var(--cfm-matrix-selected); }
35
+ .cfm-badge { width:40px; height:40px; border-radius:8px; color:#fff; display:flex; align-items:center; justify-content:center; font-weight:600; border:1px solid #d1d5db; box-sizing:border-box; }
36
+ .cfm-badge.selected { border:2px solid var(--cfm-input-focus-ring); background:var(--cfm-matrix-selected); color:#fff; }
37
+ .cfm-slider-track { height:8px; background:var(--cfm-slider-track,#e5e7eb); border-radius:4px; position:relative; margin:24px 0; }
38
+ .cfm-slider-thumb { width:20px; height:20px; background:var(--cfm-slider-thumb,var(--cfm-input-focus-ring)); border-radius:50%; position:absolute; top:-6px; left:60%; border:2px solid var(--cfm-input-focus-ring); box-sizing:border-box; }
39
+ .cfm-tick { min-width:var(--cfm-slider-tick-size,24px); height:var(--cfm-slider-tick-size,24px); background:var(--cfm-slider-tick,#6b7280); border-radius:4px; font-size:11px; display:flex; align-items:center; justify-content:center; color:#fff; padding:0 4px; }
40
+ .cfm-slider-tick-label { font-size:12px; font-weight:500; color:var(--cfm-slider-tick-label-color,#4b5563); background:var(--cfm-slider-tick-label-bg,transparent); padding:2px 4px; border-radius:4px; }
41
+ .cfm-dropzone { border-width:2px; border-style:var(--cfm-upload-border-style,dashed); border-color:var(--cfm-upload-border-color,#d1d5db); border-radius:var(--cfm-border-radius,12px); padding:var(--cfm-upload-padding,24px); background:var(--cfm-upload-fill,#f9fafb); text-align:center; color:var(--cfm-text); }
31
42
  .cfm-input { width:100%; height:var(--cfm-input-height); border:1px solid var(--cfm-input-border); border-radius:var(--cfm-input-radius); padding:0 var(--cfm-input-padding); font-family:inherit; box-sizing:border-box; }
32
- .cfm-rank-item { background:var(--cfm-rank-item-bg); padding:var(--cfm-rank-item-padding); margin-bottom:var(--cfm-rank-item-gap); border-radius:var(--cfm-border-radius); display:flex; align-items:center; gap:12px; }
33
- .cfm-rank-badge { background:var(--cfm-rank-badge); color:white; width:24px; height:24px; border-radius:50%; display:flex; align-items:center; justify-content:center; font-size:12px; }
34
- .cfm-handle { color:var(--cfm-rank-handle); cursor:grab; }
35
- .cfm-pin { width:var(--cfm-heatmap-pin-size); height:var(--cfm-heatmap-pin-size); background:var(--cfm-heatmap-pin); border:2px solid white; border-radius:50%; position:absolute; }
43
+ .cfm-input:focus { outline:2px solid var(--cfm-input-focus-ring); }
44
+ .cfm-rank-item { background:var(--cfm-rank-item-bg,#fff); padding:var(--cfm-rank-item-padding,12px 16px); margin-bottom:var(--cfm-rank-item-gap,8px); border-radius:var(--cfm-border-radius,8px); display:flex; align-items:center; gap:12px; }
45
+ .cfm-rank-badge { background:var(--cfm-rank-badge,var(--cfm-primary)); color:#fff; width:24px; height:24px; border-radius:50%; display:flex; align-items:center; justify-content:center; font-size:12px; flex-shrink:0; }
46
+ .cfm-handle { color:var(--cfm-rank-handle,#9ca3af); cursor:grab; }
47
+ .cfm-rank-select { border-color:var(--cfm-rank-select-border,#d1d5db) !important; }
48
+ .cfm-pin { width:var(--cfm-heatmap-pin-size,16px); height:var(--cfm-heatmap-pin-size,16px); background:var(--cfm-heatmap-pin,#f97316); border:2px solid var(--cfm-heatmap-pin-border,#fff); border-radius:50%; position:absolute; box-sizing:border-box; }
36
49
  .cfm-media { max-width:var(--cfm-media-max-width); border-radius:var(--cfm-media-radius); background:#e5e7eb; height:200px; display:flex; align-items:center; justify-content:center; }
37
50
  .cfm-btn-primary { background:var(--cfm-primary); color:var(--cfm-button-text); border:none; border-radius:var(--cfm-border-radius); padding:10px 20px; }
38
51
  .cfm-btn-back { background:transparent; color:var(--cfm-primary); border:1px solid var(--cfm-primary); border-radius:var(--cfm-border-radius); padding:10px 20px; }
39
52
  `;
40
53
 
54
+ const TRAFFIC = ['#ef4444', '#f97316', '#eab308', '#84cc16', '#22c55e', '#14b8a6', '#06b6d4', '#3b82f6', '#6366f1', '#8b5cf6', '#a855f7'];
55
+
41
56
  function cells(n) {
42
57
  return Array.from({ length: n }, (_, i) =>
43
58
  `<div class="cfm-matrix-cell${i === 2 ? ' selected' : ''}"></div>`,
44
59
  ).join('');
45
60
  }
46
61
 
47
- function matrixPreview(label) {
48
- return `<div class="cfm-matrix-row"><div class="cfm-matrix-label">${label}</div><div class="cfm-matrix-cells">${cells(5)}</div></div>`;
62
+ function colLabels(n) {
63
+ return Array.from({ length: n }, (_, i) => `<span class="cfm-matrix-col-label">${i + 1}</span>`).join('');
64
+ }
65
+
66
+ function matrixPreview(label, mode) {
67
+ if (mode === 'emoji') {
68
+ return `<div class="cfm-matrix-row"><div class="cfm-matrix-label">${label}</div><div class="cfm-matrix-cells"><span class="cfm-emoji">😞</span><span class="cfm-emoji selected">🙂</span><span class="cfm-emoji">😊</span></div></div>`;
69
+ }
70
+ return `<div class="cfm-matrix-row" style="background:transparent;margin-bottom:4px"><div class="cfm-matrix-label"></div><div class="cfm-matrix-cells">${colLabels(5)}</div></div>
71
+ <div class="cfm-matrix-row"><div class="cfm-matrix-label">${label}</div><div class="cfm-matrix-cells">${cells(5)}</div></div>`;
72
+ }
73
+
74
+ function npsPreview() {
75
+ const badges = Array.from({ length: 11 }, (_, i) =>
76
+ `<div class="cfm-nps-badge${i === 8 ? ' selected' : ''}" data-cfm-number-badge="${i}">${i}</div>`,
77
+ ).join('');
78
+ const radios = Array.from({ length: 11 }, (_, i) =>
79
+ `<div class="cfm-nps-radio${i === 8 ? ' selected' : ''}"></div>`,
80
+ ).join('');
81
+ return `<p style="font-weight:600;margin-bottom:8px">How likely are you to recommend us?</p>
82
+ <div class="cfm-hint-row"><span class="cfm-hint-min" data-cfm-hint-min>Not likely</span><span class="cfm-hint-max" data-cfm-hint-max>Very likely</span></div>
83
+ <div class="cfm-nps-badges">${badges}</div>
84
+ <div class="cfm-nps-track"><div class="cfm-nps-row">${radios}</div></div>`;
49
85
  }
50
86
 
51
87
  const npsCells = Array.from({ length: 11 }, (_, i) =>
52
- `<div class="cfm-nps-cell${i === 8 ? ' selected' : ''}">${i}</div>`,
88
+ `<div class="cfm-nps-radio${i === 8 ? ' selected' : ''}"></div>`,
53
89
  ).join('');
54
90
 
55
91
  const PREVIEW_PAGE_DEFS = [
56
92
  { key: 'header', title: 'Header', body: `<header class="cfm-header"><img data-cfm-logo class="cfm-header-logo" src="" alt="Logo" style="display:none" /><span data-cfm-company class="cfm-header-company">Company Name</span></header>` },
57
93
  { key: 'footer', title: 'Footer', body: `<footer class="cfm-footer"><div><img data-cfm-logo style="width:var(--cfm-footer-logo-width);height:var(--cfm-footer-logo-height);object-fit:contain;display:none" src="" alt="" /><p data-cfm-copyright style="font-size:12px;margin-top:8px">© Company</p></div><nav data-cfm-footer-links class="cfm-footer-links"></nav></footer>` },
58
94
  { key: 'chrome', title: 'Chrome', body: `<header class="cfm-header"><img data-cfm-logo class="cfm-header-logo" src="" alt="" style="display:none" /><span data-cfm-company class="cfm-header-company">Company</span></header><div class="cfm-progress"><div class="cfm-progress-fill"></div></div><p data-cfm-survey-title style="font-size:20px;font-weight:var(--cfm-heading-weight);margin:16px 0">Survey Title</p><div style="display:flex;gap:12px;margin-top:24px"><button data-cfm-btn-back class="cfm-btn-back">Back</button><button data-cfm-btn-next class="cfm-btn-primary">Next</button></div>` },
59
- { key: 'survey_page', title: 'Survey Page', body: `<header class="cfm-header"><img data-cfm-logo class="cfm-header-logo" src="" alt="" style="display:none" /><span data-cfm-company class="cfm-header-company">Company</span></header><div data-cfm-progress class="cfm-progress"><div class="cfm-progress-fill" style="width:35%"></div></div><div data-cfm-language style="text-align:right;margin:16px 0"><select class="cfm-input" style="width:auto"><option>English</option></select></div><p style="font-weight:600"><span data-cfm-question-number>1.</span> Sample question <span data-cfm-required style="color:red">*</span></p><div class="cfm-mcq-option selected"><span>◉</span> Option A</div><p style="font-weight:600;margin-top:24px"><span data-cfm-question-number>2.</span> NPS question</p><div class="cfm-nps-row">${npsCells}</div><div style="display:flex;gap:12px;margin-top:24px"><button data-cfm-btn-back class="cfm-btn-back">Back</button><button data-cfm-btn-next class="cfm-btn-primary">Next</button></div><footer class="cfm-footer" style="margin-top:32px"><p data-cfm-copyright>© Company</p><nav data-cfm-footer-links class="cfm-footer-links"></nav></footer>` },
60
- { key: 'intro', title: 'Intro', body: `<h1 data-cfm-survey-title style="font-size:28px;font-weight:var(--cfm-heading-weight)">Welcome</h1><p style="margin:16px 0;color:#6b7280">Please share your feedback.</p><button class="cfm-btn-primary">Start Survey</button>` },
95
+ { key: 'survey_page', title: 'Survey Page', body: `<header class="cfm-header"><img data-cfm-logo class="cfm-header-logo" src="" alt="" style="display:none" /><span data-cfm-company class="cfm-header-company">Company</span></header><div data-cfm-progress class="cfm-progress"><div class="cfm-progress-fill" style="width:35%"></div></div><div data-cfm-language style="text-align:right;margin:16px 0"><select class="cfm-input" style="width:auto"><option>English</option><option>Deutsch</option></select></div><p style="font-weight:600"><span data-cfm-question-number>1.</span> Sample question <span data-cfm-required style="color:red">*</span></p><div class="cfm-mcq-option selected"><span>◉</span> Option A</div><p style="font-weight:600;margin-top:24px"><span data-cfm-question-number>2.</span> NPS question</p><div class="cfm-nps-track"><div class="cfm-nps-row">${npsCells}</div></div><div style="display:flex;gap:12px;margin-top:24px"><button data-cfm-btn-back class="cfm-btn-back">Back</button><button data-cfm-btn-next class="cfm-btn-primary">Next</button></div><footer class="cfm-footer" style="margin-top:32px"><p data-cfm-copyright>© Company</p><nav data-cfm-footer-links class="cfm-footer-links"></nav></footer>` },
96
+ { key: 'intro', title: 'Intro', body: `<h1 data-cfm-survey-title style="font-size:28px;font-weight:var(--cfm-heading-weight)">Welcome</h1><p style="margin:16px 0;color:#6b7280">Please take a few minutes to share your feedback.</p><button class="cfm-btn-primary">Start Survey</button>` },
61
97
  { key: 'end', title: 'End', body: `<h1 style="font-size:28px;font-weight:var(--cfm-heading-weight)">Thank you!</h1><p data-cfm-thank-you style="margin:16px 0">Thank you for your feedback.</p>` },
62
- { key: 'MCQ_single', title: 'MCQ Single', body: `<p style="font-weight:600;margin-bottom:16px">1. Select one option</p><div data-cfm-mcq-style="outlined"><div class="cfm-mcq-option selected"><span>◉</span> Option A</div><div class="cfm-mcq-option"><span>○</span> Option B</div></div>` },
98
+ { key: 'MCQ_single', title: 'MCQ Single', body: `<p style="font-weight:600;margin-bottom:16px">1. Select one option</p><div data-cfm-mcq-style="outlined"><div class="cfm-mcq-option selected"><span>◉</span> Option A</div><div class="cfm-mcq-option"><span>○</span> Option B</div><div class="cfm-mcq-option"><span>○</span> Option C</div></div>` },
63
99
  { key: 'MCQ_multiple', title: 'MCQ Multiple', body: `<p style="font-weight:600;margin-bottom:16px">Select all that apply</p><div class="cfm-mcq-option selected"><span>☑</span> Option A</div><div class="cfm-mcq-option"><span>☐</span> Option B</div>` },
64
- { key: 'TEXTFIELD_short', title: 'Text Short', body: `<p style="font-weight:600;margin-bottom:8px">Your name</p><input class="cfm-input" placeholder="Type here..." />` },
65
- { key: 'TEXTFIELD_long', title: 'Text Long', body: `<p style="font-weight:600;margin-bottom:8px">Comments</p><textarea class="cfm-input" style="height:120px;padding:12px" placeholder="Type your response..."></textarea>` },
66
- { key: 'NPS_SCALE', title: 'NPS', body: `<p style="font-weight:600;margin-bottom:8px">How likely are you to recommend us?</p><div class="cfm-nps-row">${npsCells}</div>` },
67
- { key: 'CFM_likert', title: 'CFM Likert', body: matrixPreview('Statement item 1') },
68
- { key: 'CFM_bipolar', title: 'CFM Bipolar', body: `<div class="cfm-matrix-row"><div class="cfm-matrix-label" style="width:var(--cfm-matrix-bipolar-width)">Left</div><div class="cfm-matrix-cells">${cells(5)}</div><div class="cfm-matrix-label" style="width:var(--cfm-matrix-bipolar-width);text-align:right">Right</div></div>` },
69
- { key: 'CFM_transpose', title: 'CFM Transpose', body: matrixPreview('Scale item (transposed)') },
70
- { key: 'CFM_carousel', title: 'CFM Carousel', body: `${matrixPreview('Row 1 of 3')}<div style="text-align:center;margin-top:12px"><span style="color:var(--cfm-matrix-carousel-dot)">●</span> ○ ○</div>` },
71
- { key: 'CFM_dropdown', title: 'CFM Dropdown', body: `<div class="cfm-matrix-label">Statement 1</div><select class="cfm-input" style="margin-top:8px"><option>Select...</option></select>` },
72
- { key: 'CSAT_emoji', title: 'CSAT Emoji', body: `<div class="cfm-matrix-row"><div class="cfm-matrix-label">How satisfied?</div><div class="cfm-matrix-cells"><span class="cfm-emoji selected">😞</span><span class="cfm-emoji">🙂</span><span class="cfm-emoji">😊</span></div></div>` },
73
- { key: 'CSAT_star', title: 'CSAT Star', body: `<div class="cfm-matrix-row"><div class="cfm-matrix-label">Rate us</div><div class="cfm-matrix-cells"><span class="cfm-star selected">★</span><span class="cfm-star selected">★</span><span class="cfm-star">★</span></div></div>` },
74
- { key: 'CSAT_numbered', title: 'CSAT Numbered', body: `<div class="cfm-matrix-row"><div class="cfm-matrix-label">Score</div><div class="cfm-matrix-cells"><div class="cfm-badge">1</div><div class="cfm-badge">2</div><div class="cfm-badge">3</div></div></div>` },
100
+ { key: 'TEXTFIELD_short', title: 'Text Short', body: `<p style="font-weight:600;margin-bottom:8px">Your name</p><input class="cfm-input" data-cfm-textfield placeholder="Type here..." />` },
101
+ { key: 'TEXTFIELD_long', title: 'Text Long', body: `<p style="font-weight:600;margin-bottom:8px">Comments</p><textarea class="cfm-input" data-cfm-textfield style="height:120px;padding:12px" placeholder="Type your response..."></textarea>` },
102
+ { key: 'NPS_SCALE', title: 'NPS', body: npsPreview() },
103
+ { key: 'CFM_likert', title: 'CFM Likert', body: `${matrixPreview('Statement item 1', 'radio')}<div class="cfm-matrix-row" data-cfm-matrix-row-extra style="display:none"><div class="cfm-matrix-label">Statement item 2</div><div class="cfm-matrix-cells">${cells(5)}</div></div>` },
104
+ { key: 'CFM_bipolar', title: 'CFM Bipolar', body: `<div class="cfm-matrix-row"><div class="cfm-matrix-label" style="width:var(--cfm-matrix-bipolar-width,25%)">Left statement</div><div class="cfm-matrix-cells">${cells(5)}</div><div class="cfm-matrix-label" style="width:var(--cfm-matrix-bipolar-width,25%);text-align:right">Right statement</div></div>` },
105
+ { key: 'CFM_transpose', title: 'CFM Transpose', body: matrixPreview('Scale item (transposed row)', 'radio') },
106
+ { key: 'CFM_carousel', title: 'CFM Carousel', body: `${matrixPreview('Row 1 of 3', 'radio')}<div style="text-align:center;margin-top:12px"><span style="color:var(--cfm-matrix-selected)">●</span> ○ ○</div>` },
107
+ { key: 'CFM_dropdown', title: 'CFM Dropdown', body: `<div class="cfm-matrix-label" style="width:100%;margin-bottom:8px">Statement 1</div><select class="cfm-input"><option>Select...</option><option>Col 1</option><option>Col 2</option></select>` },
108
+ { key: 'CSAT_emoji', title: 'CSAT Emoji', body: `<div class="cfm-matrix-row" style="background:transparent;margin-bottom:4px"><div class="cfm-matrix-label"></div><div class="cfm-matrix-cells">${colLabels(5)}</div></div><div class="cfm-matrix-row"><div class="cfm-matrix-label">How satisfied?</div><div class="cfm-matrix-cells"><span class="cfm-emoji selected">😞</span><span class="cfm-emoji">😐</span><span class="cfm-emoji">🙂</span><span class="cfm-emoji">😊</span><span class="cfm-emoji">🤩</span></div></div>` },
109
+ { key: 'CSAT_star', title: 'CSAT Star', body: `<div class="cfm-matrix-row"><div class="cfm-matrix-label">Rate us</div><div class="cfm-matrix-cells"><span class="cfm-star selected">★</span><span class="cfm-star selected">★</span><span class="cfm-star">★</span><span class="cfm-star">★</span><span class="cfm-star">★</span></div></div>` },
110
+ { key: 'CSAT_numbered', title: 'CSAT Numbered', body: `<div class="cfm-matrix-row"><div class="cfm-matrix-label">Score</div><div class="cfm-matrix-cells">${[1,2,3,4,5].map((n,i)=>`<div class="cfm-badge${i===2?' selected':''}" data-cfm-number-badge="${i}">${n}</div>`).join('')}</div></div>` },
75
111
  { key: 'CSAT_graphics', title: 'CSAT Graphics', body: `<div class="cfm-matrix-row"><div class="cfm-matrix-label">Slider row</div><div style="flex:1;padding:0 16px"><div class="cfm-slider-track"><div class="cfm-slider-thumb"></div></div></div></div>` },
76
- { key: 'CSAT_carousel', title: 'CSAT Carousel', body: `${matrixPreview('Carousel row')}<div style="text-align:center"><span style="color:var(--cfm-csat-accent)">●</span> ○</div>` },
112
+ { key: 'CSAT_carousel', title: 'CSAT Carousel', body: `${matrixPreview('Carousel row', 'emoji')}<div style="text-align:center"><span style="color:var(--cfm-matrix-selected)">●</span> ○</div>` },
77
113
  { key: 'CSAT_dropdown', title: 'CSAT Dropdown', body: `<div class="cfm-matrix-label">Statement</div><select class="cfm-input" style="margin-top:8px"><option>Choose rating...</option></select>` },
78
- { key: 'RATING_star', title: 'Rating Star', body: `<div class="cfm-matrix-cells" style="justify-content:flex-start;padding:16px"><span class="cfm-star selected">★</span><span class="cfm-star selected">★</span><span class="cfm-star">★</span></div>` },
79
- { key: 'RATING_numbered', title: 'Rating Numbered', body: `<div class="cfm-matrix-cells" style="padding:16px"><div class="cfm-badge">1</div><div class="cfm-badge">2</div><div class="cfm-badge">3</div></div>` },
80
- { key: 'RATING_radio', title: 'Rating Radio', body: matrixPreview('Recommend to a friend') },
114
+ { key: 'RATING_star', title: 'Rating Star', body: `<div class="cfm-matrix-cells" style="justify-content:flex-start;padding:16px"><span class="cfm-star selected">★</span><span class="cfm-star selected">★</span><span class="cfm-star">★</span><span class="cfm-star">★</span><span class="cfm-star">★</span></div>` },
115
+ { key: 'RATING_numbered', title: 'Rating Numbered', body: `<div class="cfm-matrix-cells" style="padding:16px">${[1,2,3,4,5].map((n,i)=>`<div class="cfm-badge${i===1?' selected':''}" data-cfm-number-badge="${i}">${n}</div>`).join('')}</div>` },
116
+ { key: 'RATING_radio', title: 'Rating Radio', body: matrixPreview('Recommend to a friend', 'radio') },
81
117
  { key: 'RATING_emoji', title: 'Rating Emoji', body: `<div class="cfm-matrix-cells" style="padding:16px"><span class="cfm-emoji">🙂</span><span class="cfm-emoji selected">😊</span><span class="cfm-emoji">🤩</span></div>` },
82
118
  { key: 'RATING_graphics', title: 'Rating Graphics', body: `<div class="cfm-matrix-row"><div class="cfm-matrix-label">Experience</div><div style="flex:1;padding:0 16px"><div class="cfm-slider-track"><div class="cfm-slider-thumb"></div></div></div></div>` },
83
119
  { key: 'RATING_slider', title: 'Rating Slider', body: `<div class="cfm-matrix-row"><div class="cfm-matrix-label">Rate on slider</div><div style="flex:1;padding:0 16px"><div class="cfm-slider-track"><div class="cfm-slider-thumb"></div></div></div></div>` },
84
- { key: 'SLIDER_matrix', title: 'Slider Matrix', body: `<div class="cfm-matrix-row"><div class="cfm-matrix-label">Statement 1</div><div style="flex:1;padding:0 16px"><div class="cfm-slider-track"><div class="cfm-slider-thumb"></div></div></div></div>` },
85
- { key: 'FILE_UPLOAD', title: 'File Upload', body: `<div data-cfm-dropzone-style="dashed" class="cfm-dropzone">Drop files here or click to upload</div>` },
86
- { key: 'TEXT_AND_MEDIA', title: 'Text & Media', body: `<div class="cfm-media">Media preview</div>` },
87
- { key: 'HEATMAP', title: 'Heatmap', body: `<div style="position:relative;background:#e5e7eb;height:240px;border-radius:var(--cfm-border-radius)"><div class="cfm-pin" style="top:40%;left:55%"></div></div>` },
120
+ { key: 'SLIDER_matrix', title: 'Slider Matrix', body: `<div style="display:flex;gap:4px;margin-bottom:16px;padding-left:var(--cfm-matrix-row-width,180px)">${[0,1,2,3,4,5].map((n,i)=>`<div class="cfm-tick" data-cfm-number-badge="${i}">${n}</div>`).join('')}</div><div style="display:flex;gap:8px;margin-bottom:8px;padding-left:var(--cfm-matrix-row-width,180px)"><span class="cfm-slider-tick-label">Low</span><span class="cfm-slider-tick-label" style="margin-left:auto">High</span></div><div class="cfm-matrix-row"><div class="cfm-matrix-label">Statement 1</div><div style="flex:1;padding:0 16px"><div class="cfm-slider-track"><div class="cfm-slider-thumb"></div></div></div></div><div class="cfm-matrix-row" data-cfm-matrix-row-extra style="display:none"><div class="cfm-matrix-label">Statement 2</div><div style="flex:1;padding:0 16px"><div class="cfm-slider-track"><div class="cfm-slider-thumb" style="left:30%"></div></div></div></div>` },
121
+ { key: 'FILE_UPLOAD', title: 'File Upload', body: `<div class="cfm-dropzone" data-cfm-dropzone>Drop files here or click to upload</div>` },
122
+ { key: 'TEXT_AND_MEDIA', title: 'Text & Media', body: `<div class="cfm-media">Media preview</div><p style="font-size:var(--cfm-media-caption-size,12px);color:var(--cfm-media-caption-color,#6b7280);margin-top:8px">Caption text</p>` },
123
+ { key: 'HEATMAP', title: 'Heatmap', body: `<div style="position:relative;background:#e5e7eb;height:240px;border-radius:var(--cfm-border-radius)"><div class="cfm-pin" style="top:40%;left:55%"></div><div class="cfm-pin" style="top:60%;left:30%"></div></div>` },
88
124
  { key: 'RANK_drag', title: 'Rank Drag', body: `<div class="cfm-rank-item"><span class="cfm-handle">⠿</span><span class="cfm-rank-badge">1</span> Item A</div><div class="cfm-rank-item"><span class="cfm-handle">⠿</span><span class="cfm-rank-badge">2</span> Item B</div>` },
89
- { key: 'RANK_dropdown', title: 'Rank Dropdown', body: `<div style="display:flex;align-items:center;gap:12px"><span>Item A</span><select class="cfm-input" style="width:80px"><option>1</option></select></div>` },
125
+ { key: 'RANK_dropdown', title: 'Rank Dropdown', body: `<div class="cfm-rank-item"><span>Item A</span><select class="cfm-input cfm-rank-select" style="width:80px;margin-left:auto"><option>1</option><option>2</option></select></div>` },
90
126
  ];
91
127
 
92
128
  function buildPreviewHtml(def, inlineCss, inlineJs) {
@@ -105,4 +141,4 @@ function buildPreviewHtml(def, inlineCss, inlineJs) {
105
141
  </html>`;
106
142
  }
107
143
 
108
- module.exports = { PREVIEW_PAGE_DEFS, buildPreviewHtml, sharedStyles, PREVIEW_KEYS: PREVIEW_PAGE_DEFS.map((d) => d.key) };
144
+ module.exports = { PREVIEW_PAGE_DEFS, buildPreviewHtml, sharedStyles, PREVIEW_KEYS: PREVIEW_PAGE_DEFS.map((d) => d.key), TRAFFIC };
@@ -3,9 +3,8 @@ export type PreviewPageDef = { key: string; title: string; body: string };
3
3
 
4
4
  const sharedStyles = `
5
5
  .cfm-header { height:var(--cfm-header-height); background:var(--cfm-header-bg); border-bottom:1px solid var(--cfm-header-border); display:flex; align-items:center; padding:0 var(--cfm-header-padding-x); box-shadow:0 2px 8px rgba(0,0,0,0.06); }
6
- .cfm-header-brand { display:flex; align-items:center; gap:var(--cfm-header-brand-gap,16px); }
7
- .cfm-header-logo { width:var(--cfm-header-logo-width); height:var(--cfm-header-logo-height); object-fit:contain; padding:var(--cfm-header-logo-padding); background:var(--cfm-header-logo-bg,transparent); }
8
- .cfm-header-company { font-size:var(--cfm-header-company-size); font-weight:var(--cfm-header-company-weight); color:var(--cfm-header-company-color); }
6
+ .cfm-header-logo { width:var(--cfm-header-logo-width); height:var(--cfm-header-logo-height); object-fit:contain; padding:var(--cfm-header-logo-padding); background:var(--cfm-primary); }
7
+ .cfm-header-company { font-size:var(--cfm-header-company-size); font-weight:var(--cfm-header-company-weight); color:var(--cfm-header-company-color); margin-left:16px; }
9
8
  .cfm-footer { background:var(--cfm-footer-bg); color:var(--cfm-footer-text); padding:var(--cfm-footer-padding-y) var(--cfm-footer-padding-x); display:flex; flex-wrap:wrap; gap:16px; justify-content:space-between; align-items:flex-end; }
10
9
  .cfm-footer-links { display:flex; gap:24px; flex-wrap:wrap; }
11
10
  .cfm-footer-links a { color:var(--cfm-footer-link); text-decoration:none; font-size:12px; }
@@ -29,7 +28,7 @@ const sharedStyles = `
29
28
  .cfm-slider-track { height:8px; background:var(--cfm-slider-track); border-radius:4px; position:relative; margin:24px 0; }
30
29
  .cfm-slider-thumb { width:20px; height:20px; background:var(--cfm-slider-thumb); border-radius:50%; position:absolute; top:-6px; left:60%; }
31
30
  .cfm-tick { width:var(--cfm-slider-tick-size); height:var(--cfm-slider-tick-size); background:var(--cfm-slider-tick); border-radius:4px; font-size:11px; display:flex; align-items:center; justify-content:center; color:white; }
32
- .cfm-dropzone { border:2px dashed var(--cfm-upload-border-color); border-radius:var(--cfm-border-radius); padding:var(--cfm-upload-padding); background:var(--cfm-upload-fill); text-align:center; color:var(--cfm-text); }
31
+ .cfm-dropzone { border:2px dashed var(--cfm-upload-accent); border-radius:var(--cfm-border-radius); padding:var(--cfm-upload-padding); text-align:center; color:var(--cfm-text); }
33
32
  .cfm-input { width:100%; height:var(--cfm-input-height); border:1px solid var(--cfm-input-border); border-radius:var(--cfm-input-radius); padding:0 var(--cfm-input-padding); font-family:inherit; }
34
33
  .cfm-input:focus { outline:2px solid var(--cfm-input-focus-ring); }
35
34
  .cfm-rank-item { background:var(--cfm-rank-item-bg); padding:var(--cfm-rank-item-padding); margin-bottom:var(--cfm-rank-item-gap); border-radius:var(--cfm-border-radius); display:flex; align-items:center; gap:12px; }
@@ -60,7 +59,10 @@ export const PREVIEW_PAGE_DEFS: PreviewPageDef[] = [
60
59
  {
61
60
  key: 'header',
62
61
  title: 'Header',
63
- body: `<header class="cfm-header"><div class="cfm-header-brand"><img data-cfm-logo class="cfm-header-logo" src="" alt="Logo" style="display:none" /><span data-cfm-company class="cfm-header-company"></span></div></header>`,
62
+ body: `<header class="cfm-header">
63
+ <img data-cfm-logo class="cfm-header-logo" src="" alt="Logo" style="display:none" />
64
+ <span data-cfm-company class="cfm-header-company">Company Name</span>
65
+ </header>`,
64
66
  },
65
67
  {
66
68
  key: 'footer',
@@ -46,7 +46,28 @@ function QuestionPreviewInner({ question, initialValue }: QuestionPreviewProps)
46
46
  };
47
47
  }
48
48
 
49
- if (previewState.hintMinText || previewState.hintMaxText) {
49
+ if ('statementRows' in q && Array.isArray(q.statementRows)) {
50
+ const rows = q.statementRows;
51
+ if (previewState.multiStatement) {
52
+ q = {
53
+ ...q,
54
+ statementRows:
55
+ rows.length >= 2
56
+ ? rows
57
+ : [
58
+ ...rows,
59
+ {
60
+ id: 'r2',
61
+ statementText: 'Second statement row',
62
+ },
63
+ ],
64
+ } as typeof q;
65
+ } else {
66
+ q = { ...q, statementRows: rows.slice(0, 1) } as typeof q;
67
+ }
68
+ }
69
+
70
+ if (previewState.hintMinText !== undefined || previewState.hintMaxText !== undefined) {
50
71
  if (q.type === 'NPS_SCALE') {
51
72
  q = {
52
73
  ...q,
@@ -65,19 +86,6 @@ function QuestionPreviewInner({ question, initialValue }: QuestionPreviewProps)
65
86
  }
66
87
  }
67
88
 
68
- const matrixTypes = new Set(['CFM_MATRIX', 'CSAT_MATRIX', 'RATING_MATRIX', 'SLIDER_MATRIX']);
69
- if ('statementRows' in q && matrixTypes.has(q.type)) {
70
- const rows = q.statementRows ?? [];
71
- if (previewState.multiStatement) {
72
- if (rows.length === 1) {
73
- const extra = { id: 'r2', statementText: 'Second statement row' };
74
- q = { ...q, statementRows: [...rows, extra] } as typeof q;
75
- }
76
- } else if (rows.length > 1) {
77
- q = { ...q, statementRows: rows.slice(0, 1) } as typeof q;
78
- }
79
- }
80
-
81
89
  if (previewState.rankVariant === 'drag' && q.type === 'RANK_ORDER') {
82
90
  q = { ...q, interactionMode: 'dragAndDrop', optionDisplay: 'textOnly' };
83
91
  }
@@ -11,6 +11,15 @@ const base = (partial: Partial<SurveyQuestion> & { type: SurveyQuestion['type']
11
11
  ...partial,
12
12
  }) as SurveyQuestion;
13
13
 
14
+ const MATRIX_ROW_SINGLE = [
15
+ { id: 'r1', statementText: 'The product is easy to use' },
16
+ ];
17
+
18
+ const MATRIX_ROWS_MULTI = [
19
+ { id: 'r1', statementText: 'The product is easy to use' },
20
+ { id: 'r2', statementText: 'Support was helpful' },
21
+ ];
22
+
14
23
  export const FIXTURE_INTRO: SpecialPage = {
15
24
  id: 'preview-intro',
16
25
  header: 'Welcome to our survey',
@@ -94,7 +103,7 @@ export const FIXTURE_TEXT_LONG = base({
94
103
 
95
104
  export const FIXTURE_CFM_LIKERT = base({
96
105
  type: 'CFM_MATRIX',
97
- statementRows: [{ id: 'r1', statementText: 'The product is easy to use' }],
106
+ statementRows: MATRIX_ROW_SINGLE,
98
107
  scaleColumns: [
99
108
  { id: 'c1', label: 'Strongly disagree' },
100
109
  { id: 'c2', label: 'Disagree' },
@@ -227,7 +236,9 @@ export const FIXTURE_RATING_SLIDER = base({
227
236
 
228
237
  export const FIXTURE_SLIDER = base({
229
238
  type: 'SLIDER_MATRIX',
230
- statementRows: [{ id: 'r1', statementText: 'Ease of use', min: 0, max: 10, step: 1 }],
239
+ statementRows: [
240
+ { id: 'r1', statementText: 'Ease of use', min: 0, max: 10, step: 1 },
241
+ ],
231
242
  scaleAnchorLabels: ['Not at all', 'Extremely'],
232
243
  ticks: {
233
244
  count: 11,
@@ -75,20 +75,15 @@ html.cfm-live-edit .max-w-4xl {
75
75
  max-width: var(--cfm-max-width, 48rem) !important;
76
76
  }
77
77
 
78
- html.cfm-live-edit .cfm-header-brand {
79
- display: flex !important;
80
- align-items: center !important;
81
- gap: var(--cfm-header-brand-gap, 16px) !important;
82
- }
83
-
84
- html.cfm-live-edit [data-cfm-logo] {
85
- background: var(--cfm-header-logo-bg, transparent) !important;
86
- object-fit: contain !important;
78
+ html.cfm-live-edit .cfm-header-inner {
79
+ flex-direction: var(--cfm-header-flex-direction, column) !important;
80
+ justify-content: var(--cfm-header-justify, flex-start) !important;
81
+ align-items: var(--cfm-header-brand-align, flex-start) !important;
87
82
  }
88
83
 
89
84
  html.cfm-live-edit [data-cfm-company] {
90
- margin-top: 0 !important;
91
- margin-left: 0 !important;
85
+ margin-top: var(--cfm-header-company-margin-top, 8px) !important;
86
+ margin-left: var(--cfm-header-company-margin-left, 0) !important;
92
87
  }
93
88
 
94
89
  html.cfm-live-edit .cfm-footer-inner {