astro-tractstack 2.0.17 → 2.0.18

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 (60) hide show
  1. package/dist/index.js +18 -0
  2. package/package.json +1 -1
  3. package/templates/src/components/codehooks/FeaturedArticleSetup.tsx +1 -1
  4. package/templates/src/components/codehooks/ListContentSetup.tsx +1 -1
  5. package/templates/src/components/compositor/Compositor.tsx +1 -0
  6. package/templates/src/components/compositor/Node.tsx +41 -17
  7. package/templates/src/components/compositor/nodes/GhostInsertBlock.tsx +9 -6
  8. package/templates/src/components/compositor/nodes/GridLayout.tsx +124 -0
  9. package/templates/src/components/compositor/nodes/GridLayout_eraser.tsx +33 -0
  10. package/templates/src/components/compositor/nodes/Markdown.tsx +67 -37
  11. package/templates/src/components/compositor/nodes/Markdown_eraser.tsx +56 -0
  12. package/templates/src/components/compositor/preview/FeaturedArticlePreview.tsx +8 -2
  13. package/templates/src/components/edit/PanelSwitch.tsx +232 -75
  14. package/templates/src/components/edit/SettingsPanel.tsx +0 -1
  15. package/templates/src/components/edit/pane/AddPanePanel_codehook.tsx +3 -3
  16. package/templates/src/components/edit/pane/AddPanePanel_new.tsx +167 -145
  17. package/templates/src/components/edit/pane/AddPanePanel_reuse.tsx +2 -2
  18. package/templates/src/components/edit/pane/ConfigPanePanel.tsx +1 -7
  19. package/templates/src/components/edit/pane/PanePanel_impression.tsx +1 -1
  20. package/templates/src/components/edit/pane/RestylePaneModal.tsx +8 -5
  21. package/templates/src/components/edit/pane/steps/AiDesignStep.tsx +6 -6
  22. package/templates/src/components/edit/pane/steps/CopyInputStep.tsx +3 -3
  23. package/templates/src/components/edit/pane/steps/DesignLibraryStep.tsx +4 -4
  24. package/templates/src/components/edit/panels/StyleElementPanel.tsx +11 -4
  25. package/templates/src/components/edit/panels/StyleElementPanel_add.tsx +8 -8
  26. package/templates/src/components/edit/panels/StyleElementPanel_remove.tsx +14 -4
  27. package/templates/src/components/edit/panels/StyleElementPanel_update.tsx +16 -4
  28. package/templates/src/components/edit/panels/StyleImagePanel.tsx +8 -3
  29. package/templates/src/components/edit/panels/StyleImagePanel_add.tsx +9 -2
  30. package/templates/src/components/edit/panels/StyleImagePanel_remove.tsx +5 -2
  31. package/templates/src/components/edit/panels/StyleImagePanel_update.tsx +5 -2
  32. package/templates/src/components/edit/panels/StyleLiElementPanel.tsx +7 -3
  33. package/templates/src/components/edit/panels/StyleLiElementPanel_add.tsx +9 -2
  34. package/templates/src/components/edit/panels/StyleLiElementPanel_remove.tsx +5 -2
  35. package/templates/src/components/edit/panels/StyleLiElementPanel_update.tsx +5 -2
  36. package/templates/src/components/edit/panels/StyleParentPanel.tsx +530 -171
  37. package/templates/src/components/edit/panels/StyleParentPanel_add.tsx +77 -42
  38. package/templates/src/components/edit/panels/StyleParentPanel_deleteLayer.tsx +38 -22
  39. package/templates/src/components/edit/panels/StyleParentPanel_remove.tsx +171 -66
  40. package/templates/src/components/edit/panels/StyleParentPanel_update.tsx +166 -98
  41. package/templates/src/components/edit/panels/StyleWidgetPanel.tsx +7 -3
  42. package/templates/src/components/edit/panels/StyleWidgetPanel_add.tsx +9 -2
  43. package/templates/src/components/edit/panels/StyleWidgetPanel_remove.tsx +5 -2
  44. package/templates/src/components/edit/panels/StyleWidgetPanel_update.tsx +6 -2
  45. package/templates/src/components/edit/state/SaveModal.tsx +10 -2
  46. package/templates/src/components/edit/state/SaveToLibraryModal.tsx +6 -6
  47. package/templates/src/components/fields/PaneBreakShapeSelector.tsx +1 -1
  48. package/templates/src/components/widgets/ImpressionWrapper.tsx +4 -1
  49. package/templates/src/constants/prompts.json +1 -1
  50. package/templates/src/stores/nodes.ts +110 -33
  51. package/templates/src/stores/storykeep.ts +3 -1
  52. package/templates/src/types/compositorTypes.ts +37 -2
  53. package/templates/src/utils/compositor/TemplateNodes.ts +8 -0
  54. package/templates/src/utils/compositor/nodesHelper.ts +229 -0
  55. package/templates/src/utils/compositor/reduceNodesClassNames.ts +40 -1
  56. package/templates/src/utils/compositor/typeGuards.ts +7 -0
  57. package/templates/src/utils/etl/extractor.ts +1 -5
  58. package/templates/src/utils/etl/index.ts +1 -0
  59. package/templates/src/utils/etl/transformer.ts +70 -25
  60. package/utils/inject-files.ts +18 -0
