astro-tractstack 2.0.18 → 2.0.19

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "astro-tractstack",
3
- "version": "2.0.18",
3
+ "version": "2.0.19",
4
4
  "description": "Astro integration for TractStack - redeeming the web from boring experiences",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -3,6 +3,8 @@ import { useState, useCallback } from 'react';
3
3
  import DocumentPlusIcon from '@heroicons/react/24/outline/DocumentPlusIcon';
4
4
  import SparklesIcon from '@heroicons/react/24/outline/SparklesIcon';
5
5
  import SwatchIcon from '@heroicons/react/24/outline/SwatchIcon';
6
+ import SquaresPlusIcon from '@heroicons/react/24/outline/SquaresPlusIcon';
7
+ import DocumentIcon from '@heroicons/react/24/outline/DocumentIcon';
6
8
  import { NodesContext, getCtx } from '@/stores/nodes';
7
9
  import { cloneDeep } from '@/utils/helpers';
8
10
  import { hasAssemblyAIStore } from '@/stores/storykeep';
@@ -25,6 +27,7 @@ type Step =
25
27
  | 'initial'
26
28
  | 'copyInput'
27
29
  | 'designLibrary'
30
+ | 'layoutChoice'
28
31
  | 'aiDesign'
29
32
  | 'loading'
30
33
  | 'error'
@@ -32,6 +35,8 @@ type Step =
32
35
 
33
36
  type InitialChoice = 'library' | 'ai' | 'blank';
34
37
  type CopyMode = 'prompt' | 'raw';
38
+ type LayoutChoice = 'standard' | 'grid';
39
+ type ColumnPresetKey = 'left' | 'right';
35
40
 
