@morphika/andami 0.5.1 → 0.5.3

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 (147) hide show
  1. package/README.md +27 -2
  2. package/app/admin/assets/page.tsx +6 -6
  3. package/app/admin/database/page.tsx +302 -302
  4. package/app/admin/error.tsx +53 -53
  5. package/app/admin/layout.tsx +332 -320
  6. package/app/admin/navigation/page.tsx +255 -255
  7. package/app/admin/pages/[slug]/page.tsx +44 -27
  8. package/app/admin/pages/page.tsx +24 -19
  9. package/app/admin/projects/page.tsx +30 -21
  10. package/app/admin/setup/page.tsx +1 -1
  11. package/app/admin/styles/page.tsx +1 -1
  12. package/app/api/admin/assets/register/route.ts +51 -14
  13. package/app/api/admin/assets/registry/route.ts +4 -1
  14. package/app/api/admin/assets/relink/confirm/route.ts +4 -1
  15. package/app/api/admin/assets/relink/route.ts +4 -1
  16. package/app/api/admin/assets/scan/route.ts +4 -1
  17. package/app/api/admin/backups/restore-data/route.ts +4 -1
  18. package/app/api/admin/r2/connect/route.ts +4 -1
  19. package/app/api/admin/r2/delete/route.ts +4 -1
  20. package/app/api/admin/r2/rename/route.ts +4 -1
  21. package/app/api/admin/r2/upload-url/route.ts +4 -1
  22. package/app/api/admin/revalidate/route.ts +4 -1
  23. package/app/api/admin/storage/switch/route.ts +4 -1
  24. package/app/api/custom-sections/[id]/route.ts +5 -6
  25. package/components/admin/MetadataEditor.tsx +6 -6
  26. package/components/admin/PublishToggle.tsx +2 -2
  27. package/components/admin/nav-builder/NavBuilder.tsx +1 -1
  28. package/components/admin/nav-builder/NavBuilderGrid.tsx +3 -3
  29. package/components/admin/nav-builder/NavGridCell.tsx +48 -48
  30. package/components/admin/nav-builder/NavGridItem.tsx +8 -6
  31. package/components/admin/nav-builder/NavItemSettings.tsx +331 -331
  32. package/components/admin/nav-builder/NavItemTypePicker.tsx +102 -102
  33. package/components/admin/nav-builder/NavLivePreview.tsx +1 -1
  34. package/components/admin/nav-builder/NavMobileLivePreview.tsx +226 -226
  35. package/components/admin/nav-builder/NavMobileSettings.tsx +242 -242
  36. package/components/admin/nav-builder/NavSettingsFields.tsx +518 -514
  37. package/components/admin/setup-wizard/BrandingStep.tsx +3 -3
  38. package/components/admin/setup-wizard/DatabaseStep.tsx +2 -2
  39. package/components/admin/setup-wizard/DoneStep.tsx +1 -1
  40. package/components/admin/setup-wizard/SetupWizard.tsx +4 -4
  41. package/components/admin/setup-wizard/StorageStep.tsx +2 -2
  42. package/components/admin/setup-wizard/WelcomeStep.tsx +2 -2
  43. package/components/admin/styles/ColorsEditor.tsx +9 -8
  44. package/components/admin/styles/FontsEditor.tsx +9 -7
  45. package/components/admin/styles/GridLayoutEditor.tsx +9 -9
  46. package/components/admin/styles/LinksButtonsEditor.tsx +5 -5
  47. package/components/admin/styles/TypographyEditor.tsx +6 -6
  48. package/components/admin/styles/shared.tsx +68 -68
  49. package/components/blocks/AudioBlockRenderer.tsx +286 -286
  50. package/components/blocks/CoverSectionRenderer.tsx +7 -1
  51. package/components/blocks/MarqueeBlockRenderer.tsx +316 -0
  52. package/components/blocks/ProjectCarouselBlockRenderer.tsx +1 -1
  53. package/components/blocks/SectionV2Renderer.tsx +8 -1
  54. package/components/builder/BlockCardIcons.tsx +316 -316
  55. package/components/builder/BlockTypePicker.tsx +1 -1
  56. package/components/builder/BubbleIcons.tsx +104 -0
  57. package/components/builder/BuilderCanvas.tsx +2 -0
  58. package/components/builder/CanvasMinimap.tsx +66 -49
  59. package/components/builder/CanvasToolbar.tsx +31 -41
  60. package/components/builder/CoverSectionCanvas.tsx +363 -363
  61. package/components/builder/DeviceFrame.tsx +1 -1
  62. package/components/builder/DndWrapper.tsx +3 -3
  63. package/components/builder/InsertionLines.tsx +1 -1
  64. package/components/builder/SectionCardIcons.tsx +421 -320
  65. package/components/builder/SectionEditorBar.tsx +5 -3
  66. package/components/builder/SectionTypePicker.tsx +7 -5
  67. package/components/builder/SectionV2Canvas.tsx +1 -1
  68. package/components/builder/SectionV2Column.tsx +82 -68
  69. package/components/builder/SettingsPanel.tsx +21 -17
  70. package/components/builder/SortableBlock.tsx +93 -73
  71. package/components/builder/SortableRow.tsx +33 -35
  72. package/components/builder/VirtualAssetGrid.tsx +10 -4
  73. package/components/builder/asset-browser/R2BrowserContent.tsx +18 -14
  74. package/components/builder/blockStyles.tsx +192 -185
  75. package/components/builder/color-picker/AlphaSlider.tsx +141 -141
  76. package/components/builder/color-picker/ColorInputs.tsx +105 -105
  77. package/components/builder/color-picker/EyedropperButton.tsx +75 -74
  78. package/components/builder/color-picker/HueSlider.tsx +124 -124
  79. package/components/builder/color-picker/SaturationCanvas.tsx +142 -142
  80. package/components/builder/color-picker/SwatchBar.tsx +98 -93
  81. package/components/builder/color-picker/UnifiedColorPicker.tsx +11 -6
  82. package/components/builder/editors/AudioBlockEditor.tsx +242 -242
  83. package/components/builder/editors/BeforeAfterBlockEditor.tsx +360 -360
  84. package/components/builder/editors/ButtonBlockEditor.tsx +4 -4
  85. package/components/builder/editors/EnterAnimationPicker.tsx +2 -2
  86. package/components/builder/editors/HoverEffectPicker.tsx +2 -2
  87. package/components/builder/editors/ImageBlockEditor.tsx +2 -2
  88. package/components/builder/editors/ImageGridBlockEditor.tsx +8 -6
  89. package/components/builder/editors/MarqueeBlockEditor.tsx +622 -0
  90. package/components/builder/editors/ProjectCarouselBlockEditor.tsx +443 -443
  91. package/components/builder/editors/ProjectGridEditor.tsx +21 -16
  92. package/components/builder/editors/SpacerBlockEditor.tsx +29 -27
  93. package/components/builder/editors/StaggerSettings.tsx +109 -109
  94. package/components/builder/editors/TextBlockEditor.tsx +22 -17
  95. package/components/builder/editors/TextStylePicker.tsx +1 -1
  96. package/components/builder/editors/VideoBlockEditor.tsx +2 -2
  97. package/components/builder/editors/index.ts +11 -10
  98. package/components/builder/editors/shared.tsx +10 -8
  99. package/components/builder/live-preview/LiveAudioPreview.tsx +120 -120
  100. package/components/builder/live-preview/LiveBeforeAfterPreview.tsx +1 -1
  101. package/components/builder/live-preview/LiveImageGridPreview.tsx +10 -2
  102. package/components/builder/live-preview/LiveImagePreview.tsx +4 -2
  103. package/components/builder/live-preview/LiveMarqueePreview.tsx +39 -0
  104. package/components/builder/live-preview/LiveProjectCarouselPreview.tsx +1 -1
  105. package/components/builder/live-preview/LiveVideoPreview.tsx +1 -1
  106. package/components/builder/live-preview/ProjectCardWrapper.tsx +293 -291
  107. package/components/builder/live-preview/RichTextBubbleMenu.tsx +10 -6
  108. package/components/builder/live-preview/shared.tsx +5 -2
  109. package/components/builder/settings-panel/AnimationTab.tsx +138 -138
  110. package/components/builder/settings-panel/BlockLayoutTab.tsx +11 -9
  111. package/components/builder/settings-panel/CardEntranceSection.tsx +114 -114
  112. package/components/builder/settings-panel/ColumnV2LayoutTab.tsx +242 -0
  113. package/components/builder/settings-panel/ColumnV2Settings.tsx +5 -5
  114. package/components/builder/settings-panel/CoverSectionLayoutTab.tsx +71 -71
  115. package/components/builder/settings-panel/CoverSectionSettings.tsx +337 -335
  116. package/components/builder/settings-panel/PageSettings.tsx +3 -3
  117. package/components/builder/settings-panel/ParallaxSlideSettings.tsx +2 -2
  118. package/components/builder/settings-panel/SectionV2AnimationTab.tsx +4 -4
  119. package/components/builder/settings-panel/SectionV2LayoutTab.tsx +356 -356
  120. package/components/builder/settings-panel/SectionV2Settings.tsx +25 -20
  121. package/components/builder/settings-panel/TRBLInputs.tsx +1 -1
  122. package/components/builder/settings-panel/index.ts +1 -0
  123. package/lib/animation/enter-types.ts +1 -0
  124. package/lib/animation/hover-effect-presets.ts +210 -210
  125. package/lib/animation/hover-effect-types.ts +1 -0
  126. package/lib/builder/block-registrations.ts +468 -417
  127. package/lib/builder/constants.ts +111 -111
  128. package/lib/builder/serializer/normalizers.ts +14 -0
  129. package/lib/builder/serializer/serializers.ts +27 -0
  130. package/lib/builder/store-sections.ts +23 -2
  131. package/lib/builder/types-slices.ts +428 -414
  132. package/lib/builder/types.ts +4 -1
  133. package/lib/config/index.ts +27 -27
  134. package/lib/sanity/queries.ts +48 -0
  135. package/lib/sanity/types.ts +112 -1
  136. package/lib/version.ts +1 -1
  137. package/package.json +7 -5
  138. package/sanity/schemas/blocks/audioBlock.ts +69 -69
  139. package/sanity/schemas/blocks/index.ts +12 -11
  140. package/sanity/schemas/blocks/marqueeBlock.ts +292 -0
  141. package/sanity/schemas/index.ts +120 -117
  142. package/sanity/schemas/objects/coverSection.ts +32 -0
  143. package/sanity/schemas/objects/parallaxSlide.ts +32 -0
  144. package/sanity/schemas/pageSectionV2.ts +32 -0
  145. package/styles/admin.css +85 -85
  146. package/styles/animations.css +237 -237
  147. package/styles/base.css +114 -114
