Files
WooNooW/woonoow-page-editor-v4-canvas-ui.md

33 KiB

WooNooW Page Editor - Enhanced UI/UX Brief (v4)

Overview

Redesign the Page Editor from a card-list-based interface to a live-canvas-based interface that shows actual rendered content in real-time. Leverage JSON-based rendering (no iframe/MutationObserver overhead) for faster, simpler implementation.


Core Philosophy

One Mental Model: What You See Is What You Get (WYSIWYG)

  • Canvas shows the actual rendered output merchants will see on the front-end
  • No iframe isolation → Direct component rendering → Instant visual feedback
  • Edit sections on the canvas → Settings panel updates → Preview updates in real-time
  • No context switching between "list view" and "edit mode"

New Layout Structure

Full-Screen Layout (No iframe, Direct React Rendering)

┌──────────────────────────────────────────────────────────────────┐
│ [☰] WooNooW    Appearance > Pages > [About Us]  [D/M]  [Save]   │
├─────────────────┬────────────────────────┬──────────────────────┤
│                 │                        │                      │
│  LEFT SIDEBAR   │  MAIN CANVAS           │  RIGHT INSPECTOR    │
│  (180px)        │  (Dynamic, ~60-70%)    │  (Collapse ◄)       │
│                 │                        │  (200-280px)        │
│  📄 PAGES       │ ╔════════════════════╗ │                      │
│  ├─ Home        │ ║  LIVE PAGE RENDER  ║ │  ┌─────────────────┐ │
│  ├─ About ✓     │ ║                    ║ │  │ Section Config  │ │
│  └─ Contact     │ ║ ┌──────────────┐   ║ │  └─────────────────┘ │
│                 │ ║ │  HERO        │   ║ │                      │
│  📋 TEMPLATES   │ ║ │  Welcome     │←──╋─┼─→ [Hero section selected]
│  ├─ Post       │ ║ │  [Image]     │   ║ │                      │
│  └─ Portfolio  │ ║ │  Join us     │   ║ │  Layout:            │
│                 │ ║ └──────────────┘   ║ │  [Hero-Left ▼]      │
│  [+ Create]    │ ║                    ║ │                      │
│                 │ ║ ┌──────────────┐   ║ │  Title:             │
│                 │ ║ │  IMAGE+TEXT  │   ║ │  [Welcome______]    │
│                 │ ║ │ [IMG] [TEXT] │   ║ │                      │
│                 │ ║ └──────────────┘   ║ │  Image:             │
│                 │ ║                    ║ │  [Choose Image ▼]   │
│                 │ ║ ┌──────────────┐   ║ │                      │
│                 │ ║ │   CTA        │   ║ │  [Delete Section]   │
│                 │ ║ │ "Shop Now"   │   ║ │                      │
│                 │ ║ └──────────────┘   ║ │  [Hide Inspector]   │
│                 │ ║                    ║ │                      │
│                 │ ║ [+ Add section]    ║ │                      │
│                 │ ╚════════════════════╝ │                      │
│                 │                        │                      │
│                 │ Responsive toggle:     │                      │
│                 │ [Desktop] [Mobile]     │                      │
│                 │                        │                      │
└─────────────────┴────────────────────────┴──────────────────────┘
│ [Discard Changes]  [Save Changes]  [Preview in New Tab]         │
└──────────────────────────────────────────────────────────────────┘

Key Differences from v3

Aspect v3 (Card List) v4 (Live Canvas)
Main Column Card list (read-only) Live rendered sections
Rendering Screenshot/preview Actual React components
Interactivity Click card → settings Click section → settings
Visual Feedback Static cards Actual page appearance
Preview Size Small box on right Full mobile/desktop
Edit Speed Click → settings → change See change instantly
Context List + Settings + Preview One coherent interface

Canvas Rendering System (JSON → React Components)

Architecture: Zero-Iframe, Direct Component Rendering

