/ templates / design_canvas.jsx
design_canvas.jsx
  1  /**
  2   * DesignCanvas — Variation side-by-side grid layout
  3   *
  4   * Displays 2+ static design variations for comparison and selection.
  5   * Each variation has a label and zoom-on-hover.
  6   *
  7   * Usage:
  8   *   <DesignCanvas
  9   *     title="Hero Section Design Exploration"
 10   *     subtitle="Comparing 3 directions"
 11   *     columns={3}
 12   *   >
 13   *     <Variation label="Minimal" description="Minimalist restraint">
 14   *       <div>...your design 1...</div>
 15   *     </Variation>
 16   *     <Variation label="Editorial" description="Magazine editorial style">
 17   *       <div>...your design 2...</div>
 18   *     </Variation>
 19   *     <Variation label="Brutalist" description="Raw brutalist">
 20   *       <div>...your design 3...</div>
 21   *     </Variation>
 22   *   </DesignCanvas>
 23   *
 24   * For use with React+Babel inline. Include in a script tag, then
 25   * window.DesignCanvas / window.Variation are available.
 26   */
 27  
 28  const canvasStyles = {
 29    container: {
 30      minHeight: '100vh',
 31      background: '#F5F5F0',
 32      padding: '40px 60px',
 33      fontFamily: '-apple-system, "SF Pro Text", "PingFang SC", sans-serif',
 34    },
 35    header: {
 36      marginBottom: 48,
 37      maxWidth: 900,
 38    },
 39    title: {
 40      fontSize: 36,
 41      fontWeight: 600,
 42      marginBottom: 12,
 43      color: '#1A1A1A',
 44      letterSpacing: '-0.02em',
 45    },
 46    subtitle: {
 47      fontSize: 16,
 48      color: '#666',
 49      lineHeight: 1.5,
 50    },
 51    grid: {
 52      display: 'grid',
 53      gap: 32,
 54    },
 55    cell: {
 56      display: 'flex',
 57      flexDirection: 'column',
 58      gap: 12,
 59    },
 60    cellHeader: {
 61      display: 'flex',
 62      alignItems: 'baseline',
 63      gap: 12,
 64      paddingBottom: 8,
 65      borderBottom: '1px solid #E0E0DA',
 66    },
 67    label: {
 68      fontSize: 14,
 69      fontWeight: 600,
 70      color: '#1A1A1A',
 71      letterSpacing: '-0.01em',
 72    },
 73    description: {
 74      fontSize: 13,
 75      color: '#888',
 76    },
 77    frame: {
 78      background: '#fff',
 79      borderRadius: 4,
 80      border: '1px solid #E0E0DA',
 81      overflow: 'hidden',
 82      position: 'relative',
 83      transition: 'transform 0.2s ease, box-shadow 0.2s ease',
 84      cursor: 'pointer',
 85    },
 86    frameInner: {
 87      position: 'relative',
 88      width: '100%',
 89    },
 90    badge: {
 91      position: 'absolute',
 92      top: 12,
 93      left: 12,
 94      background: 'rgba(0, 0, 0, 0.7)',
 95      color: '#fff',
 96      padding: '3px 8px',
 97      borderRadius: 4,
 98      fontSize: 11,
 99      fontWeight: 500,
100      letterSpacing: '0.5px',
101      textTransform: 'uppercase',
102      zIndex: 10,
103      pointerEvents: 'none',
104    },
105  };
106  
107  function DesignCanvas({ title, subtitle, columns = 3, children }) {
108    const [expanded, setExpanded] = React.useState(null);
109  
110    const gridStyle = {
111      ...canvasStyles.grid,
112      gridTemplateColumns: `repeat(${columns}, 1fr)`,
113    };
114  
115    return (
116      <div style={canvasStyles.container}>
117        {(title || subtitle) && (
118          <div style={canvasStyles.header}>
119            {title && <h1 style={canvasStyles.title}>{title}</h1>}
120            {subtitle && <p style={canvasStyles.subtitle}>{subtitle}</p>}
121          </div>
122        )}
123  
124        <div style={gridStyle}>
125          {React.Children.map(children, (child, idx) =>
126            React.isValidElement(child)
127              ? React.cloneElement(child, {
128                  _index: idx,
129                  _expanded: expanded === idx,
130                  _onToggle: () => setExpanded(expanded === idx ? null : idx),
131                })
132              : child
133          )}
134        </div>
135  
136        {expanded !== null && (
137          <div
138            onClick={() => setExpanded(null)}
139            style={{
140              position: 'fixed',
141              inset: 0,
142              background: 'rgba(0, 0, 0, 0.75)',
143              zIndex: 1000,
144              display: 'flex',
145              alignItems: 'center',
146              justifyContent: 'center',
147              padding: 40,
148              cursor: 'zoom-out',
149            }}
150          >
151            <div
152              onClick={e => e.stopPropagation()}
153              style={{
154                background: '#fff',
155                borderRadius: 8,
156                overflow: 'hidden',
157                maxWidth: '90vw',
158                maxHeight: '90vh',
159                position: 'relative',
160              }}
161            >
162              {React.Children.toArray(children)[expanded]}
163            </div>
164          </div>
165        )}
166      </div>
167    );
168  }
169  
170  function Variation({ label, description, number, children, _index, _expanded, _onToggle, aspectRatio = '4 / 3' }) {
171    const displayNumber = number || String(_index + 1).padStart(2, '0');
172  
173    return (
174      <div style={canvasStyles.cell}>
175        <div style={canvasStyles.cellHeader}>
176          <span style={{ ...canvasStyles.label, color: '#999', fontFamily: 'ui-monospace, monospace', fontSize: 12 }}>
177            {displayNumber}
178          </span>
179          <span style={canvasStyles.label}>{label}</span>
180          {description && <span style={canvasStyles.description}>— {description}</span>}
181        </div>
182  
183        <div
184          onClick={_onToggle}
185          style={{
186            ...canvasStyles.frame,
187            aspectRatio,
188          }}
189          onMouseEnter={e => {
190            e.currentTarget.style.boxShadow = '0 8px 24px rgba(0,0,0,0.08)';
191          }}
192          onMouseLeave={e => {
193            e.currentTarget.style.boxShadow = 'none';
194          }}
195        >
196          <div style={canvasStyles.frameInner}>
197            {children}
198          </div>
199        </div>
200      </div>
201    );
202  }
203  
204  if (typeof window !== 'undefined') {
205    Object.assign(window, { DesignCanvas, Variation });
206  }