@@ -1,141 +1,209 @@
1
- import { useState, useCallback, useEffect } from 'react';
1
+ import { useState, useCallback, useMemo } from 'react';
2
2
  import { settingsPanelStore } from '@/stores/storykeep';
3
3
  import { getCtx } from '@/stores/nodes';
4
- import ViewportComboBox from '@/components/fields/ViewportComboBox';
5
4
  import { tailwindClasses } from '@/utils/compositor/tailwindClasses';
6
- import { isMarkdownPaneFragmentNode } from '@/utils/compositor/typeGuards';
7
5
  import { cloneDeep } from '@/utils/helpers';
6
+ import ViewportComboBox from '@/components/fields/ViewportComboBox';
7
+ import {
8
+ isMarkdownPaneFragmentNode,
9
+ isGridLayoutNode,
10
+ } from '@/utils/compositor/typeGuards';
8
11
  import type {
9
- BasePanelProps,
12
+ ParentBasePanelProps,
10
13
  MarkdownPaneFragmentNode,
14
+ GridLayoutNode,
15
+ DefaultClassValue,
11
16
  } from '@/types/compositorTypes';
12
17
 
13
- const StyleParentUpdatePanel = ({
18
+ type StyleableNode = MarkdownPaneFragmentNode | GridLayoutNode;
19
+
20
+ type ViewportValues = {
21
+ mobile: string;
22
+ tablet: string;
23
+ desktop: string;
24
+ };
25
+
26
+ type CustomPanelProps = ParentBasePanelProps & {
27
+ targetProperty: 'parentClasses' | 'gridClasses';
28
+ };
29
+
30
+ const StyleParentPanelUpdate = ({
14
31
  node,
15
32
  layer,
16
33
  className,
34
+ targetProperty,
17
35
  config,
18
- }: BasePanelProps) => {
19
- if (!node || !className || !layer) return null;
20
- if (!isMarkdownPaneFragmentNode(node)) return null;
21
-
22
- const [mobileValue, setMobileValue] = useState<string>(``);
23
- const [tabletValue, setTabletValue] = useState<string>(``);
24
- const [desktopValue, setDesktopValue] = useState<string>(``);
25
- const [pendingUpdate, setPendingUpdate] = useState<{
26
- value: string;
27
- viewport: 'mobile' | 'tablet' | 'desktop';
28
- } | null>(null);
29
-
30
- const friendlyName = tailwindClasses[className]?.title || className;
31
- const values = tailwindClasses[className]?.values || [];
32
-
33
- const resetStore = () => {
34
- if (node?.id)
35
- settingsPanelStore.set({
36
- nodeId: node.id,
37
- layer: layer,
38
- action: `style-parent`,
39
- expanded: true,
40
- });
36
+ }: CustomPanelProps) => {
37
+ const ctx = getCtx();
38
+ const styleableNode = node as StyleableNode | null;
39
+
40
+ if (
41
+ !styleableNode ||
42
+ (!isMarkdownPaneFragmentNode(styleableNode) &&
43
+ !isGridLayoutNode(styleableNode)) ||
44
+ !layer ||
45
+ !className ||
46
+ !config // Ensure config exists
47
+ ) {
48
+ return (
49
+ <div>
50
+ Error: Could not find node, layer, class name, or config to update.
51
+ <pre>
52
+ {JSON.stringify(
53
+ {
54
+ nodeExists: !!styleableNode,
55
+ layer,
56
+ className,
57
+ targetProperty,
58
+ configExists: !!config,
59
+ },
60
+ null,
61
+ 2
62
+ )}
63
+ </pre>
64
+ </div>
65
+ );
66
+ }
67
+
68
+ const tailwindConfig = tailwindClasses[className];
69
+ if (!tailwindConfig) {
70
+ return <div>Error: Tailwind config not found for {className}</div>;
71
+ }
72
+
73
+ const getInitialValues = (): ViewportValues => {
74
+ let classesSource: DefaultClassValue | undefined;
75
+
76
+ if (targetProperty === 'parentClasses') {
77
+ const layerIndex = layer - 1;
78
+ classesSource = styleableNode.parentClasses?.[layerIndex];
79
+ } else if (
80
+ targetProperty === 'gridClasses' &&
81
+ isMarkdownPaneFragmentNode(styleableNode)
82
+ ) {
83
+ classesSource = styleableNode.gridClasses;
84
+ }
85
+
86
+ return {
87
+ mobile: classesSource?.mobile?.[className] || '',
88
+ tablet: classesSource?.tablet?.[className] || '',
89
+ desktop: classesSource?.desktop?.[className] || '',
90
+ };
41
91
  };
92
+
93
+ const [values, setValues] = useState<ViewportValues>(getInitialValues);
94
+
95
+ const validTailwindValues = useMemo(() => {
96
+ return ['', ...tailwindConfig.values];
97
+ }, [tailwindConfig]);
98
+
42
99
  const handleCancel = () => {
43
- resetStore();
100
+ settingsPanelStore.set({
101
+ nodeId: styleableNode.id,
102
+ layer: layer,
103
+ action: 'style-parent',
104
+ expanded: true,
105
+ });
44
106
  };
45
107
 
46
- // Initialize values from current node state
47
- useEffect(() => {
48
- const layerIndex = layer - 1;
49
- const layerClasses = node?.parentClasses?.[layerIndex];
50
- if (!layerClasses) return;
51
-
52
- setMobileValue(layerClasses.mobile[className] || '');
53
- setTabletValue(layerClasses.tablet[className] || '');
54
- setDesktopValue(layerClasses.desktop[className] || '');
55
- }, [node, layer, className]);
56
-
57
- // Handle updates after state changes
58
- useEffect(() => {
59
- if (!pendingUpdate) return;
60
-
61
- const ctx = getCtx();
62
- const allNodes = ctx.allNodes.get();
63
- const markdownNode = cloneDeep(
64
- allNodes.get(node.id)
65
- ) as MarkdownPaneFragmentNode;
66
- if (!markdownNode) return;
67
-
68
- const layerIndex = layer - 1;
69
- const layerClasses = markdownNode.parentClasses?.[layerIndex];
70
- if (!layerClasses) return;
71
-
72
- switch (pendingUpdate.viewport) {
73
- case 'mobile':
74
- layerClasses.mobile[className] = pendingUpdate.value;
75
- setMobileValue(pendingUpdate.value);
76
- break;
77
- case 'tablet':
78
- layerClasses.tablet[className] = pendingUpdate.value;
79
- setTabletValue(pendingUpdate.value);
80
- break;
81
- case 'desktop':
82
- layerClasses.desktop[className] = pendingUpdate.value;
83
- setDesktopValue(pendingUpdate.value);
84
- break;
85
- }
108
+ const handleUpdate = useCallback(
109
+ (value: string, viewport: 'mobile' | 'tablet' | 'desktop') => {
110
+ const newValues = { ...values, [viewport]: value };
111
+ setValues(newValues);
86
112
 
87
- ctx.modifyNodes([{ ...markdownNode, isChanged: true }]);
88
- setPendingUpdate(null);
89
- }, [pendingUpdate, node.id, layer, className]);
113
+ const updatedNode = cloneDeep(styleableNode);
114
+ let classesTarget: DefaultClassValue | undefined;
90
115
 
91
- const handleFinalChange = useCallback(
92
- (value: string, viewport: 'mobile' | 'tablet' | 'desktop') => {
93
- setPendingUpdate({ value, viewport });
116
+ if (targetProperty === 'parentClasses') {
117
+ const layerIndex = layer - 1;
118
+ if (!updatedNode.parentClasses) {
119
+ updatedNode.parentClasses = [];
120
+ }
121
+ while (updatedNode.parentClasses.length <= layerIndex) {
122
+ updatedNode.parentClasses.push({
123
+ mobile: {},
124
+ tablet: {},
125
+ desktop: {},
126
+ });
127
+ }
128
+ if (!updatedNode.parentClasses[layerIndex]) {
129
+ updatedNode.parentClasses[layerIndex] = {
130
+ mobile: {},
131
+ tablet: {},
132
+ desktop: {},
133
+ };
134
+ }
135
+ classesTarget = updatedNode.parentClasses[layerIndex];
136
+ } else if (
137
+ targetProperty === 'gridClasses' &&
138
+ isMarkdownPaneFragmentNode(updatedNode)
139
+ ) {
140
+ if (!updatedNode.gridClasses) {
141
+ updatedNode.gridClasses = { mobile: {}, tablet: {}, desktop: {} };
142
+ }
143
+ classesTarget = updatedNode.gridClasses;
144
+ }
145
+
146
+ if (classesTarget) {
147
+ if (!classesTarget.mobile) classesTarget.mobile = {};
148
+ if (!classesTarget.tablet) classesTarget.tablet = {};
149
+ if (!classesTarget.desktop) classesTarget.desktop = {};
150
+
151
+ classesTarget[viewport][className] = value;
152
+
153
+ updatedNode.isChanged = true;
154
+ ctx.modifyNodes([updatedNode]);
155
+ ctx.notifyNode('root');
156
+ }
94
157
  },
95
- []
158
+ [styleableNode, layer, className, targetProperty, values, ctx]
96
159
  );
97
160
 
98
161
  return (
99
- <div className="isolate z-50 space-y-4">
162
+ <div className="space-y-4">
100
163
  <div className="flex flex-row flex-nowrap justify-between">
101
- <h3 className="text-xl font-bold">
102
- <span className="font-bold">{friendlyName}</span> (Layer {layer})
103
- </h3>
104
164
  <button
105
- className="text-myblue hover:text-black"
106
165
  title="Return to preview pane"
107
- onClick={() => handleCancel()}
166
+ onClick={handleCancel}
167
+ className="text-myblue hover:text-black"
108
168
  >
109
169
  Go Back
110
170
  </button>
111
171
  </div>
112
- <div className="text-mydarkgrey my-3 flex flex-col gap-y-2.5 text-xl">
172
+ <div>
173
+ <h3 className="text-xl font-bold">{tailwindConfig.title}</h3>
174
+ <p className="text-mydarkgrey mt-1 font-mono text-sm">
175
+ {tailwindConfig.className}
176
+ </p>
177
+ </div>
178
+
179
+ <div className="space-y-3">
113
180
  <ViewportComboBox
114
- value={mobileValue}
115
- onFinalChange={handleFinalChange}
116
- values={values}
117
181
  viewport="mobile"
118
- config={config!}
182
+ value={values.mobile}
183
+ values={validTailwindValues}
184
+ onFinalChange={handleUpdate}
185
+ config={config}
186
+ allowNegative={tailwindConfig.allowNegative}
119
187
  />
120
188
  <ViewportComboBox
121
- value={tabletValue}
122
- onFinalChange={handleFinalChange}
123
- values={values}
124
189
  viewport="tablet"
125
- isInferred={tabletValue === mobileValue}
126
- config={config!}
190
+ value={values.tablet}
191
+ values={validTailwindValues}
192
+ onFinalChange={handleUpdate}
193
+ config={config}
194
+ allowNegative={tailwindConfig.allowNegative}
127
195
  />
128
196
  <ViewportComboBox
129
- value={desktopValue}
130
- onFinalChange={handleFinalChange}
131
- values={values}
132
197
  viewport="desktop"
133
- isInferred={desktopValue === tabletValue}
134
- config={config!}
198
+ value={values.desktop}
199
+ values={validTailwindValues}
200
+ onFinalChange={handleUpdate}
201
+ config={config}
202
+ allowNegative={tailwindConfig.allowNegative}
135
203
  />
136
204
  </div>
137
205
  </div>
138
206
  );
139
207
  };
140
208
 
141
- export default StyleParentUpdatePanel;
209
+ export default StyleParentPanelUpdate;
@@ -3,19 +3,23 @@ import Cog6ToothIcon from '@heroicons/react/24/outline/Cog6ToothIcon';
3
3
  import { settingsPanelStore } from '@/stores/storykeep';
4
4
  import { StylesMemory } from '@/components/edit/state/StylesMemory';
5
5
  import SelectedTailwindClass from '@/components/fields/SelectedTailwindClass';
6
- import { isMarkdownPaneFragmentNode } from '@/utils/compositor/typeGuards';
6
+ import {
7
+ isMarkdownPaneFragmentNode,
8
+ isGridLayoutNode,
9
+ } from '@/utils/compositor/typeGuards';
7
10
  import { regexpHook, widgetMeta } from '@/constants';
8
11
  import { getCtx } from '@/stores/nodes';
9
12
  import type {
10
13
  FlatNode,
11
14
  MarkdownPaneFragmentNode,
15
+ GridLayoutNode,
12
16
  } from '@/types/compositorTypes';
13
17
 
14
18
  interface StyleWidgetPanelProps {
15
19
  node: FlatNode;
16
20
  containerNode: FlatNode;
17
21
  outerContainerNode: FlatNode;
18
- parentNode: MarkdownPaneFragmentNode;
22
+ parentNode: MarkdownPaneFragmentNode | GridLayoutNode;
19
23
  }
20
24
 
21
25
  const StyleWidgetPanel = ({
@@ -28,7 +32,7 @@ const StyleWidgetPanel = ({
28
32
  !node?.tagName ||
29
33
  !containerNode?.tagName ||
30
34
  !outerContainerNode?.tagName ||
31
- !isMarkdownPaneFragmentNode(parentNode)
35
+ (!isMarkdownPaneFragmentNode(parentNode) && !isGridLayoutNode(parentNode))
32
36
  ) {
33
37
  return null;
34
38
  }
@@ -6,7 +6,10 @@ import CheckIcon from '@heroicons/react/24/outline/CheckIcon';
6
6
  import { settingsPanelStore } from '@/stores/storykeep';
7
7
  import { getCtx } from '@/stores/nodes';
8
8
  import { tailwindClasses } from '@/utils/compositor/tailwindClasses';
9
- import { isMarkdownPaneFragmentNode } from '@/utils/compositor/typeGuards';
9
+ import {
10
+ isMarkdownPaneFragmentNode,
11
+ isGridLayoutNode,
12
+ } from '@/utils/compositor/typeGuards';
10
13
  import { useDropdownDirection } from '@/utils/helpers';
11
14
  import type { BasePanelProps } from '@/types/compositorTypes';
12
15
 
@@ -72,7 +75,11 @@ const StyleWidgetPanelAdd = ({ node, parentNode, childId }: BasePanelProps) => {
72
75
  const comboboxRef = useRef<HTMLDivElement>(null);
73
76
  const { openAbove } = useDropdownDirection(comboboxRef);
74
77
 
75
- if (!node?.tagName || !parentNode || !isMarkdownPaneFragmentNode(parentNode))
78
+ if (
79
+ !node?.tagName ||
80
+ !parentNode ||
81
+ (!isMarkdownPaneFragmentNode(parentNode) && !isGridLayoutNode(parentNode))
82
+ )
76
83
  return null;
77
84
 
78
85
  const isOuterContainer = node.tagName === 'ul' || node.tagName === 'ol';
@@ -1,7 +1,10 @@
1
1
  import { settingsPanelStore } from '@/stores/storykeep';
2
2
  import { getCtx } from '@/stores/nodes';
3
3
  import { tailwindClasses } from '@/utils/compositor/tailwindClasses';
4
- import { isMarkdownPaneFragmentNode } from '@/utils/compositor/typeGuards';
4
+ import {
5
+ isMarkdownPaneFragmentNode,
6
+ isGridLayoutNode,
7
+ } from '@/utils/compositor/typeGuards';
5
8
  import { cloneDeep } from '@/utils/helpers';
6
9
  import type { BasePanelProps, FlatNode } from '@/types/compositorTypes';
7
10
 
@@ -15,7 +18,7 @@ const StyleWidgetRemovePanel = ({
15
18
  !className ||
16
19
  !node?.tagName ||
17
20
  !parentNode ||
18
- !isMarkdownPaneFragmentNode(parentNode)
21
+ (!isMarkdownPaneFragmentNode(parentNode) && !isGridLayoutNode(parentNode))
19
22
  ) {
20
23
  return null;
21
24
  }
@@ -3,12 +3,16 @@ import { settingsPanelStore } from '@/stores/storykeep';
3
3
  import ViewportComboBox from '@/components/fields/ViewportComboBox';
4
4
  import { tailwindClasses } from '@/utils/compositor/tailwindClasses';
5
5
  import { getCtx } from '@/stores/nodes';
6
- import { isMarkdownPaneFragmentNode } from '@/utils/compositor/typeGuards';
6
+ import {
7
+ isMarkdownPaneFragmentNode,
8
+ isGridLayoutNode,
9
+ } from '@/utils/compositor/typeGuards';
7
10
  import { cloneDeep } from '@/utils/helpers';
8
11
  import type {
9
12
  BasePanelProps,
10
13
  FlatNode,
11
14
  MarkdownPaneFragmentNode,
15
+ GridLayoutNode,
12
16
  } from '@/types/compositorTypes';
13
17
 
14
18
  const StyleWidgetUpdatePanel = ({
@@ -22,7 +26,7 @@ const StyleWidgetUpdatePanel = ({
22
26
  !node ||
23
27
  !className ||
24
28
  !parentNode ||
25
- !isMarkdownPaneFragmentNode(parentNode)
29
+ (!isMarkdownPaneFragmentNode(parentNode) && !isGridLayoutNode(parentNode))
26
30
  )
27
31
  return null;
28
32
 
@@ -65,8 +65,14 @@ const INDETERMINATE_STAGES: SaveStage[] = [
65
65
  const SandboxUpgradeNotice = ({ onClose }: { onClose: () => void }) => (
66
66
  <Dialog.Root open={true} onOpenChange={() => onClose()} modal={true}>
67
67
  <Portal>
68
- <Dialog.Backdrop className="fixed inset-0 z-[9005] bg-black bg-opacity-75" />
69
- <Dialog.Positioner className="fixed inset-0 z-[9005] flex items-center justify-center p-4">
68
+ <Dialog.Backdrop
69
+ className="fixed inset-0 bg-black bg-opacity-75"
70
+ style={{ zIndex: 9005 }}
71
+ />
72
+ <Dialog.Positioner
73
+ className="fixed inset-0 flex items-center justify-center p-4"
74
+ style={{ zIndex: 9005 }}
75
+ >
70
76
  <Dialog.Content className="w-full max-w-md overflow-hidden rounded-lg bg-white shadow-xl">
71
77
  <div className="p-6 text-center">
72
78
  <Dialog.Title className="text-xl font-bold text-gray-900">
@@ -447,6 +453,8 @@ export default function SaveModal({
447
453
  `Processing ${dirtyPanes.length} panes via -> POST ${endpoint}`
448
454
  );
449
455
 
456
+ //console.log(`bulkPayload`, bulkPayload)
457
+
450
458
  try {
451
459
  const response = await fetch(endpoint, {
452
460
  method: 'POST',
@@ -101,7 +101,7 @@ export function SaveToLibraryModal({
101
101
  onClick={(e) => e.stopPropagation()}
102
102
  >
103
103
  <div className="flex items-center justify-between">
104
- <h2 className="text-lg font-medium text-gray-900">
104
+ <h2 className="text-lg font-bold text-gray-900">
105
105
  Save Pane to Library
106
106
  </h2>
107
107
  </div>
@@ -112,7 +112,7 @@ export function SaveToLibraryModal({
112
112
  <div>
113
113
  <label
114
114
  htmlFor="category-select"
115
- className="block text-sm font-medium text-gray-700"
115
+ className="block text-sm font-bold text-gray-700"
116
116
  >
117
117
  Category
118
118
  </label>
@@ -139,7 +139,7 @@ export function SaveToLibraryModal({
139
139
  </div>
140
140
 
141
141
  <div>
142
- <label className="block text-sm font-medium text-gray-700">
142
+ <label className="block text-sm font-bold text-gray-700">
143
143
  Content Mode
144
144
  </label>
145
145
  <fieldset className="mt-2">
@@ -158,7 +158,7 @@ export function SaveToLibraryModal({
158
158
  />
159
159
  <label
160
160
  htmlFor={option.id}
161
- className="ml-3 block text-sm font-medium text-gray-700"
161
+ className="ml-3 block text-sm font-bold text-gray-700"
162
162
  >
163
163
  {option.title}
164
164
  <p className="text-xs text-gray-500">
@@ -178,7 +178,7 @@ export function SaveToLibraryModal({
178
178
  <button
179
179
  type="button"
180
180
  disabled={saveState !== 'idle'}
181
- className="rounded-md border border-gray-300 bg-white px-4 py-2 text-sm font-medium text-gray-700 shadow-sm hover:bg-gray-50 disabled:opacity-50"
181
+ className="rounded-md border border-gray-300 bg-white px-4 py-2 text-sm font-bold text-gray-700 shadow-sm hover:bg-gray-50 disabled:opacity-50"
182
182
  onClick={onClose}
183
183
  >
184
184
  Cancel
@@ -186,7 +186,7 @@ export function SaveToLibraryModal({
186
186
  <button
187
187
  type="button"
188
188
  disabled={saveState !== 'idle'}
189
- className="flex min-w-36 items-center justify-center rounded-md border border-transparent bg-cyan-600 px-4 py-2 text-sm font-medium text-white shadow-sm hover:bg-cyan-700 disabled:cursor-not-allowed disabled:opacity-50"
189
+ className="flex min-w-36 items-center justify-center rounded-md border border-transparent bg-cyan-600 px-4 py-2 text-sm font-bold text-white shadow-sm hover:bg-cyan-700 disabled:cursor-not-allowed disabled:opacity-50"
190
190
  onClick={handleSave}
191
191
  >
192
192
  {saveState === 'idle' && 'Save to Library'}
@@ -48,7 +48,7 @@ export default function PaneBreakShapeSelector({
48
48
 
49
49
  const svgData = SvgBreaks[fullShapeName];
50
50
  return (
51
- <div className="flex w-[150px] items-center justify-center">
51
+ <div className="flex w-56 items-center justify-center">
52
52
  <svg
53
53
  viewBox={`0 0 ${svgData.viewBox[0]} ${svgData.viewBox[1]}`}
54
54
  className="h-auto w-full"
@@ -141,7 +141,10 @@ const ImpressionWrapper = ({
141
141
  const currentImpression = visibleImpressions[currentImpressionIndex];
142
142
 
143
143
  return (
144
- <div className="impression-overlay fixed bottom-4 right-4 z-50 w-96 max-w-[calc(100vw-2rem)]">
144
+ <div
145
+ className="impression-overlay fixed bottom-4 right-4 z-50 w-96"
146
+ style={{ maxWidth: 'calc(100vw - 2rem)' }}
147
+ >
145
148
  <div className="relative rounded-lg border border-gray-200 bg-white shadow-lg">
146
149
  {/* Close button */}
147
150
  <button
@@ -45,7 +45,7 @@
45
45
  },
46
46
  "aiPaneCopyPrompt": {
47
47
  "system": "You are an expert **web designer and copywriter**. Your task is to generate a single, visually compelling block of HTML content. You must ensure the content is well-written, engaging, **beautifully spaced**, and **highly readable**.",
48
- "user_template": "Here is the design 'shell' and 'theme' (bgColour, parentClasses, and defaultClasses) you must write your HTML for. Your HTML will be placed *inside* this shell. Use the `defaultClasses` as your base theme for styling:\n{{SHELL_JSON}}\n\nNow, generate the HTML content based on these inputs:\n\nContent Prompt: \"{{COPY_INPUT}}\"\nDesign Style: **strictly for visual reference** when choosing element styles **DO NOT** include any words or concepts from the `Design Style` input in the written copy text itself.\"{{DESIGN_INPUT}}\"\nLayout Type: \"{{LAYOUT_TYPE}}\"\n\nCRITICAL RULES:\n1. You are responsible for the **inner layout and visual rhythm**. You MUST add appropriate vertical margins (e.g., `mt-4`, `mt-6`, `mt-8`) directly to any HTML block elements that *deviate* from the default spacing. **Elements must not touch.**\n2. You **MUST NOT** use `<h1>` tags. You must use `<h2>`, `<h3>`, and `<p>` tags for all text content.\n3. For responsive styles, you *must* only use `md:` and `xl:` prefixes.\n4. To make headlines pop, you MUST wrap key words in `<span>` tags with creative classes (e.g., gradient text, different colors).\n5. **All text**, even short links or phrases (like 'Learn more →'), **must** be wrapped in a block element like `<p>` or `<button>`.\n6. You MUST include at least one `<button>` tag for the primary call-to-action.\n7. Verify that **all text elements**, including text within `<span>` tags, `<button>` elements, and any elements using override classes, maintain **high contrast** (meeting at least WCAG AA standards - 4.5:1 for normal text, 3:1 for large text) against the `bgColour` provided in the `SHELL_JSON`. **Prioritize readability above all else**.\n8. Respond *only* with the raw HTML.\n\nEXAMPLE of a good, well-spaced response:\n<h2 class=\"text-4xl font-bold tracking-tight text-white md:text-6xl\"><span class=\"bg-gradient-to-r from-purple-500 to-indigo-400 bg-clip-text text-transparent\">Own the Art.</span> Possess the Reality.</h2>\n<p class=\"mt-6 text-lg leading-8 text-gray-300 md:text-xl\">Every Sneaky Productions NFT is your key. This is where digital rarity meets tangible legacy.</p>\n<button class=\"mt-8 rounded-md bg-indigo-600 px-5 py-3 text-base font-semibold text-white shadow-sm hover:bg-indigo-500\">Secure Your Drop</button>\n<p class=\"mt-4 text-sm text-gray-400\">Learn more <span>→</span></p>",
48
+ "user_template": "Here is the design 'shell' and 'theme' (bgColour, parentClasses, and defaultClasses) you must write your HTML for. Your HTML will be placed *inside* this shell. Use the `defaultClasses` as your base theme for styling:\n{{SHELL_JSON}}\n\nNow, generate the HTML content based on these inputs:\n\nContent Prompt: \"{{COPY_INPUT}}\"\nDesign Style: **strictly for visual reference** when choosing element styles **DO NOT** include any words or concepts from the `Design Style` input in the written copy text itself.\"{{DESIGN_INPUT}}\"\nLayout Type: \"{{LAYOUT_TYPE}}\"\n\nCRITICAL RULES:\n1. You are responsible for the **inner layout and visual rhythm**. You MUST add appropriate vertical margins (e.g., `mt-4`, `mt-6`, `mt-8`) directly to any HTML block elements that *deviate* from the default spacing. **Elements must not touch.**\n2. You **MUST NOT** use `<h1>` tags. You must use `<h2>`, `<h3>`, and `<p>` tags for all text content.\n3. For responsive styles, you *must* only use `md:` and `xl:` prefixes.\n4. To make headlines pop, you MUST wrap key words in `<span>` tags with creative classes (e.g., gradient text, different colors).\n5. **All text**, even short links or phrases (like 'Learn more →'), **must** be wrapped in a block element like `<p>` or `<button>`.\n6. You MUST include at least one `<button>` tag for the primary call-to-action.\n7. Verify that **all text elements**, including text within `<span>` tags, `<button>` elements, and any elements using override classes, maintain **high contrast** (meeting at least WCAG AA standards - 4.5:1 for normal text, 3:1 for large text) against the `bgColour` provided in the `SHELL_JSON`. **Prioritize readability above all else**.\n8. Respond *only* with the raw HTML.\n\nEXAMPLE of a good, well-spaced response:\n<h2 class=\"text-4xl font-bold tracking-tight text-white md:text-6xl\"><span class=\"bg-gradient-to-r from-purple-500 to-indigo-400 bg-clip-text text-transparent\">Own the Art.</span> Possess the Reality.</h2>\n<p class=\"mt-6 text-lg leading-8 text-gray-300 md:text-xl\">Every Sneaky Productions NFT is your key. This is where digital rarity meets tangible legacy.</p>\n<button class=\"mt-8 rounded-md bg-indigo-600 px-5 py-3 text-base font-bold text-white shadow-sm hover:bg-indigo-500\">Secure Your Drop</button>\n<p class=\"mt-4 text-sm text-gray-400\">Learn more <span>→</span></p>",
49
49
  "heroDefault": "A compelling hero section for a website about [topic]. It should have a strong, attention-grabbing headline, a brief paragraph explaining the core value proposition, and a clear call-to-action.",
50
50
  "contentDefault": "A content section that follows a hero. It should elaborate on a key feature or benefit related to [topic]. Include a sub-headline and a descriptive paragraph."
51
51
  }