┌──────────────────────────────────────────┐
│  Page Editor State (Redux/Zustand)      │
│  {                                       │
│    sections: [                           │
│      { type: "hero", props: {...} },    │
│      { type: "content", props: {...} }  │
│    ],                                    │
│    selectedSectionId: "sec-1"           │
│  }                                       │
└──────────────────────────────────────────┘
         ↓ (Real-time)
┌──────────────────────────────────────────┐
│  CanvasRenderer Component                │
│  (Renders JSON → React components)      │
│                                          │
│  { sections.map((sec) =>                │
│    <SectionComponent                    │
│      data={sec}                         │
│      isSelected={...}                   │
│      onSelect={...}                     │
│    />                                    │
│  )}                                      │
└──────────────────────────────────────────┘
         ↓
┌──────────────────────────────────────────┐
│  Live Canvas Preview                     │
│  (Shows exact front-end appearance)     │
│                                          │
│  ╔═══════════════════════════════════╗  │
│  ║        HERO SECTION               ║  │
│  ║      (Selected, blue border)      ║  │
│  ╚═══════════════════════════════════╝  │
│                                          │
│  ┌───────────────────────────────────┐  │
│  │    IMAGE + TEXT                   │  │
│  │  (Not selected, gray border)      │  │
│  └───────────────────────────────────┘  │
└──────────────────────────────────────────┘

Why This Is Simpler Than Page Builder Plugins

Page Builder Plugins (e.g., Elementor, Beaver):

  • Use iframes to sandbox editing environment
  • Must observe DOM mutations to track changes
  • Heavy JavaScript overhead
  • Slow preview updates

WooNooW Approach:

  • JSON state → React components (unidirectional)
  • No iframe = no sandbox overhead
  • No mutation observers = simple state management
  • Change state → re-render → instant preview
  • Single component tree (not isolated)

Canvas Interaction Model

Section States

At Rest (Not Selected):

┌─────────────────────────────────┐
│  Hero Section                   │  ← Subtle border (gray)
│  [Actual rendered content]      │  ← Hover shows subtle highlight
│  "Welcome to our store"         │  ← Cursor change on hover
│  [Featured image]               │
└─────────────────────────────────┘

On Hover:

╔════════════════════════════════╗
║  Hero Section                   ║  ← Blue border appears
║  [Actual rendered content]      ║  ← Handles show: [↑][↓][◆]
║  "Welcome to our store"         ║  ← "Click to edit" hint
║  [Featured image]               ║
║  Drag handle: [:::]             ║  ← Reorder handle visible
╚════════════════════════════════╝

On Click (Selected):

╔════════════════════════════════╗
║  Hero Section                   ║  ← Bold blue border
║  [Actual rendered content]      ║  ← Selection handles
║  "Welcome to our store"         ║  ← Full edit mode
║  [Featured image]               ║  ← Right panel populated
║  [≣ Move ↑ ↓] [⊕ Duplicate] [✕]║  ← Context menu
╚════════════════════════════════╝
   ↓ Right panel updates:
   Section Config
   ├─ Layout: [Hero-Left ▼]
   ├─ Color: [Primary ▼]
   ├─ Title: [Welcome__________]
   └─ Image: [Choose Image ▼]

Interaction Handlers

// Canvas Section Component
<CanvasSection
  section={section}
  isSelected={selectedId === section.id}
  
  // Selection
  onClick={() => setSelectedSection(section.id)}
  onMouseEnter={() => setHoveredId(section.id)}
  onMouseLeave={() => setHoveredId(null)}
  
  // Reordering
  onDragStart={() => initDragReorder(section.id)}
  onDragOver={(e) => e.preventDefault()}
  onDrop={() => performDragReorder(section.id)}
  
  // Quick actions
  onDuplicate={() => duplicateSection(section.id)}
  onDelete={() => deleteSection(section.id)}
  onMoveUp={() => moveSection(section.id, 'up')}
  onMoveDown={() => moveSection(section.id, 'down')}
/>

Inspector Panel (Right Side)

Inspector Behavior

Collapsible:

┌──────────────────┐
│ [Inspector] [◄]  │  ← Click to collapse (inspector slides out)
│                  │
│ Section Config   │
│ ├─ Layout: [▼]   │
│ ├─ Title: [____] │
│ └─ Image: [▼]    │
└──────────────────┘

When collapsed:
[Canvas takes 70% width instead of 60%]

Responsive to Selection:

No section selected:
┌──────────────────┐
│ Page Settings    │
├─ Title: [About]  │
├─ Slug: [/about]  │
└─ SEO: [Manage→]  │
└──────────────────┘

Hero section selected:
┌──────────────────┐
│ Section Config   │
│ Type: Hero       │
├─ Layout: [▼]     │
├─ Title: [____]   │
└─ Image: [▼]      │
└──────────────────┘

Content section selected:
┌──────────────────┐
│ Section Config   │
│ Type: Content    │
├─ Source: [▼]     │  ← Dynamic: post_content
│  ○ Static text   │
│  ◉ Post Body     │
└─ [Dynamic ◆]     │
└──────────────────┘

Inspector Field Types

For Static Fields:

Title Field
[Text input____________]

Description
[Large textarea___
_________________
_________________]

Image
[Choose from Media] or [Upload new]
[Preview thumbnail]

For Dynamic Fields (CPT Templates):

Title Source
○ Static text: [__________]
◉ Dynamic: [Post Title ▼]
  • Post Title
  • Post Excerpt
  • Custom Field X

[◆ Dynamic] indicator shown

Layout Controls:

Layout Variant
[Hero-Left ▼]
├─ Hero-Left
├─ Hero-Right
├─ Hero-Center
└─ Hero-Full

Color Scheme
[Primary ▼]
├─ Primary
├─ Secondary
├─ Accent
└─ Dark

Canvas UI Features

Section Card Appearance

In Canvas:

Rendered like the actual front-end:

╔═══════════════════════════════════╗
║ HERO SECTION                      ║  ← Looks like real page
║                                   ║
║ Welcome to our store              ║  ← Actual text
║ ┌─────────────────────────────┐   ║
║ │  [Featured Image]           │   ║  ← Actual image
║ │  (500x300px)                │   ║
║ └─────────────────────────────┘   ║
║                                   ║
║ Join our community today          ║  ← Button
║ [SHOP NOW →]                      ║
║                                   ║
╚═══════════════════════════════════╝

NOT like current cards:

❌ WRONG (Current):
┌─────────────────────────┐
│ Hero [default]          │
│ [↑] [↓] [x]             │
│ ─────────────────────── │

Add Section UI

Floating Button Below Sections:

Canvas shows sections:

╔═══════════════════════════════════╗
║  HERO SECTION                     ║
╚═══════════════════════════════════╝

[+ Add section]  ← Click to open section picker
  ├─ Hero
  ├─ Image + Text
  ├─ Feature Grid
  ├─ Related Items
  ├─ CTA Banner
  └─ Content

Or inline:

╔═══════════════════════════════════╗
║  CTA BANNER                       ║
╚═══════════════════════════════════╝

[+ Add section below]  ← Click here

Responsive Preview Toggle

Canvas shows device toggle:

[Desktop] [Mobile]  ← Toggle between previews

Desktop (full width, e.g., 1024px)
╔═══════════════════════════════════╗
║  Page at desktop width            ║
╚═══════════════════════════════════╝

Mobile (narrow width, e.g., 375px)
╔════════════╗
║ Page at    ║
║ mobile     ║
║ width      ║
╚════════════╝

Data Flow & State Management

Single Source of Truth

