pict-section-formeditor 1.0.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 (178) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +118 -0
  3. package/docs/.nojekyll +0 -0
  4. package/docs/README.md +162 -0
  5. package/docs/_sidebar.md +23 -0
  6. package/docs/_topbar.md +5 -0
  7. package/docs/cover.md +12 -0
  8. package/docs/css/docuserve.css +73 -0
  9. package/docs/index.html +39 -0
  10. package/docs/retold-catalog.json +224 -0
  11. package/docs/retold-keyword-index.json +46846 -0
  12. package/example_applications/form_editor/.quackage.json +10 -0
  13. package/example_applications/form_editor/FormEditor-Example-Application.js +226 -0
  14. package/example_applications/form_editor/html/icon-chooser.html +375 -0
  15. package/example_applications/form_editor/html/index.html +54 -0
  16. package/example_applications/form_editor/package.json +50 -0
  17. package/package.json +55 -0
  18. package/sample_manifests/Complex-Table.json +974 -0
  19. package/sample_manifests/Distill-Example.json +200 -0
  20. package/sample_manifests/Gradebook-Assignment.json +38 -0
  21. package/sample_manifests/Gradebook-Student.json +40 -0
  22. package/sample_manifests/Manyfest-Editor.json +347 -0
  23. package/sample_manifests/Simple-Form.json +232 -0
  24. package/sample_manifests/Simple-Table.json +79 -0
  25. package/source/Pict-Section-FormEditor-DefaultConfiguration.js +3321 -0
  26. package/source/Pict-Section-FormEditor.js +35 -0
  27. package/source/providers/Pict-Provider-ChildPictManager-Application.js +40 -0
  28. package/source/providers/Pict-Provider-ChildPictManager.js +238 -0
  29. package/source/providers/Pict-Provider-FormEditorDocumentation.js +356 -0
  30. package/source/providers/Pict-Provider-FormEditorDragDrop.js +535 -0
  31. package/source/providers/Pict-Provider-FormEditorIconography.js +1002 -0
  32. package/source/providers/Pict-Provider-FormEditorManifestOps.js +1443 -0
  33. package/source/providers/Pict-Provider-FormEditorRendering.js +730 -0
  34. package/source/providers/Pict-Provider-FormEditorUtilities.js +862 -0
  35. package/source/providers/Pict-Provider-PreviewCSS.js +42 -0
  36. package/source/views/PictView-FormEditor-InlineEditing.js +309 -0
  37. package/source/views/PictView-FormEditor-InputTypePicker.js +532 -0
  38. package/source/views/PictView-FormEditor-PropertiesPanel.js +7730 -0
  39. package/source/views/PictView-FormEditor.js +681 -0
  40. package/test/Pict-Section-FormEditor_tests.js +4102 -0
  41. package/user-documentation/.pict_documentation_topics.json +695 -0
  42. package/user-documentation/Getting-Started.md +32 -0
  43. package/user-documentation/Groups.md +52 -0
  44. package/user-documentation/Inputs.md +98 -0
  45. package/user-documentation/Sections.md +36 -0
  46. package/user-documentation/Shortcuts.md +44 -0
  47. package/user-documentation/Solver-Expression-Walkthrough.md +176 -0
  48. package/user-documentation/Solver-Expressions-Advanced.md +344 -0
  49. package/user-documentation/Solver-Functions.md +213 -0
  50. package/user-documentation/Solvers.md +81 -0
  51. package/user-documentation/ToC.md +18 -0
  52. package/user-documentation/solverfunctions/abs.md +84 -0
  53. package/user-documentation/solverfunctions/aggregationhistogram.md +83 -0
  54. package/user-documentation/solverfunctions/aggregationhistogrambyobject.md +64 -0
  55. package/user-documentation/solverfunctions/arrayconcat.md +64 -0
  56. package/user-documentation/solverfunctions/avg.md +81 -0
  57. package/user-documentation/solverfunctions/bucketset.md +69 -0
  58. package/user-documentation/solverfunctions/ceil.md +70 -0
  59. package/user-documentation/solverfunctions/cleanvaluearray.md +66 -0
  60. package/user-documentation/solverfunctions/cleanvalueobject.md +68 -0
  61. package/user-documentation/solverfunctions/colorgroupbackground.md +60 -0
  62. package/user-documentation/solverfunctions/colorinputbackground.md +62 -0
  63. package/user-documentation/solverfunctions/colorinputbackgroundtabular.md +64 -0
  64. package/user-documentation/solverfunctions/colorsectionbackground.md +59 -0
  65. package/user-documentation/solverfunctions/compare.md +72 -0
  66. package/user-documentation/solverfunctions/concat.md +73 -0
  67. package/user-documentation/solverfunctions/concatraw.md +73 -0
  68. package/user-documentation/solverfunctions/cos.md +75 -0
  69. package/user-documentation/solverfunctions/count.md +73 -0
  70. package/user-documentation/solverfunctions/countset.md +65 -0
  71. package/user-documentation/solverfunctions/countsetelements.md +63 -0
  72. package/user-documentation/solverfunctions/createarrayfromabsolutevalues.md +63 -0
  73. package/user-documentation/solverfunctions/createvalueobjectbyhashes.md +69 -0
  74. package/user-documentation/solverfunctions/cumulativesummation.md +96 -0
  75. package/user-documentation/solverfunctions/dateadddays.md +79 -0
  76. package/user-documentation/solverfunctions/dateaddhours.md +74 -0
  77. package/user-documentation/solverfunctions/dateaddmilliseconds.md +65 -0
  78. package/user-documentation/solverfunctions/dateaddminutes.md +72 -0
  79. package/user-documentation/solverfunctions/dateaddmonths.md +74 -0
  80. package/user-documentation/solverfunctions/dateaddseconds.md +66 -0
  81. package/user-documentation/solverfunctions/dateaddweeks.md +73 -0
  82. package/user-documentation/solverfunctions/dateaddyears.md +74 -0
  83. package/user-documentation/solverfunctions/datedaydifference.md +84 -0
  84. package/user-documentation/solverfunctions/datefromparts.md +81 -0
  85. package/user-documentation/solverfunctions/datehourdifference.md +64 -0
  86. package/user-documentation/solverfunctions/datemathadd.md +72 -0
  87. package/user-documentation/solverfunctions/datemilliseconddifference.md +64 -0
  88. package/user-documentation/solverfunctions/dateminutedifference.md +64 -0
  89. package/user-documentation/solverfunctions/datemonthdifference.md +66 -0
  90. package/user-documentation/solverfunctions/dateseconddifference.md +64 -0
  91. package/user-documentation/solverfunctions/dateweekdifference.md +65 -0
  92. package/user-documentation/solverfunctions/dateyeardifference.md +64 -0
  93. package/user-documentation/solverfunctions/differencearrays.md +59 -0
  94. package/user-documentation/solverfunctions/disablesolverordinal.md +58 -0
  95. package/user-documentation/solverfunctions/distributionhistogram.md +96 -0
  96. package/user-documentation/solverfunctions/distributionhistogrambyobject.md +64 -0
  97. package/user-documentation/solverfunctions/enablesolverordinal.md +57 -0
  98. package/user-documentation/solverfunctions/entryinset.md +72 -0
  99. package/user-documentation/solverfunctions/euler.md +77 -0
  100. package/user-documentation/solverfunctions/exp.md +74 -0
  101. package/user-documentation/solverfunctions/findfirstvaluebyexactmatch.md +67 -0
  102. package/user-documentation/solverfunctions/findfirstvaluebystringincludes.md +67 -0
  103. package/user-documentation/solverfunctions/flatten.md +76 -0
  104. package/user-documentation/solverfunctions/floor.md +70 -0
  105. package/user-documentation/solverfunctions/gaussianelimination.md +75 -0
  106. package/user-documentation/solverfunctions/generatearrayofobjectsfromsets.md +70 -0
  107. package/user-documentation/solverfunctions/generatehtmlhexcolor.md +67 -0
  108. package/user-documentation/solverfunctions/getvalue.md +90 -0
  109. package/user-documentation/solverfunctions/getvaluearray.md +64 -0
  110. package/user-documentation/solverfunctions/getvalueobject.md +67 -0
  111. package/user-documentation/solverfunctions/hidesections.md +58 -0
  112. package/user-documentation/solverfunctions/if.md +109 -0
  113. package/user-documentation/solverfunctions/iterativeseries.md +107 -0
  114. package/user-documentation/solverfunctions/join.md +75 -0
  115. package/user-documentation/solverfunctions/joinraw.md +64 -0
  116. package/user-documentation/solverfunctions/largestinset.md +63 -0
  117. package/user-documentation/solverfunctions/leastsquares.md +66 -0
  118. package/user-documentation/solverfunctions/linest.md +58 -0
  119. package/user-documentation/solverfunctions/log.md +74 -0
  120. package/user-documentation/solverfunctions/logvalues.md +65 -0
  121. package/user-documentation/solverfunctions/match.md +71 -0
  122. package/user-documentation/solverfunctions/matrixinverse.md +67 -0
  123. package/user-documentation/solverfunctions/matrixmultiply.md +71 -0
  124. package/user-documentation/solverfunctions/matrixtranspose.md +72 -0
  125. package/user-documentation/solverfunctions/matrixvectormultiply.md +69 -0
  126. package/user-documentation/solverfunctions/max.md +73 -0
  127. package/user-documentation/solverfunctions/mean.md +63 -0
  128. package/user-documentation/solverfunctions/median.md +79 -0
  129. package/user-documentation/solverfunctions/min.md +73 -0
  130. package/user-documentation/solverfunctions/mode.md +66 -0
  131. package/user-documentation/solverfunctions/objectkeystoarray.md +66 -0
  132. package/user-documentation/solverfunctions/objectvaluessortbyexternalobjectarray.md +65 -0
  133. package/user-documentation/solverfunctions/objectvaluestoarray.md +67 -0
  134. package/user-documentation/solverfunctions/percent.md +75 -0
  135. package/user-documentation/solverfunctions/pi.md +77 -0
  136. package/user-documentation/solverfunctions/polynomialregression.md +69 -0
  137. package/user-documentation/solverfunctions/predict.md +71 -0
  138. package/user-documentation/solverfunctions/rad.md +85 -0
  139. package/user-documentation/solverfunctions/randomfloat.md +63 -0
  140. package/user-documentation/solverfunctions/randomfloatbetween.md +72 -0
  141. package/user-documentation/solverfunctions/randomfloatupto.md +65 -0
  142. package/user-documentation/solverfunctions/randominteger.md +56 -0
  143. package/user-documentation/solverfunctions/randomintegerbetween.md +72 -0
  144. package/user-documentation/solverfunctions/randomintegerupto.md +64 -0
  145. package/user-documentation/solverfunctions/refreshtabularsection.md +57 -0
  146. package/user-documentation/solverfunctions/resolvehtmlentities.md +64 -0
  147. package/user-documentation/solverfunctions/round.md +111 -0
  148. package/user-documentation/solverfunctions/runsolvers.md +49 -0
  149. package/user-documentation/solverfunctions/setconcatenate.md +64 -0
  150. package/user-documentation/solverfunctions/setgroupvisibility.md +60 -0
  151. package/user-documentation/solverfunctions/setsectionvisibility.md +59 -0
  152. package/user-documentation/solverfunctions/setsolverordinalenabled.md +59 -0
  153. package/user-documentation/solverfunctions/settabularrowlength.md +57 -0
  154. package/user-documentation/solverfunctions/setvalue.md +65 -0
  155. package/user-documentation/solverfunctions/showsections.md +58 -0
  156. package/user-documentation/solverfunctions/sin.md +83 -0
  157. package/user-documentation/solverfunctions/slice.md +80 -0
  158. package/user-documentation/solverfunctions/smallestinset.md +63 -0
  159. package/user-documentation/solverfunctions/sortarray.md +58 -0
  160. package/user-documentation/solverfunctions/sorthistogram.md +70 -0
  161. package/user-documentation/solverfunctions/sorthistogrambykeys.md +69 -0
  162. package/user-documentation/solverfunctions/sortset.md +75 -0
  163. package/user-documentation/solverfunctions/sqrt.md +85 -0
  164. package/user-documentation/solverfunctions/stdev.md +81 -0
  165. package/user-documentation/solverfunctions/stdeva.md +58 -0
  166. package/user-documentation/solverfunctions/stdevp.md +83 -0
  167. package/user-documentation/solverfunctions/stringcountsegments.md +66 -0
  168. package/user-documentation/solverfunctions/stringgetsegments.md +74 -0
  169. package/user-documentation/solverfunctions/subtractingsummation.md +66 -0
  170. package/user-documentation/solverfunctions/sum.md +78 -0
  171. package/user-documentation/solverfunctions/tan.md +78 -0
  172. package/user-documentation/solverfunctions/tofixed.md +75 -0
  173. package/user-documentation/solverfunctions/unionarrays.md +59 -0
  174. package/user-documentation/solverfunctions/uniquearray.md +58 -0
  175. package/user-documentation/solverfunctions/var.md +67 -0
  176. package/user-documentation/solverfunctions/vara.md +58 -0
  177. package/user-documentation/solverfunctions/varp.md +66 -0
  178. package/user-documentation/solverfunctions/when.md +98 -0