@@ -100,7 +100,7 @@ export function BrandingStep({ onNext, onBack }: WizardStepProps) {
100
100
  }, [siteTitle, onNext]);
101
101
 
102
102
  const inputClass =
103
- "w-full rounded-lg border border-black/[0.08] bg-white px-3 py-2.5 text-sm text-[#333] placeholder:text-[#bbb] focus:outline-none focus:ring-2 focus:ring-[#076bff]/20 focus:border-[#076bff]/40";
103
+ "w-full rounded-lg border border-black/[0.08] bg-white px-3 py-2.5 text-sm text-[#333] placeholder:text-[#bbb] focus:outline-none focus:ring-2 focus:ring-[#3580f9]/20 focus:border-[#3580f9]/40";
104
104
 
105
105
  if (loading) {
106
106
  return (
@@ -196,7 +196,7 @@ export function BrandingStep({ onNext, onBack }: WizardStepProps) {
196
196
  <button
197
197
  onClick={handleSave}
198
198
  disabled={saving || !siteTitle.trim()}
199
- className="px-5 py-2.5 bg-[#076bff] text-white text-sm font-medium rounded-lg hover:bg-[#0559d4] transition-colors disabled:opacity-50"
199
+ className="px-5 py-2.5 bg-[#3580f9] text-white text-sm font-medium rounded-lg hover:bg-[#2d6dd4] transition-colors disabled:opacity-50"
200
200
  >
201
201
  {saving ? "Saving..." : "Save & Continue"}
202
202
  </button>
@@ -206,7 +206,7 @@ export function BrandingStep({ onNext, onBack }: WizardStepProps) {
206
206
  {saved && (
207
207
  <button
208
208
  onClick={onNext}
209
- className="px-5 py-2.5 bg-[#076bff] text-white text-sm font-medium rounded-lg hover:bg-[#0559d4] transition-colors"
209
+ className="px-5 py-2.5 bg-[#3580f9] text-white text-sm font-medium rounded-lg hover:bg-[#2d6dd4] transition-colors"
210
210
  >
211
211
  Next
212
212
  </button>
@@ -309,7 +309,7 @@ export function DatabaseStep({ onNext, onBack }: WizardStepProps) {
309
309
  <button
310
310
  onClick={seedDocuments}
311
311
  disabled={seedState === "seeding"}
312
- className="px-4 py-2 text-sm bg-[#076bff] text-white rounded-lg hover:bg-[#0559d4] transition-colors disabled:opacity-50"
312
+ className="px-4 py-2 text-sm bg-[#3580f9] text-white rounded-lg hover:bg-[#2d6dd4] transition-colors disabled:opacity-50"
313
313
  >
314
314
  {seedState === "seeding" ? "Creating..." : "Create Initial Documents"}
315
315
  </button>
@@ -319,7 +319,7 @@ export function DatabaseStep({ onNext, onBack }: WizardStepProps) {
319
319
  {canAdvance && (
320
320
  <button
321
321
  onClick={onNext}
322
- className="px-5 py-2.5 bg-[#076bff] text-white text-sm font-medium rounded-lg hover:bg-[#0559d4] transition-colors"
322
+ className="px-5 py-2.5 bg-[#3580f9] text-white text-sm font-medium rounded-lg hover:bg-[#2d6dd4] transition-colors"
323
323
  >
324
324
  Next
325
325
  </button>
@@ -177,7 +177,7 @@ export function DoneStep({ onBack }: WizardStepProps) {
177
177
  <button
178
178
  onClick={handleComplete}
179
179
  disabled={completing}
180
- className="px-6 py-2.5 bg-[#076bff] text-white text-sm font-medium rounded-lg hover:bg-[#0559d4] transition-colors disabled:opacity-50"
180
+ className="px-6 py-2.5 bg-[#3580f9] text-white text-sm font-medium rounded-lg hover:bg-[#2d6dd4] transition-colors disabled:opacity-50"
181
181
  >
182
182
  {completing ? "Starting..." : "Start Building"}
183
183
  </button>
@@ -105,9 +105,9 @@ export function SetupWizard({ initialStatus }: SetupWizardProps) {
105
105
  <div
106
106
  className={`w-6 h-6 rounded-full flex items-center justify-center text-[10px] font-semibold transition-colors ${
107
107
  isActive
108
- ? "bg-[#076bff] text-white"
108
+ ? "bg-[#3580f9] text-white"
109
109
  : isDone
110
- ? "bg-[#076bff]/20 text-[#076bff]"
110
+ ? "bg-[#3580f9]/20 text-[#3580f9]"
111
111
  : "bg-black/[0.06] text-[#999]"
112
112
  }`}
113
113
  >
@@ -124,7 +124,7 @@ export function SetupWizard({ initialStatus }: SetupWizardProps) {
124
124
  isActive
125
125
  ? "text-[#333] font-medium"
126
126
  : isDone
127
- ? "text-[#076bff]"
127
+ ? "text-[#3580f9]"
128
128
  : "text-[#999]"
129
129
  }`}
130
130
  >
@@ -135,7 +135,7 @@ export function SetupWizard({ initialStatus }: SetupWizardProps) {
135
135
  {i < STEPS.length - 1 && (
136
136
  <div
137
137
  className={`w-8 h-px transition-colors ${
138
- i < currentStep ? "bg-[#076bff]/30" : "bg-black/[0.08]"
138
+ i < currentStep ? "bg-[#3580f9]/30" : "bg-black/[0.08]"
139
139
  }`}
140
140
  />
141
141
  )}
@@ -128,7 +128,7 @@ export function StorageStep({ onNext, onBack }: WizardStepProps) {
128
128
  );
129
129
 
130
130
  const inputClass =
131
- "w-full rounded-lg border border-black/[0.08] bg-white px-3 py-2 text-sm text-[#333] placeholder:text-[#bbb] focus:outline-none focus:ring-2 focus:ring-[#076bff]/20 focus:border-[#076bff]/40";
131
+ "w-full rounded-lg border border-black/[0.08] bg-white px-3 py-2 text-sm text-[#333] placeholder:text-[#bbb] focus:outline-none focus:ring-2 focus:ring-[#3580f9]/20 focus:border-[#3580f9]/40";
132
132
  const labelClass = "block text-xs font-medium text-[#666] mb-1";
133
133
 
134
134
  if (state === "loading") {
@@ -296,7 +296,7 @@ export function StorageStep({ onNext, onBack }: WizardStepProps) {
296
296
  {state === "connected" && (
297
297
  <button
298
298
  onClick={onNext}
299
- className="px-5 py-2.5 bg-[#076bff] text-white text-sm font-medium rounded-lg hover:bg-[#0559d4] transition-colors"
299
+ className="px-5 py-2.5 bg-[#3580f9] text-white text-sm font-medium rounded-lg hover:bg-[#2d6dd4] transition-colors"
300
300
  >
301
301
  Next
302
302
  </button>
@@ -75,7 +75,7 @@ export function WelcomeStep({ onNext }: WizardStepProps) {
75
75
  },
76
76
  ].map((item) => (
77
77
  <li key={item.label} className="flex items-start gap-3">
78
- <span className="text-[#076bff] mt-0.5 shrink-0">{item.icon}</span>
78
+ <span className="text-[#3580f9] mt-0.5 shrink-0">{item.icon}</span>
79
79
  <div>
80
80
  <span className="text-sm font-medium text-[#333]">{item.label}</span>
81
81
  <p className="text-xs text-[#999] mt-0.5">{item.desc}</p>
@@ -87,7 +87,7 @@ export function WelcomeStep({ onNext }: WizardStepProps) {
87
87
 
88
88
  <button
89
89
  onClick={onNext}
90
- className="px-6 py-2.5 bg-[#076bff] text-white text-sm font-medium rounded-lg hover:bg-[#0559d4] transition-colors"
90
+ className="px-6 py-2.5 bg-[#3580f9] text-white text-sm font-medium rounded-lg hover:bg-[#2d6dd4] transition-colors"
91
91
  >
92
92
  Get Started
93
93
  </button>
@@ -5,6 +5,7 @@ import ColorPicker, { isValidHex } from "../../../components/builder/ColorPicker
5
5
  import { invalidatePaletteCache } from "../../../components/builder/ColorSwatchPicker";
6
6
  import type { SiteStyles, ColorSwatch } from "../../../lib/sanity/types";
7
7
  import { Section, SaveButton } from "./shared";
8
+ import { BubbleTooltip } from "../../builder/BubbleIcons";
8
9
 
9
10
  function getContrastColor(hex: string): string {
10
11
  if (!isValidHex(hex)) return "#000";
@@ -119,14 +120,14 @@ export function ColorsEditor({
119
120
  <div className="absolute inset-0 bg-black/15 flex items-center justify-center gap-1.5">
120
121
  <button
121
122
  onClick={(e) => { e.stopPropagation(); setEditingIndex(i); setPickerOpen(true); }}
122
- className="w-6 h-6 rounded-full bg-white/90 flex items-center justify-center text-[11px] cursor-pointer hover:bg-white transition-colors border-none"
123
- title="Edit color"
124
- >✎</button>
123
+ className="group/bb relative w-6 h-6 rounded-full bg-white/90 flex items-center justify-center text-[11px] cursor-pointer hover:bg-white transition-colors border-none"
124
+ aria-label="Edit color"
125
+ >✎<BubbleTooltip>Edit color</BubbleTooltip></button>
125
126
  <button
126
127
  onClick={(e) => { e.stopPropagation(); removeSwatch(i); }}
127
- className="w-6 h-6 rounded-full bg-white/90 flex items-center justify-center text-[13px] text-red-500 cursor-pointer hover:bg-white transition-colors border-none"
128
- title="Remove"
129
- >×</button>
128
+ className="group/bb relative w-6 h-6 rounded-full bg-white/90 flex items-center justify-center text-[13px] text-red-500 cursor-pointer hover:bg-white transition-colors border-none"
129
+ aria-label="Remove"
130
+ >×<BubbleTooltip>Remove</BubbleTooltip></button>
130
131
  </div>
131
132
  )}
132
133
  </div>
@@ -156,7 +157,7 @@ export function ColorsEditor({
156
157
  {!pickerOpen && (
157
158
  <button
158
159
  onClick={() => { setEditingIndex(null); setPickerOpen(true); }}
159
- className="rounded-xl border-2 border-dashed border-neutral-200 h-[100px] flex flex-col items-center justify-center gap-1 cursor-pointer hover:border-[#076bff] hover:text-[#076bff] text-neutral-400 transition-colors bg-transparent"
160
+ className="rounded-xl border-2 border-dashed border-neutral-200 h-[100px] flex flex-col items-center justify-center gap-1 cursor-pointer hover:border-[#3580f9] hover:text-[#3580f9] text-neutral-400 transition-colors bg-transparent"
160
161
  >
161
162
  <span className="text-xl leading-none">+</span>
162
163
  <span className="text-[10px] uppercase tracking-wider">Add</span>
@@ -190,7 +191,7 @@ export function ColorsEditor({
190
191
  {pickerOpen && (
191
192
  <div className="flex justify-center mb-4">
192
193
  <ColorPicker
193
- color={editingIndex !== null ? swatches[editingIndex].hex : "#076bff"}
194
+ color={editingIndex !== null ? swatches[editingIndex].hex : "#3580f9"}
194
195
  onChange={addSwatch}
195
196
  onClose={() => { setPickerOpen(false); setEditingIndex(null); }}
196
197
  confirmLabel={editingIndex !== null ? "Update color" : "Add to palette"}
@@ -4,6 +4,7 @@ import { useState, useEffect, useRef } from "react";
4
4
  import { csrfHeaders } from "../../../lib/csrf-client";
5
5
  import type { FontFamily, FontVariant } from "../../../lib/sanity/types";
6
6
  import { Section, SaveButton } from "./shared";
7
+ import { BubbleTooltip } from "../../builder/BubbleIcons";
7
8
 
8
9
  export function FontsEditor({ fonts, onSave, saving }: { fonts: FontFamily[]; onSave: (fonts: FontFamily[]) => void; saving: boolean }) {
9
10
  const [localFonts, setLocalFonts] = useState<FontFamily[]>(fonts);
@@ -115,7 +116,7 @@ export function FontsEditor({ fonts, onSave, saving }: { fonts: FontFamily[]; on
115
116
  key={font._key}
116
117
  className={`rounded-xl border overflow-hidden transition-all cursor-pointer ${
117
118
  isExpanded
118
- ? "border-[#076bff] shadow-sm ring-1 ring-[#076bff]/20"
119
+ ? "border-[#3580f9] shadow-sm ring-1 ring-[#3580f9]/20"
119
120
  : "border-neutral-200 hover:shadow-md hover:-translate-y-0.5"
120
121
  }`}
121
122
  onClick={() => setExpandedFont(isExpanded ? null : font._key)}
@@ -147,7 +148,7 @@ export function FontsEditor({ fonts, onSave, saving }: { fonts: FontFamily[]; on
147
148
  {/* Add font card */}
148
149
  <button
149
150
  onClick={addFont}
150
- className="rounded-xl border-2 border-dashed border-neutral-200 min-h-[120px] flex flex-col items-center justify-center gap-1.5 cursor-pointer hover:border-[#076bff] hover:text-[#076bff] text-neutral-400 transition-colors bg-transparent"
151
+ className="rounded-xl border-2 border-dashed border-neutral-200 min-h-[120px] flex flex-col items-center justify-center gap-1.5 cursor-pointer hover:border-[#3580f9] hover:text-[#3580f9] text-neutral-400 transition-colors bg-transparent"
151
152
  >
152
153
  <span className="text-xl leading-none">+</span>
153
154
  <span className="text-[10px] uppercase tracking-wider">Add Font Family</span>
@@ -166,7 +167,7 @@ export function FontsEditor({ fonts, onSave, saving }: { fonts: FontFamily[]; on
166
167
  value={font.family}
167
168
  onChange={(e) => updateFont(font._key, { family: e.target.value })}
168
169
  placeholder="Font Family Name (e.g. Inter)"
169
- className="flex-1 rounded-lg border border-neutral-200 bg-white px-3 py-1.5 text-sm text-neutral-900 focus:border-[#076bff] focus:outline-none"
170
+ className="flex-1 rounded-lg border border-neutral-200 bg-white px-3 py-1.5 text-sm text-neutral-900 focus:border-[#3580f9] focus:outline-none"
170
171
  disabled={font.is_builtin}
171
172
  onClick={(e) => e.stopPropagation()}
172
173
  />
@@ -190,7 +191,7 @@ export function FontsEditor({ fonts, onSave, saving }: { fonts: FontFamily[]; on
190
191
  <select
191
192
  value={v.weight}
192
193
  onChange={(e) => updateVariant(font._key, v._key, { weight: e.target.value })}
193
- className="rounded border border-neutral-200 bg-white px-2 py-1 text-xs focus:border-[#076bff] focus:outline-none"
194
+ className="rounded border border-neutral-200 bg-white px-2 py-1 text-xs focus:border-[#3580f9] focus:outline-none"
194
195
  >
195
196
  {["100", "300", "400", "500", "600", "700", "800", "900"].map((w) => (
196
197
  <option key={w} value={w}>{w}</option>
@@ -199,13 +200,14 @@ export function FontsEditor({ fonts, onSave, saving }: { fonts: FontFamily[]; on
199
200
  <select
200
201
  value={v.style}
201
202
  onChange={(e) => updateVariant(font._key, v._key, { style: e.target.value as "normal" | "italic" })}
202
- className="rounded border border-neutral-200 bg-white px-2 py-1 text-xs focus:border-[#076bff] focus:outline-none"
203
+ className="rounded border border-neutral-200 bg-white px-2 py-1 text-xs focus:border-[#3580f9] focus:outline-none"
203
204
  >
204
205
  <option value="normal">Normal</option>
205
206
  <option value="italic">Italic</option>
206
207
  </select>
207
- <span className="text-neutral-400 truncate flex-1" title={v.original_filename}>
208
+ <span className="group/bb relative text-neutral-400 truncate flex-1" aria-label={v.original_filename}>
208
209
  {v.original_filename}
210
+ <BubbleTooltip>{v.original_filename}</BubbleTooltip>
209
211
  </span>
210
212
  {!font.is_builtin && (
211
213
  <button
@@ -225,7 +227,7 @@ export function FontsEditor({ fonts, onSave, saving }: { fonts: FontFamily[]; on
225
227
  fileInputRef.current?.click();
226
228
  }}
227
229
  disabled={uploading}
228
- className="text-xs text-[#076bff] hover:text-[#0559d4] transition-colors mt-1"
230
+ className="text-xs text-[#3580f9] hover:text-[#2d6dd4] transition-colors mt-1"
229
231
  >
230
232
  {uploading && uploadTarget === font._key ? "Uploading..." : "+ Add variant file"}
231
233
  </button>
@@ -15,7 +15,7 @@ const DEFAULT_GRID = {
15
15
 
16
16
  /** SVG illustration for the Column Gutter card */
17
17
  function GutterIcon() {
18
- const ACCENT = "#4794E2";
18
+ const ACCENT = "#3580f9";
19
19
  return (
20
20
  <svg viewBox="0 0 120 72" fill="none" className="w-full h-full">
21
21
  {/* 5 columns (ghost blue) */}
@@ -44,7 +44,7 @@ function GutterIcon() {
44
44
  /** SVG illustration for the Max Page Width card */
45
45
  function MaxWidthIcon() {
46
46
  // Frame/dots/content stay neutral; arrows + dashed boundaries are the accent.
47
- const ACCENT = "#4794E2";
47
+ const ACCENT = "#3580f9";
48
48
  return (
49
49
  <svg viewBox="0 0 120 72" fill="none" className="w-full h-full">
50
50
  {/* Browser frame */}
@@ -73,7 +73,7 @@ function MaxWidthIcon() {
73
73
  /** SVG illustration for the Scroll Animations card */
74
74
  function ScrollAnimIcon() {
75
75
  // 3 blue layers with increasing opacity convey the fade-in effect; accent arrow below.
76
- const ACCENT = "#4794E2";
76
+ const ACCENT = "#3580f9";
77
77
  return (
78
78
  <svg viewBox="0 0 120 72" fill="none" className="w-full h-full">
79
79
  {/* 3 stacked layers (fade-in effect) */}
@@ -169,7 +169,7 @@ export function GridLayoutEditor({
169
169
  type="text"
170
170
  value={local.grid_gutter_desktop}
171
171
  onChange={(e) => setLocal({ ...local, grid_gutter_desktop: e.target.value })}
172
- className="w-full rounded-lg border border-neutral-200 bg-white px-3 py-1.5 pr-8 text-sm text-neutral-900 focus:border-[#076bff] focus:outline-none"
172
+ className="w-full rounded-lg border border-neutral-200 bg-white px-3 py-1.5 pr-8 text-sm text-neutral-900 focus:border-[#3580f9] focus:outline-none"
173
173
  placeholder="30"
174
174
  />
175
175
  <span className="absolute right-3 top-1/2 -translate-y-1/2 text-[11px] text-neutral-400 pointer-events-none">px</span>
@@ -187,7 +187,7 @@ export function GridLayoutEditor({
187
187
  type="text"
188
188
  value={local.grid_width}
189
189
  onChange={(e) => setLocal({ ...local, grid_width: e.target.value })}
190
- className="w-full rounded-lg border border-neutral-200 bg-white px-3 py-1.5 pr-8 text-sm text-neutral-900 focus:border-[#076bff] focus:outline-none"
190
+ className="w-full rounded-lg border border-neutral-200 bg-white px-3 py-1.5 pr-8 text-sm text-neutral-900 focus:border-[#3580f9] focus:outline-none"
191
191
  placeholder={DEFAULT_GRID_WIDTH}
192
192
  />
193
193
  <span className="absolute right-3 top-1/2 -translate-y-1/2 text-[11px] text-neutral-400 pointer-events-none">px</span>
@@ -206,7 +206,7 @@ export function GridLayoutEditor({
206
206
  type="button"
207
207
  onClick={() => setAnimLocal(!animLocal)}
208
208
  className={`relative w-9 h-5 rounded-full transition-colors shrink-0 ${
209
- animLocal ? "bg-[#076bff]" : "bg-neutral-300"
209
+ animLocal ? "bg-[#3580f9]" : "bg-neutral-300"
210
210
  }`}
211
211
  >
212
212
  <span
@@ -234,7 +234,7 @@ export function GridLayoutEditor({
234
234
  type="text"
235
235
  value={local.grid_gutter_desktop}
236
236
  onChange={(e) => setLocal({ ...local, grid_gutter_desktop: e.target.value })}
237
- className="w-full rounded-lg border border-neutral-200 bg-white px-3 py-1.5 pr-8 text-sm text-neutral-900 focus:border-[#076bff] focus:outline-none"
237
+ className="w-full rounded-lg border border-neutral-200 bg-white px-3 py-1.5 pr-8 text-sm text-neutral-900 focus:border-[#3580f9] focus:outline-none"
238
238
  placeholder="30"
239
239
  />
240
240
  <span className="absolute right-3 top-1/2 -translate-y-1/2 text-[11px] text-neutral-400 pointer-events-none">px</span>
@@ -253,7 +253,7 @@ export function GridLayoutEditor({
253
253
  type="text"
254
254
  value={local.grid_gutter_responsive}
255
255
  onChange={(e) => setLocal({ ...local, grid_gutter_responsive: e.target.value })}
256
- className="w-full rounded-lg border border-neutral-200 bg-white px-3 py-1.5 pr-8 text-sm text-neutral-900 focus:border-[#076bff] focus:outline-none"
256
+ className="w-full rounded-lg border border-neutral-200 bg-white px-3 py-1.5 pr-8 text-sm text-neutral-900 focus:border-[#3580f9] focus:outline-none"
257
257
  placeholder="30"
258
258
  />
259
259
  <span className="absolute right-3 top-1/2 -translate-y-1/2 text-[11px] text-neutral-400 pointer-events-none">px</span>
@@ -272,7 +272,7 @@ export function GridLayoutEditor({
272
272
  type="text"
273
273
  value={local.grid_gutter_phone || "16"}
274
274
  onChange={(e) => setLocal({ ...local, grid_gutter_phone: e.target.value })}
275
- className="w-full rounded-lg border border-neutral-200 bg-white px-3 py-1.5 pr-8 text-sm text-neutral-900 focus:border-[#076bff] focus:outline-none"
275
+ className="w-full rounded-lg border border-neutral-200 bg-white px-3 py-1.5 pr-8 text-sm text-neutral-900 focus:border-[#3580f9] focus:outline-none"
276
276
  placeholder="16"
277
277
  />
278
278
  <span className="absolute right-3 top-1/2 -translate-y-1/2 text-[11px] text-neutral-400 pointer-events-none">px</span>
@@ -16,8 +16,8 @@ export function LinksButtonsEditor({
16
16
  saving: boolean;
17
17
  }) {
18
18
  const [local, setLocal] = useState({
19
- link_color: linkStyle?.color || "#076bff",
20
- link_hover_color: linkStyle?.hover_color || "#0559d4",
19
+ link_color: linkStyle?.color || "#3580f9",
20
+ link_hover_color: linkStyle?.hover_color || "#2d6dd4",
21
21
  link_underline: linkStyle?.underline ?? true,
22
22
  button_primary_bg: buttonStyle?.primary_bg || "#ffffff",
23
23
  button_primary_text: buttonStyle?.primary_text || "#000000",
@@ -28,8 +28,8 @@ export function LinksButtonsEditor({
28
28
 
29
29
  useEffect(() => {
30
30
  setLocal({
31
- link_color: linkStyle?.color || "#076bff",
32
- link_hover_color: linkStyle?.hover_color || "#0559d4",
31
+ link_color: linkStyle?.color || "#3580f9",
32
+ link_hover_color: linkStyle?.hover_color || "#2d6dd4",
33
33
  link_underline: linkStyle?.underline ?? true,
34
34
  button_primary_bg: buttonStyle?.primary_bg || "#ffffff",
35
35
  button_primary_text: buttonStyle?.primary_text || "#000000",
@@ -51,7 +51,7 @@ export function LinksButtonsEditor({
51
51
  type="checkbox"
52
52
  checked={local.link_underline}
53
53
  onChange={(e) => setLocal({ ...local, link_underline: e.target.checked })}
54
- className="accent-[#076bff]"
54
+ className="accent-[#3580f9]"
55
55
  />
56
56
  <span className="text-xs text-neutral-700">Underline links</span>
57
57
  </label>
@@ -179,7 +179,7 @@ export function TypographyEditor({
179
179
  <select
180
180
  value={t.font_family || ""}
181
181
  onChange={(e) => updateLevel(level, { font_family: e.target.value })}
182
- className="w-full rounded-lg border border-neutral-200 bg-white px-2 py-1.5 text-[11px] text-neutral-700 focus:border-[#076bff] focus:outline-none"
182
+ className="w-full rounded-lg border border-neutral-200 bg-white px-2 py-1.5 text-[11px] text-neutral-700 focus:border-[#3580f9] focus:outline-none"
183
183
  >
184
184
  <option value="">Inherit</option>
185
185
  {fontFamilies.map((f) => (
@@ -193,7 +193,7 @@ export function TypographyEditor({
193
193
  type="text"
194
194
  value={t.font_size}
195
195
  onChange={(e) => updateLevel(level, { font_size: e.target.value })}
196
- className="w-full rounded-lg border border-neutral-200 bg-white px-2 py-1.5 text-[11px] text-neutral-700 focus:border-[#076bff] focus:outline-none"
196
+ className="w-full rounded-lg border border-neutral-200 bg-white px-2 py-1.5 text-[11px] text-neutral-700 focus:border-[#3580f9] focus:outline-none"
197
197
  placeholder="3rem"
198
198
  />
199
199
  </div>
@@ -202,7 +202,7 @@ export function TypographyEditor({
202
202
  <select
203
203
  value={t.font_weight}
204
204
  onChange={(e) => updateLevel(level, { font_weight: e.target.value })}
205
- className="w-full rounded-lg border border-neutral-200 bg-white px-2 py-1.5 text-[11px] text-neutral-700 focus:border-[#076bff] focus:outline-none"
205
+ className="w-full rounded-lg border border-neutral-200 bg-white px-2 py-1.5 text-[11px] text-neutral-700 focus:border-[#3580f9] focus:outline-none"
206
206
  >
207
207
  {["100", "300", "400", "500", "600", "700", "800", "900"].map((w) => (
208
208
  <option key={w} value={w}>{w}</option>
@@ -215,7 +215,7 @@ export function TypographyEditor({
215
215
  type="text"
216
216
  value={t.line_height}
217
217
  onChange={(e) => updateLevel(level, { line_height: e.target.value })}
218
- className="w-full rounded-lg border border-neutral-200 bg-white px-2 py-1.5 text-[11px] text-neutral-700 focus:border-[#076bff] focus:outline-none"
218
+ className="w-full rounded-lg border border-neutral-200 bg-white px-2 py-1.5 text-[11px] text-neutral-700 focus:border-[#3580f9] focus:outline-none"
219
219
  placeholder="1.1"
220
220
  />
221
221
  </div>
@@ -225,7 +225,7 @@ export function TypographyEditor({
225
225
  type="text"
226
226
  value={t.letter_spacing}
227
227
  onChange={(e) => updateLevel(level, { letter_spacing: e.target.value })}
228
- className="w-full rounded-lg border border-neutral-200 bg-white px-2 py-1.5 text-[11px] text-neutral-700 focus:border-[#076bff] focus:outline-none"
228
+ className="w-full rounded-lg border border-neutral-200 bg-white px-2 py-1.5 text-[11px] text-neutral-700 focus:border-[#3580f9] focus:outline-none"
229
229
  placeholder="-0.02em"
230
230
  />
231
231
  </div>
@@ -234,7 +234,7 @@ export function TypographyEditor({
234
234
  <select
235
235
  value={t.text_transform || "none"}
236
236
  onChange={(e) => updateLevel(level, { text_transform: e.target.value as TypographyLevel["text_transform"] })}
237
- className="w-full rounded-lg border border-neutral-200 bg-white px-2 py-1.5 text-[11px] text-neutral-700 focus:border-[#076bff] focus:outline-none"
237
+ className="w-full rounded-lg border border-neutral-200 bg-white px-2 py-1.5 text-[11px] text-neutral-700 focus:border-[#3580f9] focus:outline-none"
238
238
  >
239
239
  <option value="none">None</option>
240
240
  <option value="uppercase">UPPERCASE</option>
@@ -1,68 +1,68 @@
1
- "use client";
2
-
3
- export function Section({ title, description, children }: { title: string; description?: string; children: React.ReactNode }) {
4
- return (
5
- <section className="bg-white rounded-2xl border border-neutral-200 p-6">
6
- <h2 className="text-lg font-semibold text-neutral-900 mb-1">{title}</h2>
7
- {description && <p className="text-xs text-neutral-500 mb-5">{description}</p>}
8
- {!description && <div className="mb-5" />}
9
- {children}
10
- </section>
11
- );
12
- }
13
-
14
- export function ColorField({ label, value, onChange }: { label: string; value: string; onChange: (v: string) => void }) {
15
- return (
16
- <div className="flex items-center gap-3">
17
- <input
18
- type="color"
19
- value={value}
20
- onChange={(e) => onChange(e.target.value)}
21
- className="w-8 h-8 rounded-lg border border-neutral-200 cursor-pointer bg-transparent [&::-webkit-color-swatch-wrapper]:p-0 [&::-webkit-color-swatch]:rounded-md [&::-webkit-color-swatch]:border-none"
22
- />
23
- <div className="flex-1">
24
- <label className="text-xs text-neutral-500 block mb-0.5">{label}</label>
25
- <input
26
- type="text"
27
- value={value}
28
- onChange={(e) => onChange(e.target.value)}
29
- className="w-full rounded-lg border border-neutral-200 bg-white px-2 py-1 text-xs text-neutral-900 focus:border-[#076bff] focus:outline-none"
30
- />
31
- </div>
32
- </div>
33
- );
34
- }
35
-
36
- export function SaveButton({ onClick, saving, label = "Save" }: { onClick: () => void; saving: boolean; label?: string }) {
37
- return (
38
- <button
39
- onClick={onClick}
40
- disabled={saving}
41
- className="rounded-lg bg-[#076bff] px-5 py-1.5 text-sm font-medium text-white hover:bg-[#0559d4] transition-colors disabled:opacity-50"
42
- >
43
- {saving ? "Saving..." : label}
44
- </button>
45
- );
46
- }
47
-
48
- export function FieldInput({ label, value, onChange, placeholder, helpText }: {
49
- label: string;
50
- value: string;
51
- onChange: (v: string) => void;
52
- placeholder?: string;
53
- helpText?: string;
54
- }) {
55
- return (
56
- <div>
57
- <label className="text-[10px] text-neutral-400 uppercase tracking-wider block mb-1">{label}</label>
58
- <input
59
- type="text"
60
- value={value}
61
- onChange={(e) => onChange(e.target.value)}
62
- className="w-full rounded-lg border border-neutral-200 bg-white px-3 py-2 text-sm text-neutral-900 focus:border-[#076bff] focus:outline-none"
63
- placeholder={placeholder}
64
- />
65
- {helpText && <p className="text-[10px] text-neutral-400 mt-1">{helpText}</p>}
66
- </div>
67
- );
68
- }
1
+ "use client";
2
+
3
+ export function Section({ title, description, children }: { title: string; description?: string; children: React.ReactNode }) {
4
+ return (
5
+ <section className="bg-white rounded-2xl border border-neutral-200 p-6">
6
+ <h2 className="text-lg font-semibold text-neutral-900 mb-1">{title}</h2>
7
+ {description && <p className="text-xs text-neutral-500 mb-5">{description}</p>}
8
+ {!description && <div className="mb-5" />}
9
+ {children}
10
+ </section>
11
+ );
12
+ }
13
+
14
+ export function ColorField({ label, value, onChange }: { label: string; value: string; onChange: (v: string) => void }) {
15
+ return (
16
+ <div className="flex items-center gap-3">
17
+ <input
18
+ type="color"
19
+ value={value}
20
+ onChange={(e) => onChange(e.target.value)}
21
+ className="w-8 h-8 rounded-lg border border-neutral-200 cursor-pointer bg-transparent [&::-webkit-color-swatch-wrapper]:p-0 [&::-webkit-color-swatch]:rounded-md [&::-webkit-color-swatch]:border-none"
22
+ />
23
+ <div className="flex-1">
24
+ <label className="text-xs text-neutral-500 block mb-0.5">{label}</label>
25
+ <input
26
+ type="text"
27
+ value={value}
28
+ onChange={(e) => onChange(e.target.value)}
29
+ className="w-full rounded-lg border border-neutral-200 bg-white px-2 py-1 text-xs text-neutral-900 focus:border-[#3580f9] focus:outline-none"
30
+ />
31
+ </div>
32
+ </div>
33
+ );
34
+ }
35
+
36
+ export function SaveButton({ onClick, saving, label = "Save" }: { onClick: () => void; saving: boolean; label?: string }) {
37
+ return (
38
+ <button
39
+ onClick={onClick}
40
+ disabled={saving}
41
+ className="rounded-lg bg-[#3580f9] px-5 py-1.5 text-sm font-medium text-white hover:bg-[#2d6dd4] transition-colors disabled:opacity-50"
42
+ >
43
+ {saving ? "Saving..." : label}
44
+ </button>
45
+ );
46
+ }
47
+
48
+ export function FieldInput({ label, value, onChange, placeholder, helpText }: {
49
+ label: string;
50
+ value: string;
51
+ onChange: (v: string) => void;
52
+ placeholder?: string;
53
+ helpText?: string;
54
+ }) {
55
+ return (
56
+ <div>
57
+ <label className="text-[10px] text-neutral-400 uppercase tracking-wider block mb-1">{label}</label>
58
+ <input
59
+ type="text"
60
+ value={value}
61
+ onChange={(e) => onChange(e.target.value)}
62
+ className="w-full rounded-lg border border-neutral-200 bg-white px-3 py-2 text-sm text-neutral-900 focus:border-[#3580f9] focus:outline-none"
63
+ placeholder={placeholder}
64
+ />
65
+ {helpText && <p className="text-[10px] text-neutral-400 mt-1">{helpText}</p>}
66
+ </div>
67
+ );
68
+ }