┌─────────────────────────────────┐
│  Editor State (Zustand/Redux)  │
│                                 │
│  {                              │
│    pageId: "about",            │
│    pageType: "page",            │
│    title: "About Us",           │
│    slug: "about",               │
│    sections: [                  │
│      {                          │
│        id: "sec-1",             │
│        type: "hero",            │
│        layoutVariant: "left",   │
│        props: {                 │
│          title: {               │
│            type: "static",      │
│            value: "Welcome"     │
│          },                     │
│          image: {               │
│            type: "static",      │
│            value: "/img.jpg"    │
│          }                      │
│        }                        │
│      }                          │
│    ],                           │
│    selectedSectionId: "sec-1",  │
│    hasUnsavedChanges: true      │
│  }                              │
└─────────────────────────────────┘
   ↓ (Consumed by):
   
   ├─ CanvasRenderer
   │  └─ Renders sections to UI
   │
   ├─ InspectorPanel
   │  └─ Shows/edits selected section
   │
   ├─ PreviewToggle
   │  └─ Desktop/Mobile responsive
   │
   └─ SaveButton
      └─ POST to /wp-json/woonoow/v1/pages/{slug}

Real-Time Updates

User changes field in Inspector:
[Title input] → onChange event
   ↓
Store update:
  sections[0].props.title.value = "New Title"
   ↓
React re-renders:
  <CanvasRenderer sections={sections} />
   ↓
Canvas shows updated title instantly
(No save needed, live preview)

Component Structure

Main Components

<PageEditor>  ← Top container
  ├─ <Sidebar>  ← Left navigation
  │  ├─ <PagesList>  ← Structural pages
  │  └─ <TemplatesList>  ← CPT templates
  │
  ├─ <Canvas>  ← Main workspace
  │  ├─ <ResponsiveToggle>  ← Desktop/Mobile
  │  └─ <CanvasRenderer>  ← Renders sections
  │      ├─ <CanvasSection>  ← Each section
  │      │  ├─ <HeroRenderer>  ← Section-specific
  │      │  ├─ <ContentRenderer>
  │      │  ├─ <FeatureGridRenderer>
  │      │  └─ ...
  │      └─ <AddSectionButton>
  │
  ├─ <Inspector>  ← Right panel
  │  ├─ <PageSettings>  ← When no section selected
  │  ├─ <SectionSettings>  ← When section selected
  │  └─ <PreviewToggle>  ← Collapse button
  │
  └─ <BottomBar>  ← Actions
     ├─ <DiscardButton>
     ├─ <SaveButton>
     └─ <PreviewButton>

Canvas Section Component Pattern

// Each section type follows same pattern:

function HeroSectionRenderer({ section, isSelected, onSelect }) {
  return (
    <CanvasSection
      isSelected={isSelected}
      onClick={onSelect}
      className="section-hero"
    >
      {/* Renders actual content */}
      <h1>{section.props.title.value}</h1>
      <img src={section.props.image.value} />
      <p>{section.props.subtitle.value}</p>
      
      {/* Selection UI overlays */}
      {isSelected && (
        <SelectionOverlay>
          <DragHandle />
          <ContextMenu />
        </SelectionOverlay>
      )}
    </CanvasSection>
  );
}

// Same for Content, FeatureGrid, CTA, etc.

Selection & Interaction Feedback

Visual Selection Indicators

Unselected:

┌─────────────────────────────┐
│  Hero Section               │  ← Gray border (1px)
│  [Content]                  │  ← Normal opacity
└─────────────────────────────┘

Hovered:

┌─────────────────────────────┐
│  Hero Section               │  ← Light blue border (1px)
│  [Content]                  │  ← Subtle shadow
│ [↑ ↓]                        │  ← Move handles appear
└─────────────────────────────┘

Selected:

╔═════════════════════════════╗
║  Hero Section               ║  ← Bold blue border (2px)
║  [Content]                  ║  ← Highlight background
║                             ║
║ [≣] [↑↓] [⊕] [✕]            ║  ← Full action menu
╚═════════════════════════════╝

Drag & Drop Reordering

// User drags section up:

Before:
╔═══════════════════╗
║  HERO (drag me)   ║  ← Dragging
║  [↑ ↓]            ║
╚═══════════════════╝

╔═══════════════════╗
║  IMAGE+TEXT       ║  ← Drop zone appears
║  [↑ ↓]            ║
╚═══════════════════╝