@@ -0,0 +1,10 @@
1
+ {
2
+ "GulpExecutions": [
3
+ {
4
+ "Hash": "default",
5
+ "Name": "Default standard build.",
6
+ "BuildFileLabel": "",
7
+ "BrowsersListRC": "since 2022"
8
+ }
9
+ ]
10
+ }
@@ -0,0 +1,226 @@
1
+ const libPict = require('pict');
2
+ const libPictApplication = require('pict-application');
3
+ const libPictSectionFormEditor = require('../../source/Pict-Section-FormEditor.js');
4
+
5
+ // The list of available manifests for the selector
6
+ const _ManifestList =
7
+ [
8
+ { Name: 'New Form', File: false },
9
+ { Name: 'Simple Form', File: 'manifests/Simple-Form.json' },
10
+ { Name: 'Simple Table', File: 'manifests/Simple-Table.json' },
11
+ { Name: 'Complex Table', File: 'manifests/Complex-Table.json' },
12
+ { Name: 'Manyfest Editor', File: 'manifests/Manyfest-Editor.json' },
13
+ { Name: 'Gradebook - Student', File: 'manifests/Gradebook-Student.json' },
14
+ { Name: 'Gradebook - Assignment', File: 'manifests/Gradebook-Assignment.json' },
15
+ { Name: 'Distill (Entity Bundles)', File: 'manifests/Distill-Example.json' }
16
+ ];
17
+
18
+ class FormEditorExampleApplication extends libPictApplication
19
+ {
20
+ constructor(pFable, pOptions, pServiceHash)
21
+ {
22
+ super(pFable, pOptions, pServiceHash);
23
+
24
+ this._ManifestList = _ManifestList;
25
+ this._FormEditorView = null;
26
+ this._DragDropEnabled = false;
27
+ this._ShowHashes = false;
28
+ }
29
+
30
+ onAfterInitializeAsync(fCallback)
31
+ {
32
+ // Start with an empty manifest; the first sample will be loaded after render
33
+ this.pict.AppData.FormConfig =
34
+ {
35
+ Scope: 'NewForm',
36
+ Sections: [],
37
+ Descriptors: {}
38
+ };
39
+
40
+ // Store the manifest list for the selector template
41
+ this.pict.AppData.ManifestList = this._ManifestList;
42
+
43
+ // Add the FormEditor view
44
+ this._FormEditorView = this.pict.addView('FormEditor',
45
+ {
46
+ ViewIdentifier: 'FormEditor',
47
+ ManifestDataAddress: 'AppData.FormConfig',
48
+ DefaultDestinationAddress: '#FormEditor-Container',
49
+ ActiveTab: 'visual',
50
+ Renderables:
51
+ [
52
+ {
53
+ RenderableHash: 'FormEditor-Container',
54
+ TemplateHash: 'FormEditor-Container-Template',
55
+ DestinationAddress: '#FormEditor-Container',
56
+ RenderMethod: 'replace'
57
+ }
58
+ ]
59
+ }, libPictSectionFormEditor);
60
+
61
+ this._FormEditorView.initialize();
62
+ this._FormEditorView.render();
63
+
64
+ // Render the selector bar
65
+ this.renderSelector();
66
+
67
+ // Load the first file-based sample by default
68
+ let tmpDefaultIndex = 1;
69
+ let tmpSelect = document.getElementById('FormEditor-ManifestSelect');
70
+ if (tmpSelect)
71
+ {
72
+ tmpSelect.value = String(tmpDefaultIndex);
73
+ }
74
+ this.loadManifest(tmpDefaultIndex);
75
+
76
+ return super.onAfterInitializeAsync(fCallback);
77
+ }
78
+
79
+ renderSelector()
80
+ {
81
+ let tmpHTML = '';
82
+ tmpHTML += '<div class="pict-fe-selector-bar">';
83
+ tmpHTML += '<label class="pict-fe-selector-label" for="FormEditor-ManifestSelect">Load Configuration:</label>';
84
+ tmpHTML += '<select class="pict-fe-selector-select" id="FormEditor-ManifestSelect">';
85
+ for (let i = 0; i < this._ManifestList.length; i++)
86
+ {
87
+ tmpHTML += `<option value="${i}">${this._escapeHTML(this._ManifestList[i].Name)}</option>`;
88
+ }
89
+ tmpHTML += '</select>';
90
+ tmpHTML += `<button class="pict-fe-selector-btn" onclick="${this.pict.browserAddress}.PictApplication.loadSelectedManifest()">Load</button>`;
91
+ tmpHTML += `<button class="pict-fe-selector-btn" id="FormEditor-DragDropToggle" onclick="${this.pict.browserAddress}.PictApplication.toggleDragAndDrop()" style="margin-left:auto; background:#8A7F72;">Enable Drag &amp; Drop</button>`;
92
+ tmpHTML += `<button class="pict-fe-selector-btn" id="FormEditor-DisplayModeToggle" onclick="${this.pict.browserAddress}.PictApplication.toggleDisplayMode()" style="background:#8A7F72;">Show Hashes</button>`;
93
+ tmpHTML += '</div>';
94
+
95
+ this.pict.ContentAssignment.assignContent('#FormEditor-Selector', tmpHTML);
96
+ }
97
+
98
+ toggleDragAndDrop()
99
+ {
100
+ this._DragDropEnabled = !this._DragDropEnabled;
101
+
102
+ if (this._FormEditorView)
103
+ {
104
+ this._FormEditorView._DragDropProvider.setDragAndDropEnabled(this._DragDropEnabled);
105
+ }
106
+
107
+ let tmpToggleBtn = document.getElementById('FormEditor-DragDropToggle');
108
+ if (tmpToggleBtn)
109
+ {
110
+ tmpToggleBtn.textContent = this._DragDropEnabled ? 'Disable Drag & Drop' : 'Enable Drag & Drop';
111
+ tmpToggleBtn.style.background = this._DragDropEnabled ? '#E76F51' : '#8A7F72';
112
+ }
113
+ }
114
+
115
+ toggleDisplayMode()
116
+ {
117
+ this._ShowHashes = !this._ShowHashes;
118
+
119
+ if (this._FormEditorView)
120
+ {
121
+ this._FormEditorView._UtilitiesProvider.setInputDisplayMode(this._ShowHashes ? 'hash' : 'name');
122
+ }
123
+
124
+ let tmpToggleBtn = document.getElementById('FormEditor-DisplayModeToggle');
125
+ if (tmpToggleBtn)
126
+ {
127
+ tmpToggleBtn.textContent = this._ShowHashes ? 'Show Names' : 'Show Hashes';
128
+ tmpToggleBtn.style.background = this._ShowHashes ? '#5B6E5D' : '#8A7F72';
129
+ }
130
+ }
131
+
132
+ loadSelectedManifest()
133
+ {
134
+ let tmpSelect = document.getElementById('FormEditor-ManifestSelect');
135
+ if (tmpSelect)
136
+ {
137
+ this.loadManifest(parseInt(tmpSelect.value, 10));
138
+ }
139
+ }
140
+
141
+ loadManifest(pIndex)
142
+ {
143
+ if (pIndex < 0 || pIndex >= this._ManifestList.length)
144
+ {
145
+ return;
146
+ }
147
+
148
+ let tmpEntry = this._ManifestList[pIndex];
149
+
150
+ if (!tmpEntry.File)
151
+ {
152
+ // "New Form" — empty manifest
153
+ this.pict.AppData.FormConfig =
154
+ {
155
+ Scope: 'NewForm',
156
+ Sections: [],
157
+ Descriptors: {}
158
+ };
159
+ this._refreshEditor();
160
+ return;
161
+ }
162
+
163
+ // Fetch the manifest JSON
164
+ let tmpXHR = new XMLHttpRequest();
165
+ tmpXHR.open('GET', tmpEntry.File, true);
166
+ tmpXHR.onreadystatechange = () =>
167
+ {
168
+ if (tmpXHR.readyState === 4)
169
+ {
170
+ if (tmpXHR.status === 200)
171
+ {
172
+ try
173
+ {
174
+ this.pict.AppData.FormConfig = JSON.parse(tmpXHR.responseText);
175
+ this._refreshEditor();
176
+ }
177
+ catch (pError)
178
+ {
179
+ this.log.error(`Error parsing manifest JSON from ${tmpEntry.File}: ${pError.message}`);
180
+ }
181
+ }
182
+ else
183
+ {
184
+ this.log.error(`Error loading manifest from ${tmpEntry.File}: HTTP ${tmpXHR.status}`);
185
+ }
186
+ }
187
+ };
188
+ tmpXHR.send();
189
+ }
190
+
191
+ _refreshEditor()
192
+ {
193
+ if (this._FormEditorView)
194
+ {
195
+ this._FormEditorView.render();
196
+ }
197
+ }
198
+
199
+ _escapeHTML(pString)
200
+ {
201
+ if (typeof pString !== 'string')
202
+ {
203
+ return '';
204
+ }
205
+ return pString
206
+ .replace(/&/g, '&amp;')
207
+ .replace(/</g, '&lt;')
208
+ .replace(/>/g, '&gt;')
209
+ .replace(/"/g, '&quot;');
210
+ }
211
+ }
212
+
213
+ module.exports = FormEditorExampleApplication;
214
+
215
+ module.exports.default_configuration = (
216
+ {
217
+ Name: 'FormEditorExample',
218
+ Hash: 'FormEditorExample',
219
+ MainViewportViewIdentifier: 'FormEditor',
220
+ AutoSolveAfterInitialize: false,
221
+ AutoRenderMainViewportViewAfterInitialize: false,
222
+ pict_configuration:
223
+ {
224
+ Product: 'FormEditorExample'
225
+ }
226
+ });
@@ -0,0 +1,375 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1">
6
+ <title>Form Editor Iconography Chooser</title>
7
+ <style>
8
+ *, *::before, *::after { box-sizing: border-box; }
9
+ body { margin: 0; padding: 0; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif; background: #FDFCFA; color: #3D3229; }
10
+
11
+ /* --- Header --- */
12
+ .header { display: flex; align-items: stretch; background: #264653; border-bottom: 3px solid #E76F51; }
13
+ .header-badge { background: #E76F51; color: #fff; padding: 0.6rem 1rem; font-size: 0.7rem; font-weight: 800; text-transform: uppercase; letter-spacing: 0.1em; display: flex; align-items: center; gap: 0.5rem; }
14
+ .header-badge svg { width: 14px; height: 14px; fill: #fff; flex-shrink: 0; }
15
+ .header-title { padding: 0.6rem 1rem; color: #FAEDCD; font-size: 1.1rem; font-weight: 600; display: flex; align-items: center; }
16
+ .header-sub { margin-left: auto; padding: 0.6rem 1rem; color: #D4A373; font-size: 0.75rem; display: flex; align-items: center; }
17
+
18
+ /* --- Controls --- */
19
+ .controls { display: flex; align-items: center; gap: 16px; padding: 16px 24px; background: #F5F0E8; border-bottom: 1px solid #E8E3DA; flex-wrap: wrap; }
20
+ .control-group { display: flex; align-items: center; gap: 6px; }
21
+ .control-group label { font-size: 12px; font-weight: 600; color: #8A7F72; text-transform: uppercase; letter-spacing: 0.4px; white-space: nowrap; }
22
+ .control-group input[type="range"] { width: 120px; accent-color: #9E6B47; }
23
+ .control-group input[type="color"] { width: 32px; height: 24px; border: 1px solid #D4CFC6; border-radius: 3px; padding: 0; cursor: pointer; }
24
+ .control-group select { padding: 4px 8px; border: 1px solid #D4CFC6; border-radius: 4px; font-size: 13px; color: #3D3229; background: #FFF; }
25
+ .size-display { font-size: 13px; font-weight: 600; color: #3D3229; min-width: 36px; text-align: center; }
26
+
27
+ /* --- Content --- */
28
+ .content { padding: 24px; }
29
+
30
+ /* --- Category Section --- */
31
+ .category { margin-bottom: 32px; }
32
+ .category-header { display: flex; align-items: center; gap: 10px; margin-bottom: 12px; padding-bottom: 6px; border-bottom: 2px solid #E8E3DA; }
33
+ .category-name { font-size: 18px; font-weight: 700; color: #3D3229; }
34
+ .category-desc { font-size: 12px; color: #8A7F72; }
35
+ .category-count { font-size: 11px; color: #9E6B47; background: #F5F0E8; border: 1px solid #E8E3DA; border-radius: 10px; padding: 1px 8px; font-weight: 600; }
36
+
37
+ /* --- Icon Grid --- */
38
+ .icon-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(140px, 1fr)); gap: 12px; }
39
+ .icon-card { display: flex; flex-direction: column; align-items: center; gap: 6px; padding: 16px 12px 12px; border: 2px solid #E8E3DA; border-radius: 8px; background: #FFF; cursor: pointer; transition: border-color 0.15s, box-shadow 0.15s, background 0.15s; }
40
+ .icon-card:hover { border-color: #C5BFAE; box-shadow: 0 2px 8px rgba(61, 50, 41, 0.08); }
41
+ .icon-card.selected { border-color: #9E6B47; background: #FBF6F0; box-shadow: 0 0 0 3px rgba(158, 107, 71, 0.15); }
42
+ .icon-card .icon-preview { display: flex; align-items: center; justify-content: center; min-height: 48px; }
43
+ .icon-card .icon-preview-bg { display: flex; align-items: center; justify-content: center; background: #FDFCFA; border: 1px solid #F0ECE4; border-radius: 6px; padding: 8px; }
44
+ .icon-card .icon-name { font-size: 11px; font-weight: 600; color: #3D3229; text-align: center; }
45
+ .icon-card .icon-sizes { display: flex; gap: 6px; align-items: flex-end; margin-top: 2px; }
46
+ .icon-card .icon-sizes .sz { opacity: 0.4; }
47
+
48
+ /* --- Selection Panel --- */
49
+ .selection-panel { position: sticky; bottom: 0; left: 0; right: 0; background: #FFF; border-top: 2px solid #E8E3DA; padding: 12px 24px; display: none; z-index: 10; box-shadow: 0 -4px 16px rgba(61, 50, 41, 0.08); }
50
+ .selection-panel.visible { display: flex; align-items: center; gap: 16px; flex-wrap: wrap; }
51
+ .selection-summary { display: flex; align-items: center; gap: 16px; flex: 1; flex-wrap: wrap; }
52
+ .selection-item { display: flex; align-items: center; gap: 6px; padding: 4px 10px; background: #F5F0E8; border: 1px solid #E8E3DA; border-radius: 6px; font-size: 12px; font-weight: 500; }
53
+ .selection-item .sel-label { font-weight: 700; color: #9E6B47; }
54
+ .selection-item .sel-variant { color: #3D3229; }
55
+ .btn-clear { padding: 6px 14px; border: 1px solid #C5BFAE; border-radius: 4px; background: #F5F0E8; font-size: 12px; font-weight: 600; color: #3D3229; cursor: pointer; }
56
+ .btn-clear:hover { background: #E8E3DA; }
57
+
58
+ /* --- Dark bg option --- */
59
+ .icon-card .icon-preview-bg.dark-bg { background: #3D3229; border-color: #5A4E42; }
60
+ </style>
61
+ </head>
62
+ <body>
63
+ <div class="header">
64
+ <div class="header-badge">
65
+ <svg viewBox="0 0 16 16"><polygon points="8,1 10,6 16,6 11,9.5 13,15 8,11.5 3,15 5,9.5 0,6 6,6"/></svg>
66
+ Pict Example
67
+ </div>
68
+ <div class="header-title">Iconography Chooser</div>
69
+ <div class="header-sub">pict-section-formeditor</div>
70
+ </div>
71
+
72
+ <div class="controls" id="Controls">
73
+ <div class="control-group">
74
+ <label>Preview Size</label>
75
+ <input type="range" id="SizeSlider" min="12" max="64" value="32" step="1" oninput="onSizeChange(this.value)">
76
+ <span class="size-display" id="SizeDisplay">32px</span>
77
+ </div>
78
+ <div class="control-group">
79
+ <label>Stroke</label>
80
+ <input type="range" id="StrokeSlider" min="0.5" max="3.5" value="1.8" step="0.1" oninput="onStrokeChange(this.value)">
81
+ <span class="size-display" id="StrokeDisplay">1.8</span>
82
+ </div>
83
+ <div class="control-group">
84
+ <label>Primary</label>
85
+ <input type="color" id="ColorPrimary" value="#3D3229" oninput="onColorChange()">
86
+ </div>
87
+ <div class="control-group">
88
+ <label>Accent</label>
89
+ <input type="color" id="ColorAccent" value="#9E6B47" oninput="onColorChange()">
90
+ </div>
91
+ <div class="control-group">
92
+ <label>Muted</label>
93
+ <input type="color" id="ColorMuted" value="#B0A89E" oninput="onColorChange()">
94
+ </div>
95
+ <div class="control-group">
96
+ <label>Fill</label>
97
+ <input type="color" id="ColorFill" value="#F5F0E8" oninput="onColorChange()">
98
+ </div>
99
+ <div class="control-group">
100
+ <label>Background</label>
101
+ <select id="BgMode" onchange="onBgChange()">
102
+ <option value="light">Light</option>
103
+ <option value="dark">Dark</option>
104
+ </select>
105
+ </div>
106
+ </div>
107
+
108
+ <div class="content" id="Content"></div>
109
+
110
+ <div class="selection-panel" id="SelectionPanel">
111
+ <div class="selection-summary" id="SelectionSummary"></div>
112
+ <button class="btn-clear" onclick="clearSelections()">Clear All</button>
113
+ </div>
114
+
115
+ <script>
116
+ // ============================================================
117
+ // Inline the icon factory functions from the provider
118
+ // ============================================================
119
+ function _svg(pSize, pInner)
120
+ {
121
+ return '<svg xmlns="http://www.w3.org/2000/svg" width="' + pSize + '" height="' + pSize + '" viewBox="0 0 24 24" fill="none" stroke-linecap="round" stroke-linejoin="round">' + pInner + '</svg>';
122
+ }
123
+
124
+ var Icons = {
125
+ Section: {
126
+ Default: function(s, c, w) { return _svg(s, '<path d="M14 2H6a2 2 0 00-2 2v16a2 2 0 002 2h12a2 2 0 002-2V8z" fill="'+c.Fill+'" stroke="'+c.Primary+'" stroke-width="'+w+'"/><polyline points="14 2 14 8 20 8" stroke="'+c.Primary+'" stroke-width="'+w+'"/>'); },
127
+ Layers: function(s, c, w) { return _svg(s, '<rect x="6" y="6" width="14" height="14" rx="2" fill="'+c.Fill+'" stroke="'+c.Primary+'" stroke-width="'+w+'"/><path d="M4 16V6a2 2 0 012-2h10" stroke="'+c.Muted+'" stroke-width="'+w+'"/>'); },
128
+ Clipboard: function(s, c, w) { return _svg(s, '<rect x="5" y="4" width="14" height="18" rx="2" fill="'+c.Fill+'" stroke="'+c.Primary+'" stroke-width="'+w+'"/><rect x="9" y="2" width="6" height="4" rx="1" fill="'+c.Fill+'" stroke="'+c.Accent+'" stroke-width="'+w+'"/><line x1="9" y1="11" x2="15" y2="11" stroke="'+c.Muted+'" stroke-width="'+w+'"/><line x1="9" y1="15" x2="13" y2="15" stroke="'+c.Muted+'" stroke-width="'+w+'"/>'); },
129
+ Layout: function(s, c, w) { return _svg(s, '<rect x="3" y="3" width="18" height="18" rx="2" fill="'+c.Fill+'" stroke="'+c.Primary+'" stroke-width="'+w+'"/><line x1="3" y1="9" x2="21" y2="9" stroke="'+c.Muted+'" stroke-width="'+w+'"/><line x1="3" y1="15" x2="21" y2="15" stroke="'+c.Muted+'" stroke-width="'+w+'"/>'); },
130
+ Book: function(s, c, w) { return _svg(s, '<path d="M2 3h6a4 4 0 014 4v14a3 3 0 00-3-3H2z" fill="'+c.Fill+'" stroke="'+c.Primary+'" stroke-width="'+w+'"/><path d="M22 3h-6a4 4 0 00-4 4v14a3 3 0 013-3h7z" fill="'+c.Fill+'" stroke="'+c.Primary+'" stroke-width="'+w+'"/>'); },
131
+ Window: function(s, c, w) { return _svg(s, '<rect x="2" y="3" width="20" height="18" rx="2" fill="'+c.Fill+'" stroke="'+c.Primary+'" stroke-width="'+w+'"/><line x1="2" y1="8" x2="22" y2="8" stroke="'+c.Primary+'" stroke-width="'+w+'"/><circle cx="5.5" cy="5.5" r="0.8" fill="'+c.Accent+'"/><circle cx="8.5" cy="5.5" r="0.8" fill="'+c.Accent+'"/>'); },
132
+ FileText: function(s, c, w) { return _svg(s, '<path d="M14 2H6a2 2 0 00-2 2v16a2 2 0 002 2h12a2 2 0 002-2V8z" fill="'+c.Fill+'" stroke="'+c.Primary+'" stroke-width="'+w+'"/><polyline points="14 2 14 8 20 8" stroke="'+c.Primary+'" stroke-width="'+w+'"/><line x1="8" y1="13" x2="16" y2="13" stroke="'+c.Muted+'" stroke-width="'+w+'"/><line x1="8" y1="17" x2="14" y2="17" stroke="'+c.Muted+'" stroke-width="'+w+'"/>'); },
133
+ Columns: function(s, c, w) { return _svg(s, '<rect x="3" y="3" width="18" height="18" rx="2" fill="'+c.Fill+'" stroke="'+c.Primary+'" stroke-width="'+w+'"/><line x1="12" y1="3" x2="12" y2="21" stroke="'+c.Muted+'" stroke-width="'+w+'"/>'); },
134
+ Bookmark: function(s, c, w) { return _svg(s, '<path d="M5 3a2 2 0 012-2h10a2 2 0 012 2v18l-7-4-7 4V3z" fill="'+c.Fill+'" stroke="'+c.Primary+'" stroke-width="'+w+'"/><line x1="9" y1="7" x2="15" y2="7" stroke="'+c.Accent+'" stroke-width="'+w+'"/>'); },
135
+ Shield: function(s, c, w) { return _svg(s, '<path d="M12 2l8 4v6c0 5.25-3.5 8.25-8 10-4.5-1.75-8-4.75-8-10V6l8-4z" fill="'+c.Fill+'" stroke="'+c.Primary+'" stroke-width="'+w+'"/><line x1="9" y1="11" x2="15" y2="11" stroke="'+c.Muted+'" stroke-width="'+w+'"/>'); }
136
+ },
137
+ Group: {
138
+ Default: function(s, c, w) { return _svg(s, '<path d="M2 6a2 2 0 012-2h5l2 2h9a2 2 0 012 2v10a2 2 0 01-2 2H4a2 2 0 01-2-2V6z" fill="'+c.Fill+'" stroke="'+c.Primary+'" stroke-width="'+w+'"/>'); },
139
+ Panel: function(s, c, w) { return _svg(s, '<rect x="2" y="4" width="20" height="16" rx="3" fill="'+c.Fill+'" stroke="'+c.Primary+'" stroke-width="'+w+'"/><line x1="2" y1="9" x2="22" y2="9" stroke="'+c.Accent+'" stroke-width="'+w+'"/>'); },
140
+ Stack: function(s, c, w) { return _svg(s, '<rect x="4" y="2" width="16" height="6" rx="1.5" fill="'+c.Fill+'" stroke="'+c.Primary+'" stroke-width="'+w+'"/><rect x="4" y="10" width="16" height="6" rx="1.5" fill="'+c.Fill+'" stroke="'+c.Primary+'" stroke-width="'+w+'"/><rect x="4" y="18" width="16" height="4" rx="1.5" fill="'+c.Fill+'" stroke="'+c.Muted+'" stroke-width="'+w+'"/>'); },
141
+ Grid: function(s, c, w) { return _svg(s, '<rect x="3" y="3" width="8" height="8" rx="1.5" fill="'+c.Fill+'" stroke="'+c.Primary+'" stroke-width="'+w+'"/><rect x="13" y="3" width="8" height="8" rx="1.5" fill="'+c.Fill+'" stroke="'+c.Primary+'" stroke-width="'+w+'"/><rect x="3" y="13" width="8" height="8" rx="1.5" fill="'+c.Fill+'" stroke="'+c.Primary+'" stroke-width="'+w+'"/><rect x="13" y="13" width="8" height="8" rx="1.5" fill="'+c.Fill+'" stroke="'+c.Primary+'" stroke-width="'+w+'"/>'); },
142
+ Box: function(s, c, w) { return _svg(s, '<rect x="3" y="3" width="18" height="18" rx="2" fill="'+c.Fill+'" stroke="'+c.Primary+'" stroke-width="'+w+'"/><circle cx="12" cy="12" r="1.5" fill="'+c.Accent+'"/>'); },
143
+ Package: function(s, c, w) { return _svg(s, '<path d="M12 2L2 7v10l10 5 10-5V7L12 2z" fill="'+c.Fill+'" stroke="'+c.Primary+'" stroke-width="'+w+'"/><line x1="12" y1="12" x2="12" y2="22" stroke="'+c.Muted+'" stroke-width="'+w+'"/><polyline points="2 7 12 12 22 7" stroke="'+c.Muted+'" stroke-width="'+w+'"/>'); },
144
+ Bracket: function(s, c, w) { return _svg(s, '<path d="M8 3H6a2 2 0 00-2 2v4.5a1.5 1.5 0 01-1.5 1.5 1.5 1.5 0 011.5 1.5V17a2 2 0 002 2h2" stroke="'+c.Primary+'" stroke-width="'+w+'" fill="none"/><path d="M16 3h2a2 2 0 012 2v4.5a1.5 1.5 0 001.5 1.5 1.5 1.5 0 00-1.5 1.5V17a2 2 0 01-2 2h-2" stroke="'+c.Primary+'" stroke-width="'+w+'" fill="none"/>'); },
145
+ Tab: function(s, c, w) { return _svg(s, '<rect x="2" y="7" width="20" height="14" rx="2" fill="'+c.Fill+'" stroke="'+c.Primary+'" stroke-width="'+w+'"/><rect x="3" y="3" width="8" height="5" rx="1.5" fill="'+c.Fill+'" stroke="'+c.Accent+'" stroke-width="'+w+'"/><line x1="13" y1="5" x2="17" y2="5" stroke="'+c.Muted+'" stroke-width="'+w+'"/>'); },
146
+ Sidebar: function(s, c, w) { return _svg(s, '<rect x="3" y="3" width="18" height="18" rx="2" fill="'+c.Fill+'" stroke="'+c.Primary+'" stroke-width="'+w+'"/><line x1="9" y1="3" x2="9" y2="21" stroke="'+c.Primary+'" stroke-width="'+w+'"/><line x1="13" y1="8" x2="17" y2="8" stroke="'+c.Muted+'" stroke-width="'+w+'"/><line x1="13" y1="12" x2="17" y2="12" stroke="'+c.Muted+'" stroke-width="'+w+'"/>'); },
147
+ Puzzle: function(s, c, w) { return _svg(s, '<path d="M4 7h3a2 2 0 100-4h0a2 2 0 100 4h3v3a2 2 0 104 0V7h3a2 2 0 012 2v3a2 2 0 10 0 4v3a2 2 0 01-2 2h-3a2 2 0 100-4H10a2 2 0 100 4H7a2 2 0 01-2-2v-3a2 2 0 100-4V9a2 2 0 01-1-2z" fill="'+c.Fill+'" stroke="'+c.Primary+'" stroke-width="'+w+'"/>'); }
148
+ },
149
+ Row: {
150
+ Default: function(s, c, w) { return _svg(s, '<line x1="3" y1="8" x2="21" y2="8" stroke="'+c.Primary+'" stroke-width="'+w+'"/><line x1="3" y1="12" x2="21" y2="12" stroke="'+c.Accent+'" stroke-width="'+w+'"/><line x1="3" y1="16" x2="21" y2="16" stroke="'+c.Muted+'" stroke-width="'+w+'"/>'); },
151
+ Stripe: function(s, c, w) { return _svg(s, '<rect x="2" y="8" width="20" height="8" rx="2" fill="'+c.Fill+'" stroke="'+c.Primary+'" stroke-width="'+w+'"/>'); },
152
+ Columns: function(s, c, w) { return _svg(s, '<rect x="2" y="6" width="5.5" height="12" rx="1.5" fill="'+c.Fill+'" stroke="'+c.Primary+'" stroke-width="'+w+'"/><rect x="9.25" y="6" width="5.5" height="12" rx="1.5" fill="'+c.Fill+'" stroke="'+c.Primary+'" stroke-width="'+w+'"/><rect x="16.5" y="6" width="5.5" height="12" rx="1.5" fill="'+c.Fill+'" stroke="'+c.Primary+'" stroke-width="'+w+'"/>'); },
153
+ ArrowRight: function(s, c, w) { return _svg(s, '<line x1="3" y1="12" x2="21" y2="12" stroke="'+c.Primary+'" stroke-width="'+w+'"/><polyline points="17 8 21 12 17 16" stroke="'+c.Accent+'" stroke-width="'+w+'" fill="none"/>'); },
154
+ Divider: function(s, c, w) { return _svg(s, '<line x1="2" y1="12" x2="8" y2="12" stroke="'+c.Muted+'" stroke-width="'+w+'"/><circle cx="12" cy="12" r="1.2" fill="'+c.Primary+'"/><line x1="16" y1="12" x2="22" y2="12" stroke="'+c.Muted+'" stroke-width="'+w+'"/>'); },
155
+ Layer: function(s, c, w) { return _svg(s, '<polygon points="12 2 22 8.5 12 15 2 8.5" fill="'+c.Fill+'" stroke="'+c.Primary+'" stroke-width="'+w+'"/><polyline points="2 15.5 12 22 22 15.5" stroke="'+c.Muted+'" stroke-width="'+w+'" fill="none"/>'); },
156
+ Grip: function(s, c, w) { return _svg(s, '<circle cx="8" cy="6" r="1.3" fill="'+c.Primary+'"/><circle cx="16" cy="6" r="1.3" fill="'+c.Primary+'"/><circle cx="8" cy="12" r="1.3" fill="'+c.Primary+'"/><circle cx="16" cy="12" r="1.3" fill="'+c.Primary+'"/><circle cx="8" cy="18" r="1.3" fill="'+c.Primary+'"/><circle cx="16" cy="18" r="1.3" fill="'+c.Primary+'"/>'); },
157
+ Table: function(s, c, w) { return _svg(s, '<rect x="2" y="4" width="20" height="16" rx="2" fill="'+c.Fill+'" stroke="'+c.Primary+'" stroke-width="'+w+'"/><line x1="2" y1="10" x2="22" y2="10" stroke="'+c.Muted+'" stroke-width="'+w+'"/><line x1="2" y1="16" x2="22" y2="16" stroke="'+c.Muted+'" stroke-width="'+w+'"/><line x1="9" y1="4" x2="9" y2="20" stroke="'+c.Muted+'" stroke-width="'+w+'"/>'); },
158
+ Split: function(s, c, w) { return _svg(s, '<rect x="2" y="6" width="9" height="12" rx="1.5" fill="'+c.Fill+'" stroke="'+c.Primary+'" stroke-width="'+w+'"/><rect x="13" y="6" width="9" height="12" rx="1.5" fill="'+c.Fill+'" stroke="'+c.Primary+'" stroke-width="'+w+'"/>'); },
159
+ Slider: function(s, c, w) { return _svg(s, '<line x1="3" y1="12" x2="21" y2="12" stroke="'+c.Muted+'" stroke-width="'+w+'"/><circle cx="9" cy="12" r="3" fill="'+c.Fill+'" stroke="'+c.Accent+'" stroke-width="'+w+'"/>'); }
160
+ },
161
+ Input: {
162
+ Default: function(s, c, w) { return _svg(s, '<rect x="3" y="6" width="18" height="12" rx="2" fill="'+c.Fill+'" stroke="'+c.Primary+'" stroke-width="'+w+'"/><line x1="7" y1="10" x2="7" y2="14" stroke="'+c.Accent+'" stroke-width="'+w+'"/>'); },
163
+ Checkbox: function(s, c, w) { return _svg(s, '<rect x="4" y="4" width="16" height="16" rx="2" fill="'+c.Fill+'" stroke="'+c.Primary+'" stroke-width="'+w+'"/><polyline points="8 12 11 15 16 9" stroke="'+c.Accent+'" stroke-width="'+w+'" fill="none"/>'); },
164
+ Toggle: function(s, c, w) { return _svg(s, '<rect x="2" y="7" width="20" height="10" rx="5" fill="'+c.Fill+'" stroke="'+c.Primary+'" stroke-width="'+w+'"/><circle cx="16" cy="12" r="3.5" fill="'+c.Accent+'"/>'); },
165
+ Dropdown: function(s, c, w) { return _svg(s, '<rect x="3" y="6" width="18" height="12" rx="2" fill="'+c.Fill+'" stroke="'+c.Primary+'" stroke-width="'+w+'"/><polyline points="15 10 18 13 15 16" stroke="'+c.Accent+'" stroke-width="'+w+'" fill="none"/>'); },
166
+ TextArea: function(s, c, w) { return _svg(s, '<rect x="3" y="3" width="18" height="18" rx="2" fill="'+c.Fill+'" stroke="'+c.Primary+'" stroke-width="'+w+'"/><line x1="7" y1="8" x2="17" y2="8" stroke="'+c.Muted+'" stroke-width="'+w+'"/><line x1="7" y1="12" x2="17" y2="12" stroke="'+c.Muted+'" stroke-width="'+w+'"/><line x1="7" y1="16" x2="13" y2="16" stroke="'+c.Muted+'" stroke-width="'+w+'"/>'); },
167
+ Number: function(s, c, w) { return _svg(s, '<rect x="3" y="6" width="18" height="12" rx="2" fill="'+c.Fill+'" stroke="'+c.Primary+'" stroke-width="'+w+'"/><text x="8" y="15.5" font-size="9" font-family="sans-serif" fill="'+c.Primary+'">#</text><polyline points="16 10 18 8 20 10" stroke="'+c.Muted+'" stroke-width="1.2" fill="none"/><polyline points="16 14 18 16 20 14" stroke="'+c.Muted+'" stroke-width="1.2" fill="none"/>'); },
168
+ Radio: function(s, c, w) { return _svg(s, '<circle cx="12" cy="12" r="8" fill="'+c.Fill+'" stroke="'+c.Primary+'" stroke-width="'+w+'"/><circle cx="12" cy="12" r="3.5" fill="'+c.Accent+'"/>'); },
169
+ Calendar: function(s, c, w) { return _svg(s, '<rect x="3" y="4" width="18" height="17" rx="2" fill="'+c.Fill+'" stroke="'+c.Primary+'" stroke-width="'+w+'"/><line x1="3" y1="10" x2="21" y2="10" stroke="'+c.Primary+'" stroke-width="'+w+'"/><line x1="8" y1="2" x2="8" y2="6" stroke="'+c.Accent+'" stroke-width="'+w+'"/><line x1="16" y1="2" x2="16" y2="6" stroke="'+c.Accent+'" stroke-width="'+w+'"/>'); },
170
+ Lock: function(s, c, w) { return _svg(s, '<rect x="5" y="11" width="14" height="10" rx="2" fill="'+c.Fill+'" stroke="'+c.Primary+'" stroke-width="'+w+'"/><path d="M8 11V7a4 4 0 018 0v4" stroke="'+c.Accent+'" stroke-width="'+w+'" fill="none"/><circle cx="12" cy="16" r="1.2" fill="'+c.Primary+'"/>'); },
171
+ Search: function(s, c, w) { return _svg(s, '<circle cx="10.5" cy="10.5" r="6.5" fill="'+c.Fill+'" stroke="'+c.Primary+'" stroke-width="'+w+'"/><line x1="15.5" y1="15.5" x2="21" y2="21" stroke="'+c.Accent+'" stroke-width="'+w+'"/>'); }
172
+ }
173
+ };
174
+
175
+ var InputTypeIcons = {
176
+ TextArea: Icons.Input.TextArea,
177
+ Markdown: function(s, c, w) { return _svg(s, '<rect x="2" y="4" width="20" height="16" rx="2" fill="'+c.Fill+'" stroke="'+c.Primary+'" stroke-width="'+w+'"/><path d="M6 15V9l3 3.5L12 9v6" stroke="'+c.Accent+'" stroke-width="'+w+'" fill="none"/><polyline points="16 12 18 15 20 12" stroke="'+c.Accent+'" stroke-width="'+w+'" fill="none"/>'); },
178
+ HTML: function(s, c, w) { return _svg(s, '<polyline points="8 7 3 12 8 17" stroke="'+c.Primary+'" stroke-width="'+w+'" fill="none"/><polyline points="16 7 21 12 16 17" stroke="'+c.Primary+'" stroke-width="'+w+'" fill="none"/><line x1="14" y1="4" x2="10" y2="20" stroke="'+c.Accent+'" stroke-width="'+w+'"/>'); },
179
+ Option: function(s, c, w) { return _svg(s, '<rect x="3" y="6" width="18" height="12" rx="2" fill="'+c.Fill+'" stroke="'+c.Primary+'" stroke-width="'+w+'"/><polyline points="9 10 12 14 15 10" stroke="'+c.Accent+'" stroke-width="'+w+'" fill="none"/>'); },
180
+ Boolean: Icons.Input.Toggle,
181
+ Color: function(s, c, w) { return _svg(s, '<circle cx="12" cy="12" r="9" fill="'+c.Fill+'" stroke="'+c.Primary+'" stroke-width="'+w+'"/><circle cx="12" cy="8" r="2.5" fill="'+c.Accent+'"/><circle cx="8.5" cy="14" r="2.5" fill="#6B7F5A"/><circle cx="15.5" cy="14" r="2.5" fill="#5A6B7F"/>'); },
182
+ DisplayOnly: function(s, c, w) { return _svg(s, '<path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8S1 12 1 12z" stroke="'+c.Primary+'" stroke-width="'+w+'" fill="none"/><circle cx="12" cy="12" r="3" fill="'+c.Fill+'" stroke="'+c.Accent+'" stroke-width="'+w+'"/>'); },
183
+ ReadOnly: function(s, c, w) { return _svg(s, '<rect x="5" y="11" width="14" height="10" rx="2" fill="'+c.Fill+'" stroke="'+c.Primary+'" stroke-width="'+w+'"/><path d="M8 11V7a4 4 0 018 0v4" stroke="'+c.Accent+'" stroke-width="'+w+'" fill="none"/>'); },
184
+ PreciseNumberReadOnly: function(s, c, w) { return _svg(s, '<rect x="3" y="5" width="18" height="14" rx="2" fill="'+c.Fill+'" stroke="'+c.Primary+'" stroke-width="'+w+'"/><text x="7" y="16" font-size="10" font-family="sans-serif" fill="'+c.Accent+'">.00</text>'); },
185
+ Hidden: function(s, c, w) { return _svg(s, '<path d="M17.94 17.94A10.07 10.07 0 0112 20c-7 0-11-8-11-8a18.45 18.45 0 015.06-5.94" stroke="'+c.Muted+'" stroke-width="'+w+'" fill="none"/><path d="M9.9 4.24A9.12 9.12 0 0112 4c7 0 11 8 11 8a18.5 18.5 0 01-2.16 3.19" stroke="'+c.Muted+'" stroke-width="'+w+'" fill="none"/><line x1="1" y1="1" x2="23" y2="23" stroke="'+c.Primary+'" stroke-width="'+w+'"/>'); },
186
+ Chart: function(s, c, w) { return _svg(s, '<rect x="3" y="12" width="4" height="9" rx="1" fill="'+c.Fill+'" stroke="'+c.Primary+'" stroke-width="'+w+'"/><rect x="10" y="6" width="4" height="15" rx="1" fill="'+c.Fill+'" stroke="'+c.Accent+'" stroke-width="'+w+'"/><rect x="17" y="3" width="4" height="18" rx="1" fill="'+c.Fill+'" stroke="'+c.Primary+'" stroke-width="'+w+'"/>'); },
187
+ Link: function(s, c, w) { return _svg(s, '<path d="M10 13a5 5 0 007.54.54l3-3a5 5 0 00-7.07-7.07l-1.72 1.71" stroke="'+c.Primary+'" stroke-width="'+w+'" fill="none"/><path d="M14 11a5 5 0 00-7.54-.54l-3 3a5 5 0 007.07 7.07l1.71-1.71" stroke="'+c.Accent+'" stroke-width="'+w+'" fill="none"/>'); },
188
+ TabSectionSelector: function(s, c, w) { return _svg(s, '<rect x="2" y="8" width="20" height="13" rx="2" fill="'+c.Fill+'" stroke="'+c.Primary+'" stroke-width="'+w+'"/><rect x="3" y="3" width="6" height="6" rx="1" fill="'+c.Fill+'" stroke="'+c.Accent+'" stroke-width="'+w+'"/><rect x="10" y="3" width="6" height="6" rx="1" fill="none" stroke="'+c.Muted+'" stroke-width="'+w+'"/>'); },
189
+ TabGroupSelector: function(s, c, w) { return _svg(s, '<rect x="2" y="8" width="20" height="13" rx="2" fill="'+c.Fill+'" stroke="'+c.Primary+'" stroke-width="'+w+'"/><rect x="3" y="3" width="5" height="6" rx="1" fill="'+c.Fill+'" stroke="'+c.Accent+'" stroke-width="'+w+'"/><rect x="9" y="3" width="5" height="6" rx="1" fill="none" stroke="'+c.Muted+'" stroke-width="'+w+'"/><rect x="15" y="3" width="5" height="6" rx="1" fill="none" stroke="'+c.Muted+'" stroke-width="'+w+'"/>'); },
190
+ Templated: function(s, c, w) { return _svg(s, '<rect x="3" y="3" width="18" height="18" rx="2" fill="'+c.Fill+'" stroke="'+c.Primary+'" stroke-width="'+w+'"/><path d="M8 12h3a1.5 1.5 0 100-3 1.5 1.5 0 100 3h3" stroke="'+c.Accent+'" stroke-width="'+w+'" fill="none"/><line x1="8" y1="16" x2="16" y2="16" stroke="'+c.Muted+'" stroke-width="'+w+'"/>'); },
191
+ TemplatedEntityLookup: function(s, c, w) { return _svg(s, '<rect x="2" y="3" width="14" height="18" rx="2" fill="'+c.Fill+'" stroke="'+c.Primary+'" stroke-width="'+w+'"/><circle cx="17" cy="15" r="4" fill="'+c.Fill+'" stroke="'+c.Accent+'" stroke-width="'+w+'"/><line x1="20" y1="18" x2="22" y2="20" stroke="'+c.Accent+'" stroke-width="'+w+'"/>'); }
192
+ };
193
+
194
+ // ============================================================
195
+ // State
196
+ // ============================================================
197
+ var currentSize = 32;
198
+ var currentStroke = 1.8;
199
+ var currentColors = { Primary: '#3D3229', Accent: '#9E6B47', Muted: '#B0A89E', Fill: '#F5F0E8' };
200
+ var darkBg = false;
201
+ var selections = { Section: null, Group: null, Row: null, Input: null };
202
+
203
+ var categoryDescriptions = {
204
+ Section: 'Page / document metaphor. The largest structural container in a form.',
205
+ Group: 'Folder / panel metaphor. A logical container within a section.',
206
+ Row: 'Horizontal bar / layer metaphor. A row of inputs within a group.',
207
+ Input: 'Field / data entry metaphor. The smallest unit of user interaction.'
208
+ };
209
+
210
+ // ============================================================
211
+ // Render
212
+ // ============================================================
213
+ function render()
214
+ {
215
+ var html = '';
216
+ var categories = ['Section', 'Group', 'Row', 'Input'];
217
+
218
+ for (var ci = 0; ci < categories.length; ci++)
219
+ {
220
+ var cat = categories[ci];
221
+ var variants = Object.keys(Icons[cat]);
222
+
223
+ html += '<div class="category">';
224
+ html += '<div class="category-header">';
225
+ html += '<span class="category-name">' + cat + '</span>';
226
+ html += '<span class="category-count">' + variants.length + ' variants</span>';
227
+ html += '<span class="category-desc">' + categoryDescriptions[cat] + '</span>';
228
+ html += '</div>';
229
+ html += '<div class="icon-grid">';
230
+
231
+ for (var vi = 0; vi < variants.length; vi++)
232
+ {
233
+ var variant = variants[vi];
234
+ var isSelected = (selections[cat] === variant);
235
+ var svgMain = Icons[cat][variant](currentSize, currentColors, currentStroke);
236
+ var svg16 = Icons[cat][variant](14, currentColors, currentStroke);
237
+ var svg24 = Icons[cat][variant](20, currentColors, currentStroke);
238
+ var svg48 = Icons[cat][variant](40, currentColors, currentStroke);
239
+
240
+ html += '<div class="icon-card' + (isSelected ? ' selected' : '') + '" onclick="selectIcon(\'' + cat + '\', \'' + variant + '\')">';
241
+ html += '<div class="icon-preview"><div class="icon-preview-bg' + (darkBg ? ' dark-bg' : '') + '">' + svgMain + '</div></div>';
242
+ html += '<div class="icon-name">' + variant + '</div>';
243
+ html += '<div class="icon-sizes"><span class="sz">' + svg16 + '</span><span class="sz">' + svg24 + '</span><span class="sz">' + svg48 + '</span></div>';
244
+ html += '</div>';
245
+ }
246
+
247
+ html += '</div>'; // icon-grid
248
+ html += '</div>'; // category
249
+ }
250
+
251
+ // InputType Icons section
252
+ html += '<div class="category">';
253
+ html += '<div class="category-header">';
254
+ html += '<span class="category-name">InputType Icons</span>';
255
+ var inputTypeCount = Object.keys(InputTypeIcons).length;
256
+ html += '<span class="category-count">' + inputTypeCount + ' types</span>';
257
+ html += '<span class="category-desc">One icon per InputType definition. Used sparingly in the picker and UI hints.</span>';
258
+ html += '</div>';
259
+ html += '<div class="icon-grid">';
260
+
261
+ var itKeys = Object.keys(InputTypeIcons);
262
+ for (var iti = 0; iti < itKeys.length; iti++)
263
+ {
264
+ var itHash = itKeys[iti];
265
+ var itSvg = InputTypeIcons[itHash](currentSize, currentColors, currentStroke);
266
+ var itSvg16 = InputTypeIcons[itHash](14, currentColors, currentStroke);
267
+ var itSvg24 = InputTypeIcons[itHash](20, currentColors, currentStroke);
268
+ var itSvg48 = InputTypeIcons[itHash](40, currentColors, currentStroke);
269
+
270
+ html += '<div class="icon-card">';
271
+ html += '<div class="icon-preview"><div class="icon-preview-bg' + (darkBg ? ' dark-bg' : '') + '">' + itSvg + '</div></div>';
272
+ html += '<div class="icon-name">' + itHash + '</div>';
273
+ html += '<div class="icon-sizes"><span class="sz">' + itSvg16 + '</span><span class="sz">' + itSvg24 + '</span><span class="sz">' + itSvg48 + '</span></div>';
274
+ html += '</div>';
275
+ }
276
+
277
+ html += '</div>'; // icon-grid
278
+ html += '</div>'; // category
279
+
280
+ document.getElementById('Content').innerHTML = html;
281
+ renderSelectionPanel();
282
+ }
283
+
284
+ function renderSelectionPanel()
285
+ {
286
+ var panel = document.getElementById('SelectionPanel');
287
+ var summary = document.getElementById('SelectionSummary');
288
+ var hasAny = false;
289
+ var html = '';
290
+
291
+ var categories = ['Section', 'Group', 'Row', 'Input'];
292
+ for (var i = 0; i < categories.length; i++)
293
+ {
294
+ var cat = categories[i];
295
+ if (selections[cat])
296
+ {
297
+ hasAny = true;
298
+ var miniSvg = Icons[cat][selections[cat]](16, currentColors, currentStroke);
299
+ html += '<div class="selection-item">';
300
+ html += miniSvg;
301
+ html += '<span class="sel-label">' + cat + ':</span>';
302
+ html += '<span class="sel-variant">' + selections[cat] + '</span>';
303
+ html += '</div>';
304
+ }
305
+ }
306
+
307
+ if (hasAny)
308
+ {
309
+ panel.classList.add('visible');
310
+ summary.innerHTML = html;
311
+ }
312
+ else
313
+ {
314
+ panel.classList.remove('visible');
315
+ }
316
+ }
317
+
318
+ // ============================================================
319
+ // Event Handlers
320
+ // ============================================================
321
+ function selectIcon(pCategory, pVariant)
322
+ {
323
+ if (selections[pCategory] === pVariant)
324
+ {
325
+ selections[pCategory] = null;
326
+ }
327
+ else
328
+ {
329
+ selections[pCategory] = pVariant;
330
+ }
331
+ render();
332
+ }
333
+
334
+ function clearSelections()
335
+ {
336
+ selections = { Section: null, Group: null, Row: null, Input: null };
337
+ render();
338
+ }
339
+
340
+ function onSizeChange(pValue)
341
+ {
342
+ currentSize = parseInt(pValue, 10);
343
+ document.getElementById('SizeDisplay').textContent = currentSize + 'px';
344
+ render();
345
+ }
346
+
347
+ function onStrokeChange(pValue)
348
+ {
349
+ currentStroke = parseFloat(pValue);
350
+ document.getElementById('StrokeDisplay').textContent = currentStroke.toFixed(1);
351
+ render();
352
+ }
353
+
354
+ function onColorChange()
355
+ {
356
+ currentColors.Primary = document.getElementById('ColorPrimary').value;
357
+ currentColors.Accent = document.getElementById('ColorAccent').value;
358
+ currentColors.Muted = document.getElementById('ColorMuted').value;
359
+ currentColors.Fill = document.getElementById('ColorFill').value;
360
+ render();
361
+ }
362
+
363
+ function onBgChange()
364
+ {
365
+ darkBg = (document.getElementById('BgMode').value === 'dark');
366
+ render();
367
+ }
368
+
369
+ // ============================================================
370
+ // Initialize
371
+ // ============================================================
372
+ render();
373
+ </script>
374
+ </body>
375
+ </html>