36
41
  interface GenerationResponse {
37
42
  success: boolean;
@@ -135,10 +140,23 @@ const AddPaneNewPanel = ({
135
140
  const [initialChoice, setInitialChoice] = useState<InitialChoice | null>(
136
141
  null
137
142
  );
143
+ const [layoutChoice, setLayoutChoice] = useState<LayoutChoice>('standard');
138
144
  const [error, setError] = useState<string | null>(null);
145
+
139
146
  const [copyMode, setCopyMode] = useState<CopyMode>('prompt');
140
147
  const [promptValue, setPromptValue] = useState('');
141
148
  const [copyValue, setCopyValue] = useState('');
149
+
150
+ const [overallPrompt, setOverallPrompt] = useState(
151
+ prompts.aiPaneCopyPrompt_2cols.presets.heroDefault.default
152
+ );
153
+ const [copyModeCol1, setCopyModeCol1] = useState<CopyMode>('prompt');
154
+ const [promptValueCol1, setPromptValueCol1] = useState('');
155
+ const [copyValueCol1, setCopyValueCol1] = useState('');
156
+ const [copyModeCol2, setCopyModeCol2] = useState<CopyMode>('prompt');
157
+ const [promptValueCol2, setPromptValueCol2] = useState('');
158
+ const [copyValueCol2, setCopyValueCol2] = useState('');
159
+
142
160
  const [selectedLibraryEntry, setSelectedLibraryEntry] =
143
161
  useState<DesignLibraryEntry | null>(null);
144
162
  const [aiDesignConfig, setAiDesignConfig] = useState<AiDesignConfig>({
@@ -158,10 +176,15 @@ const AddPaneNewPanel = ({
158
176
  } else if (choice === 'library') {
159
177
  setStep('designLibrary');
160
178
  } else if (choice === 'ai') {
161
- setStep('aiDesign');
179
+ setStep('layoutChoice');
162
180
  }
163
181
  };
164
182
 
183
+ const handleLayoutChoice = (choice: LayoutChoice) => {
184
+ setLayoutChoice(choice);
185
+ setStep('aiDesign');
186
+ };
187
+
165
188
  const handleBack = () => {
166
189
  setError(null);
167
190
  if (step === 'copyInput') {
@@ -172,13 +195,13 @@ const AddPaneNewPanel = ({
172
195
  } else {
173
196
  setStep('initial');
174
197
  }
198
+ } else if (step === 'aiDesign') {
199
+ setStep('layoutChoice');
175
200
  } else if (step === 'directInject') {
176
201
  setStep('aiDesign');
177
- } else if (
178
- step === 'designLibrary' ||
179
- step === 'aiDesign' ||
180
- step === 'error'
181
- ) {
202
+ } else if (step === 'layoutChoice') {
203
+ setStep('initial');
204
+ } else if (step === 'designLibrary' || step === 'error') {
182
205
  setStep('initial');
183
206
  }
184
207
  };
@@ -214,7 +237,7 @@ const AddPaneNewPanel = ({
214
237
  setStep('copyInput');
215
238
  };
216
239
 
217
- const handleApplyTemplate = async (template: TemplatePane) => {
240
+ const handleApplyTemplate = async (template: any) => {
218
241
  if (!ctx) return;
219
242
  try {
220
243
  const insertTemplate = cloneDeep(template);
@@ -326,38 +349,102 @@ const AddPaneNewPanel = ({
326
349
  if (aiDesignConfig.additionalNotes)
327
350
  designInput += ` Refine with these notes: "${aiDesignConfig.additionalNotes}"`;
328
351
 
329
- const shellPromptDetails = prompts.aiPaneShellPrompt;
330
- const copyPromptDetails = prompts.aiPaneCopyPrompt;
331
352
  const layout = 'Text Only';
332
353
 
333
- const formattedShellPrompt = shellPromptDetails.user_template
334
- .replace('{{DESIGN_INPUT}}', designInput)
335
- .replace('{{LAYOUT_TYPE}}', layout);
336
-
337
- const shellResult = await callAskLemurAPI(
338
- formattedShellPrompt,
339
- shellPromptDetails.system || '',
340
- true,
341
- isSandboxMode
342
- );
343
-
344
- const copyInputContent =
345
- copyMode === 'prompt' ? promptValue : copyValue;
346
- const formattedCopyPrompt = copyPromptDetails.user_template
347
- .replace('{{COPY_INPUT}}', copyInputContent)
348
- .replace('{{DESIGN_INPUT}}', designInput)
349
- .replace('{{LAYOUT_TYPE}}', layout)
350
- .replace('{{SHELL_JSON}}', shellResult);
351
-
352
- const copyResult = await callAskLemurAPI(
353
- formattedCopyPrompt,
354
- copyPromptDetails.system || '',
355
- false,
356
- isSandboxMode
357
- );
358
-
359
- const finalPane = parseAiPane(shellResult, copyResult, layout);
360
- handleApplyTemplate(finalPane);
354
+ if (layoutChoice === 'standard') {
355
+ const shellPromptDetails = prompts.aiPaneShellPrompt;
356
+ const formattedShellPrompt = shellPromptDetails.user_template
357
+ .replace('{{DESIGN_INPUT}}', designInput)
358
+ .replace('{{LAYOUT_TYPE}}', layout);
359
+
360
+ const shellResult = await callAskLemurAPI(
361
+ formattedShellPrompt,
362
+ shellPromptDetails.system || '',
363
+ true,
364
+ isSandboxMode
365
+ );
366
+
367
+ const copyPromptDetails = prompts.aiPaneCopyPrompt;
368
+ const copyInputContent =
369
+ copyMode === 'prompt' ? promptValue : copyValue;
370
+ const formattedCopyPrompt = copyPromptDetails.user_template
371
+ .replace('{{COPY_INPUT}}', copyInputContent)
372
+ .replace('{{DESIGN_INPUT}}', designInput)
373
+ .replace('{{LAYOUT_TYPE}}', layout)
374
+ .replace('{{SHELL_JSON}}', shellResult);
375
+
376
+ const copyResult = await callAskLemurAPI(
377
+ formattedCopyPrompt,
378
+ copyPromptDetails.system || '',
379
+ false,
380
+ isSandboxMode
381
+ );
382
+ const finalPane = parseAiPane(shellResult, copyResult, layout);
383
+ handleApplyTemplate(finalPane);
384
+ } else if (layoutChoice === 'grid') {
385
+ const shellPromptDetails = prompts.aiPaneShellPrompt_2cols;
386
+ const formattedShellPrompt = shellPromptDetails.user_template
387
+ .replace('{{COPY_INPUT}}', overallPrompt)
388
+ .replace('{{DESIGN_INPUT}}', designInput);
389
+
390
+ const shellResult = await callAskLemurAPI(
391
+ formattedShellPrompt,
392
+ shellPromptDetails.system || '',
393
+ true,
394
+ isSandboxMode
395
+ );
396
+
397
+ const copyPromptDetails = prompts.aiPaneCopyPrompt_2cols;
398
+ const preset = copyPromptDetails.presets.heroDefault;
399
+ const copyResults: string[] = [];
400
+
401
+ const promptsToRun: {
402
+ prompt: string;
403
+ copy: string;
404
+ mode: CopyMode;
405
+ presetKey: ColumnPresetKey;
406
+ }[] = [
407
+ {
408
+ prompt: promptValueCol1,
409
+ copy: copyValueCol1,
410
+ mode: copyModeCol1,
411
+ presetKey: 'left',
412
+ },
413
+ {
414
+ prompt: promptValueCol2,
415
+ copy: copyValueCol2,
416
+ mode: copyModeCol2,
417
+ presetKey: 'right',
418
+ },
419
+ ];
420
+
421
+ for (const item of promptsToRun) {
422
+ if (item.mode === 'raw') {
423
+ copyResults.push(item.copy);
424
+ continue;
425
+ }
426
+
427
+ const columnPreset = preset[item.presetKey];
428
+ const formattedCopyPrompt = copyPromptDetails.user_template
429
+ .replace('{{SHELL_JSON}}', shellResult)
430
+ .replace('{{COPY_INPUT}}', overallPrompt)
431
+ .replace('{{COLUMN_PROMPT}}', item.prompt)
432
+ .replace('{{DESIGN_INPUT}}', designInput)
433
+ .replace('{{LAYOUT_TYPE}}', layout)
434
+ .replace('{{COLUMN_EXAMPLE}}', columnPreset.example);
435
+
436
+ const copyResult = await callAskLemurAPI(
437
+ formattedCopyPrompt,
438
+ copyPromptDetails.system || '',
439
+ false,
440
+ isSandboxMode
441
+ );
442
+ copyResults.push(copyResult);
443
+ }
444
+
445
+ const finalPane = parseAiPane(shellResult, copyResults, layout);
446
+ handleApplyTemplate(finalPane);
447
+ }
361
448
  }
362
449
  } catch (err: any) {
363
450
  setError(err.message || 'Failed to generate AI pane.');
@@ -368,8 +455,16 @@ const AddPaneNewPanel = ({
368
455
  copyMode,
369
456
  promptValue,
370
457
  copyValue,
458
+ overallPrompt,
459
+ copyModeCol1,
460
+ promptValueCol1,
461
+ copyValueCol1,
462
+ copyModeCol2,
463
+ promptValueCol2,
464
+ copyValueCol2,
371
465
  isSandboxMode,
372
466
  initialChoice,
467
+ layoutChoice,
373
468
  selectedLibraryEntry,
374
469
  handleApplyTemplate,
375
470
  ]);
@@ -416,41 +511,157 @@ const AddPaneNewPanel = ({
416
511
  </div>
417
512
  );
418
513
 
419
- const renderContentStep = () => (
420
- <div className="space-y-4 p-4">
421
- <CopyInputStep
422
- copyMode={copyMode}
423
- onCopyModeChange={setCopyMode}
424
- promptValue={promptValue}
425
- onPromptValueChange={setPromptValue}
426
- copyValue={copyValue}
427
- onCopyValueChange={setCopyValue}
428
- defaultPrompt={
429
- first
430
- ? prompts.aiPaneCopyPrompt.heroDefault
431
- : prompts.aiPaneCopyPrompt.contentDefault
432
- }
433
- />
434
- <div className="flex justify-between">
514
+ const renderLayoutChoiceStep = () => (
515
+ <div className="p-4">
516
+ <h3 className="font-action mb-4 text-center text-xl font-bold text-gray-800">
517
+ Choose a Layout Structure
518
+ </h3>
519
+ <div className="grid grid-cols-1 gap-4 md:grid-cols-2">
435
520
  <button
436
- onClick={handleBack}
437
- 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"
521
+ onClick={() => handleLayoutChoice('standard')}
522
+ className="group flex flex-col items-center space-y-3 rounded-lg border bg-white p-6 text-center shadow-sm transition-all hover:border-cyan-600 hover:shadow-lg"
438
523
  >
439
- Back
524
+ <DocumentIcon className="h-10 w-10 text-gray-500 transition-colors group-hover:text-cyan-600" />
525
+ <h4 className="font-bold text-gray-800">Standard Layout</h4>
526
+ <p className="text-sm text-gray-600">
527
+ A single, continuous column of content.
528
+ </p>
440
529
  </button>
441
530
  <button
442
- onClick={handleFinalGenerate}
443
- disabled={
444
- copyMode === 'prompt' ? !promptValue.trim() : !copyValue.trim()
445
- }
446
- className="rounded-md bg-cyan-600 px-4 py-2 text-sm font-bold text-white shadow-sm hover:bg-cyan-700 disabled:cursor-not-allowed disabled:bg-gray-400"
531
+ onClick={() => handleLayoutChoice('grid')}
532
+ className="group flex flex-col items-center space-y-3 rounded-lg border bg-white p-6 text-center shadow-sm transition-all hover:border-cyan-600 hover:shadow-lg"
447
533
  >
448
- Generate Pane
534
+ <SquaresPlusIcon className="h-10 w-10 text-gray-500 transition-colors group-hover:text-cyan-600" />
535
+ <h4 className="font-bold text-gray-800">2-Column Grid</h4>
536
+ <p className="text-sm text-gray-600">
537
+ Side-by-side content that stacks on mobile.
538
+ </p>
539
+ </button>
540
+ </div>
541
+ <div className="mt-6 flex justify-center">
542
+ <button
543
+ onClick={handleBack}
544
+ 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"
545
+ >
546
+ ← Back
449
547
  </button>
450
548
  </div>
451
549
  </div>
452
550
  );
453
551
 
552
+ const renderContentStep = () => {
553
+ if (layoutChoice === 'grid') {
554
+ const isGenerateDisabled =
555
+ (copyModeCol1 === 'prompt' && !promptValueCol1.trim()) ||
556
+ (copyModeCol1 === 'raw' && !copyValueCol1.trim()) ||
557
+ (copyModeCol2 === 'prompt' && !promptValueCol2.trim()) ||
558
+ (copyModeCol2 === 'raw' && !copyValueCol2.trim()) ||
559
+ !overallPrompt.trim();
560
+
561
+ return (
562
+ <div className="space-y-4 p-4">
563
+ <div>
564
+ <h4 className="mb-2 block text-sm font-bold text-gray-700">
565
+ Overall Component Brief
566
+ </h4>
567
+ <textarea
568
+ value={overallPrompt}
569
+ onChange={(e) => setOverallPrompt(e.target.value)}
570
+ placeholder="e.g., A compelling hero section for a website about Tract Stack..."
571
+ rows={3}
572
+ className="block w-full rounded-md border-gray-300 p-2 shadow-sm focus:border-cyan-500 focus:ring-cyan-500 sm:text-sm"
573
+ />
574
+ </div>
575
+ <div className="grid grid-cols-1 gap-6 md:grid-cols-2">
576
+ <div>
577
+ <h4 className="mb-2 block text-sm font-bold text-gray-700">
578
+ Left Column Content
579
+ </h4>
580
+ <CopyInputStep
581
+ copyMode={copyModeCol1}
582
+ onCopyModeChange={setCopyModeCol1}
583
+ promptValue={promptValueCol1}
584
+ onPromptValueChange={setPromptValueCol1}
585
+ copyValue={copyValueCol1}
586
+ onCopyValueChange={setCopyValueCol1}
587
+ defaultPrompt={
588
+ prompts.aiPaneCopyPrompt_2cols.presets.heroDefault.left.prompt
589
+ }
590
+ />
591
+ </div>
592
+ <div>
593
+ <h4 className="mb-2 block text-sm font-bold text-gray-700">
594
+ Right Column Content
595
+ </h4>
596
+ <CopyInputStep
597
+ copyMode={copyModeCol2}
598
+ onCopyModeChange={setCopyModeCol2}
599
+ promptValue={promptValueCol2}
600
+ onPromptValueChange={setPromptValueCol2}
601
+ copyValue={copyValueCol2}
602
+ onCopyValueChange={setCopyValueCol2}
603
+ defaultPrompt={
604
+ prompts.aiPaneCopyPrompt_2cols.presets.heroDefault.right
605
+ .prompt
606
+ }
607
+ />
608
+ </div>
609
+ </div>
610
+ <div className="flex justify-between">
611
+ <button
612
+ onClick={handleBack}
613
+ 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"
614
+ >
615
+ ← Back
616
+ </button>
617
+ <button
618
+ onClick={handleFinalGenerate}
619
+ disabled={isGenerateDisabled}
620
+ className="rounded-md bg-cyan-600 px-4 py-2 text-sm font-bold text-white shadow-sm hover:bg-cyan-700 disabled:cursor-not-allowed disabled:bg-gray-400"
621
+ >
622
+ ✨ Generate Pane
623
+ </button>
624
+ </div>
625
+ </div>
626
+ );
627
+ }
628
+
629
+ return (
630
+ <div className="space-y-4 p-4">
631
+ <CopyInputStep
632
+ copyMode={copyMode}
633
+ onCopyModeChange={setCopyMode}
634
+ promptValue={promptValue}
635
+ onPromptValueChange={setPromptValue}
636
+ copyValue={copyValue}
637
+ onCopyValueChange={setCopyValue}
638
+ defaultPrompt={
639
+ first
640
+ ? prompts.aiPaneCopyPrompt.heroDefault
641
+ : prompts.aiPaneCopyPrompt.contentDefault
642
+ }
643
+ />
644
+ <div className="flex justify-between">
645
+ <button
646
+ onClick={handleBack}
647
+ 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"
648
+ >
649
+ ← Back
650
+ </button>
651
+ <button
652
+ onClick={handleFinalGenerate}
653
+ disabled={
654
+ copyMode === 'prompt' ? !promptValue.trim() : !copyValue.trim()
655
+ }
656
+ className="rounded-md bg-cyan-600 px-4 py-2 text-sm font-bold text-white shadow-sm hover:bg-cyan-700 disabled:cursor-not-allowed disabled:bg-gray-400"
657
+ >
658
+ ✨ Generate Pane
659
+ </button>
660
+ </div>
661
+ </div>
662
+ );
663
+ };
664
+
454
665
  const renderDesignLibraryStep = () => (
455
666
  <div className="space-y-4 p-4">
456
667
  <div className="flex justify-start">
@@ -532,6 +743,8 @@ const AddPaneNewPanel = ({
532
743
  switch (step) {
533
744
  case 'initial':
534
745
  return renderInitialStep();
746
+ case 'layoutChoice':
747
+ return renderLayoutChoiceStep();
535
748
  case 'copyInput':
536
749
  return renderContentStep();
537
750
  case 'designLibrary':
@@ -406,7 +406,7 @@ export const RestylePaneModal = ({ config }: RestylePaneModalProps) => {
406
406
  <Dialog.Positioner className="z-104 fixed inset-0 flex items-center justify-center">
407
407
  <Dialog.Content
408
408
  className="flex flex-col rounded-lg bg-white shadow-2xl"
409
- style={{ height: '90vw', width: '90vw' }}
409
+ style={{ maxHeight: '90vw', width: '90vw' }}
410
410
  >
411
411
  <header className="flex items-center justify-between border-b p-4">
412
412
  <Dialog.Title className="text-xl font-bold">
@@ -28,7 +28,6 @@ const StyleImagePanel = ({
28
28
  outerContainerNode,
29
29
  parentNode,
30
30
  }: StyleImagePanelProps) => {
31
- console.log(`StyleImagePanel`, parentNode, node);
32
31
  const [altDescription, setAltDescription] = useState(node.alt || '');
33
32
 
34
33
  const imgDefaultClasses = parentNode.defaultClasses?.[node.tagName];
@@ -41,12 +41,33 @@
41
41
  },
42
42
  "aiPaneShellPrompt": {
43
43
  "system": "You are an expert web designer. Your task is to generate the structural design for a component as a single JSON object. Respond *only* with the JSON.",
44
- "user_template": "Generate the design JSON for a component with the following characteristics:\n\nDesign Style: \"{{DESIGN_INPUT}}\"\nLayout Type: \"{{LAYOUT_TYPE}}\"\n\nCRITICAL RULES:\n1. You must respond with a JSON object with keys: `bgColour`, `parentClasses`, `defaultClasses`.\n2. The `parentClasses` value *must* be structured with our internal responsive object schema (e.g., `[ { \"mobile\": { \"px\": \"4\", \"py\": \"12\" } } ]`).\n3. The `defaultClasses` value *must* be structured with responsive keys (`mobile`, `tablet`, `desktop`) containing Tailwind class strings.\n4. Ensure the selected `bgColour` provides **high contrast** (meeting at least WCAG AA standards - 4.5:1 for normal text, 3:1 for large text) with the primary text colors defined in `defaultClasses`.\n\nEXAMPLE:\n{\n \"bgColour\": \"#050710\",\n \"parentClasses\": [\n { \"mobile\": { \"px\": \"6\", \"py\": \"24\" }, \"tablet\": { \"px\": \"8\", \"py\": \"32\" } },\n { \"mobile\": { \"mx\": \"auto\", \"maxW\": \"2xl\", \"textALIGN\": \"center\" }, \"tablet\": { \"maxW\": \"4xl\" } }\n ],\n \"defaultClasses\": {\n \"h2\": { \"mobile\": \"text-4xl font-bold tracking-tight text-white mt-4\", \"tablet\": \"text-6xl\", \"desktop\": \"text-7xl\" },\n \"p\": { \"mobile\": \"text-lg leading-8 text-gray-300 mt-6\", \"tablet\": \"text-xl\", \"desktop\": \"\" }\n }\n}"
44
+ "user_template": "Generate the design JSON for a component with the following characteristics:\n\nDesign Style: \"{{DESIGN_INPUT}}\"\nLayout Type: \"{{LAYOUT_TYPE}}\"\n\nCRITICAL RULES:\n1. You must respond with a JSON object with keys: `bgColour`, `parentClasses`, `defaultClasses`.\n2. The `parentClasses` value *must* be an array of objects. Each object must have `mobile`, `tablet`, or `desktop` keys, and their values *must* be a single string of space-separated Tailwind CSS classes.\n3. The `defaultClasses` value *must* be structured with responsive keys (`mobile`, `tablet`, `desktop`) containing Tailwind class strings.\n4. Ensure the selected `bgColour` provides **high contrast** (meeting at least WCAG AA standards - 4.5:1 for normal text, 3:1 for large text) with the primary text colors defined in `defaultClasses`.\n\nEXAMPLE:\n{\n \"bgColour\": \"#050710\",\n \"parentClasses\": [\n { \"mobile\": \"mx-auto max-w-2xl text-center\", \"tablet\": \"max-w-4xl\" },\n { \"mobile\": \"px-6 py-24\", \"tablet\": \"px-8 py-32\" }\n ],\n \"defaultClasses\": {\n \"h2\": { \"mobile\": \"text-4xl font-bold tracking-tight text-white mt-4\", \"tablet\": \"text-6xl\", \"desktop\": \"text-7xl\" },\n \"p\": { \"mobile\": \"text-lg leading-8 text-gray-300 mt-6\", \"tablet\": \"text-xl\", \"desktop\": \"\" }\n }\n}"
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
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
+ },
52
+ "aiPaneShellPrompt_2cols": {
53
+ "system": "You are an expert web designer. Your task is to generate the structural design for a component as a single JSON object. Respond *only* with the JSON.",
54
+ "user_template": "Generate the design JSON for the following component. The component is a 2-column grid layout where the columns stack vertically on mobile. Your task is to design the outer container shell (`parentClasses`), the shared typography theme (`defaultClasses`), and the specific styles for the individual columns (`columns`).\n\nComponent Brief: \"{{COPY_INPUT}}\"\n\nDesign Style: \"{{DESIGN_INPUT}}\"\n\nCRITICAL RULES:\n1. You must respond with a JSON object with the top-level keys: `bgColour`, `parentClasses`, `defaultClasses`, and `columns`.\n2. The `parentClasses` value is for the OUTER container's spacing and width ONLY (e.g. `max-w-7xl`, `py-24`). It is **FORBIDDEN** to include `grid`, `grid-cols-*`, or `gap-*` properties in `parentClasses`. The application will handle the grid creation. Any response violating this rule will be rejected. The `parentClasses` value *must* be an ARRAY of objects.\n3. The `defaultClasses` value defines the theme and *must* be structured with responsive keys (`mobile`, `tablet`, `desktop`) containing Tailwind class strings.\n4. The `columns` key *must* be an array containing exactly **two** objects. Each object represents an individual column and must have a `gridClasses` key. The value for `gridClasses` is a responsive object where each key's value is a string of Tailwind classes. This is used to style the column's wrapper. Crucially, you must remember that Tailwind is mobile-first, so you must reset styles at larger breakpoints if needed. For example, to add spacing for the mobile stack that is removed on larger screens, you would use `{ \"mobile\": \"mt-12\", \"tablet\": \"mt-0\" }`.\n5. Ensure the selected `bgColour` provides **high contrast** (meeting at least WCAG AA standards) with the primary text colors defined in `defaultClasses`. Prioritize readability.\n\nEXAMPLE:\n{\n \"bgColour\": \"#0d1117\",\n \"parentClasses\": [\n { \n \"mobile\": \"mx-auto max-w-7xl\"\n },\n {\n \"mobile\": \"px-6 py-24\",\n \"tablet\": \"px-8 py-32\"\n }\n ],\n \"defaultClasses\": {\n \"h2\": {\n \"mobile\": \"text-4xl font-bold tracking-tight text-white\",\n \"tablet\": \"text-6xl\"\n },\n \"p\": {\n \"mobile\": \"text-lg leading-8 text-gray-300 mt-6\"\n }\n },\n \"columns\": [\n {\n \"gridClasses\": {\n \"mobile\": \"text-center\",\n \"tablet\": \"text-left\"\n }\n },\n {\n \"gridClasses\": {\n \"mobile\": \"flex flex-col items-center mt-12\",\n \"tablet\": \"items-start mt-0\"\n }\n }\n ]\n}"
55
+ },
56
+ "aiPaneCopyPrompt_2cols": {
57
+ "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**.",
58
+ "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* a column defined by this shell. Use the `defaultClasses` as your base theme for styling:\n{{SHELL_JSON}}\n\nNow, generate the HTML content based on these inputs. Your primary goal is to perform the following specific task for this single column. Use the \"Overall Component Brief\" only for context about the product or topic.\n\nSPECIFIC TASK: \"{{COLUMN_PROMPT}}\"\nOverall Component Brief (for context only): \"{{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**. Each element should be a separate block element.\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, including secondary links, must** be wrapped in a block element like `<p>` or `<button>`. Even when placing CTAs side-by-side in a `div`, this rule MUST be followed.\n6. 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**.\n7. Respond *only* with the raw HTML.\n\nEXAMPLE of a good response for THIS SPECIFIC TASK:\n{{COLUMN_EXAMPLE}}",
59
+ "presets": {
60
+ "heroDefault": {
61
+ "default": "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.",
62
+ "left": {
63
+ "prompt": "Generate a slogan with very large typography. Just the slogan in this column.",
64
+ "example": "<h2 class=\"text-4xl font-bold tracking-tight text-slate-100 md:text-5xl xl:text-6xl\">\n<span class=\"block\">Orchestrate Complexity.</span>\n<span class=\"block bg-gradient-to-r from-sky-400 to-cyan-300 bg-clip-text text-transparent md:mt-2\">Deploy with Certainty.</span>\n</h2>"
65
+ },
66
+ "right": {
67
+ "prompt": "Generate a small sub-heading, a brief compelling paragraph of hook text, and two vertically stacked call-to-action buttons below it.",
68
+ "example": "<h3 class=\"text-xl font-bold text-white\">Join us this week</h3>\n<p class=\"mt-4 text-lg leading-8 text-gray-300\">We're all about creating a place where anyone and everyone can know God & encounter Him.</p>\n<button class=\"mt-8 rounded-md bg-white px-4 py-3 text-base font-semibold text-gray-900 shadow-sm hover:bg-gray-100 w-full\">Plan a Visit</button>\n<button class=\"mt-4 rounded-md border border-white px-4 py-3 text-base font-semibold text-white shadow-sm hover:bg-white/10 w-full\">Get Involved</button>"
69
+ }
70
+ }
71
+ }
51
72
  }
52
73
  }