After drop:
╔═══════════════════╗
║  HERO             ║
╚═══════════════════╝

╔═══════════════════╗
║  IMAGE+TEXT       ║
╚═══════════════════╝

Save & Persistence Flow

Auto-Save vs Manual Save

Current approach: Manual Save

User edits → Real-time canvas update (no server call)
[Save Changes] button → POST to API → Save to wp_options/postmeta
[Discard Changes] button → Reset to last saved state

Optional: Auto-Save (Future)

User edits → Real-time canvas update
[Unsaved changes indicator] appears
Auto-save after 3 seconds of inactivity
Or manual save with [Save] button

Save Endpoints

POST /wp-json/woonoow/v1/pages/about
{
  "sections": [...]
}
→ Saves to wp_postmeta (_wn_page_structure)

POST /wp-json/woonoow/v1/templates/post
{
  "sections": [...]
}
→ Saves to wp_options (wn_template_post)

Responsive Canvas Behavior

Desktop View (Default)

Full-width canvas showing page at 1024px width

┌─────────────────────────────────┐
│  [Desktop] [Mobile]             │
│                                 │
│  Canvas width: 1024px           │
│  Shows full page layout         │
└─────────────────────────────────┘

Mobile View

Canvas showing page at 375px width

┌──────────┐
│[D][M]    │
│          │
│ Canvas   │
│ width:   │
│ 375px    │
│          │
│ Shows    │
│ mobile   │
│ layout   │
│          │
└──────────┘

Scroll Behavior

  • Canvas is scrollable (overflow-y: auto)
  • Sections stack vertically
  • Inspector panel stays fixed (top-right)
  • Bottom action bar always visible

Merchant Experience Flow

Typical Edit Session

  1. Select page from sidebar

    Sidebar: [About Us] ← Click
    Canvas loads page structure
    Inspector shows page settings
    
  2. Click section to edit

    Canvas: [Hero section] ← Click
    Section highlights (blue border)
    Inspector updates: Section Config for Hero
    
  3. Change fields in Inspector

    Inspector: Title = [Welcome_____] ← Type "Welcome to us"
    Canvas updates instantly
    No server call needed yet
    
  4. Reorder sections (optional)

    Canvas: Drag [Hero] above [Image+Text]
    Sections reorder instantly
    
  5. Add new section

    Canvas: [+ Add section] ← Click
    Section picker popup
    Select "CTA Banner"
    New section appears at bottom
    Inspector shows CTA settings
    
  6. Save all changes

    Bottom bar: [Save Changes] ← Click
    POST to /wp-json/woonoow/v1/pages/about
    Sections saved to database
    "Page saved" toast notification
    

Implementation Phases (Enhanced)

Phase 1: Canvas Rendering System (Priority)

  • Refactor state from Redux/Zustand (unified store)
  • Build CanvasRenderer component
    • Maps sections array to React components
    • Zero-iframe approach (direct rendering)
    • Responsive device simulation (CSS media queries)
  • Build section renderers (Hero, Content, Feature Grid, CTA, Related)
    • Render actual content, not placeholder cards
    • Show dynamic placeholders with [◆] badges
  • Add responsive toggle (Desktop/Mobile)
  • Test real-time re-rendering on state changes

Phase 2: Canvas Interaction (Click, Hover, Select)

  • Click handler → Set selected section in state
  • Hover handler → Show selection handles
  • Visual feedback (borders, shadows, highlights)
  • Context menu (Move up, Down, Duplicate, Delete)
  • Add section button (opens section picker)

Phase 3: Inspector Panel (Settings)

  • Collapse/expand button
  • Page settings vs Section settings
  • Dynamic field type detection
    • Static: text input, textarea, image picker
    • Dynamic: dropdown with available sources
  • Real-time sync (change field → canvas updates)

Phase 4: Drag & Drop Reordering

  • Drag start/over/drop handlers
  • Visual drag feedback (opacity, shadow)
  • Reorder state updates
  • Test with multiple sections

