- {/* ── Color Schemes ──────────────────────────────────────────────── */}
+ {/* ── Color Schemes (Enhanced with Presets) ─────────────────────── */}
+
updateTheme({ colors: { ...theme.colors, ...colors } })}
+ />
+
+ {/* Advanced Color Controls */}
}
+ defaultOpen={false}
>
-
- {DEFAULT_COLOR_SCHEMES.map((scheme) => {
- const active = isSchemeActive(scheme);
- return (
-
- );
- })}
-
-
-
-
{/* Per-color pickers */}
{COLOR_LABELS.map(({ key, label, description }) => (
@@ -233,111 +178,13 @@ export function ThemeSettingsPanel() {
- {/* ── Typography ─────────────────────────────────────────────────── */}
- }
- >
- {/* Body font */}
-
-
-
-
-
- {/* Heading font */}
-
-
-
-
-
- {/* Base font size */}
-
-
-
-
- {theme.typography.baseFontSize}px
-
-
-
- updateTheme({
- typography: { ...theme.typography, baseFontSize: v },
- })
- }
- aria-label="Base font size"
- aria-valuetext={`${theme.typography.baseFontSize} pixels`}
- />
-
-
- {/* Heading scale */}
-
-
-
-
- {theme.typography.headingScale.toFixed(3)}
-
-
-
- updateTheme({
- typography: { ...theme.typography, headingScale: v },
- })
- }
- aria-label="Heading scale"
- aria-valuetext={`${theme.typography.headingScale.toFixed(3)} ratio`}
- />
-
-
+ {/* ── Typography (Enhanced with Presets) ───────────────────────── */}
+
+ updateTheme({ typography: { ...theme.typography, ...settings } })
+ }
+ />
{/* ── Layout ─────────────────────────────────────────────────────── */}
) => void;
+}
+
+/**
+ * Typography preset card component with preview
+ */
+function TypographyPresetCard({
+ preset,
+ isSelected,
+ onSelect,
+}: {
+ preset: TypographyPreset;
+ isSelected: boolean;
+ onSelect: () => void;
+}) {
+ const headingFont = preset.settings.headingFontFamily ?? preset.settings.fontFamily;
+ const bodyFont = preset.settings.fontFamily;
+
+ return (
+
+ );
+}
+
+/**
+ * Map font family IDs to CSS font family names
+ */
+function getFontFamilyName(fontId: string): string {
+ const fontMap: Record = {
+ inter: 'Inter, sans-serif',
+ roboto: 'Roboto, sans-serif',
+ poppins: 'Poppins, sans-serif',
+ playfair: '"Playfair Display", serif',
+ montserrat: 'Montserrat, sans-serif',
+ geist: 'Geist, sans-serif',
+ };
+ return fontMap[fontId] ?? 'Inter, sans-serif';
+}
+
+export function TypographyPresetSelector({
+ currentSettings,
+ onSettingsChange,
+}: TypographyPresetSelectorProps) {
+ // Check if current settings match any preset
+ const currentPresetId = TYPOGRAPHY_PRESETS.find((preset) => {
+ return (
+ preset.settings.fontFamily === currentSettings.fontFamily &&
+ preset.settings.headingFontFamily === currentSettings.headingFontFamily &&
+ preset.settings.headingScale === currentSettings.headingScale
+ );
+ })?.id;
+
+ const handleApplyPreset = (preset: TypographyPreset) => {
+ const updatedSettings = applyTypographyPreset(preset, currentSettings);
+ onSettingsChange(updatedSettings);
+ };
+
+ const headingSizes = calculateHeadingSizes(
+ currentSettings.baseFontSize,
+ currentSettings.headingScale,
+ );
+
+ return (
+
+
+ Typography Presets
+
+ Choose from predefined font pairings or customize settings below
+
+
+
+ {/* Preset Grid */}
+
+ {TYPOGRAPHY_PRESETS.map((preset) => (
+ handleApplyPreset(preset)}
+ />
+ ))}
+
+
+ {/* Custom Settings */}
+
+
+
+
+
+
+ onSettingsChange({ baseFontSize: value })
+ }
+ aria-label="Base font size"
+ />
+
+ Default text size (14-18px recommended)
+
+
+
+
+
+
+
+
+ onSettingsChange({ headingScale: value })
+ }
+ aria-label="Heading scale multiplier"
+ />
+
+ Controls heading size relationships (1.125-1.5 recommended)
+
+
+
+ {/* Heading Size Preview */}
+
+
+
+
H1: {headingSizes.h1}px
+
H2: {headingSizes.h2}px
+
H3: {headingSizes.h3}px
+
H4: {headingSizes.h4}px
+
H5: {headingSizes.h5}px
+
H6: {headingSizes.h6}px
+
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/src/components/storefront/inspector-overlay.tsx b/src/components/storefront/inspector-overlay.tsx
new file mode 100644
index 00000000..78703bc3
--- /dev/null
+++ b/src/components/storefront/inspector-overlay.tsx
@@ -0,0 +1,276 @@
+'use client';
+
+/**
+ * Inspector Overlay
+ *
+ * Visual overlay for inspector mode that shows section boundaries,
+ * edit buttons, and highlights on hover. Communicates with the parent
+ * editor window via PostMessage to select sections.
+ *
+ * Accessibility: Keyboard navigation, focus management, ARIA attributes
+ */
+
+import { useEffect, useState, useCallback, useRef } from 'react';
+import { Button } from '@/components/ui/button';
+import { Pencil } from 'lucide-react';
+
+interface InspectorOverlayProps {
+ enabled?: boolean;
+}
+
+export function InspectorOverlay({ enabled: enabledProp }: InspectorOverlayProps) {
+ const [inspectorMode, setInspectorMode] = useState(enabledProp ?? false);
+ const [hoveredSection, setHoveredSection] = useState(null);
+ const [hoveredRect, setHoveredRect] = useState(null);
+ const overlayRef = useRef(null);
+
+ // Sync external enabled prop with internal state
+ useEffect(() => {
+ if (enabledProp !== undefined) {
+ setInspectorMode(enabledProp);
+ if (!enabledProp) {
+ setHoveredSection(null);
+ setHoveredRect(null);
+ }
+ }
+ }, [enabledProp]);
+
+ // ─── PostMessage listener ───────────────────────────────────────────
+ useEffect(() => {
+ const handleMessage = (event: MessageEvent) => {
+ // Only accept messages from same origin (parent editor)
+ if (event.origin !== window.location.origin) return;
+
+ const data = event.data;
+ if (!data || typeof data.type !== 'string') return;
+
+ if (data.type === 'STORMCOM_SET_INSPECTOR') {
+ setInspectorMode(!!data.enabled);
+ if (!data.enabled) {
+ setHoveredSection(null);
+ setHoveredRect(null);
+ }
+ }
+ };
+
+ window.addEventListener('message', handleMessage);
+ return () => window.removeEventListener('message', handleMessage);
+ }, []);
+
+ // ─── Track hovered section ─────────────────────────────────────────
+ const handleMouseMove = useCallback(
+ (e: MouseEvent) => {
+ if (!inspectorMode) return;
+
+ const target = e.target as HTMLElement;
+ const section = target.closest('[data-section-id]') as HTMLElement;
+
+ if (section) {
+ const sectionId = section.getAttribute('data-section-id');
+ if (sectionId !== hoveredSection) {
+ setHoveredSection(sectionId);
+ setHoveredRect(section.getBoundingClientRect());
+ }
+ } else if (hoveredSection !== null) {
+ setHoveredSection(null);
+ setHoveredRect(null);
+ }
+ },
+ [inspectorMode, hoveredSection],
+ );
+
+ useEffect(() => {
+ if (inspectorMode) {
+ document.addEventListener('mousemove', handleMouseMove);
+ return () => document.removeEventListener('mousemove', handleMouseMove);
+ }
+ }, [inspectorMode, handleMouseMove]);
+
+ // ─── Handle section selection ──────────────────────────────────────
+ const handleSelectSection = useCallback(
+ (sectionId: string) => {
+ if (window.parent !== window) {
+ window.parent.postMessage(
+ {
+ type: 'STORMCOM_SELECT_SECTION',
+ sectionId,
+ },
+ window.location.origin,
+ );
+ }
+ },
+ [],
+ );
+
+ // ─── Handle click to edit ──────────────────────────────────────────
+ const handleClick = useCallback(
+ (e: MouseEvent) => {
+ if (!inspectorMode) return;
+
+ const target = e.target as HTMLElement;
+ const section = target.closest('[data-section-id]');
+
+ if (section) {
+ e.preventDefault();
+ e.stopPropagation();
+ const sectionId = section.getAttribute('data-section-id');
+ if (sectionId) {
+ handleSelectSection(sectionId);
+ }
+ }
+ },
+ [inspectorMode, handleSelectSection],
+ );
+
+ useEffect(() => {
+ if (inspectorMode) {
+ document.addEventListener('click', handleClick, true);
+ return () => document.removeEventListener('click', handleClick, true);
+ }
+ }, [inspectorMode, handleClick]);
+
+ // ─── Keyboard navigation ───────────────────────────────────────────
+ const handleKeyDown = useCallback(
+ (e: KeyboardEvent) => {
+ if (!inspectorMode) return;
+
+ // Escape to exit inspector mode
+ if (e.key === 'Escape') {
+ if (window.parent !== window) {
+ window.parent.postMessage(
+ {
+ type: 'STORMCOM_SET_INSPECTOR',
+ enabled: false,
+ },
+ window.location.origin,
+ );
+ }
+ }
+
+ // Enter to select hovered section
+ if (e.key === 'Enter' && hoveredSection) {
+ handleSelectSection(hoveredSection);
+ }
+ },
+ [inspectorMode, hoveredSection, handleSelectSection],
+ );
+
+ useEffect(() => {
+ if (inspectorMode) {
+ document.addEventListener('keydown', handleKeyDown);
+ return () => document.removeEventListener('keydown', handleKeyDown);
+ }
+ }, [inspectorMode, handleKeyDown]);
+
+ // ─── Add inspector class to body ───────────────────────────────────
+ useEffect(() => {
+ if (inspectorMode) {
+ document.body.classList.add('stormcom-inspector-active');
+ return () => {
+ document.body.classList.remove('stormcom-inspector-active');
+ };
+ }
+ }, [inspectorMode]);
+
+ if (!inspectorMode || !hoveredSection || !hoveredRect) return null;
+
+ // Calculate overlay position (fixed position relative to viewport)
+ const overlayStyle = {
+ position: 'fixed' as const,
+ top: hoveredRect.top,
+ left: hoveredRect.left,
+ width: hoveredRect.width,
+ height: hoveredRect.height,
+ pointerEvents: 'none' as const,
+ };
+
+ return (
+ <>
+ {/* Global inspector styles */}
+
+
+ {/* Overlay with edit button */}
+
+ {/* Semi-transparent overlay */}
+
+
+ {/* Edit button toolbar */}
+
+
+
+
+ {/* Section label */}
+
+ {formatSectionName(hoveredSection)}
+
+
+
+ {/* Keyboard shortcut hint */}
+
+
Inspector Mode Active
+
+
Click to edit section
+
Enter to select
+
Esc to exit
+
+
+ >
+ );
+}
+
+/**
+ * Format section ID for display
+ */
+function formatSectionName(sectionId: string): string {
+ // Convert camelCase to Title Case
+ return sectionId
+ .replace(/([A-Z])/g, ' $1')
+ .replace(/^./, (str) => str.toUpperCase())
+ .trim();
+}
diff --git a/src/components/storefront/preview-bridge.tsx b/src/components/storefront/preview-bridge.tsx
index 0b66599d..40ac1202 100644
--- a/src/components/storefront/preview-bridge.tsx
+++ b/src/components/storefront/preview-bridge.tsx
@@ -16,6 +16,7 @@
*/
import { useEffect, useState, useCallback } from 'react';
+import { InspectorOverlay } from './inspector-overlay';
import type { StorefrontConfig } from '@/lib/storefront/types';
export function PreviewBridge() {
@@ -56,63 +57,8 @@ export function PreviewBridge() {
return () => window.removeEventListener('message', handleMessage);
}, []);
- // ─── Inspector mode: click-to-select sections ──────────────────────
- const handleClick = useCallback(
- (e: MouseEvent) => {
- if (!inspectorMode) return;
-
- const target = e.target as HTMLElement;
- const section = target.closest('[data-section-id]');
- if (section && window.parent !== window) {
- e.preventDefault();
- e.stopPropagation();
- window.parent.postMessage(
- {
- type: 'STORMCOM_SELECT_SECTION',
- sectionId: section.getAttribute('data-section-id'),
- },
- window.location.origin,
- );
- }
- },
- [inspectorMode],
- );
-
- useEffect(() => {
- document.addEventListener('click', handleClick, true);
- return () => document.removeEventListener('click', handleClick, true);
- }, [handleClick]);
-
- // ─── Inspector mode: highlight hover ────────────────────────────────
- useEffect(() => {
- if (!inspectorMode) return;
-
- // Add a class to the body so CSS can style data-section-id elements
- document.body.classList.add('stormcom-inspector-active');
- return () => {
- document.body.classList.remove('stormcom-inspector-active');
- };
- }, [inspectorMode]);
-
- // Render nothing — purely side-effect
- return (
- <>
- {inspectorMode && (
-
- )}
- >
- );
+ // Render the inspector overlay when inspector mode is active
+ return ;
}
// ---------------------------------------------------------------------------
diff --git a/src/lib/storefront/color-schemes.ts b/src/lib/storefront/color-schemes.ts
new file mode 100644
index 00000000..0a22c741
--- /dev/null
+++ b/src/lib/storefront/color-schemes.ts
@@ -0,0 +1,152 @@
+/**
+ * Color Scheme Presets for Theme Editor
+ *
+ * Predefined color palettes inspired by modern design systems.
+ * Each scheme provides a complete set of colors for immediate application.
+ */
+
+import type { ColorScheme, ThemeColors } from './types';
+
+/**
+ * Predefined color schemes
+ * Following Web Content Accessibility Guidelines (WCAG) AA for contrast
+ */
+export const COLOR_SCHEMES: ColorScheme[] = [
+ {
+ id: 'modern-blue',
+ name: 'Modern Blue',
+ colors: {
+ background: '#FFFFFF',
+ foreground: '#1A1A1A',
+ primary: '#3B82F6',
+ secondary: '#8B5CF6',
+ accent: '#10B981',
+ muted: '#F3F4F6',
+ },
+ },
+ {
+ id: 'elegant-purple',
+ name: 'Elegant Purple',
+ colors: {
+ background: '#FAFAFA',
+ foreground: '#18181B',
+ primary: '#7C3AED',
+ secondary: '#DB2777',
+ accent: '#F59E0B',
+ muted: '#F4F4F5',
+ },
+ },
+ {
+ id: 'professional-slate',
+ name: 'Professional Slate',
+ colors: {
+ background: '#FFFFFF',
+ foreground: '#0F172A',
+ primary: '#475569',
+ secondary: '#64748B',
+ accent: '#06B6D4',
+ muted: '#F1F5F9',
+ },
+ },
+ {
+ id: 'vibrant-coral',
+ name: 'Vibrant Coral',
+ colors: {
+ background: '#FFFFFF',
+ foreground: '#1F2937',
+ primary: '#F97316',
+ secondary: '#EC4899',
+ accent: '#14B8A6',
+ muted: '#FEF3C7',
+ },
+ },
+ {
+ id: 'minimal-mono',
+ name: 'Minimal Monochrome',
+ colors: {
+ background: '#FFFFFF',
+ foreground: '#000000',
+ primary: '#404040',
+ secondary: '#737373',
+ accent: '#171717',
+ muted: '#F5F5F5',
+ },
+ },
+ {
+ id: 'dark-mode',
+ name: 'Dark Mode',
+ colors: {
+ background: '#0A0A0A',
+ foreground: '#FAFAFA',
+ primary: '#60A5FA',
+ secondary: '#A78BFA',
+ accent: '#34D399',
+ muted: '#1F1F1F',
+ },
+ },
+ {
+ id: 'nature-green',
+ name: 'Nature Green',
+ colors: {
+ background: '#F9FAFB',
+ foreground: '#1F2937',
+ primary: '#10B981',
+ secondary: '#059669',
+ accent: '#84CC16',
+ muted: '#ECFDF5',
+ },
+ },
+ {
+ id: 'luxury-gold',
+ name: 'Luxury Gold',
+ colors: {
+ background: '#FFFBF5',
+ foreground: '#1C1917',
+ primary: '#D97706',
+ secondary: '#B45309',
+ accent: '#92400E',
+ muted: '#FEF3C7',
+ },
+ },
+];
+
+/**
+ * Get a color scheme by ID
+ */
+export function getColorScheme(id: string): ColorScheme | undefined {
+ return COLOR_SCHEMES.find((scheme) => scheme.id === id);
+}
+
+/**
+ * Apply a color scheme to theme colors
+ */
+export function applyColorScheme(
+ scheme: ColorScheme,
+ currentColors: ThemeColors,
+): ThemeColors {
+ return {
+ ...currentColors,
+ ...scheme.colors,
+ };
+}
+
+/**
+ * Color categories for organized display
+ */
+export const COLOR_CATEGORIES = [
+ {
+ id: 'modern',
+ name: 'Modern',
+ schemes: ['modern-blue', 'professional-slate', 'minimal-mono'],
+ },
+ {
+ id: 'vibrant',
+ name: 'Vibrant',
+ schemes: ['vibrant-coral', 'elegant-purple', 'nature-green'],
+ },
+ {
+ id: 'luxury',
+ name: 'Luxury',
+ schemes: ['luxury-gold', 'dark-mode'],
+ },
+] as const;
diff --git a/src/lib/storefront/section-registry.ts b/src/lib/storefront/section-registry.ts
new file mode 100644
index 00000000..a2c4a04f
--- /dev/null
+++ b/src/lib/storefront/section-registry.ts
@@ -0,0 +1,267 @@
+/**
+ * Section Registry
+ *
+ * Central configuration for all available sections that can be added to the storefront.
+ * Provides metadata, categories, and filtering capabilities for the "Add Section" modal.
+ */
+
+import type { SectionId } from './types';
+
+/**
+ * Section category for organization
+ */
+export type SectionCategory =
+ | 'header'
+ | 'content'
+ | 'commerce'
+ | 'marketing'
+ | 'social'
+ | 'footer';
+
+/**
+ * Section registry entry with metadata
+ */
+export interface SectionRegistryEntry {
+ id: SectionId;
+ name: string;
+ description: string;
+ category: SectionCategory;
+ icon: string; // Lucide icon name
+ preview?: string; // Preview image URL
+ tags: string[]; // For search/filtering
+ isEnabled: boolean; // Can be toggled on/off
+ isPremium?: boolean; // Premium feature flag
+ defaultEnabled: boolean; // Default state for new stores
+}
+
+/**
+ * Complete section registry
+ */
+export const SECTION_REGISTRY: SectionRegistryEntry[] = [
+ // Header Section
+ {
+ id: 'hero',
+ name: 'Hero Section',
+ description: 'Large banner with headline, description, and call-to-action buttons',
+ category: 'header',
+ icon: 'LayoutDashboard',
+ tags: ['banner', 'header', 'cta', 'hero', 'landing'],
+ isEnabled: true,
+ defaultEnabled: true,
+ },
+
+ // Commerce Sections
+ {
+ id: 'featuredProducts',
+ name: 'Featured Products',
+ description: 'Showcase your best-selling or featured products in a grid',
+ category: 'commerce',
+ icon: 'ShoppingBag',
+ tags: ['products', 'shop', 'commerce', 'featured', 'bestsellers'],
+ isEnabled: true,
+ defaultEnabled: true,
+ },
+ {
+ id: 'categories',
+ name: 'Product Categories',
+ description: 'Display product categories with images for easy navigation',
+ category: 'commerce',
+ icon: 'LayoutGrid',
+ tags: ['categories', 'navigation', 'browse', 'collections'],
+ isEnabled: true,
+ defaultEnabled: true,
+ },
+
+ // Marketing Sections
+ {
+ id: 'discountBanners',
+ name: 'Discount Banners',
+ description: 'Promotional banners for sales, discounts, and special offers',
+ category: 'marketing',
+ icon: 'Tag',
+ tags: ['discount', 'sale', 'promotion', 'banner', 'offer'],
+ isEnabled: true,
+ defaultEnabled: false,
+ },
+ {
+ id: 'trustBadges',
+ name: 'Trust Badges',
+ description: 'Build credibility with trust badges (secure payment, free shipping, etc.)',
+ category: 'marketing',
+ icon: 'Shield',
+ tags: ['trust', 'badges', 'security', 'shipping', 'guarantee'],
+ isEnabled: true,
+ defaultEnabled: true,
+ },
+ {
+ id: 'newsletter',
+ name: 'Newsletter Signup',
+ description: 'Collect email addresses with a subscription form',
+ category: 'marketing',
+ icon: 'Mail',
+ tags: ['email', 'newsletter', 'subscribe', 'signup', 'form'],
+ isEnabled: true,
+ defaultEnabled: false,
+ },
+
+ // Social Proof Sections
+ {
+ id: 'testimonials',
+ name: 'Customer Testimonials',
+ description: 'Display customer reviews and ratings to build trust',
+ category: 'social',
+ icon: 'MessageSquare',
+ tags: ['reviews', 'testimonials', 'ratings', 'feedback', 'social proof'],
+ isEnabled: true,
+ defaultEnabled: false,
+ },
+ {
+ id: 'brands',
+ name: 'Brand Logos',
+ description: 'Showcase partner brands or media logos in a carousel',
+ category: 'social',
+ icon: 'Sparkles',
+ tags: ['brands', 'partners', 'logos', 'carousel', 'media'],
+ isEnabled: true,
+ defaultEnabled: false,
+ },
+
+ // Content Sections
+ {
+ id: 'content',
+ name: 'Custom Content',
+ description: 'Flexible section with text, images, videos, and more',
+ category: 'content',
+ icon: 'FileText',
+ tags: ['content', 'text', 'image', 'video', 'custom', 'blocks'],
+ isEnabled: true,
+ defaultEnabled: false,
+ },
+];
+
+/**
+ * Section categories with metadata
+ */
+export const SECTION_CATEGORIES: Array<{
+ id: SectionCategory;
+ name: string;
+ description: string;
+ icon: string;
+}> = [
+ {
+ id: 'header',
+ name: 'Header',
+ description: 'Hero banners and top sections',
+ icon: 'LayoutDashboard',
+ },
+ {
+ id: 'commerce',
+ name: 'Commerce',
+ description: 'Product displays and shopping features',
+ icon: 'ShoppingCart',
+ },
+ {
+ id: 'marketing',
+ name: 'Marketing',
+ description: 'Promotions and conversion elements',
+ icon: 'Target',
+ },
+ {
+ id: 'social',
+ name: 'Social Proof',
+ description: 'Reviews, testimonials, and trust signals',
+ icon: 'Users',
+ },
+ {
+ id: 'content',
+ name: 'Content',
+ description: 'Text, images, and media blocks',
+ icon: 'FileText',
+ },
+ {
+ id: 'footer',
+ name: 'Footer',
+ description: 'Footer sections and links',
+ icon: 'PanelBottom',
+ },
+];
+
+/**
+ * Get a section by ID
+ */
+export function getSection(id: SectionId): SectionRegistryEntry | undefined {
+ return SECTION_REGISTRY.find((section) => section.id === id);
+}
+
+/**
+ * Get sections by category
+ */
+export function getSectionsByCategory(
+ category: SectionCategory,
+): SectionRegistryEntry[] {
+ return SECTION_REGISTRY.filter((section) => section.category === category);
+}
+
+/**
+ * Search sections by query (name, description, or tags)
+ */
+export function searchSections(query: string): SectionRegistryEntry[] {
+ const lowerQuery = query.toLowerCase().trim();
+ if (!lowerQuery) return SECTION_REGISTRY;
+
+ return SECTION_REGISTRY.filter((section) => {
+ const searchableText = [
+ section.name,
+ section.description,
+ ...section.tags,
+ ]
+ .join(' ')
+ .toLowerCase();
+
+ return searchableText.includes(lowerQuery);
+ });
+}
+
+/**
+ * Filter sections by availability and premium status
+ */
+export function filterSections(filters: {
+ category?: SectionCategory;
+ enabled?: boolean;
+ premium?: boolean;
+}): SectionRegistryEntry[] {
+ return SECTION_REGISTRY.filter((section) => {
+ if (filters.category && section.category !== filters.category) {
+ return false;
+ }
+ if (
+ filters.enabled !== undefined &&
+ section.isEnabled !== filters.enabled
+ ) {
+ return false;
+ }
+ if (filters.premium !== undefined) {
+ const isPremium = section.isPremium ?? false;
+ if (isPremium !== filters.premium) {
+ return false;
+ }
+ }
+ return true;
+ });
+}
+
+/**
+ * Get section icon component name
+ */
+export function getSectionIcon(id: SectionId): string {
+ const section = getSection(id);
+ return section?.icon ?? 'Square';
+}
+
+/**
+ * Get section display name
+ */
+export function getSectionName(id: SectionId): string {
+ const section = getSection(id);
+ return section?.name ?? id;
+}
diff --git a/src/lib/storefront/typography-presets.ts b/src/lib/storefront/typography-presets.ts
new file mode 100644
index 00000000..e9126a4f
--- /dev/null
+++ b/src/lib/storefront/typography-presets.ts
@@ -0,0 +1,201 @@
+/**
+ * Typography Presets for Theme Editor
+ *
+ * Predefined font pairings and typography scales following
+ * modern design best practices and WCAG readability guidelines.
+ */
+
+import type { TypographySettings, FontFamily } from './types';
+
+/**
+ * Typography preset definition
+ */
+export interface TypographyPreset {
+ id: string;
+ name: string;
+ description: string;
+ preview: {
+ heading: string;
+ body: string;
+ };
+ settings: TypographySettings;
+}
+
+/**
+ * Predefined typography presets with harmonious font pairings
+ */
+export const TYPOGRAPHY_PRESETS: TypographyPreset[] = [
+ {
+ id: 'modern-sans',
+ name: 'Modern Sans',
+ description: 'Clean and professional with Inter',
+ preview: {
+ heading: 'Aa',
+ body: 'The quick brown fox jumps',
+ },
+ settings: {
+ fontFamily: 'inter',
+ headingFontFamily: 'inter',
+ baseFontSize: 16,
+ headingScale: 1.25,
+ },
+ },
+ {
+ id: 'elegant-serif',
+ name: 'Elegant Serif',
+ description: 'Sophisticated with Playfair Display',
+ preview: {
+ heading: 'Aa',
+ body: 'The quick brown fox jumps',
+ },
+ settings: {
+ fontFamily: 'roboto',
+ headingFontFamily: 'playfair',
+ baseFontSize: 16,
+ headingScale: 1.333,
+ },
+ },
+ {
+ id: 'bold-impact',
+ name: 'Bold Impact',
+ description: 'Strong presence with Montserrat',
+ preview: {
+ heading: 'Aa',
+ body: 'The quick brown fox jumps',
+ },
+ settings: {
+ fontFamily: 'roboto',
+ headingFontFamily: 'montserrat',
+ baseFontSize: 16,
+ headingScale: 1.4,
+ },
+ },
+ {
+ id: 'friendly-rounded',
+ name: 'Friendly Rounded',
+ description: 'Approachable with Poppins',
+ preview: {
+ heading: 'Aa',
+ body: 'The quick brown fox jumps',
+ },
+ settings: {
+ fontFamily: 'inter',
+ headingFontFamily: 'poppins',
+ baseFontSize: 15,
+ headingScale: 1.3,
+ },
+ },
+ {
+ id: 'minimalist',
+ name: 'Minimalist',
+ description: 'Refined simplicity with Geist',
+ preview: {
+ heading: 'Aa',
+ body: 'The quick brown fox jumps',
+ },
+ settings: {
+ fontFamily: 'geist',
+ headingFontFamily: 'geist',
+ baseFontSize: 16,
+ headingScale: 1.2,
+ },
+ },
+ {
+ id: 'classic-editorial',
+ name: 'Classic Editorial',
+ description: 'Timeless elegance',
+ preview: {
+ heading: 'Aa',
+ body: 'The quick brown fox jumps',
+ },
+ settings: {
+ fontFamily: 'inter',
+ headingFontFamily: 'playfair',
+ baseFontSize: 17,
+ headingScale: 1.414,
+ },
+ },
+];
+
+/**
+ * Font weight recommendations for headings
+ */
+export const HEADING_WEIGHTS: Record = {
+ inter: 700,
+ roboto: 700,
+ poppins: 600,
+ playfair: 700,
+ montserrat: 700,
+ geist: 600,
+};
+
+/**
+ * Calculate heading sizes based on scale and base size
+ * Following modular scale principles for visual hierarchy
+ * Centers the scale around h4 = base size for realistic results
+ */
+export function calculateHeadingSizes(
+ baseFontSize: number,
+ scale: number,
+): {
+ h1: number;
+ h2: number;
+ h3: number;
+ h4: number;
+ h5: number;
+ h6: number;
+} {
+ // Center the scale around h4 = base size
+ // This produces realistic heading sizes:
+ // - h1-h3 are larger than base
+ // - h4 equals base
+ // - h5-h6 are smaller than base
+ return {
+ h1: Math.round(baseFontSize * Math.pow(scale, 3)),
+ h2: Math.round(baseFontSize * Math.pow(scale, 2)),
+ h3: Math.round(baseFontSize * scale),
+ h4: baseFontSize,
+ h5: Math.round(baseFontSize / scale),
+ h6: Math.round(baseFontSize / Math.pow(scale, 2)),
+ };
+}
+
+/**
+ * Get a typography preset by ID
+ */
+export function getTypographyPreset(
+ id: string,
+): TypographyPreset | undefined {
+ return TYPOGRAPHY_PRESETS.find((preset) => preset.id === id);
+}
+
+/**
+ * Apply a typography preset to current settings
+ */
+export function applyTypographyPreset(
+ preset: TypographyPreset,
+ currentSettings: TypographySettings,
+): TypographySettings {
+ return {
+ ...currentSettings,
+ ...preset.settings,
+ };
+}
+
+/**
+ * Font categories for organized display
+ */
+export const FONT_CATEGORIES = [
+ {
+ id: 'sans-serif',
+ name: 'Sans Serif',
+ description: 'Clean and modern',
+ fonts: ['inter', 'roboto', 'poppins', 'montserrat', 'geist'] as FontFamily[],
+ },
+ {
+ id: 'serif',
+ name: 'Serif',
+ description: 'Classic and elegant',
+ fonts: ['playfair'] as FontFamily[],
+ },
+] as const;