<?php // WARNING: DO NOT ADD ANY EMPTY LINES OR SPACES ABOVE THIS LINE! /* Plugin Name: Skillful Johnson Box Inserter (Premium) Plugin URI: https://skillfulplugins.com Description: A premium-grade tool to design, organize, and insert custom Johnson Boxes. Ready for EDD Software Licensing. Version: 2.1.4 Author: SkillfulPlugins Author URI: https://skillfulplugins.com License: GPLv2 or later */ if (!defined('ABSPATH')) exit; class Skillful_Johnson_Box { public function __construct() { add_action('init', array($this, 'register_shortcodes')); add_action('admin_menu', array($this, 'add_admin_menu')); add_action('admin_enqueue_scripts', array($this, 'enqueue_admin_scripts')); add_action('wp_ajax_skjbi_save_data', array($this, 'save_app_data')); add_action('wp_ajax_skjbi_upload_image', array($this, 'upload_custom_image')); // Auto-insertion hook add_filter('the_content', array($this, 'auto_insert_boxes')); // EDD INTEGRATION POINT add_action('wp_ajax_skjbi_verify_edd_license', array($this, 'verify_edd_license')); } public function verify_edd_license() { check_ajax_referer('skjbi_nonce', 'security'); if (!current_user_can('manage_options')) { wp_send_json_error('Unauthorized'); } $license_key = sanitize_text_field($_POST['license_key']); $store_url = 'https://skillfulplugins.com'; $item_name = 'Skillful Johnson Box Inserter'; $api_params = array( 'edd_action' => 'activate_license', 'license' => $license_key, 'item_name' => urlencode($item_name), 'url' => home_url() ); $response = wp_remote_post($store_url, array('timeout' => 15, 'sslverify' => false, 'body' => $api_params)); if (is_wp_error($response)) { wp_send_json_error('Could not connect to skillfulplugins.com. Please try again.'); } $license_data = json_decode(wp_remote_retrieve_body($response)); if ($license_data && $license_data->license === 'valid') { $tier = 'pro'; if (isset($license_data->price_id)) { if ($license_data->price_id == 3) { $tier = 'business'; } elseif ($license_data->price_id == 2) { $tier = 'pro'; } elseif ($license_data->price_id == 1) { $tier = 'free'; } } wp_send_json_success(array('status' => 'valid', 'tier' => $tier)); } else { $error_msg = isset($license_data->error) ? $license_data->error : 'Invalid or expired license key.'; wp_send_json_error($error_msg); } } public function register_shortcodes() { add_shortcode('skjbi_box', array($this, 'render_box_shortcode')); add_shortcode('skillful_johnson_box', array($this, 'render_legacy_shortcode')); } public function add_admin_menu() { add_menu_page( 'Johnson Box', 'Johnson Box', 'manage_options', 'skillful-johnson-box', array($this, 'render_admin_page'), 'dashicons-welcome-widgets-menus', 66 ); } public function enqueue_admin_scripts($hook_suffix) { if ('toplevel_page_skillful-johnson-box' === $hook_suffix) { wp_enqueue_media(); wp_enqueue_script('jquery'); wp_enqueue_script('jszip', 'https://cdnjs.cloudflare.com/ajax/libs/jszip/3.10.1/jszip.min.js', array(), '3.10.1', true); } } public function upload_custom_image() { if (ob_get_length()) { ob_clean(); } check_ajax_referer('skjbi_nonce', 'security'); if (!current_user_can('upload_files')) { wp_send_json_error('Unauthorized'); } require_once(ABSPATH . 'wp-admin/includes/image.php'); require_once(ABSPATH . 'wp-admin/includes/file.php'); require_once(ABSPATH . 'wp-admin/includes/media.php'); $attachment_id = media_handle_upload('skjbi_image', 0); if (is_wp_error($attachment_id)) { wp_send_json_error($attachment_id->get_error_message()); } else { wp_send_json_success(array('url' => wp_get_attachment_url($attachment_id))); } } public function save_app_data() { if (ob_get_length()) { ob_clean(); } check_ajax_referer('skjbi_nonce', 'security'); if (!current_user_can('manage_options')) { wp_send_json_error('Unauthorized'); } $data = isset($_POST['skjbi_data']) ? stripslashes($_POST['skjbi_data']) : ''; $decoded_data = json_decode($data, true); if ($decoded_data) { update_option('skjbi_app_data', $decoded_data); wp_send_json_success('Saved successfully'); } else { wp_send_json_error('Invalid data format'); } } private function hex2rgba($color, $opacity = false) { $default = 'rgb(0,0,0)'; if (empty($color) || $color === 'transparent') return 'rgba(0,0,0,0)'; if ($color[0] == '#') $color = substr($color, 1); if (strlen($color) == 6) { $hex = array($color[0] . $color[1], $color[2] . $color[3], $color[4] . $color[5]); } elseif (strlen($color) == 3) { $hex = array($color[0] . $color[0], $color[1] . $color[1], $color[2] . $color[2]); } else { return $default; } $rgb = array_map('hexdec', $hex); if ($opacity !== false) { if (abs($opacity) > 1) $opacity = 1.0; return 'rgba(' . implode(",", $rgb) . ',' . $opacity . ')'; } return 'rgb(' . implode(",", $rgb) . ')'; } private function generate_box_html($data) { if (empty($data)) return ''; // 1. Generate a Unique ID $box_id = 'skjbi-' . substr(md5($data['id'] ?? uniqid()), 0, 8); $title = esc_html($data['title'] ?? ''); $body = wp_kses_post($data['body'] ?? ''); $align = esc_attr($data['textAlign'] ?? 'left'); // 2. Background Styling Logic $bgMode = $data['bgType'] ?? 'color'; $bgUrl = esc_url($data['bgImageUrl'] ?? ''); $bgColor = esc_attr($data['bgColor'] ?? '#fcf8e3'); $bgStyle = "background-color: {$bgColor};"; if ($bgMode === 'gradient') { $bg2 = esc_attr($data['bg2'] ?? '#ffffff'); $bgAngle = (isset($data['bgAngle']) && is_numeric($data['bgAngle'])) ? (int)$data['bgAngle'] : 90; $bgPos = (isset($data['bgPos']) && is_numeric($data['bgPos'])) ? (int)$data['bgPos'] : 50; $stop1 = 100 - ($bgPos * 2); $stop2 = 200 - ($bgPos * 2); $bgStyle = "background: linear-gradient({$bgAngle}deg, {$bgColor} {$stop1}%, {$bg2} {$stop2}%);"; } elseif ($bgMode === 'image' && !empty($bgUrl)) { $bgStyle = "background-image: url('{$bgUrl}'); background-size: cover; background-position: center;"; } // 3. Size & Border Logic $boxPad = (isset($data['boxPad']) && is_numeric($data['boxPad'])) ? (int)$data['boxPad'] : 30; $boxBorderW = (isset($data['boxBorderW']) && is_numeric($data['boxBorderW'])) ? (int)$data['boxBorderW'] : 3; $boxBorderC = !empty($data['boxBorderC']) ? esc_attr($data['boxBorderC']) : '#cc0000'; $boxRadius = (isset($data['boxRadius']) && is_numeric($data['boxRadius'])) ? (int)$data['boxRadius'] : 8; $boxMaxW = (isset($data['boxMaxW']) && is_numeric($data['boxMaxW'])) ? (int)$data['boxMaxW'] : 800; $widthCss = !empty($data['boxFullW']) ? "width: 100%;" : "width: 100%; max-width: {$boxMaxW}px; margin: 25px auto;"; // 4. Font Configuration $titleFontFam = !empty($data['titleFontFamily']) ? esc_attr($data['titleFontFamily']) : 'inherit'; $bodyFontFam = !empty($data['bodyFontFamily']) ? esc_attr($data['bodyFontFamily']) : 'inherit'; $titleSize = (isset($data['titleSize']) && is_numeric($data['titleSize'])) ? (int)$data['titleSize'] : 24; $titleColor = !empty($data['titleColor']) ? esc_attr($data['titleColor']) : '#cc0000'; $bodySize = (isset($data['bodySize']) && is_numeric($data['bodySize'])) ? (int)$data['bodySize'] : 16; $bodyColor = !empty($data['bodyColor']) ? esc_attr($data['bodyColor']) : '#333333'; $tStyleOpt = !empty($data['titleFontStyle']) ? esc_attr($data['titleFontStyle']) : 'bold'; $tWeight = strpos($tStyleOpt, 'bold') !== false ? '700' : '400'; $tStyle = strpos($tStyleOpt, 'italic') !== false ? 'italic' : 'normal'; // 5. BUTTON ENGINE PRE-CALCULATIONS $btnCss = ''; $btnHtml = ''; $loadIconFont = false; $uniqueBtnId = 'btn-' . $box_id; if (!empty($data['useBuiltInBtn'])) { $btnText = !empty($data['btnText']) ? esc_html($data['btnText']) : 'Click To Buy'; $btnUrl = !empty($data['btnUrl']) ? esc_url($data['btnUrl']) : '#'; $btnRadius = (isset($data['btnRadius']) && is_numeric($data['btnRadius'])) ? (int)$data['btnRadius'] : 50; $btnPos = !empty($data['btnPos']) ? esc_attr($data['btnPos']) : 'center'; $btnColor = !empty($data['btnColor']) ? esc_attr($data['btnColor']) : '#000000'; $btnSize = (isset($data['btnSize']) && is_numeric($data['btnSize'])) ? (int)$data['btnSize'] : 16; $btnPadX = (isset($data['btnPadX']) && is_numeric($data['btnPadX'])) ? (int)$data['btnPadX'] : 30; $btnPadY = (isset($data['btnPadY']) && is_numeric($data['btnPadY'])) ? (int)$data['btnPadY'] : 15; $btnSpace = (isset($data['btnSpace']) && is_numeric($data['btnSpace'])) ? (int)$data['btnSpace'] : 20; // IRONCLAD BORDER DESTRUCTION (Safely cast to int to avoid errors) $btnBorderW = (isset($data['btnBorderW']) && is_numeric($data['btnBorderW'])) ? (int)$data['btnBorderW'] : 0; $btnBorderC = !empty($data['btnBorderC']) ? esc_attr($data['btnBorderC']) : '#000000'; $btnBorderRule = ($btnBorderW === 0) ? "border: 0px solid transparent !important; outline: none !important; -webkit-appearance: none !important; appearance: none !important;" : "border: {$btnBorderW}px solid {$btnBorderC} !important; outline: none !important; -webkit-appearance: none !important; appearance: none !important;"; $btnFont = !empty($data['btnFontFamily']) ? esc_attr($data['btnFontFamily']) : 'inherit'; $btnFontStr = $btnFont !== 'inherit' ? "font-family: '{$btnFont}', Arial, sans-serif !important;" : "font-family: inherit !important;"; $btnBgType = !empty($data['btnBgType']) ? esc_attr($data['btnBgType']) : 'color'; $btnBg = !empty($data['btnBg']) ? esc_attr($data['btnBg']) : '#ff9900'; if ($btnBgType === 'gradient') { $btnBg2 = !empty($data['btnBg2']) ? esc_attr($data['btnBg2']) : '#e68a00'; $btnBgAngle = (isset($data['btnBgAngle']) && is_numeric($data['btnBgAngle'])) ? (int)$data['btnBgAngle'] : 90; $bp = (isset($data['btnBgPos']) && is_numeric($data['btnBgPos'])) ? (int)$data['btnBgPos'] : 50; $bs1 = 100 - ($bp * 2); $bs2 = 200 - ($bp * 2); $btnBgStyle = "background: linear-gradient({$btnBgAngle}deg, {$btnBg} {$bs1}%, {$btnBg2} {$bs2}%) !important;"; } else { // Instantly block global theme background-images on solid buttons $btnBgStyle = "background-color: {$btnBg} !important; background-image: none !important;"; } $btnS = !empty($data['btnFontStyle']) ? esc_attr($data['btnFontStyle']) : 'bold'; $btnWeight = '700'; $btnStyleCss = 'normal'; if ($btnS === 'normal') { $btnWeight = '400'; $btnStyleCss = 'normal'; } elseif ($btnS === 'italic') { $btnWeight = '400'; $btnStyleCss = 'italic'; } elseif ($btnS === 'bold-italic') { $btnWeight = '700'; $btnStyleCss = 'italic'; } $flexDir = 'row'; $iconPos = !empty($data['btnIconPosition']) ? esc_attr($data['btnIconPosition']) : 'left'; $iconGap = (isset($data['btnIconGap']) && is_numeric($data['btnIconGap'])) ? (int)$data['btnIconGap'] : 8; if ($iconPos === 'right') $flexDir = 'row-reverse'; if ($iconPos === 'top') $flexDir = 'column'; if ($iconPos === 'bottom') $flexDir = 'column-reverse'; $btnIconClass = !empty($data['btnIconClass']) ? esc_attr($data['btnIconClass']) : ''; // NO SPANS. Let the <a> tag handle inheritance so the theme can't target spans. $btnInner = $btnText; if (!empty($btnIconClass)) { $loadIconFont = true; $iHtml = "<i class=\"{$btnIconClass}\" style=\"margin:0 !important; padding:0 !important; font-style:normal !important; line-height:1 !important;\"></i>"; if ($iconPos === 'icon_only') { $btnInner = $iHtml; $iconGap = 0; } else { $btnInner = "{$iHtml} {$btnText}"; } } // CRASH PREVENTION: Safely process shadows to prevent PHP 8 TypeError $bShadow = 'none'; $baseShadowForHalo = ''; $ef = !empty($data['btnEffect']) ? esc_attr($data['btnEffect']) : 'none'; if ($ef === 'glossy') { $bShadow = 'inset 0px 1px 0px rgba(255,255,255,0.5), inset 0px 15px 15px -15px rgba(255,255,255,0.8), inset 0px -5px 10px -5px rgba(0,0,0,0.5), 0px 4px 6px rgba(0,0,0,0.3)'; $baseShadowForHalo = $bShadow; } elseif ($ef === '3d') { $bShadow = '0px 6px 0px rgba(0,0,0,0.4), 0px 10px 15px rgba(0,0,0,0.2)'; $baseShadowForHalo = $bShadow; } elseif ($ef === 'shadow') { $bShadow = '0px 10px 20px rgba(0,0,0,0.15), 0px 3px 6px rgba(0,0,0,0.1)'; $baseShadowForHalo = $bShadow; } elseif ($ef === 'retro') { $bShadow = '5px 5px 0px #000'; $baseShadowForHalo = $bShadow; } elseif ($ef === 'pressed') { $bShadow = 'inset 0px 5px 10px rgba(0,0,0,0.4)'; $baseShadowForHalo = $bShadow; } elseif ($ef === 'floating') { $bShadow = '0px 15px 25px rgba(0,0,0,0.2), 0px 5px 10px rgba(0,0,0,0.1)'; $baseShadowForHalo = $bShadow; } elseif ($ef === 'glow-dark') { $bShadow = "0px 0px 15px " . $this->hex2rgba($btnBg, 0.6); $baseShadowForHalo = $bShadow; } elseif ($ef === 'glow-light') { $bShadow = '0px 0px 15px rgba(255,255,255,0.8)'; $baseShadowForHalo = $bShadow; } elseif ($ef === 'custom') { $s1Op = (isset($data['shadow1Opacity']) && is_numeric($data['shadow1Opacity'])) ? (int)$data['shadow1Opacity'] : 40; $s1Color = !empty($data['shadow1Color']) ? $data['shadow1Color'] : '#000000'; $s1X = (isset($data['shadow1X']) && is_numeric($data['shadow1X'])) ? (int)$data['shadow1X'] : 0; $s1Y = (isset($data['shadow1Y']) && is_numeric($data['shadow1Y'])) ? (int)$data['shadow1Y'] : 5; $s1B = (isset($data['shadow1Blur']) && is_numeric($data['shadow1Blur'])) ? (int)$data['shadow1Blur'] : 15; $s1S = (isset($data['shadow1Spread']) && is_numeric($data['shadow1Spread'])) ? (int)$data['shadow1Spread'] : 0; $s1 = (!empty($data['shadow1Inset']) ? 'inset ' : '') . "{$s1X}px {$s1Y}px {$s1B}px {$s1S}px " . $this->hex2rgba($s1Color, $s1Op / 100); if (!empty($data['enableShadow2'])) { $s2Op = (isset($data['shadow2Opacity']) && is_numeric($data['shadow2Opacity'])) ? (int)$data['shadow2Opacity'] : 50; $s2Color = !empty($data['shadow2Color']) ? $data['shadow2Color'] : '#ffffff'; $s2X = (isset($data['shadow2X']) && is_numeric($data['shadow2X'])) ? (int)$data['shadow2X'] : 0; $s2Y = (isset($data['shadow2Y']) && is_numeric($data['shadow2Y'])) ? (int)$data['shadow2Y'] : 0; $s2B = (isset($data['shadow2Blur']) && is_numeric($data['shadow2Blur'])) ? (int)$data['shadow2Blur'] : 15; $s2S = (isset($data['shadow2Spread']) && is_numeric($data['shadow2Spread'])) ? (int)$data['shadow2Spread'] : 0; $s1 .= ", " . (!empty($data['shadow2Inset']) ? 'inset ' : '') . "{$s2X}px {$s2Y}px {$s2B}px {$s2S}px " . $this->hex2rgba($s2Color, $s2Op / 100); } $bShadow = $s1; $baseShadowForHalo = $s1; } $boxS = $bShadow !== 'none' ? "box-shadow: {$bShadow} !important;" : "box-shadow: none !important;"; $anim = !empty($data['btnAnimation']) ? esc_attr($data['btnAnimation']) : 'none'; $kf = ''; $pseudoCss = ''; $animCss = ''; if($anim === 'pulse') { $animCss = 'animation: skjbi_pulse 2s infinite !important;'; $kf = '@keyframes skjbi_pulse { 0% { transform: scale(1); } 50% { transform: scale(1.05); } 100% { transform: scale(1); } } '; } elseif($anim === 'bounce') { $animCss = 'animation: skjbi_bounce 2s infinite !important;'; $kf = '@keyframes skjbi_bounce { 0%, 20%, 50%, 80%, 100% { transform: translateY(0); } 40% { transform: translateY(-10px); } 60% { transform: translateY(-5px); } } '; } elseif($anim === 'shake') { $animCss = 'animation: skjbi_shake 2s infinite !important;'; $kf = '@keyframes skjbi_shake { 0%, 100% { transform: translateX(0); } 10%, 30%, 50%, 70%, 90% { transform: translateX(-4px); } 20%, 40%, 60%, 80% { transform: translateX(4px); } } '; } elseif($anim === 'wobble') { $animCss = 'animation: skjbi_wobble 2s infinite !important;'; $kf = '@keyframes skjbi_wobble { 0%, 100% { transform: rotate(0deg); } 25% { transform: rotate(-3deg); } 50% { transform: rotate(3deg); } 75% { transform: rotate(-3deg); } } '; } elseif($anim === 'shine') { $kf = '@keyframes skjbi_shine { 0% { left: -100%; } 100% { left: 200%; } } '; $pseudoCss = "div#{$box_id}.skjbi-box .skjbi-button-wrapper a#{$uniqueBtnId}::after { content: ''; position: absolute; top: 0; left: -100%; width: 50%; height: 100%; background: linear-gradient(to right, rgba(255,255,255,0) 0%, rgba(255,255,255,0.6) 50%, rgba(255,255,255,0) 100%); transform: skewX(-25deg); animation: skjbi_shine 2.5s infinite linear; pointer-events: none; border-radius: inherit; }"; } elseif($anim === 'halo') { $base_bs = ($baseShadowForHalo && $baseShadowForHalo !== 'none') ? $baseShadowForHalo . ', ' : ''; $haloStart = $this->hex2rgba($btnBg, 0.7); $haloEnd = $this->hex2rgba($btnBg, 0); $animCss = "animation: skjbi_halo_{$uniqueBtnId} 2s infinite !important;"; $kf = "@keyframes skjbi_halo_{$uniqueBtnId} { 0% { box-shadow: {$base_bs}0 0 0 0 {$haloStart}; } 70% { box-shadow: {$base_bs}0 0 0 15px {$haloEnd}; } 100% { box-shadow: {$base_bs}0 0 0 0 {$haloEnd}; } } "; } elseif($anim === 'float') { $animCss = 'animation: skjbi_float 2s infinite !important;'; $kf = '@keyframes skjbi_float { 0%, 100% { transform: translateY(0); } 50% { transform: translateY(-8px); } } '; } $overflowVal = $anim === 'shine' ? 'hidden' : 'visible'; $hStr = ''; if(!empty($data['enableHoverBg'])) $hStr .= "background: " . esc_attr($data['btnHoverBg'] ?? '#005177') . " !important; "; if(!empty($data['enableHoverColor'])) $hStr .= "color: " . esc_attr($data['btnHoverColor'] ?? '#ffffff') . " !important; "; $ha = !empty($data['btnHoverAnimation']) ? esc_attr($data['btnHoverAnimation']) : 'none'; if($ha === 'scale') $hStr .= "transform: scale(1.05) !important; "; elseif($ha === 'lift') $hStr .= "transform: translateY(-4px) !important; "; elseif($ha === 'push') $hStr .= "transform: translateY(2px) !important; "; if($anim !== 'none') $hStr .= "animation-play-state: paused !important; "; // THEME OBLITERATION: Destroys hidden ::before/::after borders injected by the theme $pseudoReset = "div#{$box_id}.skjbi-box .skjbi-button-wrapper a#{$uniqueBtnId}::before { display: none !important; content: none !important; border: none !important; background: transparent !important; }"; if ($anim !== 'shine') { $pseudoReset .= "\ndiv#{$box_id}.skjbi-box .skjbi-button-wrapper a#{$uniqueBtnId}::after { display: none !important; content: none !important; border: none !important; background: transparent !important; }"; } $btnCss = " {$kf} {$pseudoCss} {$pseudoReset} div#{$box_id}.skjbi-box .skjbi-button-wrapper a#{$uniqueBtnId} { position: relative !important; overflow: {$overflowVal} !important; transition: all 0.3s ease-in-out !important; z-index: 1 !important; -webkit-font-smoothing: antialiased !important; -moz-osx-font-smoothing: grayscale !important; } div#{$box_id}.skjbi-box .skjbi-button-wrapper a#{$uniqueBtnId}:hover { {$hStr} } div#{$box_id}.skjbi-box .skjbi-button-wrapper a#{$uniqueBtnId} i { font-family: 'Font Awesome 6 Free', 'Font Awesome 6 Brands' !important; font-weight: 900 !important; color: inherit !important; border: none !important; background: transparent !important; box-shadow: none !important; } "; $alignCss = "text-align: center;"; if($btnPos === 'left') $alignCss = "text-align: left;"; if($btnPos === 'right') $alignCss = "text-align: right;"; $newTab = !empty($data['btnNewTab']) ? 'target="_blank"' : ''; $noFollow = !empty($data['btnNofollow']) ? 'rel="nofollow"' : ''; $inlineStyle = "display:inline-flex !important; align-items:center !important; justify-content:center !important; flex-direction:{$flexDir} !important; gap:{$iconGap}px !important; {$btnBgStyle} color:{$btnColor} !important; padding:{$btnPadY}px {$btnPadX}px !important; {$btnBorderRule} border-radius:{$btnRadius}px !important; text-decoration:none !important; font-size:{$btnSize}px !important; font-weight:{$btnWeight} !important; font-style:{$btnStyleCss} !important; {$boxS} {$animCss} {$btnFontStr} box-sizing:border-box !important; line-height: 1.2 !important; margin: 0 !important; letter-spacing: normal !important;"; $btnHtml = "<div class='skjbi-button-wrapper' style='margin-top:{$btnSpace}px; {$alignCss}'><a id='{$uniqueBtnId}' href='{$btnUrl}' style=\"{$inlineStyle}\" {$newTab} {$noFollow}>{$btnInner}</a></div>"; } elseif (!empty($data['customShortcode'])) { $btnHtml = "<div class='skjbi-button-wrapper' style='margin-top:20px; text-align: center;'>" . do_shortcode($data['customShortcode']) . "</div>"; } $shield = " <style> #{$box_id}.skjbi-box { box-sizing: border-box !important; overflow: hidden !important; border: {$boxBorderW}px solid {$boxBorderC} !important; background-clip: padding-box !important; } #{$box_id}.skjbi-box * { box-sizing: border-box !important; } #{$box_id}.skjbi-box .skjbi-title { font-family: '{$titleFontFam}', sans-serif !important; color: {$titleColor} !important; font-size: {$titleSize}px !important; font-weight: {$tWeight} !important; font-style: {$tStyle} !important; margin: 0 0 15px 0 !important; line-height: 1.2 !important; display: block !important; background: transparent !important; border: none !important; text-transform: none !important; } #{$box_id}.skjbi-box .skjbi-body-text { font-family: '{$bodyFontFam}', sans-serif !important; color: {$bodyColor} !important; font-size: {$bodySize}px !important; line-height: 1.6 !important; margin: 0 0 15px 0 !important; background: transparent !important; } #{$box_id}.skjbi-box .skjbi-body-text p, #{$box_id}.skjbi-box .skjbi-body-text span, #{$box_id}.skjbi-box .skjbi-body-text div { font-family: inherit !important; color: inherit !important; font-size: inherit !important; background: transparent !important; } #{$box_id}.skjbi-box ul { list-style: disc !important; margin: 0 0 15px 20px !important; padding: 0 !important; color: {$bodyColor} !important; } #{$box_id}.skjbi-box li { font-family: '{$bodyFontFam}', sans-serif !important; margin-bottom: 5px !important; } {$btnCss} </style>"; $html = $shield; if ($loadIconFont) { $html .= "<link rel='stylesheet' href='https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css'>"; } $html .= "<div id='{$box_id}' class='skjbi-box' style='{$bgStyle} padding: {$boxPad}px; border-radius: {$boxRadius}px; text-align: {$align}; {$widthCss}'>"; if (!empty($title)) { $html .= "<h3 class='skjbi-title'>{$title}</h3>"; } if (!empty($body)) { $html .= "<div class='skjbi-body-text'>" . nl2br($body) . "</div>"; } $html .= $btnHtml; $html .= "</div>"; $fonts = array_unique(array($titleFontFam, $bodyFontFam)); if (isset($data['useBuiltInBtn']) && !empty($data['btnFontFamily'])) { $fonts[] = $data['btnFontFamily']; } foreach ($fonts as $f) { if ($f !== 'inherit' && !empty($f)) { $f_url = str_replace(' ', '+', $f); $html .= "<style>@import url('https://fonts.googleapis.com/css2?family={$f_url}:wght@400;700&display=swap');</style>\n"; } } return $html; } public function render_box_shortcode($atts) { $atts = shortcode_atts(array('id' => ''), $atts, 'skjbi_box'); if (empty($atts['id'])) return ''; $app_data = get_option('skjbi_app_data', array()); $boxes = isset($app_data['boxes']) ? $app_data['boxes'] : array(); foreach ($boxes as $box) { if ($box['id'] == $atts['id']) { return $this->generate_box_html($box); } } return ''; } public function render_legacy_shortcode() { $app_data = get_option('skjbi_app_data', array()); $boxes = isset($app_data['boxes']) ? $app_data['boxes'] : array(); if (!empty($boxes)) { return $this->generate_box_html($boxes[0]); } return ''; } public function auto_insert_boxes($content) { $app_data = get_option('skjbi_app_data', array()); $boxes = isset($app_data['boxes']) ? $app_data['boxes'] : array(); if (empty($boxes)) return $content; $insertions = array(); foreach ($boxes as $box) { if (empty($box['enableInsert'])) continue; if (is_single() && empty($box['showOnPosts'])) continue; if (is_page() && empty($box['showOnPages'])) continue; if (!empty($box['specificIds'])) { $ids = array_map('trim', explode(',', $box['specificIds'])); $current_id = (string) get_the_ID(); if (!in_array($current_id, $ids)) { continue; } } $para_num = isset($box['insertPara']) ? (int) $box['insertPara'] : 1; $box_html = $this->generate_box_html($box); if (!isset($insertions[$para_num])) { $insertions[$para_num] = ''; } $insertions[$para_num] .= $box_html; } if (empty($insertions)) return $content; $paragraphs = explode('</p>', $content); $new_content = ''; if (isset($insertions[0])) { $new_content .= $insertions[0]; } foreach ($paragraphs as $index => $paragraph) { if (trim($paragraph)) { $new_content .= $paragraph . '</p>'; } $current_para = $index + 1; if (isset($insertions[$current_para])) { $new_content .= $insertions[$current_para]; } } return $new_content; } public function render_admin_page() { $default_data = array( 'folders' => array('Default'), 'boxes' => array(), 'licenseTier' => 'free' ); $app_data = get_option('skjbi_app_data', $default_data); if (!is_array($app_data)) $app_data = $default_data; if (!isset($app_data['v211_stable'])) { if (empty($app_data['boxes'])) { $app_data['boxes'][] = array( 'id' => 'box_' . time(), 'name' => 'Demo Box', 'folder' => 'Default', 'enableInsert' => false, 'insertPara' => '1', 'showOnPosts' => true, 'showOnPages' => false, 'title' => 'Important Announcement!', 'body' => "This is a highlighted section placed near the top of your page.\n\nIt grabs attention instantly.", 'useBuiltInBtn' => true, 'btnText' => 'Click Here', 'btnBg' => '#0073aa', 'btnColor' => '#ffffff', 'btnBgType' => 'color', 'btnHoverBg' => '#005177', 'bgType' => 'color', 'bgColor' => '#f0f6fc', 'boxBorderC' => '#0073aa', 'boxMaxW' => '800', 'boxMinH' => '0', 'boxFullW' => false, 'btnRadius' => '4', 'boxRadius' => '4', 'titleFontFamily' => 'inherit', 'bodyFontFamily' => 'inherit', 'btnFontFamily' => 'inherit', 'titleFontStyle' => 'bold', 'bodyFontStyle' => 'normal', 'btnFontStyle' => 'bold', 'btnEffect' => 'none', 'btnAnimation' => 'none', 'enableHoverBg' => true, 'btnHoverColor' => '#ffffff' ); } $app_data['v211_stable'] = true; update_option('skjbi_app_data', $app_data); } if (!isset($app_data['licenseTier'])) { $app_data['licenseTier'] = 'free'; } $nonce = wp_create_nonce('skjbi_nonce'); $json_data = wp_json_encode($app_data); $tier_display = 'Free Version'; if ($app_data['licenseTier'] === 'pro') $tier_display = 'Pro Version'; if ($app_data['licenseTier'] === 'business') $tier_display = 'Business Version'; ob_start(); ?> <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> <div class="wrap sbm-wrap tier-<?php echo esc_attr($app_data['licenseTier']); ?>" id="jbi-main-wrap"> <div class="jbi-plugin-header"> <div class="jbi-header-left"> <div class="jbi-header-icon">&#128230;</div> <div class="jbi-header-text"> <h1>Johnson Box Inserter</h1> <span class="jbi-header-subtitle">by SkillfulPlugins &nbsp;&middot;&nbsp; <span id="header-tier-display"><?php echo esc_html($tier_display); ?></span></span> </div> </div> <div class="jbi-header-right"> <button id="jbi-dark-toggle" class="jbi-dark-btn" title="Toggle dark mode"> <span class="jbi-sun-icon">&#9728;</span> <span class="jbi-moon-icon">&#9790;</span> </button> </div> </div> <div class="sbm-grid"> <div class="sbm-left-column"> <div class="sbm-panel"> <h2>1. Box Design Studio</h2> <div class="jbi-section"> <h3 class="jbi-section-title">A. Display Rules</h3> <label class="jbi-checkbox-label" style="font-weight:bold; color:#00a32a;"> <input type="checkbox" id="jbi-enable-insert"> Enable Auto-Insertion </label> <div class="sbm-control-group" style="margin-top:15px;"> <label>Insert after paragraph number <span id="val-insert-para">1</span></label> <input type="range" id="jbi-insert-para" min="0" max="20" value="1"> <small style="color:#666;">(Set to 0 to place at the very top of the post)</small> </div> <div class="sbm-control-group"> <label>Show on Post Types:</label> <label class="jbi-checkbox-label"><input type="checkbox" id="jbi-show-posts"> Posts</label> <label class="jbi-checkbox-label"><input type="checkbox" id="jbi-show-pages"> Pages</label> </div> <div class="sbm-control-group sbm-pro-feature"> <label>Limit to specific Post/Page IDs <span class="sbm-pro-badge">⭐ PRO</span></label> <input type="text" id="jbi-specific-ids" placeholder="e.g. 12, 45, 99"> <small style="color:#666;">Comma separated list. Leave blank to show on all.</small> </div> </div> <div class="jbi-section"> <h3 class="jbi-section-title">B. Box Content</h3> <div class="sbm-control-group"> <label>Headline / Title</label> <input type="text" id="jbi-title" value="What Is A Johnson Box?"> </div> <div class="sbm-control-group"> <label>Body Content (Accepts HTML)</label> <textarea id="jbi-body" rows="6" style="width:100%; border:1px solid #8c8f94; border-radius:4px; padding:8px;"></textarea> </div> </div> <div class="jbi-section"> <h3 class="jbi-section-title">C. Button Integration</h3> <label class="jbi-checkbox-label" style="font-weight:bold; margin-bottom:15px; padding-bottom:15px; border-bottom:1px solid #eee;"> <input type="checkbox" id="jbi-use-builtin"> Use Built-In Button Designer </label> <div id="jbi-custom-shortcode-wrap" style="display:none; background:#f0f6fc; padding:15px; border-radius:6px; border:1px solid #c8d7e1;"> <label style="font-weight:bold; display:block; margin-bottom:5px;">Paste Custom Shortcode</label> <input type="text" id="jbi-custom-shortcode" placeholder='[sbm_button id="btn_12345"]' style="font-family:monospace;"> <small style="color:#666; display:block; margin-top:5px;">Perfect for injecting your Simple Button Maker codes.</small> </div> <div id="jbi-builtin-btn-wrap"> <div style="display:flex; gap:10px;"> <div class="sbm-control-group" style="flex:1;"> <label>Button Text</label> <input type="text" id="jbi-btn-text" value="Click To Buy"> </div> <div class="sbm-control-group" style="flex:1;"> <label>Button Link</label> <input type="url" id="jbi-btn-url" placeholder="https://..."> </div> </div> <div style="display:flex; gap:15px; margin-bottom:15px;"> <label class="jbi-checkbox-label"><input type="checkbox" id="jbi-btn-new-tab"> New Tab</label> <label class="jbi-checkbox-label"><input type="checkbox" id="jbi-btn-nofollow"> NoFollow</label> </div> <div class="sbm-control-group sbm-pro-feature" style="margin-top: 15px;"> <label>Button Icon <span class="sbm-pro-badge">⭐ PRO</span></label> <select id="jbi-btn-icon-class" style="width: 100%;"> <option value="">None</option> <optgroup label="UI & Web"> <option value="fas fa-home">Home</option> <option value="fas fa-search">Search</option> <option value="fas fa-cog">Settings / Cog</option> <option value="fas fa-user">User / Profile</option> <option value="fas fa-bars">Menu / Hamburger</option> <option value="fas fa-bell">Bell / Notification</option> <option value="fas fa-star">Star</option> <option value="fas fa-heart">Heart</option> <option value="fas fa-camera">Camera</option> </optgroup> <optgroup label="Arrows"> <option value="fas fa-arrow-right">Arrow Right</option> <option value="fas fa-arrow-left">Arrow Left</option> <option value="fas fa-arrow-up">Arrow Up</option> <option value="fas fa-arrow-down">Arrow Down</option> <option value="fas fa-chevron-right">Chevron Right</option> <option value="fas fa-chevron-left">Chevron Left</option> <option value="fas fa-download">Download</option> <option value="fas fa-external-link-alt">External Link</option> </optgroup> <optgroup label="Commerce"> <option value="fas fa-shopping-cart">Cart</option> <option value="fas fa-shopping-bag">Bag</option> <option value="fas fa-credit-card">Credit Card</option> <option value="fas fa-tag">Tag / Sale</option> <option value="fas fa-receipt">Receipt</option> </optgroup> <optgroup label="Communication & Social"> <option value="fas fa-envelope">Email</option> <option value="fas fa-phone">Phone</option> <option value="fas fa-comment">Chat</option> <option value="fas fa-paper-plane">Send</option> <option value="fab fa-whatsapp">WhatsApp</option> <option value="fab fa-facebook">Facebook</option> <option value="fab fa-twitter">Twitter / X</option> <option value="fab fa-instagram">Instagram</option> <option value="fab fa-youtube">YouTube</option> <option value="fab fa-linkedin">LinkedIn</option> </optgroup> </select> </div> <div id="jbi-btn-icon-settings-group" class="sbm-pro-feature" style="display:none; background: #f6f7f7; padding: 10px; border-radius: 4px; border: 1px solid #ddd; margin-bottom: 15px;"> <div class="sbm-control-group"> <label>Icon Position</label> <select id="jbi-btn-icon-position" style="width: 100%;"> <option value="left">Left of Text</option> <option value="right">Right of Text</option> <option value="top">Top (Above Text)</option> <option value="bottom">Bottom (Below Text)</option> <option value="icon_only">Icon Only (Hide Text)</option> </select> </div> <div class="sbm-control-group" style="margin-bottom: 0;"> <label>Icon Gap <span id="val-btn-icon-gap">8px</span></label> <input type="range" id="jbi-btn-icon-gap" min="0" max="50" value="8"> </div> </div> <div style="display:grid; grid-template-columns:1fr 1fr; gap:15px; margin-top: 15px;"> <div class="sbm-control-group"> <label>Button Font</label> <select id="jbi-btn-font-family" style="width: 100%;"> <option value="inherit">Inherit from Box</option> <optgroup label="Popular"> <option value="Roboto">Roboto</option> <option value="Open Sans">Open Sans</option> <option value="Montserrat">Montserrat</option> <option value="Oswald">Oswald</option> </optgroup> <optgroup label="Handwriting"> <option value="Caveat">Caveat</option> <option value="Pacifico">Pacifico</option> <option value="Dancing Script">Dancing Script</option> </optgroup> <optgroup label="Marker Pen / Wacky"> <option value="Permanent Marker">Permanent Marker</option> <option value="Bangers">Bangers</option> <option value="Creepster">Creepster</option> <option value="Comic Neue">Comic Sans Style</option> </optgroup> </select> </div> <div class="sbm-control-group"> <label>Font Style</label> <select id="jbi-btn-font-style" style="width: 100%;"> <option value="normal">Normal</option> <option value="bold" selected>Bold</option> <option value="italic">Italic</option> <option value="bold-italic">Bold Italic</option> </select> </div> </div> <div class="sbm-control-group"> <label>Text Size <span id="val-btn-size">16px</span></label> <input type="range" id="jbi-btn-size" min="10" max="40" value="16"> </div> <div class="sbm-control-group sbm-pro-feature" style="margin-top: 20px;"> <label>Button Background Style <span class="sbm-pro-badge">⭐ PRO</span></label> <select id="jbi-btn-bg-type" style="width:100%;"> <option value="color">Solid Color</option> <option value="gradient">Gradient</option> </select> </div> <div class="sbm-control-group" id="jbi-btn-bg-color-wrap"> <label id="lbl-jbi-btn-bg">Background Color</label> <input type="color" id="jbi-btn-bg" value="#ff9900" style="width:100%; height:40px; border:none; padding:0;"> </div> <div id="jbi-btn-gradient-controls" class="sbm-pro-feature" style="display:none; background: #f6f7f7; padding: 10px; border-radius: 4px; border: 1px solid #ddd; margin-bottom: 15px;"> <div class="sbm-control-group"> <label>Secondary Color</label> <input type="color" id="jbi-btn-bg2" value="#e68a00" style="width:100%; height:40px; border:none; padding:0;"> </div> <div class="sbm-control-group"> <label>Angle <span id="val-btn-bg-angle">90°</span></label> <input type="range" id="jbi-btn-bg-angle" min="0" max="360" value="90"> </div> <div class="sbm-control-group" style="margin-bottom: 0;"> <label>Blend Position <span id="val-btn-bg-pos">50%</span></label> <input type="range" id="jbi-btn-bg-pos" min="0" max="100" value="50"> </div> </div> <div class="sbm-control-group"> <label>Text Color</label> <input type="color" id="jbi-btn-color" value="#000000" style="width:100%; height:40px; border:none; padding:0;"> </div> <div class="sbm-control-group"> <label>Design Effect (Shadows) <span class="sbm-pro-badge" id="badge-effect">⭐ PRO</span></label> <select id="jbi-btn-effect" style="width: 100%;"> <option value="none">Flat (None)</option> <option value="shadow">Soft Drop Shadow</option> <option value="retro">Retro Brutalist</option> <optgroup label="Pro Effects" class="sbm-pro-optgroup"> <option value="glossy">Glossy Shine</option> <option value="3d">3D Depth</option> <option value="pressed">Pressed (Inset)</option> <option value="floating">Floating</option> <option value="glow-dark">Neon Glow</option> <option value="custom">Advanced Dual Shadows</option> </optgroup> </select> </div> <div id="jbi-custom-shadow-wrapper" class="sbm-pro-feature" style="display:none; background: #f0f6fc; padding: 15px; border-radius: 6px; border: 1px solid #c8d7e1; margin-bottom: 15px;"> <h4 style="margin:0 0 10px 0; font-size:12px; text-transform:uppercase;">Layer 1: Base Shadow</h4> <div style="display:grid; grid-template-columns: 1fr 1fr; gap: 10px;"> <div class="sbm-control-group" style="margin-bottom:0;"> <label style="font-size:11px;">X Offset <span id="val-jbi-s1x">0px</span></label> <input type="range" id="jbi-btn-s1-x" min="-40" max="40" value="0"> </div> <div class="sbm-control-group" style="margin-bottom:0;"> <label style="font-size:11px;">Y Offset <span id="val-jbi-s1y">5px</span></label> <input type="range" id="jbi-btn-s1-y" min="-40" max="40" value="5"> </div> <div class="sbm-control-group" style="margin-bottom:0;"> <label style="font-size:11px;">Blur <span id="val-jbi-s1b">15px</span></label> <input type="range" id="jbi-btn-s1-blur" min="0" max="100" value="15"> </div> <div class="sbm-control-group" style="margin-bottom:0;"> <label style="font-size:11px;">Spread <span id="val-jbi-s1s">0px</span></label> <input type="range" id="jbi-btn-s1-spread" min="-30" max="30" value="0"> </div> </div> <div style="display:flex; gap:10px; margin-top:10px; align-items:center;"> <input type="color" id="jbi-btn-s1-color" value="#000000" style="height:30px; width:40px; border:none; padding:0;"> <div style="flex:1;"> <label style="font-size:11px;">Opacity <span id="val-jbi-s1o">40%</span></label> <input type="range" id="jbi-btn-s1-opacity" min="0" max="100" value="40" style="width:100%;"> </div> <label style="font-size:11px; display:inline-flex; align-items:center; gap: 5px;"><input type="checkbox" id="jbi-btn-s1-inset"> Inset</label> </div> <label style="display:block; margin-top:20px; border-top:1px solid #c8d7e1; padding-top:15px; font-weight:700;"> <input type="checkbox" id="jbi-btn-enable-s2"> Add Layer 2 (Glow / Accent) </label> <div id="jbi-shadow2-controls" style="display:none; margin-top:10px;"> <div style="display:grid; grid-template-columns: 1fr 1fr; gap: 10px;"> <div class="sbm-control-group" style="margin-bottom:0;"> <label style="font-size:11px;">X Offset <span id="val-jbi-s2x">0px</span></label> <input type="range" id="jbi-btn-s2-x" min="-40" max="40" value="0"> </div> <div class="sbm-control-group" style="margin-bottom:0;"> <label style="font-size:11px;">Y Offset <span id="val-jbi-s2y">0px</span></label> <input type="range" id="jbi-btn-s2-y" min="-40" max="40" value="0"> </div> <div class="sbm-control-group" style="margin-bottom:0;"> <label style="font-size:11px;">Blur <span id="val-jbi-s2b">15px</span></label> <input type="range" id="jbi-btn-s2-blur" min="0" max="100" value="15"> </div> <div class="sbm-control-group" style="margin-bottom:0;"> <label style="font-size:11px;">Spread <span id="val-jbi-s2s">0px</span></label> <input type="range" id="jbi-btn-s2-spread" min="-30" max="30" value="0"> </div> </div> <div style="display:flex; gap:10px; margin-top:10px; align-items:center;"> <input type="color" id="jbi-btn-s2-color" value="#ffffff" style="height:30px; width:40px; border:none; padding:0;"> <div style="flex:1;"> <label style="font-size:11px;">Opacity <span id="val-jbi-s2o">50%</span></label> <input type="range" id="jbi-btn-s2-opacity" min="0" max="100" value="50" style="width:100%;"> </div> <label style="font-size:11px; display:inline-flex; align-items:center; gap:5px;"><input type="checkbox" id="jbi-btn-s2-inset"> Inset</label> </div> </div> </div> <div class="sbm-control-group sbm-pro-feature"> <label>Continuous Animation (Bling) <span class="sbm-pro-badge">⭐ PRO</span></label> <select id="jbi-btn-animation" style="width: 100%;"> <option value="none">Static (None)</option> <option value="pulse">Pulse</option> <option value="bounce">Bounce</option> <option value="shake">Shake</option> <option value="wobble">Wobble</option> <option value="shine">Sheen (Sparkle Sweep)</option> <option value="halo">Glowing Halo (Pulse)</option> <option value="float">Float (Hover)</option> </select> </div> <div class="sbm-control-group sbm-pro-feature" style="background: #f9f9f9; padding: 15px; border-radius: 6px; border: 1px dashed #bbb; margin-top: 25px;"> <label style="font-size: 14px; margin-bottom: 10px; border-bottom: 1px solid #ddd; padding-bottom: 5px;"><strong>Hover Interactions</strong> <span class="sbm-pro-badge">⭐ PRO</span></label> <label style="font-weight: normal; margin-bottom: 5px; display: flex; align-items: center; gap: 5px;"> <input type="checkbox" id="jbi-btn-enable-hover-bg"> Change Background </label> <input type="color" id="jbi-btn-hover-bg" value="#005177" style="display:none; margin-bottom: 10px; height:30px; border:none; padding:0;"> <label style="font-weight: normal; margin-bottom: 5px; display: flex; align-items: center; gap: 5px;"> <input type="checkbox" id="jbi-btn-enable-hover-color"> Change Text Color </label> <input type="color" id="jbi-btn-hover-color" value="#ffffff" style="display:none; margin-bottom: 10px; height:30px; border:none; padding:0;"> <label style="margin-top: 10px;">Hover Animation</label> <select id="jbi-btn-hover-animation" style="width: 100%;"> <option value="none">None</option> <option value="scale">Scale Up</option> <option value="lift">Lift Up</option> <option value="push">Push Down</option> </select> </div> <div style="display:grid; grid-template-columns:1fr 1fr; gap:15px; margin-top: 20px;"> <div class="sbm-control-group"> <label>Border Width <span id="val-btn-border-w">0px</span></label> <input type="range" id="jbi-btn-border-w" min="0" max="10" value="0"> </div> <div class="sbm-control-group"> <label>Border Color</label> <input type="color" id="jbi-btn-border-c" value="#000000" style="width:100%; height:30px; border:none; padding:0;"> </div> </div> <div class="sbm-control-group"> <label>Rounded Corners <span id="val-btn-radius">50px</span></label> <input type="range" id="jbi-btn-radius" min="0" max="50" value="50"> </div> <div style="display:grid; grid-template-columns:1fr 1fr; gap:15px;"> <div class="sbm-control-group"> <label>Pad X (Width) <span id="val-btn-pad-x">30px</span></label> <input type="range" id="jbi-btn-pad-x" min="10" max="100" value="30"> </div> <div class="sbm-control-group"> <label>Pad Y (Height) <span id="val-btn-pad-y">15px</span></label> <input type="range" id="jbi-btn-pad-y" min="5" max="40" value="15"> </div> </div> <div class="sbm-control-group"> <label>Button Position</label> <select id="jbi-btn-pos" style="width:100%;"> <option value="left">Align Left</option> <option value="center" selected>Align Center</option> <option value="right">Align Right</option> </select> </div> <div class="sbm-control-group"> <label>Space Above Button <span id="val-btn-space">20px</span></label> <input type="range" id="jbi-btn-space" min="0" max="100" value="20"> </div> </div> </div> <div class="jbi-section"> <h3 class="jbi-section-title">D. Theme & Typography</h3> <div class="sbm-control-group sbm-pro-feature"> <label>Background Style <span class="sbm-pro-badge">⭐ PRO</span></label> <select id="jbi-bg-type" style="width:100%;"> <option value="color">Solid Color</option> <option value="gradient">Gradient</option> <option value="image">Image Background</option> </select> </div> <div class="sbm-control-group" id="jbi-bg-color-wrap"> <label id="lbl-jbi-bg-color">Background Color</label> <input type="color" id="jbi-bg-color" value="#fffbcc" style="width:100%; height:40px; border:none; padding:0;"> </div> <div id="jbi-gradient-controls" class="sbm-pro-feature" style="display:none; background: #f6f7f7; padding: 10px; border-radius: 4px; border: 1px solid #ddd; margin-bottom: 15px;"> <div class="sbm-control-group"> <label>Secondary Color</label> <input type="color" id="jbi-bg2" value="#ffffff" style="width:100%; height:40px; border:none; padding:0;"> </div> <div class="sbm-control-group"> <label>Angle <span id="val-bg-angle">90°</span></label> <input type="range" id="jbi-bg-angle" min="0" max="360" value="90"> </div> <div class="sbm-control-group" style="margin-bottom: 0;"> <label>Blend Position <span id="val-bg-pos">50%</span></label> <input type="range" id="jbi-bg-pos" min="0" max="100" value="50"> </div> </div> <div class="sbm-control-group sbm-pro-feature" id="jbi-bg-image-wrap" style="display:none;"> <label>Background Image URL</label> <div style="display:flex; gap:5px;"> <input type="text" id="jbi-bg-image-url" placeholder="https://..." style="flex:1;"> <button type="button" class="button" id="btn-upload-bg">Library</button> </div> </div> <div style="display:grid; grid-template-columns:1fr 1fr; gap:15px; margin-top:20px; border-top:1px solid #eee; padding-top:15px;"> <div class="sbm-control-group"> <label>Headline Font</label> <select id="jbi-title-font-family" style="width: 100%;"> <option value="inherit">Default Site Font</option> <optgroup label="Popular"> <option value="Roboto">Roboto</option> <option value="Open Sans">Open Sans</option> <option value="Montserrat">Montserrat</option> <option value="Oswald">Oswald</option> </optgroup> <optgroup label="Handwriting"> <option value="Caveat">Caveat</option> <option value="Pacifico">Pacifico</option> <option value="Dancing Script">Dancing Script</option> </optgroup> <optgroup label="Marker Pen / Wacky"> <option value="Permanent Marker">Permanent Marker</option> <option value="Bangers">Bangers</option> <option value="Creepster">Creepster</option> <option value="Comic Neue">Comic Sans Style</option> </optgroup> </select> </div> <div class="sbm-control-group"> <label>Headline Style</label> <select id="jbi-title-font-style" style="width: 100%;"> <option value="normal">Normal</option> <option value="bold" selected>Bold</option> <option value="italic">Italic</option> <option value="bold-italic">Bold Italic</option> </select> </div> </div> <div style="display:grid; grid-template-columns:1fr 1fr; gap:15px;"> <div class="sbm-control-group"> <label>Headline Size <span id="val-title-size">24px</span></label> <input type="range" id="jbi-title-size" min="12" max="60" value="24"> </div> <div class="sbm-control-group"> <label>Headline Color</label> <input type="color" id="jbi-title-color" value="#cc0000" style="width:100%; height:30px; border:none; padding:0;"> </div> </div> <div style="display:grid; grid-template-columns:1fr 1fr; gap:15px; margin-top: 15px; border-top:1px dashed #eee; padding-top:15px;"> <div class="sbm-control-group"> <label>Body Font</label> <select id="jbi-body-font-family" style="width: 100%;"> <option value="inherit">Default Site Font</option> <optgroup label="Popular"> <option value="Roboto">Roboto</option> <option value="Open Sans">Open Sans</option> <option value="Montserrat">Montserrat</option> <option value="Oswald">Oswald</option> </optgroup> <optgroup label="Handwriting"> <option value="Caveat">Caveat</option> <option value="Pacifico">Pacifico</option> <option value="Dancing Script">Dancing Script</option> </optgroup> <optgroup label="Marker Pen / Wacky"> <option value="Permanent Marker">Permanent Marker</option> <option value="Bangers">Bangers</option> <option value="Creepster">Creepster</option> <option value="Comic Neue">Comic Sans Style</option> </optgroup> </select> </div> <div class="sbm-control-group"> <label>Body Style</label> <select id="jbi-body-font-style" style="width: 100%;"> <option value="normal" selected>Normal</option> <option value="bold">Bold</option> <option value="italic">Italic</option> <option value="bold-italic">Bold Italic</option> </select> </div> </div> <div style="display:grid; grid-template-columns:1fr 1fr; gap:15px;"> <div class="sbm-control-group"> <label>Body Size <span id="val-body-size">16px</span></label> <input type="range" id="jbi-body-size" min="10" max="30" value="16"> </div> <div class="sbm-control-group"> <label>Body Color</label> <input type="color" id="jbi-body-color" value="#333333" style="width:100%; height:30px; border:none; padding:0;"> </div> </div> <div class="sbm-control-group"> <label>Text Alignment</label> <select id="jbi-text-align" style="width:100%;"> <option value="left">Left</option> <option value="center">Center</option> <option value="right">Right</option> <option value="justify">Justify</option> </select> </div> </div> <div class="jbi-section"> <h3 class="jbi-section-title">E. Box Structure</h3> <div class="sbm-control-group"> <label>Box Max Width <span id="val-box-max-w">800px</span></label> <input type="range" id="jbi-box-max-w" min="300" max="1200" value="800" step="10"> <label class="jbi-checkbox-label" style="margin-top:5px;"><input type="checkbox" id="jbi-box-full-w"> Override: Force 100% Full Width</label> </div> <div class="sbm-control-group"> <label>Box Minimum Height <span id="val-box-min-h">0px (Auto)</span></label> <input type="range" id="jbi-box-min-h" min="0" max="1000" value="0" step="10"> </div> <div class="sbm-control-group"> <label>Inner Padding <span id="val-box-pad">30px</span></label> <input type="range" id="jbi-box-pad" min="0" max="100" value="30"> </div> <div style="display:grid; grid-template-columns:1fr 1fr; gap:15px;"> <div class="sbm-control-group"> <label>Border Width <span id="val-box-border-w">3px</span></label> <input type="range" id="jbi-box-border-w" min="0" max="20" value="3"> </div> <div class="sbm-control-group"> <label>Border Color</label> <input type="color" id="jbi-box-border-c" value="#cc0000" style="width:100%; height:30px; border:none; padding:0;"> </div> </div> <div class="sbm-control-group"> <label>Rounded Corners <span id="val-box-radius">8px</span></label> <input type="range" id="jbi-box-radius" min="0" max="50" value="8"> </div> <div class="sbm-control-group sbm-pro-feature"> <label>Custom CSS (Optional) <span class="sbm-pro-badge">⭐ PRO</span></label> <textarea id="jbi-custom-css" rows="3" placeholder="box-shadow: 0px 10px 20px rgba(0,0,0,0.1);" style="width:100%; border:1px solid #8c8f94; border-radius:4px; padding:8px; font-family:monospace;"></textarea> </div> </div> </div> <div class="sbm-panel"> <h2>3. Save to Database</h2> <input type="hidden" id="current-loaded-box-id" value=""> <div class="sbm-control-group"> <label style="font-size:12px; text-transform:uppercase; color:#666;">Box Name</label> <input type="text" id="save-box-name" placeholder="e.g. Sales Page Box" style="padding:10px;"> </div> <div class="sbm-control-group"> <label style="font-size:12px; text-transform:uppercase; color:#666;">Folder</label> <select id="save-folder-select" style="padding:8px;"></select> </div> <div style="display:flex; gap:10px; margin-top:15px;"> <button type="button" class="button button-primary" id="save-btn" style="flex:2; height:40px; font-weight:bold;">Save Data</button> <button type="button" class="button" id="btn-reset-form" style="flex:1; height:40px;">Reset Form</button> </div> <span class="sbm-success-msg" id="save-msg" style="display:none; text-align:center; margin-top:15px;">Successfully Saved!</span> </div> <div class="sbm-panel"> <h2>4. Manual Integration</h2> <p class="description" style="margin-top: -10px; margin-bottom: 15px;">If Auto-Insertion is off, paste this specific box anywhere.</p> <div class="sbm-control-group"> <label style="font-size:12px; text-transform:uppercase;">Shortcode</label> <div style="display:flex; align-items:center; gap:5px; background:#f0f0f1; padding:8px; border-radius:4px; border:1px solid #dcdcde;"> <code id="display-active-shortcode" style="flex:1; font-size:14px; background:transparent; padding:0;">[skjbi_box]</code> <button type="button" class="button button-small" onclick="navigator.clipboard.writeText(document.getElementById('display-active-shortcode').innerText); alert('Copied!');">Copy</button> </div> </div> </div> <div class="sbm-panel"> <h2>5. Box Library</h2> <p class="description" style="margin-top: -10px; margin-bottom: 15px;">Drag handles (&#8942;&#8942;) to reorder folders and boxes.</p> <div style="margin-bottom:20px; padding-bottom:15px; border-bottom:1px solid #eee; display:flex; gap:10px;"> <input type="text" id="new-folder-name" placeholder="New Folder Name" style="flex:1;"> <button type="button" class="button" id="create-folder-btn">Create</button> </div> <div id="library-container"> </div> </div> <div class="sbm-panel"> <h2>6. Backup & Pro Export</h2> <p class="description" style="margin-top: -10px; margin-bottom: 15px;">Transfer boxes across sites or sell them.</p> <div style="display: grid; grid-template-columns: 1fr 1fr; gap: 8px;"> <button type="button" class="button" id="btn-export-json" title="Download raw JSON data">Export JSON</button> <button type="button" class="button sbm-pro-action-btn" id="btn-export-zip" title="Download all boxes as a ZIP file">Export ZIP</button> <button type="button" class="button button-primary" id="btn-import" title="Upload a ZIP or JSON backup file" style="grid-column: span 2;">Import Backup File</button> <input type="file" id="import-file-input" accept=".json, .zip, application/zip, application/json" style="display:none;"> </div> <div style="margin-top:25px; padding-top:15px; border-top:1px solid #eee; text-align:center;"> <p id="pro-status-text" style="font-weight:bold; margin-bottom:10px; font-size:13px;"></p> <button type="button" class="button button-small" id="btn-toggle-pro">Manage License</button> </div> </div> </div> <div class="sbm-right-column"> <div class="sbm-panel sbm-sticky-panel"> <h2>Live Preview</h2> <p style="font-size:12px; color:#666; margin-top:-5px; margin-bottom:15px;">Preview updates instantly. Note: External shortcodes will render as raw text in this preview.</p> <div id="preview-container"> <div id="jbi-live-preview"> </div> </div> </div> </div> </div> </div> <style> .jbi-plugin-header{display:flex;align-items:center;justify-content:space-between;background:linear-gradient(135deg,#a760e1 0%,#7057c1 100%);border-radius:10px;padding:18px 24px;margin-bottom:20px;margin-top:10px;box-shadow:0 4px 15px rgba(112,87,193,0.3);} .jbi-header-left{display:flex;align-items:center;gap:14px;} .jbi-header-icon{font-size:32px;line-height:1;} .jbi-header-text h1{color:#fff;font-size:22px;font-weight:700;margin:0;padding:0;line-height:1.2;text-shadow:0 1px 3px rgba(0,0,0,0.2);} .jbi-header-subtitle{color:rgba(255,255,255,0.85);font-size:13px;} .jbi-header-right{display:flex;align-items:center;gap:10px;} .jbi-dark-btn{background:rgba(255,255,255,0.2);border:1px solid rgba(255,255,255,0.4);border-radius:20px;padding:6px 14px;cursor:pointer;font-size:16px;transition:background 0.2s;color:#fff;display:flex;align-items:center;gap:6px;} .jbi-dark-btn:hover{background:rgba(255,255,255,0.3);} .jbi-dark-btn .jbi-moon-icon{display:none;} .jbi-dark-btn .jbi-sun-icon{display:inline;} .jbi-dark-mode .jbi-dark-btn .jbi-moon-icon{display:inline;} .jbi-dark-mode .jbi-dark-btn .jbi-sun-icon{display:none;} .sbm-wrap{max-width:1600px;margin-top:10px;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif;} .sbm-grid{display:grid;grid-template-columns:440px 1fr;gap:20px;align-items:start;} .sbm-left-column{display:flex;flex-direction:column;gap:16px;} .sbm-right-column{position:-webkit-sticky;position:sticky;top:40px;} .sbm-sticky-panel{margin-bottom:0;display:flex;flex-direction:column;} @media(max-width:1100px){.sbm-grid{grid-template-columns:1fr;}.sbm-right-column{position:static;}} .sbm-panel{background:#fff;padding:20px 22px;border:1px solid #e0e2e5;box-shadow:0 2px 8px rgba(0,0,0,0.06);border-radius:10px;} .sbm-panel h2{margin-top:0;padding-bottom:12px;border-bottom:2px solid #f0f0f1;font-size:16px;font-weight:700;color:#1d2327;display:flex;align-items:center;gap:8px;} .jbi-section{margin-bottom:20px;padding-bottom:18px;border-bottom:1px solid #f0f0f1;} .jbi-section:last-child{border-bottom:none;margin-bottom:0;padding-bottom:0;} .jbi-section-title{font-size:11px;font-weight:700;color:#7057c1;margin-top:0;margin-bottom:14px;text-transform:uppercase;letter-spacing:1px;display:flex;align-items:center;gap:6px;} .jbi-section-title::before{content:'';display:inline-block;width:3px;height:14px;background:#a760e1;border-radius:2px;} .sbm-control-group{margin-bottom:14px;} .sbm-control-group label{display:block;font-weight:600;margin-bottom:5px;color:#3c434a;font-size:13px;} .sbm-control-group label span.sbm-pro-badge{background:linear-gradient(135deg,#a760e1,#7057c1);color:#fff;font-size:9px;padding:2px 6px;border-radius:10px;float:right;font-weight:bold;letter-spacing:0.5px;} .sbm-control-group label span:not(.sbm-pro-badge){font-weight:normal;color:#646970;float:right;font-size:12px;} .sbm-control-group input[type="range"]{width:100%;margin:6px 0;accent-color:#7057c1;} .sbm-control-group input[type="text"],.sbm-control-group input[type="url"],.sbm-control-group select{width:100%;padding:7px 10px;border:1px solid #dcdcde;border-radius:6px;box-sizing:border-box;font-size:13px;transition:border-color 0.2s;} .sbm-control-group input[type="text"]:focus,.sbm-control-group input[type="url"]:focus,.sbm-control-group select:focus{border-color:#7057c1;outline:none;box-shadow:0 0 0 2px rgba(112,87,193,0.15);} .jbi-checkbox-label{display:inline-flex;align-items:center;gap:6px;font-weight:normal !important;cursor:pointer;} .jbi-checkbox-label input[type="checkbox"]{accent-color:#7057c1;width:15px;height:15px;} .tier-free .sbm-pro-feature{opacity:0.6;pointer-events:none;} .tier-free .sbm-pro-action-btn{opacity:0.6;cursor:not-allowed !important;pointer-events:none;} .tier-free .sbm-sell{background:#f0f0f1 !important;color:#a7aaad !important;border-color:#dcdcde !important;cursor:not-allowed;} .tier-pro .sbm-pro-badge,.tier-business .sbm-pro-badge{display:none;} .tier-pro .sbm-sell{background:#f0f0f1 !important;color:#a7aaad !important;border-color:#dcdcde !important;cursor:not-allowed;} .tier-business .sbm-sell{background:linear-gradient(135deg,#a760e1,#7057c1);color:#fff;border-color:#7057c1;cursor:pointer;} .tier-business .sbm-sell:hover{opacity:0.9;} #preview-container{padding:30px 20px;background:#f5f5f7;border-radius:10px;border:1px solid #e0e2e5;background-image:radial-gradient(#dcdcde 1px,transparent 1px);background-size:20px 20px;overflow:auto;display:flex;justify-content:center;align-items:flex-start;height:calc(100vh - 180px);} #jbi-live-preview{width:100%;display:flex;justify-content:center;} .sbm-folder{border:1px solid #e0e2e5;margin-bottom:12px;border-radius:8px;background:#fff;overflow:hidden;box-shadow:0 1px 4px rgba(0,0,0,0.04);} .sbm-folder-header{background:#f8f8fa;padding:11px 15px;font-weight:600;display:flex;flex-wrap:wrap;gap:10px;justify-content:space-between;align-items:center;border-bottom:1px solid transparent;transition:background 0.2s;cursor:pointer;user-select:none;font-size:13px;} .sbm-folder-header:hover{background:#f0f0f4;} .sbm-folder.is-open .sbm-folder-header{border-bottom-color:#e0e2e5;background:#f0f0f4;} .sbm-folder-title{flex:1;display:flex;align-items:center;gap:8px;min-width:120px;} .sbm-toggle-icon{font-size:10px;color:#7057c1;transition:transform 0.2s;width:12px;display:inline-block;text-align:center;} .sbm-folder-actions{display:flex;flex-wrap:wrap;gap:5px;align-items:center;} .sbm-folder-content{padding:12px;display:none;flex-direction:column;gap:8px;min-height:40px;background:#fff;} .sbm-folder.is-open .sbm-folder-content{display:flex;} .sbm-folder.is-open .sbm-toggle-icon{transform:rotate(90deg);} .sbm-saved-btn{display:flex;flex-wrap:wrap;gap:10px;justify-content:space-between;align-items:center;background:#fafafa;border:1px solid #e8e8ea;padding:10px 12px;border-radius:6px;} .sbm-saved-btn:hover{background:#f5f5f8;} .sbm-saved-btn-name{display:flex;flex-direction:column;gap:5px;flex:1;} .sbm-btn-actions{display:flex;flex-wrap:wrap;gap:5px;} .sbm-btn-actions button{font-size:11px;cursor:pointer;padding:3px 9px;border-radius:4px;border:1px solid #dcdcde;background:#fff;color:#7057c1;font-weight:500;} .sbm-btn-actions button:hover{background:#f5f0ff;border-color:#b39ddb;} .sbm-btn-actions .sbm-delete{color:#d63638;border-color:#f8c3c4;} .sbm-btn-actions .sbm-delete:hover{background:#fff5f5;} .sbm-shortcode{font-family:monospace;font-size:11px;background:#f0f0f1;padding:2px 6px;border-radius:3px;border:1px solid #dcdcde;display:inline-block;color:#7057c1;} .sbm-success-msg{color:#00a32a;font-weight:bold;} .sbm-drag-handle{cursor:grab;color:#b39ddb;font-size:18px;margin-right:12px;line-height:1;letter-spacing:-2px;padding:5px;user-select:none;} .sbm-drag-handle:active{cursor:grabbing;} .sbm-folder-drag-handle{margin-right:10px;color:#8c8f94;} .sbm-dragging{opacity:0.4;border:2px dashed #7057c1 !important;background:#f5f0ff !important;} .sbm-drag-over-top{border-top:3px solid #7057c1 !important;} .sbm-drag-over-bottom{border-bottom:3px solid #7057c1 !important;} .sbm-folder-drag-over{background:rgba(112,87,193,0.05);border:2px dashed #7057c1 !important;border-radius:4px;} .jbi-dark-mode .sbm-wrap{color:#e0e0e0;} .jbi-dark-mode .sbm-panel{background:#1e1e2e;border-color:#2d2d44;box-shadow:0 2px 8px rgba(0,0,0,0.3);} .jbi-dark-mode .sbm-panel h2{color:#e0e0f0;border-bottom-color:#2d2d44;} .jbi-dark-mode .jbi-section{border-bottom-color:#2d2d44;} .jbi-dark-mode .jbi-section-title{color:#b39ddb;} .jbi-dark-mode .sbm-control-group label{color:#c8c8e0;} .jbi-dark-mode .sbm-control-group input[type="text"],.jbi-dark-mode .sbm-control-group input[type="url"],.jbi-dark-mode .sbm-control-group select,.jbi-dark-mode textarea{background:#2a2a3e;border-color:#3d3d5c;color:#e0e0f0;} .jbi-dark-mode #preview-container{background:#1a1a2a;border-color:#2d2d44;background-image:radial-gradient(#2d2d44 1px,transparent 1px);} .jbi-dark-mode .sbm-folder{background:#1e1e2e;border-color:#2d2d44;} .jbi-dark-mode .sbm-folder-header{background:#252538;color:#e0e0f0;} .jbi-dark-mode .sbm-folder-header:hover,.jbi-dark-mode .sbm-folder.is-open .sbm-folder-header{background:#2d2d44;border-bottom-color:#3d3d5c;} .jbi-dark-mode .sbm-folder-content{background:#1e1e2e;} .jbi-dark-mode .sbm-saved-btn{background:#252538;border-color:#3d3d5c;} .jbi-dark-mode .sbm-saved-btn:hover{background:#2d2d44;} .jbi-dark-mode .sbm-btn-actions button{background:#2a2a3e;border-color:#3d3d5c;color:#b39ddb;} .jbi-dark-mode .sbm-btn-actions button:hover{background:#3d3d5c;} .jbi-dark-mode .sbm-shortcode{background:#252538;border-color:#3d3d5c;color:#b39ddb;} .jbi-dark-mode .jbi-checkbox-label{color:#c8c8e0;} .jbi-dark-mode #wpcontent{background:#13131f;} </style> <script> jQuery(document).ready(function($) { const getEl = id => document.getElementById(id); const v = el => el ? el.value : ''; const c = el => el ? el.checked : false; const txt = (el, text) => { if(el) el.innerText = text; }; const setVal = (el, val) => { if(el) el.value = val; }; const setCheck = (el, bool) => { if(el) el.checked = bool; }; function hexToRgbaStr(hex, opacity) { let c; if(/^#([A-Fa-f0-9]{3}){1,2}$/.test(hex)){ c = hex.substring(1).split(''); if(c.length== 3) { c = [c[0], c[0], c[1], c[1], c[2], c[2]]; } c = '0x'+c.join(''); return 'rgba('+[(c>>16)&255, (c>>8)&255, c&255].join(',')+','+(opacity/100)+')'; } return `rgba(0,0,0,${opacity/100})`; } function normalizeColor(c) { if (!c) return '#000000'; if (c.length === 4) return '#' + c[1] + c[1] + c[2] + c[2] + c[3] + c[3]; if (c.length === 7) return c; return '#000000'; } let appState = <?php echo empty($json_data) ? '{"folders":["Default"],"boxes":[],"licenseTier":"free"}' : $json_data; ?>; if (!appState || typeof appState !== 'object') { appState = { folders: ['Default'], boxes: [], licenseTier: 'free' }; } if (!appState.folders) appState.folders = ['Default']; if (!appState.boxes) appState.boxes = []; if (!appState.licenseTier) appState.licenseTier = 'free'; let openFolders = ['Default', 'Templates']; const els = { enableInsert: getEl('jbi-enable-insert'), insertPara: getEl('jbi-insert-para'), showPosts: getEl('jbi-show-posts'), showPages: getEl('jbi-show-pages'), specificIds: getEl('jbi-specific-ids'), title: getEl('jbi-title'), body: getEl('jbi-body'), useBuiltin: getEl('jbi-use-builtin'), customShortcodeWrap: getEl('jbi-custom-shortcode-wrap'), customShortcode: getEl('jbi-custom-shortcode'), builtinWrap: getEl('jbi-builtin-btn-wrap'), btnText: getEl('jbi-btn-text'), btnUrl: getEl('jbi-btn-url'), btnNewTab: getEl('jbi-btn-new-tab'), btnNofollow: getEl('jbi-btn-nofollow'), btnIconClass: getEl('jbi-btn-icon-class'), btnIconPosition: getEl('jbi-btn-icon-position'), btnIconGap: getEl('jbi-btn-icon-gap'), btnIconSettingsGroup: getEl('jbi-btn-icon-settings-group'), btnFontFamily: getEl('jbi-btn-font-family'), btnFontStyle: getEl('jbi-btn-font-style'), btnSize: getEl('jbi-btn-size'), btnWeight: getEl('jbi-btn-weight'), btnBgType: getEl('jbi-btn-bg-type'), btnBg: getEl('jbi-btn-bg'), btnColor: getEl('jbi-btn-color'), btnGradientControls: getEl('jbi-btn-gradient-controls'), btnBg2: getEl('jbi-btn-bg2'), btnBgAngle: getEl('jbi-btn-bg-angle'), btnBgPos: getEl('jbi-btn-bg-pos'), lblBtnBgColor: getEl('lbl-jbi-btn-bg'), btnEffect: getEl('jbi-btn-effect'), customShadowWrapper: getEl('jbi-custom-shadow-wrapper'), s1X: getEl('jbi-btn-s1-x'), s1Y: getEl('jbi-btn-s1-y'), s1Blur: getEl('jbi-btn-s1-blur'), s1Spread: getEl('jbi-btn-s1-spread'), s1Color: getEl('jbi-btn-s1-color'), s1Opacity: getEl('jbi-btn-s1-opacity'), s1Inset: getEl('jbi-btn-s1-inset'), enableS2: getEl('jbi-btn-enable-s2'), shadow2Controls: getEl('jbi-shadow2-controls'), s2X: getEl('jbi-btn-s2-x'), s2Y: getEl('jbi-btn-s2-y'), s2Blur: getEl('jbi-btn-s2-blur'), s2Spread: getEl('jbi-btn-s2-spread'), s2Color: getEl('jbi-btn-s2-color'), s2Opacity: getEl('jbi-btn-s2-opacity'), s2Inset: getEl('jbi-btn-s2-inset'), valS1X: getEl('val-jbi-s1x'), valS1Y: getEl('val-jbi-s1y'), valS1B: getEl('val-jbi-s1b'), valS1S: getEl('val-jbi-s1s'), valS1O: getEl('val-jbi-s1o'), valS2X: getEl('val-jbi-s2x'), valS2Y: getEl('val-jbi-s2y'), valS2B: getEl('val-jbi-s2b'), valS2S: getEl('val-jbi-s2s'), valS2O: getEl('val-jbi-s2o'), animation: getEl('jbi-btn-animation'), enableHoverBg: getEl('jbi-btn-enable-hover-bg'), hoverBg: getEl('jbi-btn-hover-bg'), enableHoverColor: getEl('jbi-btn-enable-hover-color'), hoverColor: getEl('jbi-btn-hover-color'), hoverAnimation: getEl('jbi-btn-hover-animation'), btnBorderW: getEl('jbi-btn-border-w'), btnBorderC: getEl('jbi-btn-border-c'), btnRadius: getEl('jbi-btn-radius'), btnPadX: getEl('jbi-btn-pad-x'), btnPadY: getEl('jbi-btn-pad-y'), btnPos: getEl('jbi-btn-pos'), btnSpace: getEl('jbi-btn-space'), bgType: getEl('jbi-bg-type'), bgColorWrap: getEl('jbi-bg-color-wrap'), bgColor: getEl('jbi-bg-color'), bgImageWrap: getEl('jbi-bg-image-wrap'), bgImageUrl: getEl('jbi-bg-image-url'), btnUploadBg: getEl('btn-upload-bg'), gradientControls: getEl('jbi-gradient-controls'), bg2: getEl('jbi-bg2'), bgAngle: getEl('jbi-bg-angle'), bgPos: getEl('jbi-bg-pos'), lblBgColor: getEl('lbl-jbi-bg-color'), fontFamily: getEl('jbi-font-family'), titleFontFamily: getEl('jbi-title-font-family'), bodyFontFamily: getEl('jbi-body-font-family'), titleFontStyle: getEl('jbi-title-font-style'), bodyFontStyle: getEl('jbi-body-font-style'), titleSize: getEl('jbi-title-size'), titleColor: getEl('jbi-title-color'), bodySize: getEl('jbi-body-size'), bodyColor: getEl('jbi-body-color'), textAlign: getEl('jbi-text-align'), boxPad: getEl('jbi-box-pad'), boxBorderW: getEl('jbi-box-border-w'), boxBorderC: getEl('jbi-box-border-c'), boxRadius: getEl('jbi-box-radius'), boxMaxW: getEl('jbi-box-max-w'), boxMinH: getEl('jbi-box-min-h'), boxFullW: getEl('jbi-box-full-w'), customCss: getEl('jbi-custom-css'), previewBox: getEl('jbi-live-preview'), currentLoadedBtnId: getEl('current-loaded-box-id'), saveBtnName: getEl('save-box-name'), saveFolderSelect: getEl('save-folder-select'), saveBtn: getEl('save-btn'), btnResetForm: getEl('btn-reset-form'), saveMsg: getEl('save-msg'), displayActiveShortcode: getEl('display-active-shortcode'), newFolderName: getEl('new-folder-name'), createFolderBtn: getEl('create-folder-btn'), libraryContainer: getEl('library-container'), btnExportJson: getEl('btn-export-json'), btnExportZip: getEl('btn-export-zip'), btnImport: getEl('btn-import'), importFileInput: getEl('import-file-input'), valInsertPara: getEl('val-insert-para'), valBtnSize: getEl('val-btn-size'), valBtnBorderW: getEl('val-btn-border-w'), valBtnRadius: getEl('val-btn-radius'), valBtnPadX: getEl('val-btn-pad-x'), valBtnPadY: getEl('val-btn-pad-y'), valBtnSpace: getEl('val-btn-space'), valTitleSize: getEl('val-title-size'), valBodySize: getEl('val-body-size'), valBoxPad: getEl('val-box-pad'), valBoxBorderW: getEl('val-box-border-w'), valBoxRadius: getEl('val-box-radius'), valBoxMaxW: getEl('val-box-max-w'), valBoxMinH: getEl('val-box-min-h'), valBgAngle: getEl('val-bg-angle'), valBgPos: getEl('val-bg-pos'), valBtnBgAngle: getEl('val-btn-bg-angle'), valBtnBgPos: getEl('val-btn-bg-pos'), valBtnIconGap: getEl('val-btn-icon-gap') }; let dynamicStyle = document.createElement('style'); dynamicStyle.id = 'jbi-dynamic-style'; document.head.appendChild(dynamicStyle); if (els.enableHoverBg) { els.enableHoverBg.addEventListener('change', function(e) { els.hoverBg.style.display = e.target.checked ? 'block' : 'none'; updatePreview(); }); } if (els.enableHoverColor) { els.enableHoverColor.addEventListener('change', function(e) { els.hoverColor.style.display = e.target.checked ? 'block' : 'none'; updatePreview(); }); } if (els.enableS2) { els.enableS2.addEventListener('change', function(e) { els.shadow2Controls.style.display = e.target.checked ? 'block' : 'none'; updatePreview(); }); } function renderProStatus() { const statusEl = document.getElementById('pro-status-text'); const toggleBtn = document.getElementById('btn-toggle-pro'); const tierDisplay = document.getElementById('header-tier-display'); if (!statusEl || !toggleBtn || !tierDisplay) return; document.querySelector('.sbm-wrap').className = `wrap sbm-wrap tier-${appState.licenseTier}`; if (appState.licenseTier === 'business') { tierDisplay.innerText = '(Business Version)'; statusEl.innerHTML = '<span style="color:#00a32a;">Business License Active 🔓</span>'; toggleBtn.innerText = 'Downgrade (Testing)'; if (els.btnEffect) els.btnEffect.querySelector('option[value="custom"]').disabled = false; } else if (appState.licenseTier === 'pro') { tierDisplay.innerText = '(Pro Version)'; statusEl.innerHTML = '<span style="color:#2271b1;">Pro License Active 🔓</span><br><span style="font-size:11px;font-weight:normal;">(Upgrade to Business to sell assets)</span>'; toggleBtn.innerText = 'Upgrade to Business'; if (els.btnEffect) els.btnEffect.querySelector('option[value="custom"]').disabled = false; } else { tierDisplay.innerText = '(Free Version)'; statusEl.innerHTML = '<span style="color:#a7aaad;">Free License Active 🔒</span>'; toggleBtn.innerText = 'Enter Activation Code'; if (els.btnEffect) els.btnEffect.querySelector('option[value="custom"]').disabled = true; if (els.btnEffect && els.btnEffect.value === 'custom') els.btnEffect.value = 'none'; } } const toggleProBtn = document.getElementById('btn-toggle-pro'); if (toggleProBtn) { toggleProBtn.addEventListener('click', function() { if (appState.licenseTier === 'business' || appState.licenseTier === 'pro') { if (confirm("Do you want to deactivate your license on this site?")) { appState.licenseTier = 'free'; syncDataToServer(); renderUI(); renderProStatus(); updatePreview(); } } else { const code = prompt("🔒 TEST MODE LICENSE VERIFICATION\n\nEnter 'PRO-TEST-KEY' for Pro Version.\nEnter 'BIZ-TEST-KEY' for Business Version:"); if (code && code.trim() === 'PRO-TEST-KEY') { appState.licenseTier = 'pro'; alert("🎉 Welcome to Pro!"); syncDataToServer(); renderUI(); renderProStatus(); updatePreview(); } else if (code && code.trim() === 'BIZ-TEST-KEY') { appState.licenseTier = 'business'; alert("💼 Welcome to Business!"); syncDataToServer(); renderUI(); renderProStatus(); updatePreview(); } else if (code !== null && code.trim() !== '') { alert("❌ Invalid Test Code."); } } }); } function updatePreview() { try { if(els.builtinWrap) els.builtinWrap.style.display = c(els.useBuiltin) ? 'block' : 'none'; if(els.customShortcodeWrap) els.customShortcodeWrap.style.display = c(els.useBuiltin) ? 'none' : 'block'; if(els.bgColorWrap) els.bgColorWrap.style.display = v(els.bgType) !== 'image' ? 'block' : 'none'; if(els.bgImageWrap) els.bgImageWrap.style.display = v(els.bgType) === 'image' ? 'block' : 'none'; if(els.gradientControls) els.gradientControls.style.display = v(els.bgType) === 'gradient' ? 'block' : 'none'; if(els.lblBgColor) els.lblBgColor.innerText = v(els.bgType) === 'gradient' ? 'Primary Color' : 'Background Color'; if(els.btnGradientControls) els.btnGradientControls.style.display = v(els.btnBgType) === 'gradient' ? 'block' : 'none'; if(els.lblBtnBgColor) els.lblBtnBgColor.innerText = v(els.btnBgType) === 'gradient' ? 'Primary Color' : 'Background Color'; if(els.btnIconSettingsGroup) els.btnIconSettingsGroup.style.display = v(els.btnIconClass) !== '' ? 'block' : 'none'; if(els.boxMaxW && els.boxFullW) els.boxMaxW.disabled = c(els.boxFullW); txt(els.valBoxMaxW, c(els.boxFullW) ? '100% (Auto)' : v(els.boxMaxW) + 'px'); txt(els.valBoxMinH, v(els.boxMinH) > 0 ? v(els.boxMinH) + 'px' : '0px (Auto)'); txt(els.valInsertPara, v(els.insertPara)); txt(els.valBtnSize, v(els.btnSize) + 'px'); txt(els.valBtnBorderW, v(els.btnBorderW) + 'px'); txt(els.valBtnRadius, v(els.btnRadius) + 'px'); txt(els.valBtnPadX, v(els.btnPadX) + 'px'); txt(els.valBtnPadY, v(els.btnPadY) + 'px'); txt(els.valBtnSpace, v(els.btnSpace) + 'px'); txt(els.valTitleSize, v(els.titleSize) + 'px'); txt(els.valBodySize, v(els.bodySize) + 'px'); txt(els.valBoxPad, v(els.boxPad) + 'px'); txt(els.valBoxBorderW, v(els.boxBorderW) + 'px'); txt(els.valBoxRadius, v(els.boxRadius) + 'px'); txt(els.valBgAngle, v(els.bgAngle) + '°'); txt(els.valBgPos, v(els.bgPos) + '%'); txt(els.valBtnBgAngle, v(els.btnBgAngle) + '°'); txt(els.valBtnBgPos, v(els.btnBgPos) + '%'); txt(els.valBtnIconGap, v(els.btnIconGap) + 'px'); if(els.valS1X) els.valS1X.innerText = v(els.s1X) + 'px'; if(els.valS1Y) els.valS1Y.innerText = v(els.s1Y) + 'px'; if(els.valS1B) els.valS1B.innerText = v(els.s1Blur) + 'px'; if(els.valS1S) els.valS1S.innerText = v(els.s1Spread) + 'px'; if(els.valS1O) els.valS1O.innerText = v(els.s1Opacity) + '%'; if(els.valS2X) els.valS2X.innerText = v(els.s2X) + 'px'; if(els.valS2Y) els.valS2Y.innerText = v(els.s2Y) + 'px'; if(els.valS2B) els.valS2B.innerText = v(els.s2Blur) + 'px'; if(els.valS2S) els.valS2S.innerText = v(els.s2Spread) + 'px'; if(els.valS2O) els.valS2O.innerText = v(els.s2Opacity) + '%'; let bgStyle = `background-color: ${v(els.bgColor)};`; if (v(els.bgType) === 'gradient') { let pos = parseInt(v(els.bgPos) || 50, 10); let s1 = 100 - (pos * 2); let s2 = 200 - (pos * 2); bgStyle = `background: linear-gradient(${v(els.bgAngle)}deg, ${v(els.bgColor)} ${s1}%, ${v(els.bg2)} ${s2}%);`; } else if (v(els.bgType) === 'image' && v(els.bgImageUrl).trim() !== '') { bgStyle = `background-image: url('${v(els.bgImageUrl)}'); background-size: cover; background-position: center;`; } let tStyle = v(els.titleFontStyle); let tWeight = '700'; let tStyleCss = 'normal'; if (tStyle === 'normal') { tWeight = '400'; tStyleCss = 'normal'; } else if (tStyle === 'italic') { tWeight = '400'; tStyleCss = 'italic'; } else if (tStyle === 'bold-italic') { tWeight = '700'; tStyleCss = 'italic'; } let bStyle = v(els.bodyFontStyle); let bWeight = '400'; let bStyleCss = 'normal'; if (bStyle === 'bold') { bWeight = '700'; bStyleCss = 'normal'; } else if (bStyle === 'italic') { bWeight = '400'; bStyleCss = 'italic'; } else if (bStyle === 'bold-italic') { bWeight = '700'; bStyleCss = 'italic'; } let tFont = v(els.titleFontFamily); let bFont = v(els.bodyFontFamily); let titleFontStr = tFont !== 'inherit' ? `font-family: '${tFont}', sans-serif;` : ''; let bodyFontStr = bFont !== 'inherit' ? `font-family: '${bFont}', sans-serif;` : ''; let widthCss = c(els.boxFullW) ? "width: 100%;" : `width: 100%; max-width: ${v(els.boxMaxW)}px; margin: 0 auto;`; let heightCss = v(els.boxMinH) > 0 ? `min-height: ${v(els.boxMinH)}px;` : ""; let html = `<div style="${bgStyle} padding: ${v(els.boxPad)}px; border: ${v(els.boxBorderW)}px solid ${v(els.boxBorderC)}; border-radius: ${v(els.boxRadius)}px; text-align: ${v(els.textAlign)}; ${bodyFontStr} font-weight: ${bWeight}; font-style: ${bStyleCss}; ${widthCss} ${heightCss} box-sizing: border-box; ${v(els.customCss)}">`; if (v(els.title).trim() !== '') { html += `<h3 style="margin-top:0; font-size:${v(els.titleSize)}px; font-weight:${tWeight}; font-style:${tStyleCss}; color:${v(els.titleColor)}; margin-bottom:15px; line-height:1.3; ${titleFontStr}">${v(els.title)}</h3>`; } if (v(els.body).trim() !== '') { let formattedBody = v(els.body).replace(/\n/g, '<br>'); html += `<div style="font-size:${v(els.bodySize)}px; color:${v(els.bodyColor)}; line-height:1.6; margin-bottom:20px;">${formattedBody}</div>`; } if (c(els.useBuiltin)) { let wStyle = `display:flex; margin-top:${v(els.btnSpace)}px;`; if(v(els.btnPos) === 'center') wStyle += ` justify-content:center;`; if(v(els.btnPos) === 'right') wStyle += ` justify-content:flex-end;`; let btnBgStyle = `background-color: ${v(els.btnBg)};`; if (v(els.btnBgType) === 'gradient') { let p = parseInt(v(els.btnBgPos) || 50, 10); let s1 = 100 - (p * 2); let s2 = 200 - (p * 2); btnBgStyle = `background: linear-gradient(${v(els.btnBgAngle)}deg, ${v(els.btnBg)} ${s1}%, ${v(els.btnBg2)} ${s2}%);`; } else { btnBgStyle = `background-color: ${v(els.btnBg)}; background-image: none !important;`; } let btnStyle = v(els.btnFontStyle); let btnWeight = '700'; let btnStyleCss = 'normal'; if (btnStyle === 'normal') { btnWeight = '400'; btnStyleCss = 'normal'; } else if (btnStyle === 'italic') { btnWeight = '400'; btnStyleCss = 'italic'; } else if (btnStyle === 'bold-italic') { btnWeight = '700'; btnStyleCss = 'italic'; } let btnFont = v(els.btnFontFamily); let btnFontStr = btnFont !== 'inherit' ? `font-family: '${btnFont}', Arial, sans-serif !important;` : `font-family: Arial, sans-serif !important;`; let flexDir = 'row'; let ip = v(els.btnIconPosition); let iconGap = v(els.btnIconGap) || 8; if(ip === 'right') flexDir = 'row-reverse'; if(ip === 'top') flexDir = 'column'; if(ip === 'bottom') flexDir = 'column-reverse'; let btnInner = v(els.btnText); if (v(els.btnIconClass)) { let iHtml = `<i class="${v(els.btnIconClass)}" style="font-style:normal !important; line-height:1 !important;"></i>`; if (ip === 'icon_only') { btnInner = iHtml; iconGap = 0; } else { btnInner = `${iHtml} ${v(els.btnText)}`; } } let ef = v(els.btnEffect); if(appState.licenseTier === 'free' && ef === 'custom') { ef = 'none'; setVal(els.btnEffect, 'none'); } let bShadow = 'none'; let baseShadowForHalo = ''; if (ef === 'none') { els.customShadowWrapper.style.display = 'none'; } else if (ef === 'glossy') { els.customShadowWrapper.style.display = 'none'; bShadow = 'inset 0px 1px 0px rgba(255,255,255,0.5), inset 0px 15px 15px -15px rgba(255,255,255,0.8), inset 0px -5px 10px -5px rgba(0,0,0,0.5), 0px 4px 6px rgba(0,0,0,0.3)'; baseShadowForHalo = bShadow; } else if (ef === '3d') { els.customShadowWrapper.style.display = 'none'; bShadow = '0px 6px 0px rgba(0,0,0,0.4), 0px 10px 15px rgba(0,0,0,0.2)'; baseShadowForHalo = bShadow; } else if (ef === 'shadow') { els.customShadowWrapper.style.display = 'none'; bShadow = '0px 10px 20px rgba(0,0,0,0.15), 0px 3px 6px rgba(0,0,0,0.1)'; baseShadowForHalo = bShadow; } else if (ef === 'retro') { els.customShadowWrapper.style.display = 'none'; bShadow = '5px 5px 0px #000'; baseShadowForHalo = bShadow; } else if (ef === 'pressed') { els.customShadowWrapper.style.display = 'none'; bShadow = 'inset 0px 5px 10px rgba(0,0,0,0.4)'; baseShadowForHalo = bShadow; } else if (ef === 'floating') { els.customShadowWrapper.style.display = 'none'; bShadow = '0px 15px 25px rgba(0,0,0,0.2), 0px 5px 10px rgba(0,0,0,0.1)'; baseShadowForHalo = bShadow; } else if (ef === 'glow-dark') { els.customShadowWrapper.style.display = 'none'; bShadow = `0px 0px 15px ${hexToRgbaStr(v(els.btnBg), 60)}`; baseShadowForHalo = bShadow; } else if (ef === 'glow-light') { els.customShadowWrapper.style.display = 'none'; bShadow = '0px 0px 15px rgba(255,255,255,0.8)'; baseShadowForHalo = bShadow; } else if (ef === 'custom') { els.customShadowWrapper.style.display = 'block'; let s1 = `${c(els.s1Inset) ? 'inset ' : ''}${v(els.s1X)}px ${v(els.s1Y)}px ${v(els.s1Blur)}px ${v(els.s1Spread)}px ${hexToRgbaStr(v(els.s1Color), v(els.s1Opacity))}`; if (c(els.enableS2)) { s1 += `, ${c(els.s2Inset) ? 'inset ' : ''}${v(els.s2X)}px ${v(els.s2Y)}px ${v(els.s2Blur)}px ${v(els.s2Spread)}px ${hexToRgbaStr(v(els.s2Color), v(els.s2Opacity))}`; } bShadow = s1; baseShadowForHalo = s1; } let boxS = bShadow !== 'none' ? `box-shadow: ${bShadow} !important;` : 'box-shadow: none !important;'; let bWidth = v(els.btnBorderW); let borderRule = (bWidth === '' || bWidth === '0') ? "border: 0px solid transparent !important; outline: none !important; -webkit-appearance: none !important; appearance: none !important;" : `border: ${bWidth}px solid ${v(els.btnBorderC)} !important; outline: none !important; -webkit-appearance: none !important; appearance: none !important;`; let anim = v(els.animation); let kf = ''; let pseudoCss = ''; let animCss = ''; let uniqueId = 'exp-btn-' + Date.now() + Math.floor(Math.random() * 1000); if(anim === 'pulse') { animCss = 'animation: sbm_pulse 2s infinite;'; kf = '@keyframes sbm_pulse { 0% { transform: scale(1); } 50% { transform: scale(1.05); } 100% { transform: scale(1); } } '; } else if(anim === 'bounce') { animCss = 'animation: sbm_bounce 2s infinite;'; kf = '@keyframes sbm_bounce { 0%, 20%, 50%, 80%, 100% { transform: translateY(0); } 40% { transform: translateY(-10px); } 60% { transform: translateY(-5px); } } '; } else if(anim === 'shake') { animCss = 'animation: sbm_shake 2s infinite;'; kf = '@keyframes sbm_shake { 0%, 100% { transform: translateX(0); } 10%, 30%, 50%, 70%, 90% { transform: translateX(-4px); } 20%, 40%, 60%, 80% { transform: translateX(4px); } } '; } else if(anim === 'wobble') { animCss = 'animation: sbm_wobble 2s infinite;'; kf = '@keyframes sbm_wobble { 0%, 100% { transform: rotate(0deg); } 25% { transform: rotate(-3deg); } 50% { transform: rotate(3deg); } 75% { transform: rotate(-3deg); } } '; } else if(anim === 'shine') { kf = '@keyframes sbm_shine { 0% { left: -100%; } 100% { left: 200%; } } '; pseudoCss = `#${uniqueId}::after { content: ''; position: absolute; top: 0; left: -100%; width: 50%; height: 100%; background: linear-gradient(to right, rgba(255,255,255,0) 0%, rgba(255,255,255,0.6) 50%, rgba(255,255,255,0) 100%); transform: skewX(-25deg); animation: sbm_shine 2.5s infinite linear; pointer-events: none; border-radius: inherit; }`; } else if(anim === 'halo') { let base_bs = (baseShadowForHalo && baseShadowForHalo !== 'none') ? baseShadowForHalo + ', ' : ''; let haloStart = hexToRgbaStr(v(els.btnBg), 70); let haloEnd = hexToRgbaStr(v(els.btnBg), 0); animCss = `animation: sbm_halo_preview 2s infinite;`; kf = `@keyframes sbm_halo_preview { 0% { box-shadow: ${base_bs}0 0 0 0 ${haloStart}; } 70% { box-shadow: ${base_bs}0 0 0 15px ${haloEnd}; } 100% { box-shadow: ${base_bs}0 0 0 0 ${haloEnd}; } } `; } else if(anim === 'float') { animCss = 'animation: sbm_float 2s infinite;'; kf = '@keyframes sbm_float { 0%, 100% { transform: translateY(0); } 50% { transform: translateY(-8px); } } '; } let overflowVal = anim === 'shine' ? 'hidden' : 'visible'; let hStr = ''; if(c(els.enableHoverBg)) hStr += `background: ${v(els.hoverBg)} !important; `; if(c(els.enableHoverColor)) hStr += `color: ${v(els.hoverColor)} !important; `; let ha = v(els.hoverAnimation); if(ha === 'scale') hStr += `transform: scale(1.05) !important; `; else if(ha === 'lift') hStr += `transform: translateY(-4px) !important; `; else if(ha === 'push') hStr += `transform: translateY(2px) !important; `; if(anim !== 'none') hStr += `animation-play-state: paused !important; `; html += ` <style> ${kf} ${pseudoCss} #${uniqueId}::before { display: none !important; content: none !important; border: none !important; background: transparent !important;} ${anim !== 'shine' ? `#${uniqueId}::after { display: none !important; content: none !important; border: none !important; background: transparent !important;}` : ''} #${uniqueId} { position: relative !important; overflow: ${overflowVal} !important; transition: all 0.3s ease-in-out !important; z-index: 1 !important; -webkit-font-smoothing: antialiased !important; -moz-osx-font-smoothing: grayscale !important; } #${uniqueId}:hover { ${hStr} } </style> <div style="margin-top:${v(els.btnSpace || 20)}px;text-align:${v(els.btnPos || 'center')};"><a id="${uniqueId}" href="${v(els.btnUrl)}" style="display:inline-flex !important; align-items:center !important; justify-content:center !important; flex-direction:${flexDir} !important; gap:${iconGap}px !important; ${btnBgStyle} color:${v(els.btnColor)} !important; padding:${v(els.btnPadY || 15)}px ${v(els.btnPadX || 30)}px !important; ${borderRule} border-radius:${v(els.btnRadius || 50)}px !important; text-decoration:none !important; font-size:${v(els.btnSize || 16)}px !important; font-weight:${btnWeight} !important; font-style:${btnStyleCss} !important; ${boxS} ${animCss} ${btnFontStr} box-sizing:border-box !important; line-height:1.2 !important; margin:0 !important; letter-spacing:normal !important;">${btnInner}</a></div>`; } html += `</div>`; let fontsToLoad = []; if (tFont && tFont !== 'inherit') fontsToLoad.push(tFont.replace(/\s+/g, '+')); if (bFont && bFont !== 'inherit') fontsToLoad.push(bFont.replace(/\s+/g, '+')); if (v(els.btnFontFamily) && v(els.btnFontFamily) !== 'inherit') fontsToLoad.push(v(els.btnFontFamily).replace(/\s+/g, '+')); fontsToLoad = [...new Set(fontsToLoad)]; fontsToLoad.forEach(ef => { html = `<style>@import url('https://fonts.googleapis.com/css2?family=${ef}:ital,wght@0,400;0,700;1,400;1,700&display=swap');</style>\n` + html; }); if (v(els.btnIconClass) && v(els.btnIconClass) !== '') { html = `<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">\n` + html; } if(els.previewBox) els.previewBox.innerHTML = html; } catch(err) { console.error('JBI Preview Generation Error:', err); } } const allInputs = Object.values(els).filter(el => el && el.tagName); allInputs.forEach(el => { if (el.tagName === 'INPUT' || el.tagName === 'SELECT' || el.tagName === 'TEXTAREA') { el.addEventListener('input', updatePreview); el.addEventListener('change', updatePreview); } }); let mediaUploader; if (els.btnUploadBg) { $(els.btnUploadBg).on('click', function(e) { e.preventDefault(); if (mediaUploader) { mediaUploader.open(); return; } if (typeof wp !== 'undefined' && wp.media) { mediaUploader = wp.media({ title: 'Select Box Background', button: { text: 'Use image' }, multiple: false }); mediaUploader.on('select', function() { const att = mediaUploader.state().get('selection').first().toJSON(); if(els.bgImageUrl) { els.bgImageUrl.value = att.url; els.bgImageUrl.dispatchEvent(new Event('input')); } }); mediaUploader.open(); } else { alert('WordPress Media not available.'); } }); } function syncDataToServer() { const formData = new FormData(); formData.append('action', 'skjbi_save_data'); formData.append('security', '<?php echo $nonce; ?>'); formData.append('skjbi_data', JSON.stringify(appState)); fetch(ajaxurl, { method: 'POST', body: formData }) .then(res => res.json()) .then(res => { if(res.success && els.saveMsg) { els.saveMsg.style.display = 'block'; setTimeout(() => { els.saveMsg.style.display = 'none'; }, 2000); } }); } if (els.saveBtn) { els.saveBtn.addEventListener('click', () => { const name = v(els.saveBtnName).trim() || 'Untitled Box'; const folder = v(els.saveFolderSelect) || 'Default'; const loadedId = v(els.currentLoadedBtnId); const boxData = { name: name, folder: folder, enableInsert: c(els.enableInsert), insertPara: v(els.insertPara), showOnPosts: c(els.showPosts), showOnPages: c(els.showPages), specificIds: v(els.specificIds), title: v(els.title), body: v(els.body), useBuiltInBtn: c(els.useBuiltin), customShortcode: v(els.customShortcode), btnText: v(els.btnText), btnUrl: v(els.btnUrl), btnNewTab: c(els.btnNewTab), btnNofollow: c(els.btnNofollow), btnIconClass: v(els.btnIconClass), btnIconPosition: v(els.btnIconPosition), btnIconGap: v(els.btnIconGap), btnFontFamily: v(els.btnFontFamily), btnFontStyle: v(els.btnFontStyle), btnSize: v(els.btnSize), btnBgType: v(els.btnBgType), btnBg: v(els.btnBg), btnBg2: v(els.btnBg2), btnBgAngle: v(els.btnBgAngle), btnBgPos: v(els.btnBgPos), btnColor: v(els.btnColor), btnBorderW: v(els.btnBorderW), btnBorderC: v(els.btnBorderC), btnRadius: v(els.btnRadius), btnPadX: v(els.btnPadX), btnPadY: v(els.btnPadY), btnPos: v(els.btnPos), btnSpace: v(els.btnSpace), btnEffect: v(els.btnEffect), shadow1X: v(els.s1X), shadow1Y: v(els.s1Y), shadow1Blur: v(els.s1Blur), shadow1Spread: v(els.s1Spread), shadow1Color: v(els.s1Color), shadow1Opacity: v(els.s1Opacity), shadow1Inset: c(els.s1Inset), enableShadow2: c(els.enableS2), shadow2X: v(els.s2X), shadow2Y: v(els.s2Y), shadow2Blur: v(els.s2Blur), shadow2Spread: v(els.s2Spread), shadow2Color: v(els.s2Color), shadow2Opacity: v(els.s2Opacity), shadow2Inset: c(els.s2Inset), btnAnimation: v(els.animation), enableHoverBg: c(els.enableHoverBg), btnHoverBg: v(els.hoverBg), enableHoverColor: c(els.enableHoverColor), btnHoverColor: v(els.hoverColor), btnHoverAnimation: v(els.hoverAnimation), bgType: v(els.bgType), bgColor: v(els.bgColor), bg2: v(els.bg2), bgAngle: v(els.bgAngle), bgPos: v(els.bgPos), bgImageUrl: v(els.bgImageUrl), titleFontFamily: v(els.titleFontFamily), bodyFontFamily: v(els.bodyFontFamily), titleFontStyle: v(els.titleFontStyle), bodyFontStyle: v(els.bodyFontStyle), titleSize: v(els.titleSize), titleColor: v(els.titleColor), bodySize: v(els.bodySize), bodyColor: v(els.bodyColor), textAlign: v(els.textAlign), boxPad: v(els.boxPad), boxBorderW: v(els.boxBorderW), boxBorderC: v(els.boxBorderC), boxRadius: v(els.boxRadius), boxMaxW: v(els.boxMaxW), boxMinH: v(els.boxMinH), boxFullW: c(els.boxFullW), customCss: v(els.customCss) }; if (loadedId) { const index = appState.boxes.findIndex(b => b.id === loadedId); if (index !== -1) { boxData.id = loadedId; appState.boxes[index] = boxData; } } else { boxData.id = 'box_' + Date.now(); appState.boxes.push(boxData); setVal(els.currentLoadedBtnId, boxData.id); } if(!openFolders.includes(folder)) openFolders.push(folder); syncDataToServer(); renderUI(); txt(els.displayActiveShortcode, `[skjbi_box id="${boxData.id}"]`); }); } if (els.btnResetForm) { els.btnResetForm.addEventListener('click', (e) => { e.preventDefault(); setVal(els.currentLoadedBtnId, ''); setVal(els.saveBtnName, ''); setVal(els.title, 'New Box'); setVal(els.body, ''); txt(els.displayActiveShortcode, '[skjbi_box]'); if(els.title) els.title.dispatchEvent(new Event('input')); }); } if (els.createFolderBtn) { els.createFolderBtn.addEventListener('click', (e) => { e.preventDefault(); const fn = v(els.newFolderName).trim(); if (fn && !appState.folders.includes(fn)) { appState.folders.push(fn); openFolders.push(fn); setVal(els.newFolderName, ''); renderUI(); syncDataToServer(); } }); } window.loadBox = function(id) { const b = appState.boxes.find(x => x.id === id); if(b) { setVal(els.currentLoadedBtnId, b.id); setVal(els.saveBtnName, b.name); setVal(els.saveFolderSelect, b.folder); setCheck(els.enableInsert, b.enableInsert || false); setVal(els.insertPara, b.insertPara || 1); setCheck(els.showPosts, b.showOnPosts !== false); setCheck(els.showPages, b.showOnPages || false); setVal(els.specificIds, b.specificIds || ''); setVal(els.title, b.title || ''); setVal(els.body, b.body ? b.body.replace(/<br\s*[\/]?>/gi, '\n') : ''); setCheck(els.useBuiltin, b.useBuiltInBtn !== false); setVal(els.customShortcode, b.customShortcode || ''); setVal(els.btnText, b.btnText || 'Click To Buy'); setVal(els.btnUrl, b.btnUrl || ''); setCheck(els.btnNewTab, b.btnNewTab !== false); setCheck(els.btnNofollow, b.btnNofollow || false); setVal(els.btnIconClass, b.btnIconClass || ''); setVal(els.btnIconPosition, b.btnIconPosition || 'left'); setVal(els.btnIconGap, b.btnIconGap !== undefined ? b.btnIconGap : '8'); setVal(els.btnFontFamily, b.btnFontFamily || 'inherit'); setVal(els.btnFontStyle, b.btnFontStyle || (b.btnWeight === '400' ? 'normal' : 'bold')); setVal(els.btnSize, b.btnSize || '16'); setVal(els.btnBgType, b.btnBgType || 'color'); setVal(els.btnBg, normalizeColor(b.btnBg)); setVal(els.btnBg2, normalizeColor(b.btnBg2 || '#e68a00')); setVal(els.btnBgAngle, b.btnBgAngle || '90'); setVal(els.btnBgPos, b.btnBgPos !== undefined ? b.btnBgPos : '50'); setVal(els.btnColor, normalizeColor(b.btnColor)); setVal(els.btnBorderW, b.btnBorderW || '0'); setVal(els.btnBorderC, normalizeColor(b.btnBorderC)); setVal(els.btnRadius, b.btnRadius || '50'); setVal(els.btnPadX, b.btnPadX || '30'); setVal(els.btnPadY, b.btnPadY || '15'); setVal(els.btnPos, b.btnPos || 'center'); setVal(els.btnSpace, b.btnSpace || '20'); setVal(els.btnEffect, b.btnEffect || 'none'); setVal(els.s1X, b.shadow1X !== undefined ? b.shadow1X : '0'); setVal(els.s1Y, b.shadow1Y !== undefined ? b.shadow1Y : '5'); setVal(els.s1Blur, b.shadow1Blur !== undefined ? b.shadow1Blur : '15'); setVal(els.s1Spread, b.shadow1Spread !== undefined ? b.shadow1Spread : '0'); setVal(els.s1Color, normalizeColor(b.shadow1Color)); setVal(els.s1Opacity, b.shadow1Opacity !== undefined ? b.shadow1Opacity : '40'); setCheck(els.s1Inset, b.shadow1Inset || false); setCheck(els.enableS2, b.enableShadow2 || false); setVal(els.s2X, b.shadow2X !== undefined ? b.shadow2X : '0'); setVal(els.s2Y, b.shadow2Y !== undefined ? b.shadow2Y : '0'); setVal(els.s2Blur, b.shadow2Blur !== undefined ? b.shadow2Blur : '15'); setVal(els.s2Spread, b.shadow2Spread !== undefined ? b.shadow2Spread : '0'); setVal(els.s2Color, normalizeColor(b.shadow2Color || '#ffffff')); setVal(els.s2Opacity, b.shadow2Opacity !== undefined ? b.shadow2Opacity : '50'); setCheck(els.s2Inset, b.shadow2Inset || false); setVal(els.animation, b.btnAnimation || 'none'); setCheck(els.enableHoverBg, b.enableHoverBg || false); setVal(els.hoverBg, normalizeColor(b.btnHoverBg || '#005177')); setCheck(els.enableHoverColor, b.enableHoverColor || false); setVal(els.hoverColor, normalizeColor(b.btnHoverColor || '#ffffff')); setVal(els.hoverAnimation, b.btnHoverAnimation || 'none'); setVal(els.bgType, b.bgType || 'color'); setVal(els.bgColor, normalizeColor(b.bgColor)); setVal(els.bg2, normalizeColor(b.bg2 || '#ffffff')); setVal(els.bgAngle, b.bgAngle || '90'); setVal(els.bgPos, b.bgPos !== undefined ? b.bgPos : '50'); setVal(els.bgImageUrl, b.bgImageUrl || ''); setVal(els.titleFontFamily, b.titleFontFamily || b.fontFamily || 'inherit'); setVal(els.bodyFontFamily, b.bodyFontFamily || b.fontFamily || 'inherit'); setVal(els.titleFontStyle, b.titleFontStyle || 'bold'); setVal(els.bodyFontStyle, b.bodyFontStyle || 'normal'); setVal(els.titleSize, b.titleSize || '24'); setVal(els.titleColor, normalizeColor(b.titleColor)); setVal(els.bodySize, b.bodySize || '16'); setVal(els.bodyColor, normalizeColor(b.bodyColor)); setVal(els.textAlign, b.textAlign || 'left'); setVal(els.boxPad, b.boxPad || '30'); setVal(els.boxBorderW, b.boxBorderW || '3'); setVal(els.boxBorderC, normalizeColor(b.boxBorderC)); setVal(els.boxRadius, b.boxRadius || '8'); setVal(els.boxMaxW, b.boxMaxW || '800'); setVal(els.boxMinH, b.boxMinH || '0'); setCheck(els.boxFullW, b.boxFullW || false); setVal(els.customCss, b.customCss || ''); txt(els.displayActiveShortcode, `[skjbi_box id="${b.id}"]`); window.scrollTo(0, 0); updatePreview(); } }; window.cloneBox = function(id) { const original = appState.boxes.find(b => b.id === id); if (original) { const cloned = JSON.parse(JSON.stringify(original)); cloned.id = 'box_' + Date.now(); cloned.name = (original.name || 'Untitled') + ' (Copy)'; appState.boxes.push(cloned); if(!openFolders.includes(cloned.folder)) openFolders.push(cloned.folder); renderUI(); syncDataToServer(); window.loadBox(cloned.id); } }; window.deleteBox = function(id) { if(confirm('Delete this box?')) { appState.boxes = appState.boxes.filter(b => b.id !== id); if (v(els.currentLoadedBtnId) === id) if(els.btnResetForm) els.btnResetForm.click(); renderUI(); syncDataToServer(); } }; window.deleteFolder = function(folderName) { if(confirm(`Delete folder "${folderName}" and all boxes inside?`)) { appState.folders = appState.folders.filter(f => f !== folderName); appState.boxes = appState.boxes.filter(b => b.folder !== folderName); renderUI(); syncDataToServer(); } }; function renderUI() { if (!els.saveFolderSelect || !els.libraryContainer) return; els.saveFolderSelect.innerHTML = ''; appState.folders.forEach(folder => { let opt = document.createElement('option'); opt.value = folder; opt.innerText = folder; els.saveFolderSelect.appendChild(opt); }); els.libraryContainer.innerHTML = ''; appState.folders.forEach(folder => { const folderDiv = document.createElement('div'); folderDiv.className = 'sbm-folder'; folderDiv.setAttribute('draggable', 'true'); folderDiv.dataset.foldername = folder; if(openFolders.includes(folder)) folderDiv.classList.add('is-open'); const headerDiv = document.createElement('div'); headerDiv.className = 'sbm-folder-header'; let sellBtnHtml = (appState.licenseTier === 'business') ? `<button type="button" class="button button-small sbm-sell btn-sell-folder" title="Package for sale">🔓 Sell Pack</button>` : `<button type="button" class="button button-small sbm-sell is-locked btn-sell-folder" title="BIZ Required">💼 Sell Pack</button>`; headerDiv.innerHTML = ` <div class="sbm-folder-title"><span class="sbm-drag-handle sbm-folder-drag-handle" title="Drag to reorder folder">&#8942;&#8942;</span><span class="sbm-toggle-icon">▶</span> 📁 ${folder}</div> <div class="sbm-folder-actions"> ${sellBtnHtml} <button type="button" class="button button-small sbm-pro-action-btn btn-zip-folder" title="Download ZIP">ZIP</button> ${folder !== 'Default' && folder !== 'Templates' ? `<button type="button" class="button button-small sbm-delete btn-del-folder">✖</button>` : ''} </div> `; const dragHandle = headerDiv.querySelector('.sbm-folder-drag-handle'); if (dragHandle) dragHandle.addEventListener('mousedown', () => folderDiv.setAttribute('draggable', 'true')); const sellBtn = headerDiv.querySelector('.btn-sell-folder'); if (sellBtn) sellBtn.addEventListener('click', (e) => { e.preventDefault(); e.stopPropagation(); sellFolder(folder); }); const zipBtn = headerDiv.querySelector('.btn-zip-folder'); if (zipBtn) zipBtn.addEventListener('click', (e) => { e.preventDefault(); e.stopPropagation(); downloadFolderZip(folder); }); const delBtn = headerDiv.querySelector('.btn-del-folder'); if (delBtn) delBtn.addEventListener('click', (e) => { e.preventDefault(); e.stopPropagation(); deleteFolder(folder); }); folderDiv.addEventListener('dragstart', (e) => { e.dataTransfer.setData('text/folder', folder); e.dataTransfer.effectAllowed = 'move'; setTimeout(() => folderDiv.classList.add('sbm-dragging'), 0); }); folderDiv.addEventListener('dragend', () => { folderDiv.classList.remove('sbm-dragging'); document.querySelectorAll('.sbm-folder').forEach(el => el.classList.remove('sbm-drag-over-top', 'sbm-drag-over-bottom')); }); folderDiv.addEventListener('dragover', (e) => { if (e.dataTransfer.types.includes('text/folder')) { e.preventDefault(); e.stopPropagation(); const r = folderDiv.getBoundingClientRect(); if (e.clientY > r.top + r.height / 2) { folderDiv.classList.remove('sbm-drag-over-top'); folderDiv.classList.add('sbm-drag-over-bottom'); } else { folderDiv.classList.remove('sbm-drag-over-bottom'); folderDiv.classList.add('sbm-drag-over-top'); } } }); folderDiv.addEventListener('dragleave', (e) => { if (e.dataTransfer.types.includes('text/folder')) { e.preventDefault(); e.stopPropagation(); folderDiv.classList.remove('sbm-drag-over-top', 'sbm-drag-over-bottom'); } }); folderDiv.addEventListener('drop', (e) => { if (e.dataTransfer.types.includes('text/folder')) { e.preventDefault(); e.stopPropagation(); folderDiv.classList.remove('sbm-drag-over-top', 'sbm-drag-over-bottom'); const df = e.dataTransfer.getData('text/folder'); if (!df || df === folder) return; const dIdx = appState.folders.indexOf(df); if (dIdx === -1) return; appState.folders.splice(dIdx, 1); const tIdx = appState.folders.indexOf(folder); const r = folderDiv.getBoundingClientRect(); if (e.clientY > r.top + r.height / 2) appState.folders.splice(tIdx + 1, 0, df); else appState.folders.splice(tIdx, 0, df); renderUI(); syncDataToServer(); } }); const toggleIconArea = headerDiv.querySelector('.sbm-toggle-icon'); if (toggleIconArea && toggleIconArea.parentElement) { toggleIconArea.parentElement.addEventListener('click', (e) => { if (e.target.classList.contains('sbm-folder-drag-handle')) return; folderDiv.classList.toggle('is-open'); if(folderDiv.classList.contains('is-open')) { if(!openFolders.includes(folder)) openFolders.push(folder); } else { openFolders = openFolders.filter(f => f !== folder); } }); } folderDiv.appendChild(headerDiv); const contentDiv = document.createElement('div'); contentDiv.className = 'sbm-folder-content'; contentDiv.addEventListener('dragover', (e) => { if (e.dataTransfer.types.includes('text/plain')) { e.preventDefault(); contentDiv.classList.add('sbm-folder-drag-over'); } }); contentDiv.addEventListener('dragleave', (e) => { if (e.dataTransfer.types.includes('text/plain')) { contentDiv.classList.remove('sbm-folder-drag-over'); } }); contentDiv.addEventListener('drop', (e) => { if (e.dataTransfer.types.includes('text/plain')) { e.preventDefault(); e.stopPropagation(); contentDiv.classList.remove('sbm-folder-drag-over'); const did = e.dataTransfer.getData('text/plain'); if (!did) return; const di = appState.boxes.findIndex(b => b.id === did); if (di === -1) return; const dItem = appState.boxes[di]; if (e.target === contentDiv || e.target.classList.contains('sbm-empty-msg')) { appState.boxes.splice(di, 1); dItem.folder = folder; appState.boxes.push(dItem); renderUI(); syncDataToServer(); } } }); const boxesInFolder = appState.boxes.filter(b => b.folder === folder); if (boxesInFolder.length === 0) { contentDiv.innerHTML = '<span class="sbm-empty-msg" style="color:#999;font-size:12px; pointer-events:none;">Drag a box here!</span>'; } else { boxesInFolder.forEach(box => { const btnDiv = document.createElement('div'); btnDiv.className = 'sbm-saved-btn'; btnDiv.setAttribute('draggable', 'true'); let miniBg = box.bgColor || '#ccc'; if (box.bgType === 'gradient') { let p = parseInt(box.bgPos || 50, 10); miniBg = `linear-gradient(${box.bgAngle || 90}deg, ${box.bgColor} ${100-(p*2)}%, ${box.bg2} ${200-(p*2)}%)`; } else if (box.bgType === 'image') { miniBg = `url('${box.bgImageUrl}') center/cover`; } btnDiv.innerHTML = ` <div style="display:flex; align-items:center; flex:1; min-width: 150px;"> <div class="sbm-drag-handle" title="Drag to reorder">&#8942;&#8942;</div> <div class="sbm-saved-btn-name"> <div><span style="display:inline-block; width:12px; height:12px; background:${miniBg}; border-radius:3px; margin-right:5px; vertical-align:middle; border:1px solid ${box.boxBorderC || '#999'};"></span> ${box.name || 'Untitled Box'}</div> </div> </div> <div class="sbm-btn-actions"> <button type="button" class="btn-edit">Edit</button> <button type="button" class="sbm-clone btn-clone">Clone</button> <button type="button" class="sbm-delete btn-delete">✖</button> </div> `; btnDiv.querySelector('.btn-edit').addEventListener('click', (e) => { e.preventDefault(); window.loadBox(box.id); }); btnDiv.querySelector('.btn-clone').addEventListener('click', (e) => { e.preventDefault(); window.cloneBox(box.id); }); btnDiv.querySelector('.btn-delete').addEventListener('click', (e) => { e.preventDefault(); window.deleteBox(box.id); }); btnDiv.addEventListener('dragstart', (e) => { e.dataTransfer.setData('text/plain', box.id); e.dataTransfer.effectAllowed = 'move'; setTimeout(() => btnDiv.classList.add('sbm-dragging'), 0); }); btnDiv.addEventListener('dragend', () => { btnDiv.classList.remove('sbm-dragging'); document.querySelectorAll('.sbm-saved-btn').forEach(el => el.classList.remove('sbm-drag-over-top', 'sbm-drag-over-bottom')); document.querySelectorAll('.sbm-folder-content').forEach(el => el.classList.remove('sbm-folder-drag-over')); }); btnDiv.addEventListener('dragover', (e) => { if (e.dataTransfer.types.includes('text/plain')) { e.preventDefault(); e.stopPropagation(); const r = btnDiv.getBoundingClientRect(); if (e.clientY > r.top + r.height / 2) { btnDiv.classList.remove('sbm-drag-over-top'); btnDiv.classList.add('sbm-drag-over-bottom'); } else { btnDiv.classList.remove('sbm-drag-over-bottom'); btnDiv.classList.add('sbm-drag-over-top'); } } }); btnDiv.addEventListener('dragleave', (e) => { if (e.dataTransfer.types.includes('text/plain')) { e.preventDefault(); e.stopPropagation(); btnDiv.classList.remove('sbm-drag-over-top', 'sbm-drag-over-bottom'); } }); btnDiv.addEventListener('drop', (e) => { if (e.dataTransfer.types.includes('text/plain')) { e.preventDefault(); e.stopPropagation(); btnDiv.classList.remove('sbm-drag-over-top', 'sbm-drag-over-bottom'); const did = e.dataTransfer.getData('text/plain'); if (!did || did === box.id) return; const dIdx = appState.boxes.findIndex(b => b.id === did); if (dIdx === -1) return; const dItem = appState.boxes[dIdx]; appState.boxes.splice(dIdx, 1); const tIdx = appState.boxes.findIndex(b => b.id === box.id); dItem.folder = appState.boxes[tIdx].folder; const r = btnDiv.getBoundingClientRect(); if (e.clientY > r.top + r.height / 2) appState.boxes.splice(tIdx + 1, 0, dItem); else appState.boxes.splice(tIdx, 0, dItem); renderUI(); syncDataToServer(); } }); contentDiv.appendChild(btnDiv); }); } folderDiv.appendChild(contentDiv); els.libraryContainer.appendChild(folderDiv); }); } function triggerDownload(filename, content, type = 'text/plain') { const blob = new Blob([content], { type: type }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.style.display = 'none'; a.href = url; a.download = filename; document.body.appendChild(a); a.click(); document.body.removeChild(a); setTimeout(() => URL.revokeObjectURL(url), 1000); } if (els.btnExportJson) els.btnExportJson.addEventListener('click', e => { e.preventDefault(); triggerDownload(`johnson-box-backup-${new Date().toISOString().slice(0,10)}.json`, JSON.stringify(appState, null, 2), 'application/json'); }); function generateBoxHtmlString(b) { let bgStyle = `background-color: ${b.bgColor};`; if (b.bgType === 'gradient') { let pos = parseInt(b.bgPos || 50, 10); let s1 = 100 - (pos * 2); let s2 = 200 - (pos * 2); bgStyle = `background: linear-gradient(${b.bgAngle || 90}deg, ${b.bgColor} ${s1}%, ${b.bg2 || '#ffffff'} ${s2}%);`; } else if (b.bgType === 'image' && b.bgImageUrl) { bgStyle = `background-image: url('${b.bgImageUrl}'); background-size: cover; background-position: center;`; } let tFont = b.titleFontFamily || b.fontFamily || 'inherit'; let bFont = b.bodyFontFamily || b.fontFamily || 'inherit'; let titleFontStr = tFont !== 'inherit' ? `font-family: '${tFont}', sans-serif;` : ''; let bodyFontStr = bFont !== 'inherit' ? `font-family: '${bFont}', sans-serif;` : ''; let tWeight = '700'; let tStyleCss = 'normal'; let ts = b.titleFontStyle || 'bold'; if (ts === 'normal') { tWeight = '400'; tStyleCss = 'normal'; } else if (ts === 'italic') { tWeight = '400'; tStyleCss = 'italic'; } else if (ts === 'bold-italic') { tWeight = '700'; tStyleCss = 'italic'; } let bWeight = '400'; let bStyleCss = 'normal'; let bs = b.bodyFontStyle || 'normal'; if (bs === 'bold') { bWeight = '700'; bStyleCss = 'normal'; } else if (bs === 'italic') { bWeight = '400'; bStyleCss = 'italic'; } else if (bs === 'bold-italic') { bWeight = '700'; bStyleCss = 'italic'; } let heightCss = b.boxMinH > 0 ? `min-height:${b.boxMinH}px;` : ''; let html = `<div style="${bgStyle} padding:${b.boxPad}px; border:${b.boxBorderW}px solid ${b.boxBorderC}; border-radius:${b.boxRadius}px; text-align:${b.textAlign}; font-weight:${bWeight}; font-style:${bStyleCss}; max-width:${b.boxFullW ? '100%' : b.boxMaxW + 'px'}; ${heightCss} margin:20px auto; ${bodyFontStr} box-sizing:border-box;">`; if(b.title) html += `<h3 style="margin-top:0;font-size:${b.titleSize}px; font-weight:${tWeight}; font-style:${tStyleCss}; color:${b.titleColor}; ${titleFontStr}">${b.title}</h3>`; if(b.body) html += `<div style="font-size:${b.bodySize}px;color:${b.bodyColor};">${b.body.replace(/\n/g, '<br>')}</div>`; if(b.useBuiltInBtn) { let btnBgStyle = `background-color: ${b.btnBg};`; if (b.btnBgType === 'gradient') { let bp = parseInt(b.btnBgPos || 50, 10); let bs1 = 100 - (bp * 2); let bs2 = 200 - (bp * 2); btnBgStyle = `background: linear-gradient(${b.btnBgAngle || 90}deg, ${b.btnBg} ${bs1}%, ${b.btnBg2 || '#e68a00'} ${bs2}%);`; } else { btnBgStyle = `background-color: ${b.btnBg}; background-image: none !important;`; } let btnFont = b.btnFontFamily || 'inherit'; let btnFontStr = btnFont !== 'inherit' ? `font-family: '${btnFont}', Arial, sans-serif !important;` : `font-family: Arial, sans-serif !important;`; let btnWeight = '700'; let btnStyleCss = 'normal'; let btnS = b.btnFontStyle || (b.btnWeight === '400' ? 'normal' : 'bold'); if (btnS === 'normal') { btnWeight = '400'; btnStyleCss = 'normal'; } else if (btnS === 'italic') { btnWeight = '400'; btnStyleCss = 'italic'; } else if (btnS === 'bold-italic') { btnWeight = '700'; btnStyleCss = 'italic'; } let flexDir = 'row'; let iconPos = b.btnIconPosition || 'left'; let iconGap = b.btnIconGap !== undefined ? b.btnIconGap : 8; if (iconPos === 'right') flexDir = 'row-reverse'; if (iconPos === 'top') flexDir = 'column'; if (iconPos === 'bottom') flexDir = 'column-reverse'; let btnInner = b.btnText; if (b.btnIconClass && b.btnIconClass !== '') { let iHtml = `<i class="${b.btnIconClass}" style="font-style:normal !important; line-height:1 !important;"></i>`; if (iconPos === 'icon_only') { btnInner = iHtml; iconGap = 0; } else { btnInner = `${iHtml} ${b.btnText}`; } } let bShadow = 'none'; let baseShadowForHalo = ''; let ef = b.btnEffect || 'none'; if (ef === 'glossy') { bShadow = 'inset 0px 1px 0px rgba(255,255,255,0.5), inset 0px 15px 15px -15px rgba(255,255,255,0.8), inset 0px -5px 10px -5px rgba(0,0,0,0.5), 0px 4px 6px rgba(0,0,0,0.3)'; baseShadowForHalo = bShadow; } else if (ef === '3d') { bShadow = '0px 6px 0px rgba(0,0,0,0.4), 0px 10px 15px rgba(0,0,0,0.2)'; baseShadowForHalo = bShadow; } else if (ef === 'shadow') { bShadow = '0px 10px 20px rgba(0,0,0,0.15), 0px 3px 6px rgba(0,0,0,0.1)'; baseShadowForHalo = bShadow; } else if (ef === 'retro') { bShadow = '5px 5px 0px #000'; baseShadowForHalo = bShadow; } else if (ef === 'pressed') { bShadow = 'inset 0px 5px 10px rgba(0,0,0,0.4)'; baseShadowForHalo = bShadow; } else if (ef === 'floating') { bShadow = '0px 15px 25px rgba(0,0,0,0.2), 0px 5px 10px rgba(0,0,0,0.1)'; baseShadowForHalo = bShadow; } else if (ef === 'glow-dark') { bShadow = `0px 0px 15px ${hexToRgbaStr(b.btnBg || '#000', 60)}`; baseShadowForHalo = bShadow; } else if (ef === 'glow-light') { bShadow = '0px 0px 15px rgba(255,255,255,0.8)'; baseShadowForHalo = bShadow; } else if (ef === 'custom') { let s1 = `${b.shadow1Inset ? 'inset ' : ''}${b.shadow1X || 0}px ${b.shadow1Y !== undefined ? b.shadow1Y : 5}px ${b.shadow1Blur !== undefined ? b.shadow1Blur : 15}px ${b.shadow1Spread || 0}px ${hexToRgbaStr(b.shadow1Color || '#000000', b.shadow1Opacity !== undefined ? b.shadow1Opacity : 40)}`; if (b.enableShadow2) { s1 += `, ${b.shadow2Inset ? 'inset ' : ''}${b.shadow2X || 0}px ${b.shadow2Y !== undefined ? b.shadow2Y : 0}px ${b.shadow2Blur !== undefined ? b.shadow2Blur : 15}px ${b.shadow2Spread || 0}px ${hexToRgbaStr(b.shadow2Color || '#ffffff', b.shadow2Opacity !== undefined ? b.shadow2Opacity : 50)}`; } bShadow = s1; baseShadowForHalo = s1; } let boxS = bShadow !== 'none' ? `box-shadow: ${bShadow} !important;` : 'box-shadow: none !important;'; let bWidth = b.btnBorderW || '0'; let borderRule = (bWidth === '' || bWidth === '0') ? "border: 0px solid transparent !important; outline: none !important; -webkit-appearance: none !important; appearance: none !important;" : `border: ${bWidth}px solid ${b.btnBorderC || '#000'} !important; outline: none !important; -webkit-appearance: none !important; appearance: none !important;`; let anim = b.btnAnimation || 'none'; let kf = ''; let pseudoCss = ''; let animCss = ''; let uniqueId = 'exp-btn-' + Date.now() + Math.floor(Math.random() * 1000); if(anim === 'pulse') { animCss = 'animation: sbm_pulse 2s infinite;'; kf = '@keyframes sbm_pulse { 0% { transform: scale(1); } 50% { transform: scale(1.05); } 100% { transform: scale(1); } } '; } else if(anim === 'bounce') { animCss = 'animation: sbm_bounce 2s infinite;'; kf = '@keyframes sbm_bounce { 0%, 20%, 50%, 80%, 100% { transform: translateY(0); } 40% { transform: translateY(-10px); } 60% { transform: translateY(-5px); } } '; } else if(anim === 'shake') { animCss = 'animation: sbm_shake 2s infinite;'; kf = '@keyframes sbm_shake { 0%, 100% { transform: translateX(0); } 10%, 30%, 50%, 70%, 90% { transform: translateX(-4px); } 20%, 40%, 60%, 80% { transform: translateX(4px); } } '; } else if(anim === 'wobble') { animCss = 'animation: sbm_wobble 2s infinite;'; kf = '@keyframes sbm_wobble { 0%, 100% { transform: rotate(0deg); } 25% { transform: rotate(-3deg); } 50% { transform: rotate(3deg); } 75% { transform: rotate(-3deg); } } '; } else if(anim === 'shine') { kf = '@keyframes sbm_shine { 0% { left: -100%; } 100% { left: 200%; } } '; pseudoCss = `#${uniqueId}::after { content: ''; position: absolute; top: 0; left: -100%; width: 50%; height: 100%; background: linear-gradient(to right, rgba(255,255,255,0) 0%, rgba(255,255,255,0.6) 50%, rgba(255,255,255,0) 100%); transform: skewX(-25deg); animation: sbm_shine 2.5s infinite linear; pointer-events: none; border-radius: inherit; }`; } else if(anim === 'halo') { let base_bs = (baseShadowForHalo && baseShadowForHalo !== 'none') ? baseShadowForHalo + ', ' : ''; let haloStart = hexToRgbaStr(b.btnBg || '#ff9900', 70); let haloEnd = hexToRgbaStr(b.btnBg || '#ff9900', 0); animCss = `animation: sbm_halo_preview 2s infinite;`; kf = `@keyframes sbm_halo_preview { 0% { box-shadow: ${base_bs}0 0 0 0 ${haloStart}; } 70% { box-shadow: ${base_bs}0 0 0 15px ${haloEnd}; } 100% { box-shadow: ${base_bs}0 0 0 0 ${haloEnd}; } } `; } else if(anim === 'float') { animCss = 'animation: sbm_float 2s infinite;'; kf = '@keyframes sbm_float { 0%, 100% { transform: translateY(0); } 50% { transform: translateY(-8px); } } '; } let overflowVal = anim === 'shine' ? 'hidden' : 'visible'; let hStr = ''; if(b.enableHoverBg) hStr += `background: ${b.btnHoverBg || '#005177'} !important; `; if(b.enableHoverColor) hStr += `color: ${b.btnHoverColor || '#ffffff'} !important; `; let ha = b.btnHoverAnimation || 'none'; if(ha === 'scale') hStr += `transform: scale(1.05) !important; `; else if(ha === 'lift') hStr += `transform: translateY(-4px) !important; `; else if(ha === 'push') hStr += `transform: translateY(2px) !important; `; if(anim !== 'none') hStr += `animation-play-state: paused !important; `; html += ` <style> ${kf} ${pseudoCss} #${uniqueId}::before { display: none !important; content: none !important; border: none !important; background: transparent !important;} ${anim !== 'shine' ? `#${uniqueId}::after { display: none !important; content: none !important; border: none !important; background: transparent !important;}` : ''} #${uniqueId} { position: relative !important; overflow: ${overflowVal} !important; transition: all 0.3s ease-in-out !important; z-index: 1 !important; -webkit-font-smoothing: antialiased !important; -moz-osx-font-smoothing: grayscale !important; } #${uniqueId}:hover { ${hStr} } </style> <div style="margin-top:${b.btnSpace || 20}px;text-align:${b.btnPos || 'center'};"><a id="${uniqueId}" href="${b.btnUrl}" style="display:inline-flex !important; align-items:center !important; justify-content:center !important; flex-direction:${flexDir} !important; gap:${iconGap}px !important; ${btnBgStyle} color:${b.btnColor} !important; padding:${b.btnPadY || 15}px ${b.btnPadX || 30}px !important; ${borderRule} border-radius:${b.btnRadius || 50}px !important; text-decoration:none !important; font-size:${b.btnSize || 16}px !important; font-weight:${btnWeight} !important; font-style:${btnStyleCss} !important; ${boxS} ${animCss} ${btnFontStr} box-sizing:border-box !important; line-height:1.2 !important; margin:0 !important; letter-spacing:normal !important;">${btnInner}</a></div>`; } html += `</div>`; let fontsToLoad = []; if (tFont && tFont !== 'inherit') fontsToLoad.push(tFont.replace(/\s+/g, '+')); if (bFont && bFont !== 'inherit') fontsToLoad.push(bFont.replace(/\s+/g, '+')); if (b.btnFontFamily && b.btnFontFamily !== 'inherit') fontsToLoad.push(b.btnFontFamily.replace(/\s+/g, '+')); fontsToLoad = [...new Set(fontsToLoad)]; fontsToLoad.forEach(ef => { html = `<style>@import url('https://fonts.googleapis.com/css2?family=${ef}:ital,wght@0,400;0,700;1,400;1,700&display=swap');</style>\n` + html; }); if (b.btnIconClass && b.btnIconClass !== '') { html = `<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">\n` + html; } return html; } window.downloadFolderZip = function(folder) { if (appState.licenseTier === 'free') { alert("⭐ Requires a Pro or Business license."); return; } const zip = new JSZip(); appState.boxes.filter(b => b.folder === folder).forEach(b => zip.file((b.name||'box').replace(/[^a-z0-9]/gi, '-')+'.html', generateBoxHtmlString(b))); zip.generateAsync({type:"blob"}).then(c => triggerDownload(`jbi-folder-${folder}.zip`, c, 'application/zip')); }; window.sellFolder = function(folder) { if (appState.licenseTier !== 'business') { alert("💼 Requires Business License."); return; } let key = prompt("🔒 SECURE KEY for Buyers:"); if (!key) return; const zip = new JSZip(); appState.boxes.filter(b => b.folder === folder).forEach(b => zip.file((b.name||'box').replace(/[^a-z0-9]/gi, '-')+'.html', `\n<script>if(prompt('Key?')==='${key}'){document.write(unescape('${escape(generateBoxHtmlString(b))}'));}else{alert('Invalid Key');}<\/script>`)); zip.generateAsync({type:"blob"}).then(c => triggerDownload(`jbi-sell-pack-${folder}.zip`, c, 'application/zip')); }; if (els.btnExportZip) els.btnExportZip.addEventListener('click', e => { e.preventDefault(); if (appState.licenseTier === 'free') { alert("⭐ Requires Pro/Biz license."); return; } const zip = new JSZip(); zip.file("jbi-backup.json", JSON.stringify(appState, null, 2)); appState.boxes.forEach(b => zip.folder(b.folder||'Default').file((b.name||'box').replace(/[^a-z0-9]/gi, '-')+'.html', generateBoxHtmlString(b))); zip.generateAsync({type:"blob"}).then(c => triggerDownload(`jbi-backup.zip`, c, 'application/zip')); }); if (els.btnImport) els.btnImport.addEventListener('click', e => { e.preventDefault(); els.importFileInput.click(); }); if (els.importFileInput) els.importFileInput.addEventListener('change', e => { const f = e.target.files[0]; if(!f) return; if(f.name.endsWith('.zip')) { JSZip.loadAsync(f).then(z => { if(z.file("jbi-backup.json")) z.file("jbi-backup.json").async("string").then(d => { try{ let pd=JSON.parse(d); pd.folders.forEach(fo=>{ if(!appState.folders.includes(fo)) appState.folders.push(fo);}); pd.boxes.forEach(bo=>{ let nb=JSON.parse(JSON.stringify(bo)); nb.id='box_'+Date.now()+Math.floor(Math.random()*1000); appState.boxes.push(nb);}); renderUI(); syncDataToServer(); alert("Imported!");}catch(e){} }); else alert("Invalid ZIP."); }); } else if(f.name.endsWith('.json')) { const r = new FileReader(); r.onload = evt => { try{ let pd=JSON.parse(evt.target.result); pd.folders.forEach(fo=>{ if(!appState.folders.includes(fo)) appState.folders.push(fo);}); pd.boxes.forEach(bo=>{ let nb=JSON.parse(JSON.stringify(bo)); nb.id='box_'+Date.now()+Math.floor(Math.random()*1000); appState.boxes.push(nb);}); renderUI(); syncDataToServer(); alert("Imported!");}catch(e){} }; r.readAsText(f); } if(els.importFileInput) els.importFileInput.value=''; }); renderProStatus(); renderUI(); setTimeout(() => { if (appState.boxes.length > 0 && !v(els.currentLoadedBtnId)) { window.loadBox(appState.boxes[0].id); } else { updatePreview(); } }, 100); }); // Dark mode toggle (function(){ var btn=document.getElementById('jbi-dark-toggle'); var KEY='jbi_dark_mode'; function applyMode(dark){if(dark){document.body.classList.add('jbi-dark-mode');}else{document.body.classList.remove('jbi-dark-mode');}} if(localStorage.getItem(KEY)==='true')applyMode(true); if(btn){btn.addEventListener('click',function(){var isDark=document.body.classList.contains('jbi-dark-mode');applyMode(!isDark);localStorage.setItem(KEY,String(!isDark));});} })(); </script> <?php echo ob_get_clean(); } } new Skillful_Johnson_Box(); Skillfulplugins https://validator.w3.org/feed/docs/rss2.html Auto Linker Pro Plugins Post Expiry Manager WP Image Resizer Johnson Box Johnson Box Heading Styler Heading Styler Skillful Pinterest Scheduler Simple Affiliate Link Manager Shopping Carousel CTA Studio Products Post Expiry Manager Put Your Content on Autopilot Instant Button Builder Refund Policy Terms of Service FAQ Schema Generator Test Instant Button Builder Skillful Pinterest Scheduler Auto Linker Pro WP Image Resizer FAQ Schema Generator Simple Affiliate Link Manager Blog Privacy Policy Confirmation Order History Transaction Failed Receipt Checkout CTA Studio Contact About Shopping Carousel