Phase 5: Polish & Launch

  • Save/Discard buttons
  • Unsaved changes warning
  • Toast notifications (saved, error)
  • Keyboard shortcuts (ESC to deselect, etc.)
  • Mobile-friendly inspector (for tablet editing)
  • Performance optimization (memoization, lazy rendering)

Code Example: Canvas Component Structure

// pages/editor/PageEditor.tsx
export function PageEditor() {
  const [pageState, dispatch] = useReducer(pageReducer, initialState);
  const [selectedSectionId, setSelectedSectionId] = useState(null);
  const [deviceMode, setDeviceMode] = useState('desktop');

  const handleSectionSelect = (sectionId) => {
    setSelectedSectionId(sectionId);
  };

  const handleSectionDelete = (sectionId) => {
    dispatch({
      type: 'DELETE_SECTION',
      payload: { sectionId }
    });
  };

  const handleSectionDuplicate = (sectionId) => {
    dispatch({
      type: 'DUPLICATE_SECTION',
      payload: { sectionId }
    });
  };

  const handleFieldChange = (sectionId, fieldPath, value) => {
    dispatch({
      type: 'UPDATE_SECTION_PROP',
      payload: { sectionId, fieldPath, value }
    });
  };

  const handleSave = async () => {
    const response = await fetch(`/wp-json/woonoow/v1/pages/${pageState.slug}`, {
      method: 'POST',
      body: JSON.stringify({ sections: pageState.sections })
    });
    // Show toast notification
  };

  return (
    <div className="page-editor">
      <Sidebar
        pages={pageState.pages}
        selectedPageId={pageState.id}
      />

      <Canvas
        sections={pageState.sections}
        selectedSectionId={selectedSectionId}
        deviceMode={deviceMode}
        onSelectSection={handleSectionSelect}
        onDeleteSection={handleSectionDelete}
        onDuplicateSection={handleSectionDuplicate}
        onFieldChange={handleFieldChange}
      />

      <Inspector
        pageState={pageState}
        selectedSectionId={selectedSectionId}
        onFieldChange={handleFieldChange}
        onDeviceChange={setDeviceMode}
      />

      <BottomBar
        onSave={handleSave}
        onDiscard={() => location.reload()}
      />
    </div>
  );
}

// components/Canvas.tsx
export function Canvas({
  sections,
  selectedSectionId,
  deviceMode,
  onSelectSection,
  onFieldChange,
  ...handlers
}) {
  return (
    <div className="canvas-container">
      <DeviceToggle mode={deviceMode} onChange={setDeviceMode} />

      <div className={`canvas-viewport canvas-${deviceMode}`}>
        {sections.map((section) => (
          <CanvasSection
            key={section.id}
            section={section}
            isSelected={selectedSectionId === section.id}
            onClick={() => onSelectSection(section.id)}
            {...handlers}
          >
            <SectionRenderer
              section={section}
              onFieldChange={(field, value) =>
                onFieldChange(section.id, field, value)
              }
            />
          </CanvasSection>
        ))}

        <AddSectionButton onClick={openSectionPicker} />
      </div>
    </div>
  );
}

// components/CanvasSection.tsx
export function CanvasSection({ section, isSelected, children, ...handlers }) {
  return (
    <div
      className={`canvas-section ${isSelected ? 'selected' : ''}`}
      onClick={handlers.onClick}
      onDragStart={handlers.onDragStart}
      onDragOver={handlers.onDragOver}
      onDrop={handlers.onDrop}
    >
      {/* Actual rendered content */}
      {children}

      {/* Selection overlay */}
      {isSelected && (
        <SelectionOverlay>
          <DragHandle />
          <ContextMenu
            onDuplicate={() => handlers.onDuplicate(section.id)}
            onDelete={() => handlers.onDelete(section.id)}
          />
        </SelectionOverlay>
      )}
    </div>
  );
}

