PluginProbe
Blockenberg — 600+ Advanced Gutenberg Blocks & AI Agent for WordPress Block Editor / 2.0.12
Blockenberg — 600+ Advanced Gutenberg Blocks & AI Agent for WordPress Block Editor v2.0.12
2.0.14 2.0.13 2.0.12 2.0.11 2.0.10 2.0.9 trunk 2.0.2 2.0.3 2.0.4 2.0.5 2.0.6 2.0.7 2.0.8
blockenberg / blocks / flip-card / index.js

index.js in Blockenberg — 600+ Advanced Gutenberg Blocks & AI Agent for WordPress Block Editor 2.0.12, at blocks/flip-card/index.js

1,001 lines 58.5 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 ( function () {
2 var el = wp.element.createElement;
3 var Fragment = wp.element.Fragment;
4 var useState = wp.element.useState;
5 var useEffect = wp.element.useEffect;
6 var useRef = wp.element.useRef;
7 var __ = wp.i18n.__;
8 var registerBlockType = wp.blocks.registerBlockType;
9 var InspectorControls = wp.blockEditor.InspectorControls;
10 var useBlockProps = wp.blockEditor.useBlockProps;
11 var RichText = wp.blockEditor.RichText;
12 var PanelBody = wp.components.PanelBody;
13 var ColorPicker = wp.components.ColorPicker;
14 var Popover = wp.components.Popover;
15 var Button = wp.components.Button;
16 var ToggleControl = wp.components.ToggleControl;
17 var RangeControl = wp.components.RangeControl;
18 var SelectControl = wp.components.SelectControl;
19 var TextControl = wp.components.TextControl;
20 var TabPanel = wp.components.TabPanel;
21 var ColorPalette = wp.components.ColorPalette;
22 var GradientPicker = wp.components.__experimentalGradientPicker;
23
24 var _fpTC, _fpTV;
25 function _tc() { return _fpTC || (_fpTC = window.bkbgTypographyControl); }
26 function _tv() { return _fpTV || (_fpTV = window.bkbgTypoCssVars); }
27
28 // ── Built-in SVG icons (same system as icon-box) ────────────────────────
29 var builtInIcons = {
30 check: el('svg', { viewBox: '0 0 24 24', xmlns: 'http://www.w3.org/2000/svg' },
31 el('path', { d: 'M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z' })
32 ),
33 check2: el('svg', { viewBox: '0 0 24 24', xmlns: 'http://www.w3.org/2000/svg' },
34 el('path', { d: 'M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-2 14.5l-4.5-4.5 1.41-1.41L10 13.67l7.59-7.59L19 7.5l-9 9z' })
35 ),
36 arrow: el('svg', { viewBox: '0 0 24 24', xmlns: 'http://www.w3.org/2000/svg' },
37 el('path', { d: 'M8.59 16.59L13.17 12 8.59 7.41 10 6l6 6-6 6-1.41-1.41z' })
38 ),
39 'arrow-right': el('svg', { viewBox: '0 0 24 24', xmlns: 'http://www.w3.org/2000/svg' },
40 el('path', { d: 'M12 4l-1.41 1.41L16.17 11H4v2h12.17l-5.58 5.59L12 20l8-8z' })
41 ),
42 star: el('svg', { viewBox: '0 0 24 24', xmlns: 'http://www.w3.org/2000/svg' },
43 el('path', { d: 'M12 17.27L18.18 21l-1.64-7.03L22 9.24l-7.19-.61L12 2 9.19 8.63 2 9.24l5.46 4.73L5.82 21z' })
44 ),
45 disc: el('svg', { viewBox: '0 0 24 24', xmlns: 'http://www.w3.org/2000/svg' },
46 el('circle', { cx: '12', cy: '12', r: '6' })
47 ),
48 square: el('svg', { viewBox: '0 0 24 24', xmlns: 'http://www.w3.org/2000/svg' },
49 el('rect', { x: '6', y: '6', width: '12', height: '12', rx: '2' })
50 ),
51 dash: el('svg', { viewBox: '0 0 24 24', xmlns: 'http://www.w3.org/2000/svg' },
52 el('path', { d: 'M4 11h16v2H4z' })
53 ),
54 cross: el('svg', { viewBox: '0 0 24 24', xmlns: 'http://www.w3.org/2000/svg' },
55 el('path', { d: 'M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z' })
56 ),
57 sparkle: el('svg', { viewBox: '0 0 24 24', xmlns: 'http://www.w3.org/2000/svg' },
58 el('path', { d: 'M12 2l2.4 7.4H22l-6.2 4.5 2.4 7.4L12 17l-6.2 4.3 2.4-7.4L2 9.4h7.6z' })
59 ),
60 bolt: el('svg', { viewBox: '0 0 24 24', xmlns: 'http://www.w3.org/2000/svg' },
61 el('path', { d: 'M7 2v11h3v9l7-12h-4l4-8z' })
62 ),
63 heart: el('svg', { viewBox: '0 0 24 24', xmlns: 'http://www.w3.org/2000/svg' },
64 el('path', { d: 'M12 21.35l-1.45-1.32C5.4 15.36 2 12.28 2 8.5 2 5.42 4.42 3 7.5 3c1.74 0 3.41.81 4.5 2.09C13.09 3.81 14.76 3 16.5 3 19.58 3 22 5.42 22 8.5c0 3.78-3.4 6.86-8.55 11.54L12 21.35z' })
65 ),
66 globe: el('svg', { viewBox: '0 0 24 24', xmlns: 'http://www.w3.org/2000/svg' },
67 el('path', { d: 'M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-1 17.93c-3.95-.49-7-3.85-7-7.93 0-.62.08-1.21.21-1.79L9 15v1c0 1.1.9 2 2 2v1.93zm6.9-2.54c-.26-.81-1-1.39-1.9-1.39h-1v-3c0-.55-.45-1-1-1H8v-2h2c.55 0 1-.45 1-1V7h2c1.1 0 2-.9 2-2v-.41c2.93 1.19 5 4.06 5 7.41 0 2.08-.8 3.97-2.1 5.39z' })
68 ),
69 shield: el('svg', { viewBox: '0 0 24 24', xmlns: 'http://www.w3.org/2000/svg' },
70 el('path', { d: 'M12 1L3 5v6c0 5.55 3.84 10.74 9 12 5.16-1.26 9-6.45 9-12V5l-9-4zm0 4l5 2.18V11c0 3.5-2.33 6.79-5 7.93-2.67-1.14-5-4.43-5-7.93V7.18L12 5z' })
71 ),
72 rocket: el('svg', { viewBox: '0 0 24 24', xmlns: 'http://www.w3.org/2000/svg' },
73 el('path', { d: 'M9.19 6.35c-2.04 2.29-3.44 5.58-3.57 5.89L2 10.69l4.05-4.05c.38-.37.88-.57 1.41-.57L9.19 6.35zM11.17 19l5.38-5.38c-.45 2.29-2.16 5.89-5.38 5.38zm8.6-14.26c.1-.46.12-.93.07-1.39-.46-.05-.93-.03-1.39.07-1.19.27-2.3.86-3.2 1.76L9.5 11H8l-3 3 3.46.96.58.58.96 3.46 3-3v-1.5l5.82-5.75c.9-.9 1.49-2.01 1.76-3.2zM15 10c-.55 0-1-.45-1-1s.45-1 1-1 1 .45 1 1-.45 1-1 1zm-6.71 9.21l-1.5-1.5c-.39-.39-.39-1.02 0-1.41l.03-.03c.39-.39 1.02-.39 1.41 0l1.5 1.5c.39.39.39 1.02 0 1.41l-.03.03c-.39.39-1.02.39-1.41 0z' })
74 )
75 };
76
77 var dashicons = [
78 'yes', 'yes-alt', 'no', 'no-alt', 'arrow-right', 'arrow-right-alt', 'arrow-right-alt2',
79 'arrow-left-alt2', 'arrow-up-alt2', 'arrow-down-alt2',
80 'star-filled', 'star-half', 'star-empty', 'heart', 'thumbs-up', 'thumbs-down',
81 'awards', 'flag', 'warning', 'info', 'lightbulb', 'marker', 'tag', 'category',
82 'location', 'location-alt', 'clock', 'calendar', 'calendar-alt',
83 'groups', 'businessman', 'businesswoman', 'admin-users', 'id', 'id-alt',
84 'products', 'cart', 'money', 'money-alt', 'bank', 'feedback', 'forms',
85 'edit', 'trash', 'upload', 'download', 'cloud', 'cloud-upload', 'cloud-saved',
86 'search', 'lock', 'unlock', 'performance', 'analytics', 'chart-bar', 'chart-pie',
87 'chart-line', 'chart-area', 'list-view', 'grid-view', 'slides',
88 'desktop', 'laptop', 'tablet', 'smartphone', 'phone',
89 'media-audio', 'media-video', 'media-document', 'format-image', 'camera',
90 'portfolio', 'book', 'book-alt', 'text-page', 'welcome-write-blog',
91 'hammer', 'art', 'schedule', 'rest-api', 'database', 'code-standards',
92 'rss', 'email', 'email-alt', 'share', 'share-alt', 'networking',
93 'facebook', 'twitter', 'instagram', 'linkedin', 'youtube',
94 'shield', 'shield-alt', 'sos', 'image-rotate', 'image-flip-horizontal',
95 'image-flip-vertical', 'image-crop', 'controls-play', 'controls-pause',
96 'controls-skipforward', 'controls-skipback', 'controls-repeat', 'controls-volumeon',
97 'controls-volumeoff', 'ribbon', 'certificate', 'megaphone', 'universal-access',
98 'visibility', 'hidden', 'welcome-add-page', 'welcome-view-site', 'welcome-widgets-menus',
99 'welcome-learn-more', 'format-video', 'format-audio', 'format-gallery', 'format-quote',
100 'format-chat', 'format-aside', 'format-links', 'format-status', 'align-left',
101 'align-right', 'align-center', 'align-none', 'align-full-width',
102 'migrate', 'shortcode', 'saved', 'dismiss', 'sticky',
103 'nametag', 'open-folder', 'vault', 'pdf', 'print', 'word'
104 ];
105
106 // ── Build icon element (mirrors icon-box logic) ───────────────────────
107 function buildIconEl(a) {
108 var type = a.iconType;
109 var sz = a.iconSize;
110 var col = a.iconColor;
111 var shape = a.iconBgShape;
112
113 if (type === 'none') return null;
114
115 var inner;
116 if (type === 'custom-image') {
117 if (!a.iconImageUrl) return null;
118 inner = el('img', {
119 src: a.iconImageUrl, alt: '',
120 style: { width: '100%', height: '100%', objectFit: 'contain', display: 'block' }
121 });
122 } else if (type === 'custom-char') {
123 inner = el('span', { className: 'bkbg-fc-char' }, a.iconChar || '�
124 ');
125 } else if (type === 'dashicon') {
126 inner = el('span', { className: 'dashicons dashicons-' + a.iconDashicon });
127 } else if (builtInIcons[type]) {
128 inner = builtInIcons[type];
129 } else {
130 inner = builtInIcons.star;
131 }
132
133 return el('span', {
134 className: 'bkbg-fc-icon',
135 style: { '--bkbg-fc-icon-size': sz + 'px', '--bkbg-fc-icon-color': col, color: col, fontSize: sz + 'px', width: sz + 'px', height: sz + 'px' },
136 'aria-hidden': 'true'
137 }, inner);
138 }
139
140 // ── Wrap icon with background shape ──────────────────────────────────
141 function wrapIconBg(iconEl, a) {
142 if (!iconEl) return null;
143 var shape = a.iconBgShape;
144 var bgSize = a.iconBgSize;
145 var bgCol = a.iconBgColor;
146 var radius = a.iconBgRadius;
147
148 var bgStyle;
149 if (shape === 'circle-filled') {
150 bgStyle = { display: 'flex', alignItems: 'center', justifyContent: 'center', width: bgSize + 'px', height: bgSize + 'px', borderRadius: '50%', background: bgCol, flexShrink: 0 };
151 } else if (shape === 'square-filled') {
152 bgStyle = { display: 'flex', alignItems: 'center', justifyContent: 'center', width: bgSize + 'px', height: bgSize + 'px', borderRadius: radius + 'px', background: bgCol, flexShrink: 0 };
153 } else if (shape === 'circle-outline') {
154 bgStyle = { display: 'flex', alignItems: 'center', justifyContent: 'center', width: bgSize + 'px', height: bgSize + 'px', borderRadius: '50%', border: '2px solid ' + bgCol, flexShrink: 0 };
155 } else if (shape === 'square-outline') {
156 bgStyle = { display: 'flex', alignItems: 'center', justifyContent: 'center', width: bgSize + 'px', height: bgSize + 'px', borderRadius: radius + 'px', border: '2px solid ' + bgCol, flexShrink: 0 };
157 } else {
158 bgStyle = { display: 'inline-flex', flexShrink: 0 };
159 }
160
161 return el('div', { className: 'bkbg-fc-icon-wrap', style: bgStyle }, iconEl);
162 }
163
164 // ── Resolved backgrounds for a face ────────────────────────────────────
165 function faceBg(bgColor, bgGradient) {
166 if (bgGradient) return { background: bgGradient };
167 return { backgroundColor: bgColor };
168 }
169
170 // ── Wrapper CSS vars ────────────────────────────────────────────────────
171 function buildWrapStyle(a) {
172 var shadows = {
173 none: 'none',
174 sm: '0 1px 3px rgba(0,0,0,.1), 0 1px 2px rgba(0,0,0,.06)',
175 md: '0 4px 6px rgba(0,0,0,.07), 0 2px 4px rgba(0,0,0,.06)',
176 lg: '0 10px 25px rgba(0,0,0,.1), 0 4px 10px rgba(0,0,0,.06)',
177 xl: '0 20px 40px rgba(0,0,0,.12), 0 8px 20px rgba(0,0,0,.08)'
178 };
179 return Object.assign({
180 '--bkbg-fc-w': a.cardWidth + 'px',
181 '--bkbg-fc-h': a.cardHeight + 'px',
182 '--bkbg-fc-radius': a.cardRadius + 'px',
183 '--bkbg-fc-speed': a.flipSpeed + 'ms',
184 '--bkbg-fc-shadow': shadows[a.cardShadow] || shadows.md
185 },
186 _tv()(a.typoFrontTitle, '--bkbg-flipc-ft-'),
187 _tv()(a.typoFrontSub, '--bkbg-flipc-fs-'),
188 _tv()(a.typoBackTitle, '--bkbg-flipc-bt-'),
189 _tv()(a.typoBackText, '--bkbg-flipc-bx-'),
190 _tv()(a.typoBtn, '--bkbg-flipc-bn-')
191 );
192 }
193
194 // ── Color picker helper ─────────────────────────────────────────────────
195 function makeColorControl(openKey, setOpenKey) {
196 return function (key, label, value, onChange) {
197 var isOpen = openKey === key;
198 return el('div', { key: key, style: { display: 'flex', alignItems: 'center', justifyContent: 'space-between', padding: '4px 0', gap: '8px' } },
199 el('span', { style: { fontSize: '12px', color: '#1e1e1e', flex: 1, lineHeight: 1.4 } }, label),
200 el('div', { style: { position: 'relative', flexShrink: 0 } },
201 el('button', {
202 type: 'button',
203 title: value || 'none',
204 onClick: function () { setOpenKey(isOpen ? null : key); },
205 style: {
206 width: '28px', height: '28px', borderRadius: '4px',
207 border: isOpen ? '2px solid #007cba' : '2px solid #ddd',
208 cursor: 'pointer', padding: 0, display: 'block',
209 background: value || '#ffffff', flexShrink: 0
210 }
211 }),
212 isOpen && el(Popover, { position: 'bottom left', onClose: function () { setOpenKey(null); } },
213 el('div', { style: { padding: '8px' }, onMouseDown: function (e) { e.stopPropagation(); } },
214 el('div', { style: { display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '6px' } },
215 el('strong', { style: { fontSize: '12px' } }, label),
216 el(Button, { icon: 'no-alt', isSmall: true, onClick: function () { setOpenKey(null); } })
217 ),
218 el(ColorPicker, { color: value, enableAlpha: true, onChange: onChange })
219 )
220 )
221 )
222 );
223 };
224 }
225
226 // ── Section header helper ───────────────────────────────────────────────
227 function sectionHeader(text) {
228 return el('p', { style: { margin: '8px 0 6px', fontWeight: 600, fontSize: '11px', textTransform: 'uppercase', color: '#888', letterSpacing: '.5px' } }, text);
229 }
230
231 // ── Option lists ────────────────────────────────────────────────────────
232 var iconTypeOptions = [
233 { label: __('Star �
234 ', 'blockenberg'), value: 'star' },
235 { label: __('Checkmark ✓', 'blockenberg'), value: 'check' },
236 { label: __('Check Circle ✓●', 'blockenberg'), value: 'check2' },
237 { label: __('Arrow ›', 'blockenberg'), value: 'arrow' },
238 { label: __('Arrow Right →', 'blockenberg'), value: 'arrow-right' },
239 { label: __('Disc •', 'blockenberg'), value: 'disc' },
240 { label: __('Square ▪', 'blockenberg'), value: 'square' },
241 { label: __('Sparkle ❖', 'blockenberg'), value: 'sparkle' },
242 { label: __('Bolt ⚡', 'blockenberg'), value: 'bolt' },
243 { label: __('Dash —', 'blockenberg'), value: 'dash' },
244 { label: __('Cross ✕', 'blockenberg'), value: 'cross' },
245 { label: __('Heart ♥', 'blockenberg'), value: 'heart' },
246 { label: __('Globe 🌐', 'blockenberg'), value: 'globe' },
247 { label: __('Shield 🛡', 'blockenberg'), value: 'shield' },
248 { label: __('Rocket 🚀', 'blockenberg'), value: 'rocket' },
249 { label: __('Custom Character', 'blockenberg'), value: 'custom-char' },
250 { label: __('Dashicon', 'blockenberg'), value: 'dashicon' },
251 { label: __('Custom Image', 'blockenberg'), value: 'custom-image' },
252 { label: __('None (no icon)', 'blockenberg'), value: 'none' }
253 ];
254 var iconBgShapeOptions = [
255 { label: __('Circle (filled)', 'blockenberg'), value: 'circle-filled' },
256 { label: __('Square (filled)', 'blockenberg'), value: 'square-filled' },
257 { label: __('Circle (outline)', 'blockenberg'), value: 'circle-outline' },
258 { label: __('Square (outline)', 'blockenberg'), value: 'square-outline' },
259 { label: __('None', 'blockenberg'), value: 'none' }
260 ];
261
262 var flipDirectionOptions = [
263 { label: __('Horizontal (left ↔ right)', 'blockenberg'), value: 'horizontal' },
264 { label: __('Vertical (top ↕ bottom)', 'blockenberg'), value: 'vertical' }
265 ];
266
267 var flipTriggerOptions = [
268 { label: __('Hover', 'blockenberg'), value: 'hover' },
269 { label: __('Click', 'blockenberg'), value: 'click' }
270 ];
271
272 var shadowOptions = [
273 { label: __('None', 'blockenberg'), value: 'none' },
274 { label: __('Small', 'blockenberg'), value: 'sm' },
275 { label: __('Medium', 'blockenberg'), value: 'md' },
276 { label: __('Large', 'blockenberg'), value: 'lg' },
277 { label: __('XL', 'blockenberg'), value: 'xl' }
278 ];
279
280 var alignOptions = [
281 { label: __('Left', 'blockenberg'), value: 'left' },
282 { label: __('Center', 'blockenberg'), value: 'center' },
283 { label: __('Right', 'blockenberg'), value: 'right' }
284 ];
285
286 var titleTagOptions = [
287 { label: 'h2', value: 'h2' },
288 { label: 'h3', value: 'h3' },
289 { label: 'h4', value: 'h4' },
290 { label: 'h5', value: 'h5' },
291 { label: 'p', value: 'p' }
292 ];
293
294 var fontWeightOptions = [
295 { label: '300 — Light', value: 300 },
296 { label: '400 — Regular', value: 400 },
297 { label: '500 — Medium', value: 500 },
298 { label: '600 — Semi-Bold', value: 600 },
299 { label: '700 — Bold', value: 700 },
300 { label: '800 — Extra Bold', value: 800 }
301 ];
302
303 var btnSizeOptions = [
304 { label: __('Small', 'blockenberg'), value: 'sm' },
305 { label: __('Medium', 'blockenberg'), value: 'md' },
306 { label: __('Large', 'blockenberg'), value: 'lg' }
307 ];
308
309 var btnStyleOptions = [
310 { label: __('Solid', 'blockenberg'), value: 'solid' },
311 { label: __('Outline', 'blockenberg'), value: 'outline' },
312 { label: __('Ghost', 'blockenberg'), value: 'ghost' }
313 ];
314
315 var subPositionOptions = [
316 { label: __('Above title', 'blockenberg'), value: 'above-title' },
317 { label: __('Below title', 'blockenberg'), value: 'below-title' }
318 ];
319
320 var cardAlignOptions = [
321 { label: __('Left', 'blockenberg'), value: 'left' },
322 { label: __('Center', 'blockenberg'), value: 'center' },
323 { label: __('Right', 'blockenberg'), value: 'right' }
324 ];
325
326 // ────────────────────────────────────────────────────────────────────────
327 registerBlockType('blockenberg/flip-card', {
328 title: __('Flip Card', 'blockenberg'),
329 icon: 'image-rotate',
330 category: 'bkbg-effects',
331 description: __('Card with front and back faces that flips on hover or click.', 'blockenberg'),
332
333 edit: function (props) {
334 var a = props.attributes;
335 var setAttributes = props.setAttributes;
336
337 var openColorKeyState = useState(null);
338 var openColorKey = openColorKeyState[0];
339 var setOpenColorKey = openColorKeyState[1];
340 var renderColorControl = makeColorControl(openColorKey, setOpenColorKey);
341
342 // Icon picker state (mirrors icon-box)
343 var iconPickerState = useState(false);
344 var iconPickerOpen = iconPickerState[0];
345 var setIconPickerOpen = iconPickerState[1];
346
347 var iconSearchState = useState('');
348 var iconSearch = iconSearchState[0];
349 var setIconSearch = iconSearchState[1];
350
351 var dashiconAnchorRef = useRef(null);
352
353 var filteredDashicons = dashicons.filter(function (ic) {
354 return !iconSearch || ic.indexOf(iconSearch) !== -1;
355 });
356
357 // Which side to preview in editor
358 var previewSideState = useState('front');
359 var previewSide = previewSideState[0];
360 var setPreviewSide = previewSideState[1];
361
362 // Unique blockId for JS click targeting
363 useEffect(function () {
364 if (!a.blockId) {
365 setAttributes({ blockId: 'bkbg-fc-' + Math.random().toString(36).slice(2, 8) });
366 }
367 }, []);
368
369 function openPhotoLibrary() {
370 var frame = wp.media({
371 title: __('Select Icon Image', 'blockenberg'),
372 button: { text: __('Use this image', 'blockenberg') },
373 multiple: false,
374 library: { type: 'image' }
375 });
376 frame.on('select', function () {
377 var attachment = frame.state().get('selection').first().toJSON();
378 setAttributes({ iconImageUrl: attachment.url });
379 });
380 frame.open();
381 }
382
383 // ── Inspector ─────────────────────────────────────────────────
384 var inspector = el(InspectorControls, {},
385
386 // ── Card Settings ─────────────────────────────────────────
387 el(PanelBody, { title: __('Card Settings', 'blockenberg'), initialOpen: true },
388 el(SelectControl, {
389 label: __('Flip Direction', 'blockenberg'),
390 value: a.flipDirection, options: flipDirectionOptions,
391 onChange: function (v) { setAttributes({ flipDirection: v }); }
392 }),
393 el(SelectControl, {
394 label: __('Flip Trigger', 'blockenberg'),
395 value: a.flipTrigger, options: flipTriggerOptions,
396 onChange: function (v) { setAttributes({ flipTrigger: v }); }
397 }),
398 el(RangeControl, {
399 label: __('Flip Speed (ms)', 'blockenberg'),
400 value: a.flipSpeed, min: 100, max: 1200, step: 50,
401 onChange: function (v) { setAttributes({ flipSpeed: v }); }
402 }),
403 el('hr', {}),
404 el(RangeControl, {
405 label: __('Card Width (px)', 'blockenberg'),
406 value: a.cardWidth, min: 160, max: 600,
407 onChange: function (v) { setAttributes({ cardWidth: v }); }
408 }),
409 el(RangeControl, {
410 label: __('Card Height (px)', 'blockenberg'),
411 value: a.cardHeight, min: 120, max: 800,
412 onChange: function (v) { setAttributes({ cardHeight: v }); }
413 }),
414 el(RangeControl, {
415 label: __('Border Radius (px)', 'blockenberg'),
416 value: a.cardRadius, min: 0, max: 64,
417 onChange: function (v) { setAttributes({ cardRadius: v }); }
418 }),
419 el(SelectControl, {
420 label: __('Shadow', 'blockenberg'),
421 value: a.cardShadow, options: shadowOptions,
422 onChange: function (v) { setAttributes({ cardShadow: v }); }
423 }),
424 el(SelectControl, {
425 label: __('Card Alignment', 'blockenberg'),
426 value: a.cardAlign, options: cardAlignOptions,
427 onChange: function (v) { setAttributes({ cardAlign: v }); }
428 })
429 ),
430
431 // ── Icon ─────────────────────────────────────────────────
432 el(PanelBody, { title: __('Icon (Front)', 'blockenberg'), initialOpen: false },
433 el(SelectControl, {
434 label: __('Icon Type', 'blockenberg'),
435 value: a.iconType, options: iconTypeOptions,
436 onChange: function (v) { setAttributes({ iconType: v }); setIconPickerOpen(false); }
437 }),
438
439 // Custom character input
440 a.iconType === 'custom-char' && el(TextControl, {
441 label: __('Character / Emoji / Symbol', 'blockenberg'),
442 help: __('Paste any character, emoji, or symbol', 'blockenberg'),
443 value: a.iconChar,
444 onChange: function (v) { setAttributes({ iconChar: v }); }
445 }),
446
447 // Dashicon picker
448 a.iconType === 'dashicon' && el(Fragment, {},
449 el('div', { style: { display: 'flex', alignItems: 'center', gap: '8px', marginBottom: '8px', flexWrap: 'wrap' } },
450 el('span', {
451 className: 'dashicons dashicons-' + a.iconDashicon,
452 style: { fontSize: '28px', width: '28px', height: '28px', color: a.iconColor }
453 }),
454 el(Button, {
455 variant: 'secondary', isSmall: true,
456 ref: dashiconAnchorRef,
457 onClick: function () { setIconPickerOpen(!iconPickerOpen); }
458 }, __('Choose Dashicon', 'blockenberg'))
459 ),
460 iconPickerOpen && el(Popover, {
461 position: 'bottom left',
462 placement: 'bottom-start',
463 anchor: dashiconAnchorRef.current,
464 onClose: function () { setIconPickerOpen(false); }
465 },
466 el('div', { className: 'bkbg-sc-icon-picker-modal', style: { padding: '12px', maxWidth: '320px' } },
467 el(TextControl, {
468 placeholder: __('Search dashicons…', 'blockenberg'),
469 value: iconSearch,
470 onChange: function (v) { setIconSearch(v); },
471 style: { marginBottom: '8px' }
472 }),
473 el('div', { className: 'bkbg-sc-dashicons-grid', style: { display: 'grid', gridTemplateColumns: 'repeat(7,1fr)', gap: '4px', maxHeight: '240px', overflowY: 'auto' } },
474 filteredDashicons.map(function (ic) {
475 return el('button', {
476 key: ic,
477 title: ic,
478 style: {
479 border: '2px solid ' + (ic === a.iconDashicon ? '#007cba' : 'transparent'),
480 borderRadius: '4px', padding: '4px', cursor: 'pointer',
481 background: ic === a.iconDashicon ? '#e8f4fd' : 'transparent',
482 display: 'flex', alignItems: 'center', justifyContent: 'center'
483 },
484 onClick: function (e) {
485 e.preventDefault();
486 setAttributes({ iconDashicon: ic });
487 setIconPickerOpen(false);
488 }
489 }, el('span', { className: 'dashicons dashicons-' + ic, style: { fontSize: '20px', width: '20px', height: '20px' } }));
490 })
491 )
492 )
493 )
494 ),
495
496 // Custom image picker
497 a.iconType === 'custom-image' && el(Fragment, {},
498 a.iconImageUrl && el('img', {
499 src: a.iconImageUrl, alt: '',
500 style: { maxWidth: '100%', maxHeight: '80px', marginBottom: '8px', borderRadius: '4px', display: 'block' }
501 }),
502 el(Button, {
503 variant: 'secondary', isSmall: true,
504 onClick: openPhotoLibrary,
505 style: { marginBottom: '8px' }
506 }, a.iconImageUrl ? __('Replace Image', 'blockenberg') : __('Choose Image', 'blockenberg')),
507 a.iconImageUrl && el(Button, {
508 variant: 'link', isDestructive: true, isSmall: true,
509 onClick: function () { setAttributes({ iconImageUrl: '' }); }
510 }, __('Remove', 'blockenberg'))
511 ),
512
513 a.iconType !== 'none' && el(Fragment, {},
514 el(RangeControl, {
515 label: __('Icon Size (px)', 'blockenberg'),
516 value: a.iconSize, min: 14, max: 120,
517 onChange: function (v) { setAttributes({ iconSize: v }); }
518 }),
519 a.iconType !== 'custom-image' &&
520 renderColorControl('iconColor', __('Icon Color', 'blockenberg'), a.iconColor, function (c) { setAttributes({ iconColor: c }); }),
521 el(SelectControl, {
522 label: __('Background Shape', 'blockenberg'),
523 value: a.iconBgShape, options: iconBgShapeOptions,
524 onChange: function (v) { setAttributes({ iconBgShape: v }); }
525 }),
526 a.iconBgShape !== 'none' && el(Fragment, {},
527 renderColorControl('iconBgColor', __('Bg Color', 'blockenberg'), a.iconBgColor, function (c) { setAttributes({ iconBgColor: c }); }),
528 el(RangeControl, {
529 label: __('Bg Container Size (px)', 'blockenberg'),
530 value: a.iconBgSize, min: 30, max: 200,
531 onChange: function (v) { setAttributes({ iconBgSize: v }); }
532 }),
533 (a.iconBgShape === 'square-filled' || a.iconBgShape === 'square-outline') &&
534 el(RangeControl, {
535 label: __('Bg Corner Radius (px)', 'blockenberg'),
536 value: a.iconBgRadius, min: 0, max: 60,
537 onChange: function (v) { setAttributes({ iconBgRadius: v }); }
538 })
539 ),
540 el(RangeControl, {
541 label: __('Gap below icon (px)', 'blockenberg'),
542 value: a.iconGapBottom, min: 0, max: 60,
543 onChange: function (v) { setAttributes({ iconGapBottom: v }); }
544 })
545 )
546 ),
547
548 // ── Front Face ───────────────────────────────────────────
549 el(PanelBody, { title: __('Front Face', 'blockenberg'), initialOpen: false },
550 renderColorControl('frontBgColor', __('Background Color', 'blockenberg'), a.frontBgColor, function (c) { setAttributes({ frontBgColor: c }); }),
551 el('div', { style:{ marginBottom:'12px' } },
552 el('p', { style:{ fontSize:'11px', fontWeight:600, margin:'0 0 6px', textTransform:'uppercase', color:'#757575' } }, __('Background Gradient', 'blockenberg')),
553 el(GradientPicker, { value: a.frontBgGradient, onChange: function (v) { setAttributes({ frontBgGradient: v }); } })
554 ),
555 el(SelectControl, {
556 label: __('Content Alignment', 'blockenberg'),
557 value: a.frontAlign, options: alignOptions,
558 onChange: function (v) { setAttributes({ frontAlign: v }); }
559 }),
560 el(RangeControl, { label: __('Pad Top (px)', 'blockenberg'), value: a.frontPadTop, min: 0, max: 80, onChange: function (v) { setAttributes({ frontPadTop: v }); } }),
561 el(RangeControl, { label: __('Pad Right (px)', 'blockenberg'), value: a.frontPadRight, min: 0, max: 80, onChange: function (v) { setAttributes({ frontPadRight: v }); } }),
562 el(RangeControl, { label: __('Pad Bottom (px)', 'blockenberg'), value: a.frontPadBottom, min: 0, max: 80, onChange: function (v) { setAttributes({ frontPadBottom: v }); } }),
563 el(RangeControl, { label: __('Pad Left (px)', 'blockenberg'), value: a.frontPadLeft, min: 0, max: 80, onChange: function (v) { setAttributes({ frontPadLeft: v }); } }),
564 el('hr', {}),
565 sectionHeader(__('Title', 'blockenberg')),
566 el(SelectControl, { label: __('Tag', 'blockenberg'), value: a.frontTitleTag, options: titleTagOptions, onChange: function (v) { setAttributes({ frontTitleTag: v }); } }),
567 renderColorControl('frontTitleColor', __('Color', 'blockenberg'), a.frontTitleColor, function (c) { setAttributes({ frontTitleColor: c }); }),
568 el('hr', {}),
569 el(ToggleControl, {
570 label: __('Show Subtitle / Tag', 'blockenberg'), checked: a.showFrontSub,
571 __nextHasNoMarginBottom: true,
572 onChange: function (v) { setAttributes({ showFrontSub: v }); }
573 }),
574 a.showFrontSub && el(Fragment, {},
575 el(SelectControl, { label: __('Position', 'blockenberg'), value: a.frontSubPosition, options: subPositionOptions, onChange: function (v) { setAttributes({ frontSubPosition: v }); } }),
576 renderColorControl('frontSubColor', __('Color', 'blockenberg'), a.frontSubColor, function (c) { setAttributes({ frontSubColor: c }); }),
577 el(RangeControl, { label: __('Gap (px)', 'blockenberg'), value: a.frontSubSpacing, min: 0, max: 40, onChange: function (v) { setAttributes({ frontSubSpacing: v }); } })
578 ),
579 el('hr', {}),
580 el(ToggleControl, {
581 label: __('Show flip hint text', 'blockenberg'), checked: a.showFlipHint,
582 __nextHasNoMarginBottom: true,
583 onChange: function (v) { setAttributes({ showFlipHint: v }); }
584 }),
585 a.showFlipHint && el(Fragment, {},
586 el(TextControl, {
587 label: __('Hint Text', 'blockenberg'),
588 value: a.flipHintText,
589 placeholder: a.flipTrigger === 'hover'
590 ? __('↻ hover to flip', 'blockenberg')
591 : __('↻ click to flip', 'blockenberg'),
592 onChange: function (v) { setAttributes({ flipHintText: v }); }
593 }),
594 renderColorControl('flipHintColor', __('Hint Color', 'blockenberg'), a.flipHintColor, function (c) { setAttributes({ flipHintColor: c }); }),
595 el(RangeControl, { label: __('Hint Size (px)', 'blockenberg'), value: a.flipHintSize, min: 9, max: 16, onChange: function (v) { setAttributes({ flipHintSize: v }); } })
596 )
597 ),
598
599 // ── Back Face ────────────────────────────────────────────
600 el(PanelBody, { title: __('Back Face', 'blockenberg'), initialOpen: false },
601 renderColorControl('backBgColor', __('Background Color', 'blockenberg'), a.backBgColor, function (c) { setAttributes({ backBgColor: c }); }),
602 el('div', { style:{ marginBottom:'12px' } },
603 el('p', { style:{ fontSize:'11px', fontWeight:600, margin:'0 0 6px', textTransform:'uppercase', color:'#757575' } }, __('Background Gradient', 'blockenberg')),
604 el(GradientPicker, { value: a.backBgGradient, onChange: function (v) { setAttributes({ backBgGradient: v }); } })
605 ),
606 el(SelectControl, {
607 label: __('Content Alignment', 'blockenberg'),
608 value: a.backAlign, options: alignOptions,
609 onChange: function (v) { setAttributes({ backAlign: v }); }
610 }),
611 el(RangeControl, { label: __('Pad Top (px)', 'blockenberg'), value: a.backPadTop, min: 0, max: 80, onChange: function (v) { setAttributes({ backPadTop: v }); } }),
612 el(RangeControl, { label: __('Pad Right (px)', 'blockenberg'), value: a.backPadRight, min: 0, max: 80, onChange: function (v) { setAttributes({ backPadRight: v }); } }),
613 el(RangeControl, { label: __('Pad Bottom (px)', 'blockenberg'), value: a.backPadBottom, min: 0, max: 80, onChange: function (v) { setAttributes({ backPadBottom: v }); } }),
614 el(RangeControl, { label: __('Pad Left (px)', 'blockenberg'), value: a.backPadLeft, min: 0, max: 80, onChange: function (v) { setAttributes({ backPadLeft: v }); } }),
615 el('hr', {}),
616 el(ToggleControl, {
617 label: __('Show Back Title', 'blockenberg'), checked: a.showBackTitle,
618 __nextHasNoMarginBottom: true,
619 onChange: function (v) { setAttributes({ showBackTitle: v }); }
620 }),
621 a.showBackTitle && el(Fragment, {},
622 sectionHeader(__('Back Title', 'blockenberg')),
623 el(SelectControl, { label: __('Tag', 'blockenberg'), value: a.backTitleTag, options: titleTagOptions, onChange: function (v) { setAttributes({ backTitleTag: v }); } }),
624 renderColorControl('backTitleColor', __('Color', 'blockenberg'), a.backTitleColor, function (c) { setAttributes({ backTitleColor: c }); }),
625 el(RangeControl, { label: __('Bottom Spacing (px)', 'blockenberg'), value: a.backTitleSpacing, min: 0, max: 60, onChange: function (v) { setAttributes({ backTitleSpacing: v }); } })
626 ),
627 el('hr', {}),
628 sectionHeader(__('Body Text', 'blockenberg')),
629 renderColorControl('backTextColor', __('Color', 'blockenberg'), a.backTextColor, function (c) { setAttributes({ backTextColor: c }); }),
630 el(RangeControl, { label: __('Bottom Spacing (px)', 'blockenberg'), value: a.backTextSpacing, min: 0, max: 60, onChange: function (v) { setAttributes({ backTextSpacing: v }); } })
631 ),
632
633 // ── Button ───────────────────────────────────────────────
634 el(PanelBody, { title: __('Button (Back)', 'blockenberg'), initialOpen: false },
635 el(ToggleControl, {
636 label: __('Show Button', 'blockenberg'), checked: a.showButton,
637 __nextHasNoMarginBottom: true,
638 onChange: function (v) { setAttributes({ showButton: v }); }
639 }),
640 a.showButton && el(Fragment, {},
641 el(TextControl, { label: __('Label', 'blockenberg'), value: a.btnLabel, onChange: function (v) { setAttributes({ btnLabel: v }); } }),
642 el(TextControl, { label: __('URL', 'blockenberg'), value: a.btnUrl, onChange: function (v) { setAttributes({ btnUrl: v }); } }),
643 el(SelectControl, {
644 label: __('Open in', 'blockenberg'),
645 value: a.btnTarget,
646 options: [
647 { label: __('Same tab', 'blockenberg'), value: '_self' },
648 { label: __('New tab', 'blockenberg'), value: '_blank' }
649 ],
650 onChange: function (v) { setAttributes({ btnTarget: v }); }
651 }),
652 el(SelectControl, { label: __('Style', 'blockenberg'), value: a.btnStyle, options: btnStyleOptions, onChange: function (v) { setAttributes({ btnStyle: v }); } }),
653 el(SelectControl, { label: __('Size', 'blockenberg'), value: a.btnSize, options: btnSizeOptions, onChange: function (v) { setAttributes({ btnSize: v }); } }),
654 renderColorControl('btnBgColor', __('Background', 'blockenberg'), a.btnBgColor, function (c) { setAttributes({ btnBgColor: c }); }),
655 renderColorControl('btnTextColor', __('Text Color', 'blockenberg'), a.btnTextColor, function (c) { setAttributes({ btnTextColor: c }); }),
656 renderColorControl('btnBorderColor', __('Border Color','blockenberg'), a.btnBorderColor, function (c) { setAttributes({ btnBorderColor: c }); }),
657 el(RangeControl, { label: __('Border Radius (px)', 'blockenberg'), value: a.btnRadius, min: 0, max: 50, onChange: function (v) { setAttributes({ btnRadius: v }); } })
658 )
659 ),
660
661 // ── Typography ───────────────────────────────────────────────
662 el(PanelBody, { title: __('Typography', 'blockenberg'), initialOpen: false },
663 _tc()({ label: __('Front Title', 'blockenberg'), value: a.typoFrontTitle, onChange: function (v) { setAttributes({ typoFrontTitle: v }); } }),
664 _tc()({ label: __('Front Subtitle', 'blockenberg'), value: a.typoFrontSub, onChange: function (v) { setAttributes({ typoFrontSub: v }); } }),
665 _tc()({ label: __('Back Title', 'blockenberg'), value: a.typoBackTitle, onChange: function (v) { setAttributes({ typoBackTitle: v }); } }),
666 _tc()({ label: __('Back Body Text', 'blockenberg'), value: a.typoBackText, onChange: function (v) { setAttributes({ typoBackText: v }); } }),
667 _tc()({ label: __('Button', 'blockenberg'), value: a.typoBtn, onChange: function (v) { setAttributes({ typoBtn: v }); } })
668 )
669 );
670
671 // ── Build faces ───────────────────────────────────────────────
672 var iconEl = wrapIconBg(buildIconEl(a), a);
673
674 var frontSubEl = a.showFrontSub
675 ? el(RichText, {
676 tagName: 'p', className: 'bkbg-fc-sub',
677 value: a.frontSub, placeholder: __('Subtitle or tag…', 'blockenberg'),
678 onChange: function (v) { setAttributes({ frontSub: v }); },
679 style: {
680 color: a.frontSubColor,
681 margin: 0, padding: 0,
682 marginTop: a.frontSubPosition === 'below-title' ? a.frontSubSpacing + 'px' : 0,
683 marginBottom: a.frontSubPosition === 'above-title' ? a.frontSubSpacing + 'px' : 0
684 },
685 allowedFormats: ['core/bold', 'core/italic', 'core/text-color']
686 })
687 : null;
688
689 var frontTitleEl = el(RichText, {
690 tagName: a.frontTitleTag, className: 'bkbg-fc-front-title',
691 value: a.frontTitle, placeholder: __('Front title…', 'blockenberg'),
692 onChange: function (v) { setAttributes({ frontTitle: v }); },
693 style: {
694 color: a.frontTitleColor,
695 margin: 0, padding: 0
696 },
697 allowedFormats: ['core/bold', 'core/italic', 'core/text-color']
698 });
699
700 var hintText = a.flipHintText || (a.flipTrigger === 'hover'
701 ? __('↻ hover to flip', 'blockenberg')
702 : __('↻ click to flip', 'blockenberg'));
703
704 var hintEl = a.showFlipHint
705 ? el('p', {
706 className: 'bkbg-fc-hint',
707 style: { margin: '0', padding: '0', fontSize: a.flipHintSize + 'px', color: a.flipHintColor, marginTop: 'auto', paddingTop: '12px' }
708 }, hintText)
709 : null;
710
711 // Editor faces: plain flex divs — no 3D transforms, no position:absolute, no backface-visibility
712 var editorFaceStyle = function (padT,padR,padB,padL, align, bg, grad, radius) {
713 return Object.assign({
714 display: 'flex', flexDirection: 'column', justifyContent: 'center',
715 width: '100%', height: '100%', boxSizing: 'border-box',
716 padding: padT + 'px ' + padR + 'px ' + padB + 'px ' + padL + 'px',
717 textAlign: align,
718 alignItems: align === 'left' ? 'flex-start' : align === 'right' ? 'flex-end' : 'center',
719 borderRadius: radius + 'px',
720 overflow: 'hidden'
721 }, faceBg(bg, grad));
722 };
723
724 var frontFace = el('div', {
725 style: editorFaceStyle(a.frontPadTop,a.frontPadRight,a.frontPadBottom,a.frontPadLeft, a.frontAlign, a.frontBgColor, a.frontBgGradient, a.cardRadius)
726 },
727 a.iconType !== 'none' && el('div', { style: { marginBottom: a.iconGapBottom + 'px', display: 'flex', justifyContent: a.frontAlign === 'left' ? 'flex-start' : a.frontAlign === 'right' ? 'flex-end' : 'center' } }, iconEl),
728 a.showFrontSub && a.frontSubPosition === 'above-title' && frontSubEl,
729 frontTitleEl,
730 a.showFrontSub && a.frontSubPosition === 'below-title' && frontSubEl,
731 hintEl
732 );
733
734 var backTitleEl = a.showBackTitle
735 ? el(RichText, {
736 tagName: a.backTitleTag, className: 'bkbg-fc-back-title',
737 value: a.backTitle, placeholder: __('Back title…', 'blockenberg'),
738 onChange: function (v) { setAttributes({ backTitle: v }); },
739 style: {
740 color: a.backTitleColor,
741 margin: 0, padding: 0, marginBottom: a.backTitleSpacing + 'px'
742 },
743 allowedFormats: ['core/bold', 'core/italic', 'core/text-color']
744 })
745 : null;
746
747 var backTextEl = el(RichText, {
748 tagName: 'p', className: 'bkbg-fc-back-text',
749 value: a.backText, placeholder: __('Back body text…', 'blockenberg'),
750 onChange: function (v) { setAttributes({ backText: v }); },
751 style: {
752 color: a.backTextColor,
753 margin: 0, padding: 0, marginBottom: a.showButton ? a.backTextSpacing + 'px' : 0
754 },
755 allowedFormats: ['core/bold', 'core/italic', 'core/text-color', 'core/link']
756 });
757
758 var btnPads = { sm: '6px 14px', md: '10px 22px', lg: '14px 28px' };
759 var btnStyle = {
760 display: 'inline-block',
761 padding: btnPads[a.btnSize] || btnPads.md,
762 borderRadius: a.btnRadius + 'px',
763 textDecoration: 'none',
764 cursor: 'pointer'
765 };
766 if (a.btnStyle === 'solid') {
767 btnStyle.background = a.btnBgColor;
768 btnStyle.color = a.btnTextColor;
769 btnStyle.border = '2px solid ' + (a.btnBorderColor || a.btnBgColor);
770 } else if (a.btnStyle === 'outline') {
771 btnStyle.background = 'transparent';
772 btnStyle.color = a.btnTextColor;
773 btnStyle.border = '2px solid ' + a.btnBorderColor;
774 } else {
775 btnStyle.background = 'transparent';
776 btnStyle.color = a.btnTextColor;
777 btnStyle.border = '2px solid transparent';
778 }
779
780 var buttonEl = a.showButton
781 ? el('a', {
782 href: '#', className: 'bkbg-fc-btn',
783 style: btnStyle,
784 onClick: function (e) { e.preventDefault(); }
785 }, a.btnLabel)
786 : null;
787
788 var backFace = el('div', {
789 style: editorFaceStyle(a.backPadTop,a.backPadRight,a.backPadBottom,a.backPadLeft, a.backAlign, a.backBgColor, a.backBgGradient, a.cardRadius)
790 },
791 backTitleEl,
792 backTextEl,
793 buttonEl
794 );
795
796 // Preview toggle
797 var previewToggle = el('div', {
798 style: {
799 display: 'flex', gap: '6px', marginBottom: '8px',
800 justifyContent: a.cardAlign === 'center' ? 'center' : a.cardAlign === 'right' ? 'flex-end' : 'flex-start'
801 }
802 },
803 el('button', {
804 type: 'button',
805 onClick: function () { setPreviewSide('front'); },
806 style: {
807 padding: '3px 10px', borderRadius: '4px', fontSize: '11px', cursor: 'pointer', fontWeight: 600,
808 background: previewSide === 'front' ? '#007cba' : '#f0f0f0',
809 color: previewSide === 'front' ? '#fff' : '#555',
810 border: 'none'
811 }
812 }, __('Front', 'blockenberg')),
813 el('button', {
814 type: 'button',
815 onClick: function () { setPreviewSide('back'); },
816 style: {
817 padding: '3px 10px', borderRadius: '4px', fontSize: '11px', cursor: 'pointer', fontWeight: 600,
818 background: previewSide === 'back' ? '#007cba' : '#f0f0f0',
819 color: previewSide === 'back' ? '#fff' : '#555',
820 border: 'none'
821 }
822 }, __('Back', 'blockenberg'))
823 );
824
825 var alignStyle = { display: 'flex', flexDirection: 'column' };
826 if (a.cardAlign === 'center') alignStyle.alignItems = 'center';
827 else if (a.cardAlign === 'right') alignStyle.alignItems = 'flex-end';
828 else alignStyle.alignItems = 'flex-start';
829
830 var blockProps = useBlockProps({ className: 'bkbg-editor-wrap', 'data-block-label': 'Flip Card' });
831
832 return el('div', blockProps,
833 inspector,
834 el('div', { style: alignStyle },
835 previewToggle,
836 el('div', {
837 className: 'bkbg-fc-outer',
838 style: Object.assign(buildWrapStyle(a), {
839 width: a.cardWidth + 'px',
840 height: a.cardHeight + 'px',
841 borderRadius: a.cardRadius + 'px',
842 boxShadow: buildWrapStyle(a)['--bkbg-fc-shadow'] || 'none',
843 overflow: 'hidden',
844 flexShrink: 0,
845 display: 'block'
846 })
847 },
848 previewSide === 'front' ? frontFace : backFace
849 )
850 )
851 );
852 },
853
854 // ── Save ────────────────────────────────────────────────────────────────
855 save: function (props) {
856 var a = props.attributes;
857 var RichTextContent = wp.blockEditor.RichText.Content;
858
859 var iconElSave = wrapIconBg(buildIconEl(a), a);
860
861 var frontSubEl = a.showFrontSub
862 ? el(RichTextContent, {
863 tagName: 'p', className: 'bkbg-fc-sub',
864 value: a.frontSub,
865 style: {
866 color: a.frontSubColor, margin: 0, padding: 0,
867 marginTop: a.frontSubPosition === 'below-title' ? a.frontSubSpacing + 'px' : 0,
868 marginBottom: a.frontSubPosition === 'above-title' ? a.frontSubSpacing + 'px' : 0
869 }
870 })
871 : null;
872
873 var frontTitleEl = el(RichTextContent, {
874 tagName: a.frontTitleTag, className: 'bkbg-fc-front-title',
875 value: a.frontTitle,
876 style: {
877 color: a.frontTitleColor,
878 margin: 0, padding: 0
879 }
880 });
881
882 var hintText = a.flipHintText || (a.flipTrigger === 'hover'
883 ? __('↻ hover to flip', 'blockenberg')
884 : __('↻ click to flip', 'blockenberg'));
885
886 var hintEl = a.showFlipHint
887 ? el('p', {
888 className: 'bkbg-fc-hint',
889 style: { margin: 0, padding: 0, paddingTop: '12px', fontSize: a.flipHintSize + 'px', color: a.flipHintColor, marginTop: 'auto' }
890 }, hintText)
891 : null;
892
893 var frontFace = el('div', {
894 className: 'bkbg-fc-face bkbg-fc-front',
895 style: Object.assign({
896 padding: a.frontPadTop + 'px ' + a.frontPadRight + 'px ' + a.frontPadBottom + 'px ' + a.frontPadLeft + 'px',
897 textAlign: a.frontAlign,
898 alignItems: a.frontAlign === 'left' ? 'flex-start' : a.frontAlign === 'right' ? 'flex-end' : 'center',
899 borderRadius: 'var(--bkbg-fc-radius)'
900 }, faceBg(a.frontBgColor, a.frontBgGradient))
901 },
902 a.iconType !== 'none' && el('div', { className: 'bkbg-fc-icon-wrap', style: { marginBottom: a.iconGapBottom + 'px', display: 'flex', justifyContent: a.frontAlign === 'left' ? 'flex-start' : a.frontAlign === 'right' ? 'flex-end' : 'center' } }, iconElSave),
903 a.showFrontSub && a.frontSubPosition === 'above-title' && frontSubEl,
904 frontTitleEl,
905 a.showFrontSub && a.frontSubPosition === 'below-title' && frontSubEl,
906 hintEl
907 );
908
909 var backTitleEl = a.showBackTitle
910 ? el(RichTextContent, {
911 tagName: a.backTitleTag, className: 'bkbg-fc-back-title',
912 value: a.backTitle,
913 style: {
914 color: a.backTitleColor,
915 margin: 0, padding: 0, marginBottom: a.backTitleSpacing + 'px'
916 }
917 })
918 : null;
919
920 var backTextEl = el(RichTextContent, {
921 tagName: 'p', className: 'bkbg-fc-back-text',
922 value: a.backText,
923 style: {
924 color: a.backTextColor,
925 margin: 0, padding: 0, marginBottom: a.showButton ? a.backTextSpacing + 'px' : 0
926 }
927 });
928
929 var btnPads = { sm: '6px 14px', md: '10px 22px', lg: '14px 28px' };
930 var btnStyleObj = {
931 display: 'inline-block',
932 padding: btnPads[a.btnSize] || btnPads.md,
933 borderRadius: a.btnRadius + 'px', textDecoration: 'none'
934 };
935 if (a.btnStyle === 'solid') {
936 btnStyleObj.background = a.btnBgColor;
937 btnStyleObj.color = a.btnTextColor;
938 btnStyleObj.border = '2px solid ' + (a.btnBorderColor || a.btnBgColor);
939 } else if (a.btnStyle === 'outline') {
940 btnStyleObj.background = 'transparent';
941 btnStyleObj.color = a.btnTextColor;
942 btnStyleObj.border = '2px solid ' + a.btnBorderColor;
943 } else {
944 btnStyleObj.background = 'transparent';
945 btnStyleObj.color = a.btnTextColor;
946 btnStyleObj.border = '2px solid transparent';
947 }
948
949 var buttonEl = a.showButton
950 ? el('a', {
951 href: a.btnUrl, className: 'bkbg-fc-btn',
952 target: a.btnTarget,
953 rel: a.btnTarget === '_blank' ? 'noopener noreferrer' : undefined,
954 style: btnStyleObj
955 }, a.btnLabel)
956 : null;
957
958 var backFace = el('div', {
959 className: 'bkbg-fc-face bkbg-fc-back',
960 style: Object.assign({
961 padding: a.backPadTop + 'px ' + a.backPadRight + 'px ' + a.backPadBottom + 'px ' + a.backPadLeft + 'px',
962 textAlign: a.backAlign,
963 alignItems: a.backAlign === 'left' ? 'flex-start' : a.backAlign === 'right' ? 'flex-end' : 'center',
964 borderRadius: 'var(--bkbg-fc-radius)'
965 }, faceBg(a.backBgColor, a.backBgGradient))
966 },
967 backTitleEl,
968 backTextEl,
969 buttonEl
970 );
971
972 var alignJustify = { left: 'flex-start', center: 'center', right: 'flex-end' };
973
974 return el('div', {
975 className: 'bkbg-fc-scene',
976 style: {
977 display: 'flex',
978 justifyContent: alignJustify[a.cardAlign] || 'center'
979 }
980 },
981 el('div', Object.assign(
982 { className: 'bkbg-fc-outer', style: buildWrapStyle(a) },
983 {
984 'data-flip-dir': a.flipDirection,
985 'data-flip-trigger': a.flipTrigger,
986 'data-block-id': a.blockId,
987 tabIndex: a.flipTrigger === 'click' ? 0 : undefined,
988 role: a.flipTrigger === 'click' ? 'button' : undefined,
989 'aria-label': a.flipTrigger === 'click' ? __('Flip card', 'blockenberg') : undefined
990 }
991 ),
992 el('div', { className: 'bkbg-fc-inner' },
993 frontFace,
994 backFace
995 )
996 )
997 );
998 }
999 });
1000 }() );
1001