<?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();