// components/Inspector.tsx
export function Inspector({ pageState, selectedSectionId, onFieldChange }) {
  if (!selectedSectionId) {
    return <PageSettings pageState={pageState} />;
  }

  const section = pageState.sections.find(s => s.id === selectedSectionId);

  return (
    <div className="inspector-panel">
      <div className="section-config">
        <h3>{section.type}</h3>

        {Object.entries(section.props).map(([fieldName, fieldData]) => (
          <InspectorField
            key={fieldName}
            fieldName={fieldName}
            fieldData={fieldData}
            onChange={(value) =>
              onFieldChange(selectedSectionId, `props.${fieldName}.value`, value)
            }
          />
        ))}

        <button onClick={() => handleDelete(selectedSectionId)}>
          Delete Section
        </button>
      </div>

      <Canvas preview />
    </div>
  );
}

// components/InspectorField.tsx
export function InspectorField({ fieldName, fieldData, onChange }) {
  if (fieldData.type === 'static') {
    return (
      <div className="inspector-field">
        <label>{fieldName}</label>
        <input
          type="text"
          value={fieldData.value}
          onChange={(e) => onChange(e.target.value)}
        />
      </div>
    );
  }

  if (fieldData.type === 'dynamic') {
    return (
      <div className="inspector-field">
        <label>{fieldName} <span className="dynamic-badge">[]</span></label>
        <select value={fieldData.source} onChange={(e) => onChange(e.target.value)}>
          <option value="post_title">Post Title</option>
          <option value="post_content">Post Content</option>
          <option value="post_featured_image">Featured Image</option>
          {/* More options */}
        </select>
      </div>
    );
  }
}

CSS Utilities for Canvas

/* Canvas container */
.canvas-container {
  flex: 1;
  background: #f5f5f5;
  overflow-y: auto;
  padding: 20px;
}

/* Device modes */
.canvas-viewport {
  background: white;
  margin: 0 auto;
  box-shadow: 0 2px 8px rgba(0,0,0,0.1);
}

.canvas-desktop {
  max-width: 1024px;
}

.canvas-mobile {
  max-width: 375px;
}

/* Sections */
.canvas-section {
  position: relative;
  padding: 0;
  border: 1px solid #e0e0e0;
  border-radius: 8px;
  margin-bottom: 16px;
  transition: all 0.2s ease;
}

.canvas-section:hover {
  border-color: #90caf9;
  box-shadow: 0 2px 8px rgba(144, 202, 249, 0.3);
}

.canvas-section.selected {
  border-color: #1976d2;
  border-width: 2px;
  box-shadow: 0 0 0 3px rgba(25, 118, 210, 0.1);
}

/* Selection overlay */
.selection-overlay {
  position: absolute;
  top: 0;
  right: 0;
  display: flex;
  gap: 8px;
  padding: 8px;
  background: rgba(25, 118, 210, 0.05);
  border-radius: 4px;
  opacity: 0;
}

.canvas-section.selected .selection-overlay {
  opacity: 1;
}

/* Dynamic badge */
.dynamic-badge {
  display: inline-block;
  width: 14px;
  height: 14px;
  background: #ff9800;
  color: white;
  border-radius: 50%;
  text-align: center;
  font-size: 10px;
  line-height: 14px;
  margin-left: 4px;
}

Benefits of This Approach

Benefit Explanation
WYSIWYG See actual page appearance while editing
No iframe overhead Direct component rendering = faster updates
No MutationObserver JSON → React = clean state management
Instant feedback Change field → canvas updates in milliseconds
Simpler code No iframe communication, no sandbox complexity
Better mobile edit Responsive canvas shows mobile view directly
Easier debugging Single React component tree, standard dev tools
Faster iteration Less complexity = faster implementation
Merchant friendly See exactly what customers will see

Next Steps

  1. Refactor state management (unified store)
  2. Build CanvasRenderer component
  3. Implement section click/hover interactions
  4. Connect Inspector panel to canvas
  5. Add drag-and-drop reordering
  6. Test responsive device modes
  7. Implement save/discard flow
  8. Polish UX (animations, feedback)