GIF94;
| Path : /var/www/wordpress/wp-content/plugins/oxymade/includes/ |
| Current File : /var/www/wordpress/wp-content/plugins/oxymade/includes/admin-settings.php |
<?php
namespace OxyMade\Admin;
// Include required files
require_once plugin_dir_path(__FILE__) . 'components.php';
/**
* Register the admin menu item and settings page
*/
function register_admin_menu()
{
add_menu_page(
'OxyMade Settings',
'OxyMade',
'manage_options',
'oxymade-settings',
__NAMESPACE__ . '\\render_settings_page',
plugin_dir_url(__FILE__) . '../assets/icon.svg',
100
);
}
add_action('admin_menu', __NAMESPACE__ . '\\register_admin_menu');
/**
* Register settings
*/
function register_settings()
{
register_setting('oxymade_settings', 'oxymade_settings');
// Add settings updated notice
if (isset($_POST['option_page']) && $_POST['option_page'] === 'oxymade_settings') {
// Add nonce verification for CSRF protection
check_admin_referer('oxymade_settings-options');
add_action('admin_notices', function () {
echo '<div class="notice notice-success is-dismissible">';
echo '<p><strong>' . esc_html__('Settings saved successfully!', 'oxymade') . '</strong> ';
echo esc_html__('Your color sync preferences have been updated.', 'oxymade') . '</p>';
echo '</div>';
});
}
}
add_action('admin_init', __NAMESPACE__ . '\\register_settings');
/**
* Add settings link to plugins page
*/
function add_settings_link($links)
{
$settings_link = '<a href="' . admin_url('admin.php?page=oxymade-settings') . '">' . __('Settings', 'oxymade') . '</a>';
array_unshift($links, $settings_link);
return $links;
}
add_filter('plugin_action_links_oxymade/plugin.php', __NAMESPACE__ . '\\add_settings_link');
/**
* Redirect to settings page after plugin activation
*/
function plugin_activation_redirect($plugin)
{
if ($plugin === plugin_basename(__FILE__)) {
exit(wp_redirect(admin_url('admin.php?page=oxymade-settings')));
}
}
add_action('activated_plugin', __NAMESPACE__ . '\\plugin_activation_redirect');
/**
* Display admin notice if setup is incomplete
*/
function admin_setup_notice()
{
// Don't show on the settings page
if (isset($_GET['page']) && $_GET['page'] === 'oxymade-settings') {
return;
}
// Get setup progress
$progress = get_setup_progress();
// Only show notice if not all required steps are complete
if ($progress['required_completed'] < $progress['required_total']) {
$missing_steps = array_filter($progress['steps'], function($step) {
return $step['required'] && !$step['completed'];
});
$missing_names = array_map(function($step) {
return $step['title'];
}, $missing_steps);
$missing_count = count($missing_names);
?>
<div class="notice notice-warning is-dismissible">
<p>
<strong>OxyMade Setup Incomplete:</strong>
<?php echo $missing_count; ?> required step<?php echo $missing_count > 1 ? 's' : ''; ?> remaining
(<?php echo implode(', ', $missing_names); ?>).
<a href="<?php echo admin_url('admin.php?page=oxymade-settings'); ?>">Complete setup now</a>
to use OxyMade with Oxygen.
</p>
</div>
<?php
}
}
add_action('admin_notices', __NAMESPACE__ . '\\admin_setup_notice');
/**
* Get setup progress information
*
* @return array Array with progress data
*/
function get_setup_progress() {
$steps = [
[
'id' => 'design_set',
'title' => 'Design Set',
'completed' => (bool) get_option('oxymade_default_designset_template', false),
'required' => true
],
[
'id' => 'variables',
'title' => 'Variables',
'completed' => (bool) get_option('oxymade_variables_synced', false),
'required' => true
],
[
'id' => 'typography',
'title' => 'Typography',
'completed' => (bool) get_option('oxymade_typography_installed', false),
'required' => true
],
[
'id' => 'global_settings',
'title' => 'Global Settings',
'completed' => (bool) get_option('oxymade_global_settings_installed', false),
'required' => false
],
[
'id' => 'selectors',
'title' => 'Selectors',
'completed' => (bool) get_option('oxymade_global_settings_installed', false),
'required' => true
],
[
'id' => 'components',
'title' => 'Components',
'completed' => (bool) get_option('oxymade_components_registered', false),
'required' => true
]
];
$completed_count = count(array_filter($steps, function($step) {
return $step['completed'];
}));
$required_steps = array_filter($steps, function($step) {
return $step['required'];
});
$required_completed = count(array_filter($required_steps, function($step) {
return $step['completed'];
}));
// Find next incomplete step
$next_step = null;
foreach ($steps as $index => $step) {
if (!$step['completed']) {
$next_step = $index + 1; // Step numbers start at 1
break;
}
}
return [
'steps' => $steps,
'total' => count($steps),
'completed' => $completed_count,
'required_total' => count($required_steps),
'required_completed' => $required_completed,
'percentage' => ($completed_count / count($steps)) * 100,
'next_step' => $next_step,
'all_complete' => $completed_count === count($steps)
];
}
/**
* Render the settings page
*/
function render_settings_page()
{
// Get license data from SureCart + Custom API
$license_data = [];
$is_license_active = false;
$license_status_display = 'Activate License';
// Check if license class exists and get data
if (class_exists('\\OxyMade\\License\\LicenseCheck')) {
$license_data = \OxyMade\License\LicenseCheck::get_license_data();
$is_license_active = $license_data['is_active'] ?? false;
$license_status_display = \OxyMade\License\LicenseCheck::get_license_status_display();
}
// Get setup progress
$progress = get_setup_progress();
// Check if variables are synced (this covers all variables including colors)
$variables_synced = get_option('oxymade_variables_synced', false);
$selectors_synced = file_exists(OXYMADE_PLUGIN_DIR . 'data/selectors-only.json');
$components_registered = get_option('oxymade_components_registered', false);
// For backward compatibility, also check if color palette exists
$palette_exists = get_option('oxymade_color_palette', false);
// Typography presets are optional - no requirement check needed
// Welcome wizard visibility: show when setup is mostly not done and user hasn't dismissed
$current_user_id = get_current_user_id();
$wizard_dismissed = get_user_meta($current_user_id, 'oxymade_wizard_dismissed', true);
$show_wizard = ($progress['completed'] <= 1) && !$wizard_dismissed;
?>
<style>
/* OxyMade Admin Styling */
.oxymade-admin-wrapper {
position: relative;
}
/* Menu Icon Styling */
#adminmenu .toplevel_page_oxymade-settings .wp-menu-image img {
width: 16px !important;
height: 16px !important;
}
/* Notice Styling - Position above heading */
.oxymade-admin-wrapper {
margin-top: 0;
}
/* Header Styling */
.oxymade-settings-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 30px;
margin-top: 24px;
padding: 10px 20px;
}
.oxymade-title-section {
flex: 1;
}
.oxymade-title-section h1 {
margin: 0;
font-size: 28px;
font-weight: 700;
color: #1f2937;
line-height: 1.2;
}
/* License Status Styling - Minimal */
.oxymade-license-status {
display: inline-flex;
align-items: center;
gap: 6px;
padding: 8px 14px;
font-size: 12px;
font-weight: 600;
border: 1px solid;
border-radius: 6px;
transition: opacity 0.15s ease;
margin-right: 16px;
white-space: nowrap;
cursor: pointer;
}
.oxymade-license-status.active {
background: #22c55e;
border-color: #22c55e;
color: white;
}
.oxymade-license-status.active:hover {
opacity: 0.9;
}
.oxymade-license-status.inactive {
background: #ef4444;
border-color: #ef4444;
color: white;
}
.oxymade-license-status.inactive:hover {
opacity: 0.9;
}
.oxymade-license-status .dashicons {
font-size: 14px;
width: 14px;
height: 14px;
}
/* Button Styling */
.oxymade-license-actions {
display: flex;
gap: 12px;
align-items: center;
flex-wrap: wrap;
}
.oxymade-license-actions .button {
padding: 10px 20px;
font-weight: 600;
font-size: 12px;
border: none;
transition: all 0.2s ease;
text-decoration: none;
display: inline-flex;
align-items: center;
gap: 8px;
}
.oxymade-license-actions .button-primary {
background: #f97316;
color: white;
box-shadow: 0 1px 3px rgba(249, 115, 22, 0.3);
}
.oxymade-license-actions .button-primary:hover {
background: #ea580c;
transform: translateY(-1px);
box-shadow: 0 2px 6px rgba(249, 115, 22, 0.4);
}
.oxymade-license-actions .button-secondary {
background: #f8fafc;
color: #64748b;
border: 1px solid #e2e8f0;
}
.oxymade-license-actions .button-secondary:hover {
background: #f1f5f9;
color: #475569;
transform: translateY(-1px);
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
}
.oxymade-license-actions .dashicons {
font-size: 16px;
}
/* Progress Bar Styles */
.oxymade-progress-container {
background: white;
border-radius: 12px;
padding: 20px 24px;
margin: 20px 0;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05);
border: 1px solid #e5e7eb;
}
.oxymade-progress-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 16px;
}
.oxymade-progress-title {
font-size: 14px;
font-weight: 600;
color: #374151;
}
.oxymade-progress-stats {
font-size: 13px;
color: #6b7280;
font-weight: 500;
}
.oxymade-progress-bar-track {
height: 8px;
background: #f3f4f6;
border-radius: 999px;
overflow: hidden;
position: relative;
}
.oxymade-progress-bar-fill {
height: 100%;
background: linear-gradient(90deg, #3b82f6, #2563eb);
border-radius: 999px;
transition: width 0.6s cubic-bezier(0.4, 0, 0.2, 1);
box-shadow: 0 0 10px rgba(59, 130, 246, 0.3);
}
.oxymade-progress-bar-fill.complete {
background: linear-gradient(90deg, #22c55e, #16a34a);
box-shadow: 0 0 10px rgba(34, 197, 94, 0.3);
}
.oxymade-progress-steps {
display: flex;
gap: 8px;
margin-top: 12px;
flex-wrap: wrap;
}
.oxymade-progress-step {
display: inline-flex;
align-items: center;
gap: 6px;
padding: 4px 10px;
border-radius: 6px;
font-size: 12px;
font-weight: 500;
background: #f9fafb;
border: 1px solid #e5e7eb;
color: #6b7280;
}
.oxymade-progress-step.completed {
background: #f0fdf4;
border-color: #86efac;
color: #16a34a;
}
.oxymade-progress-step.next {
background: #eff6ff;
border-color: #93c5fd;
color: #2563eb;
animation: pulse 2s infinite;
}
.oxymade-progress-step .dashicons {
font-size: 14px;
}
.oxymade-progress-close {
background: none;
border: none;
color: #9ca3af;
cursor: pointer;
padding: 4px;
display: inline-flex;
align-items: center;
justify-content: center;
border-radius: 4px;
transition: all 0.2s;
}
.oxymade-progress-close:hover {
background: #f3f4f6;
color: #6b7280;
}
.oxymade-progress-close .dashicons {
font-size: 18px;
}
.oxymade-progress-container.hidden {
display: none;
}
@keyframes pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.7; }
}
/* Highlight Next Step */
.oxymade-admin-card.next-step {
border: 2px solid #3b82f6;
box-shadow: 0 4px 6px -1px rgba(59, 130, 246, 0.1), 0 2px 4px -1px rgba(59, 130, 246, 0.06);
position: relative;
}
.oxymade-admin-card.next-step::before {
content: "▶ Next Step";
position: absolute;
top: -12px;
left: 16px;
background: linear-gradient(90deg, #3b82f6, #2563eb);
color: white;
padding: 4px 12px;
border-radius: 12px;
font-size: 11px;
font-weight: 600;
letter-spacing: 0.5px;
text-transform: uppercase;
box-shadow: 0 2px 8px rgba(59, 130, 246, 0.3);
}
.oxymade-admin-card.next-step .button {
animation: pulse-button 2s infinite;
}
@keyframes pulse-button {
0%, 100% {
transform: scale(1);
box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
}
50% {
transform: scale(1.02);
box-shadow: 0 4px 6px -1px rgba(59, 130, 246, 0.2), 0 2px 4px -1px rgba(59, 130, 246, 0.1);
}
}
/* Responsive Design */
@media (max-width: 782px) {
.oxymade-settings-header {
flex-direction: column;
gap: 20px;
align-items: stretch;
}
.oxymade-license-actions {
justify-content: flex-start;
flex-wrap: wrap;
gap: 8px;
}
.oxymade-license-status {
margin-right: 8px;
margin-bottom: 8px;
}
}
</style>
<div class="wrap oxymade-admin-wrapper">
<div class="oxymade-settings-header">
<div class="oxymade-title-section">
<h1 style="display: flex; align-items: center; gap: 10px; margin: 0; font-size: 28px; font-weight: 700; color: #1f2937;">
<img src="<?php echo plugin_dir_url(__FILE__) . '../assets/logo.svg'; ?>" alt="OxyMade Framework" style="height: 32px; width: auto;">
<span style="background: #f3f4f6; color: #6b7280; padding: 4px 10px; border-radius: 6px; font-size: 10px; font-weight: 600; letter-spacing: 0.5px; text-transform: uppercase; border: 1px solid #e5e7eb;">OXYGEN 6.0</span>
<span style="background: #f3f4f6; color: #6b7280; padding: 4px 10px; border-radius: 6px; font-size: 11px; font-weight: 500; border: 1px solid #e5e7eb;">v<?php echo OXYMADE_VERSION; ?></span>
<button type="button" class="oxymade-license-status <?php echo $is_license_active ? 'active' : 'inactive'; ?>" id="oxymade-license-trigger" style="margin: 0;">
<span class="dashicons <?php echo $is_license_active ? 'dashicons-yes-alt' : 'dashicons-warning'; ?>"></span>
<?php echo esc_html($license_status_display); ?>
</button>
</h1>
</div>
<div class="oxymade-license-actions">
<!-- 1rem = 10px Notice -->
<div style="background: #f0f9ff; border: 1px solid #0ea5e9; border-radius: 6px; padding: 6px 12px;">
<div style="display: flex; align-items: center; gap: 6px;">
<span class="dashicons dashicons-info" style="color: #0ea5e9; font-size: 14px;"></span>
<span style="font-size: 14px; color: #0369a1; font-weight: 500;">
<strong>1rem = 10px</strong> - Easier mental math
</span>
</div>
</div>
</div>
</div>
<?php
// Check for missing required plugins
$missing_plugins = \OxygenCustomElements\get_missing_plugins();
if (!empty($missing_plugins)):
$missing_names = array_values($missing_plugins);
?>
<!-- Required Plugins Warning - Minimal -->
<div style="display: flex; align-items: center; gap: 10px; background: #fffbeb; border: 1px solid #fcd34d; border-radius: 6px; padding: 10px 14px; margin-bottom: 16px;">
<span class="dashicons dashicons-warning" style="color: #d97706; font-size: 18px;"></span>
<span style="color: #92400e; font-size: 13px;">
<strong>Missing:</strong> <?php echo esc_html(implode(', ', $missing_names)); ?>
</span>
<a href="<?php echo admin_url('plugins.php'); ?>" style="margin-left: auto; color: #d97706; font-size: 12px; font-weight: 500; text-decoration: none;">
Install →
</a>
</div>
<?php endif; ?>
<?php
// Check if design set was changed and needs reinstall
$needs_reinstall = get_option('oxymade_design_set_needs_reinstall', false);
if ($needs_reinstall):
$current_design_set = get_option('oxymade_default_designset_template', 'Layers');
// Check which steps are already completed after design set change
$step2_done = get_option('oxymade_reinstall_step2_done', false);
$step3_done = get_option('oxymade_reinstall_step3_done', false);
$step4_done = get_option('oxymade_reinstall_step4_done', false);
?>
<!-- Design Set Changed Warning - Dynamic Status -->
<div id="oxymade-design-set-warning" style="display: flex; align-items: center; gap: 10px; background: #fffbeb; border: 1px solid #fcd34d; border-radius: 6px; padding: 10px 14px; margin-bottom: 16px;">
<span class="dashicons dashicons-update" style="color: #d97706; font-size: 18px;"></span>
<span style="color: #92400e; font-size: 13px;">
<strong>Design set changed to <?php echo esc_html($current_design_set); ?>.</strong>
<?php if ($step2_done): ?>
<span style="color: #16a34a;"><span class="dashicons dashicons-yes" style="font-size: 14px; width: 14px; height: 14px; vertical-align: middle;"></span> Step 2</span>
<?php else: ?>
<span><strong>Step 2:</strong> Re-install Colors & Spacing</span>
<?php endif; ?>
<span style="color:#d4a574;">•</span>
<?php if ($step3_done): ?>
<span style="color: #16a34a;"><span class="dashicons dashicons-yes" style="font-size: 14px; width: 14px; height: 14px; vertical-align: middle;"></span> Step 3</span>
<?php else: ?>
<span><strong>Step 3:</strong> Re-install Typography</span>
<?php endif; ?>
<span style="color:#d4a574;">•</span>
<?php if ($step4_done): ?>
<span style="color: #16a34a;"><span class="dashicons dashicons-yes" style="font-size: 14px; width: 14px; height: 14px; vertical-align: middle;"></span> Step 4</span>
<?php else: ?>
<span><strong>Step 4:</strong> Re-install Global Settings</span>
<?php endif; ?>
</span>
</div>
<?php endif; ?>
<!-- Setup Progress Tracker -->
<?php
$current_user_id = get_current_user_id();
$hide_progress = get_user_meta($current_user_id, 'oxymade_hide_progress_tracker', true);
$progress_hidden_class = $hide_progress ? ' hidden' : '';
?>
<div class="oxymade-progress-container<?php echo $progress_hidden_class; ?>">
<div class="oxymade-progress-header">
<div class="oxymade-progress-title">
<?php if ($progress['all_complete']): ?>
🎉 Setup Complete!
<?php else: ?>
Setup Progress
<?php endif; ?>
</div>
<div class="oxymade-progress-stats" style="display: flex; align-items: center; gap: 12px;">
<span>
<?php echo $progress['completed']; ?> of <?php echo $progress['total']; ?> steps
<?php if ($progress['next_step']): ?>
· Next: Step <?php echo $progress['next_step']; ?>
<?php endif; ?>
</span>
<?php if ($progress['all_complete']): ?>
<button type="button" class="oxymade-progress-close" id="oxymade-close-progress" title="Hide progress tracker">
<span class="dashicons dashicons-no-alt"></span>
</button>
<?php endif; ?>
</div>
</div>
<div class="oxymade-progress-bar-track">
<div class="oxymade-progress-bar-fill <?php echo $progress['all_complete'] ? 'complete' : ''; ?>"
style="width: <?php echo round($progress['percentage']); ?>%;">
</div>
</div>
<div class="oxymade-progress-steps">
<?php foreach ($progress['steps'] as $index => $step): ?>
<?php
$step_num = $index + 1;
$is_next = $progress['next_step'] === $step_num;
$classes = 'oxymade-progress-step';
if ($step['completed']) {
$classes .= ' completed';
} elseif ($is_next) {
$classes .= ' next';
}
?>
<span class="<?php echo $classes; ?>">
<span class="dashicons dashicons-<?php echo $step['completed'] ? 'yes-alt' : ($is_next ? 'arrow-right-alt' : 'minus'); ?>"></span>
<?php echo $step_num; ?>. <?php echo $step['title']; ?>
<?php if (!$step['required']): ?>
<span style="opacity: 0.6;">(Optional)</span>
<?php endif; ?>
</span>
<?php endforeach; ?>
</div>
</div>
<div class="oxymade-admin-container">
<?php if ($progress['next_step'] && $progress['steps'][0]['completed'] && !$progress['all_complete'] && !$show_wizard): ?>
<!-- Complete Setup Banner -->
<div id="oxymade-setup-banner" style="display: flex; align-items: center; justify-content: space-between; background: #f0f9ff; border: 1px solid #bae6fd; border-radius: 8px; padding: 14px 20px; margin-bottom: 20px;">
<div style="display: flex; align-items: center; gap: 10px;">
<span class="dashicons dashicons-controls-play" style="font-size: 20px; color: #0284c7;"></span>
<span style="font-size: 14px; color: #1f2937;">Finish remaining steps in one click</span>
</div>
<button type="button" id="oxymade-complete-setup" class="button button-primary" style="font-size: 13px; padding: 6px 20px; height: auto; background: #0284c7; border-color: #0284c7;">Complete Setup</button>
</div>
<?php endif; ?>
<!-- Steps 1-3 Grid -->
<div class="oxymade-steps-grid">
<div class="oxymade-admin-card <?php echo ($progress['next_step'] === 1) ? 'next-step' : ''; ?>">
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 15px; padding-bottom: 15px; border-bottom: 1px solid #e5e7eb;">
<h3><span class="step-badge">1</span> Default Design Set</h3>
</div>
<p>Choose or change the default design set for your site. This will determine the base styling and colors used throughout your Oxygen site.</p>
<?php $current_design_set = \get_option('oxymade_default_designset_template', false); ?>
<div style="display: inline-flex; align-items: center; gap: 12px;">
<?php if ($current_design_set): ?>
<div style="display: inline-flex; align-items: center; gap: 6px;">
<span class="dashicons dashicons-yes-alt" style="color: #22c55e; font-size: 18px;"></span>
<span style="font-weight: 500; font-size: 14px;"><?php echo \esc_html($current_design_set); ?></span>
</div>
<button type="button" class="button button-secondary" id="oxymade-install-design-set">
<span class="dashicons dashicons-art"></span>
<?php \esc_html_e('Change Design Set', 'oxymade'); ?>
</button>
<?php else: ?>
<div style="display: inline-flex; align-items: center; gap: 6px;">
<span class="dashicons dashicons-minus" style="color: #9ca3af; font-size: 18px;"></span>
<span style="color: #6b7280; font-weight: 500; font-size: 14px;">Pending</span>
</div>
<button type="button" class="button button-secondary" id="oxymade-install-design-set">
<span class="dashicons dashicons-art"></span>
<?php \esc_html_e('Install Design Set', 'oxymade'); ?>
</button>
<?php endif; ?>
</div>
<div id="oxymade-design-set-result" style="display: none;"></div>
</div>
<div class="oxymade-admin-card <?php echo ($progress['next_step'] === 2) ? 'next-step' : ''; ?>">
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 15px; padding-bottom: 15px; border-bottom: 1px solid #e5e7eb;">
<h3><span class="step-badge">2</span> Variables</h3>
<!-- Toggles -->
<div style="display: flex; align-items: center; gap: 12px;">
<!-- Fluid Spacing Toggle -->
<div style="display: flex; align-items: center; gap: 8px;">
<span style="font-size: 13px; color: #6b7280; font-weight: 500;">Fluid</span>
<?php
$current_settings = get_option('oxymade_settings', []);
if (!is_array($current_settings)) {
$current_settings = [];
}
$fluid_spacing = $current_settings['fluid_spacing'] ?? true;
?>
<label class="oxymade-toggle-switch" style="display: flex; align-items: center; gap: 8px;">
<input type="hidden" name="oxymade_settings[fluid_spacing]" value="0">
<input type="checkbox" name="oxymade_settings[fluid_spacing]" value="1" <?php checked($fluid_spacing, true); ?>>
<span class="oxymade-slider"></span>
</label>
<span class="dashicons dashicons-editor-help" style="color: #9ca3af; cursor: help;" data-tooltip="When enabled, spacing variables use fluid clamp() values. When disabled, they use fixed px values. Note: 1rem = 10px for easier calculations."></span>
</div>
<!-- Sync Toggle -->
<div style="display: flex; align-items: center; gap: 8px;">
<span style="font-size: 13px; color: #6b7280; font-weight: 500;">Sync</span>
<?php
$sync_with_oxygen = $current_settings['sync_with_oxygen'] ?? true;
?>
<label class="oxymade-toggle-switch" style="display: flex; align-items: center; gap: 8px;">
<input type="hidden" name="oxymade_settings[sync_with_oxygen]" value="0">
<input type="checkbox" name="oxymade_settings[sync_with_oxygen]" value="1" <?php checked($sync_with_oxygen, true); ?>>
<span class="oxymade-slider"></span>
</label>
<span class="dashicons dashicons-editor-help" style="color: #9ca3af; cursor: help;" data-tooltip="When enabled, OxyMade colors will sync with Oxygen color palette automatically on auto-save. When disabled, colors will only be saved to the database without syncing."></span>
</div>
</div>
</div>
<p>Install color and spacing variables for your design system. Includes color palette, spacing scale (s1-s72), and radius values based on 1rem = 10px.</p>
<div style="display: inline-flex; align-items: center; gap: 12px;">
<?php if ($variables_synced): ?>
<div style="display: inline-flex; align-items: center; gap: 6px;">
<span class="dashicons dashicons-yes-alt" style="color: #22c55e; font-size: 18px;"></span>
<span style="font-weight: 500; font-size: 14px;">Installed</span>
</div>
<button type="button" class="button button-secondary" id="oxymade-install-palette">
<span class="dashicons dashicons-admin-appearance"></span>
<?php \esc_html_e('Re-Install Colors & Spacing', 'oxymade'); ?>
</button>
<?php else: ?>
<div style="display: inline-flex; align-items: center; gap: 6px;">
<span class="dashicons dashicons-minus" style="color: #9ca3af; font-size: 18px;"></span>
<span style="color: #6b7280; font-weight: 500; font-size: 14px;">Pending</span>
</div>
<button type="button" class="button button-secondary" id="oxymade-install-palette">
<span class="dashicons dashicons-admin-appearance"></span>
<?php \esc_html_e('Install Colors & Spacing', 'oxymade'); ?>
</button>
<?php endif; ?>
</div>
<div id="oxymade-palette-result" style="display: none;"></div>
</div>
<div class="oxymade-admin-card <?php echo ($progress['next_step'] === 3) ? 'next-step' : ''; ?>">
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 15px; padding-bottom: 15px; border-bottom: 1px solid #e5e7eb;">
<h3><span class="step-badge">3</span> Typography</h3>
<div style="display: flex; align-items: center; gap: 12px;">
<?php
$current_settings = get_option('oxymade_settings', []);
if (!is_array($current_settings)) {
$current_settings = [];
}
$fluid_text_sizing = $current_settings['fluid_text_sizing'] ?? true;
?>
<!-- Fluid Toggle -->
<div style="display: flex; align-items: center; gap: 8px;">
<span style="font-size: 13px; color: #6b7280; font-weight: 500;">Fluid</span>
<label class="oxymade-toggle-switch" style="display: flex; align-items: center; gap: 8px;">
<input type="hidden" name="oxymade_settings[fluid_text_sizing]" value="0">
<input type="checkbox" name="oxymade_settings[fluid_text_sizing]" value="1" <?php checked($fluid_text_sizing, true); ?> id="oxymade-fluid-text-sizing">
<span class="oxymade-slider"></span>
</label>
<span class="dashicons dashicons-editor-help" style="color: #9ca3af; cursor: help;" data-tooltip="When enabled, text sizes (text-xs through text-7xl) use fluid clamp() values. When disabled, they use fixed px values."></span>
</div>
</div>
</div>
<p>Install typography (font size) variables. Toggle fluid for responsive text sizes. Click the gear icon to customize heading sizes.</p>
<?php
$typography_installed = get_option('oxymade_typography_installed', false);
?>
<div style="display: inline-flex; align-items: center; gap: 8px;">
<?php if ($typography_installed): ?>
<div style="display: inline-flex; align-items: center; gap: 6px;">
<span class="dashicons dashicons-yes-alt" style="color: #22c55e; font-size: 18px;"></span>
<span style="font-weight: 500; font-size: 14px;">Installed</span>
</div>
<button type="button" class="button button-secondary" id="oxymade-install-typography">
<span class="dashicons dashicons-editor-textcolor"></span>
<?php \esc_html_e('Re-Install Typography', 'oxymade'); ?>
</button>
<?php else: ?>
<div style="display: inline-flex; align-items: center; gap: 6px;">
<span class="dashicons dashicons-minus" style="color: #9ca3af; font-size: 18px;"></span>
<span style="color: #6b7280; font-weight: 500; font-size: 14px;">Pending</span>
</div>
<button type="button" class="button button-secondary" id="oxymade-install-typography">
<span class="dashicons dashicons-editor-textcolor"></span>
<?php \esc_html_e('Install Typography', 'oxymade'); ?>
</button>
<?php endif; ?>
<button type="button" class="button button-secondary" id="oxymade-typography-config" title="Configure Typography Settings">
<span class="dashicons dashicons-admin-generic"></span>
</button>
</div>
<div id="oxymade-typography-result" style="display: none;"></div>
</div>
<div class="oxymade-admin-card <?php echo ($progress['next_step'] === 4) ? 'next-step' : ''; ?>">
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 15px; padding-bottom: 15px; border-bottom: 1px solid #e5e7eb;">
<h3><span class="step-badge">4</span> Global Settings</h3>
</div>
<p>Install the default OxyMade global settings. This includes container settings, spacing configurations, and other global settings that work seamlessly with Oxygen.</p>
<?php
$global_settings_installed = get_option('oxymade_global_settings_installed', false);
?>
<div style="display: inline-flex; align-items: center; gap: 12px;">
<?php if ($global_settings_installed): ?>
<div style="display: inline-flex; align-items: center; gap: 6px;">
<span class="dashicons dashicons-yes-alt" style="color: #22c55e; font-size: 18px;"></span>
<span style="font-weight: 500; font-size: 14px;">Installed</span>
</div>
<button type="button" class="button button-secondary" id="oxymade-install-global-settings">
<span class="dashicons dashicons-admin-settings"></span>
<?php \esc_html_e('Re-Install Global Settings', 'oxymade'); ?>
</button>
<?php else: ?>
<div style="display: inline-flex; align-items: center; gap: 6px;">
<span class="dashicons dashicons-minus" style="color: #9ca3af; font-size: 18px;"></span>
<span style="color: #6b7280; font-weight: 500; font-size: 14px;">Pending</span>
</div>
<button type="button" class="button button-secondary" id="oxymade-install-global-settings">
<span class="dashicons dashicons-admin-settings"></span>
<?php \esc_html_e('Install Global Settings', 'oxymade'); ?>
</button>
<?php endif; ?>
</div>
<div id="oxymade-global-settings-result" style="display: none;"></div>
</div>
<!-- Step 5 - Selectors -->
<div class="oxymade-admin-card <?php echo ($progress['next_step'] === 5) ? 'next-step' : ''; ?>">
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 15px; padding-bottom: 15px; border-bottom: 1px solid #e5e7eb;">
<h3><span class="step-badge">5</span> Selectors</h3>
<div style="display: flex; align-items: center; gap: 8px;">
<span style="font-size: 13px; color: #6b7280; font-weight: 500;">Autocomplete</span>
<?php $autocomplete_enabled = !in_array(get_option('oxymade_autocomplete_enabled', '1'), ['0', '', false], true); ?>
<label class="oxymade-toggle-switch" style="display: flex; align-items: center; gap: 8px;">
<input type="hidden" name="oxymade_settings[autocomplete_enabled]" value="0">
<input type="checkbox" name="oxymade_settings[autocomplete_enabled]" value="1" <?php checked($autocomplete_enabled); ?>>
<span class="oxymade-slider"></span>
</label>
<span class="dashicons dashicons-editor-help" style="color: #9ca3af; cursor: help;" data-tooltip="When enabled, OxyMade shows its own autocomplete dropdown in the builder class input for quick class assignment. A fix button in the toolbar lets you batch-fix all OxyMade classes at once incase of any issues."></span>
</div>
</div>
<p style="color: #6b7280; margin: 0 0 12px;">OxyMade classes are loaded on-demand via our autocomplete in the builder class input. A fix button (<span class="dashicons dashicons-admin-tools" style="font-size: 14px; width: 14px; height: 14px; vertical-align: middle;"></span>) in the toolbar batch-injects all referenced classes at once incase of any issues.</p>
<div style="display: inline-flex; align-items: center; gap: 12px;">
<?php if ($selectors_synced): ?>
<div style="display: inline-flex; align-items: center; gap: 6px;">
<span class="dashicons dashicons-yes-alt" style="color: #22c55e; font-size: 18px;"></span>
<span style="font-weight: 500; font-size: 14px;">Active</span>
</div>
<button type="button" class="button button-secondary" id="oxymade-fix-styling" style="color: #a78bfa; border-color: #a78bfa; background: #faf5ff;" title="Scan all pages and restore used OxyMade classes">
<span class="dashicons dashicons-admin-tools"></span>
<?php \esc_html_e('Fix Styling', 'oxymade'); ?>
</button>
<button type="button" class="button button-secondary" id="oxymade-delete-selectors" style="color: #dc2626; border-color: #dc2626; background: #fef2f2;" title="Delete Selectors">
<span class="dashicons dashicons-trash"></span>
<?php \esc_html_e('Delete', 'oxymade'); ?>
</button>
<?php else: ?>
<button type="button" class="button button-secondary" id="oxymade-restore-selectors" style="color: #0284c7; border-color: #0284c7; background: #f0f9ff;" title="Scan all pages and restore OxyMade classes">
<span class="dashicons dashicons-image-rotate"></span>
<?php \esc_html_e('Restore Selectors', 'oxymade'); ?>
</button>
<?php endif; ?>
</div>
<div id="oxymade-selectors-result" style="display: none;"></div>
</div>
<!-- Step 6 - Components -->
<div class="oxymade-admin-card <?php echo ($progress['next_step'] === 6) ? 'next-step' : ''; ?>">
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 15px; padding-bottom: 15px; border-bottom: 1px solid #e5e7eb;">
<h3><span class="step-badge">6</span> Components</h3>
<!-- Toggles -->
<div style="display: flex; align-items: center; gap: 12px;">
<!-- Update Existing Toggle -->
<div style="display: flex; align-items: center; gap: 8px;">
<span style="font-size: 13px; color: #6b7280; font-weight: 500;">Update Existing</span>
<?php
$current_settings = get_option('oxymade_settings', []);
if (!is_array($current_settings)) {
$current_settings = [];
}
$update_existing_components = $current_settings['update_existing_components'] ?? true;
?>
<label class="oxymade-toggle-switch" style="display: flex; align-items: center; gap: 8px;">
<input type="hidden" name="oxymade_settings[update_existing_components]" value="0">
<input type="checkbox" name="oxymade_settings[update_existing_components]" value="1" <?php checked($update_existing_components, true); ?> id="update-existing-components">
<span class="oxymade-slider"></span>
</label>
<span class="dashicons dashicons-editor-help" style="color: #9ca3af; cursor: help;" data-tooltip="When enabled, existing components will be updated with latest framework styles. When disabled, only new components will be added."></span>
</div>
</div>
</div>
<p>Import framework components to Oxygen. When "Sync" is enabled in Step 3, heading components will also use font weight, line height, and letter spacing from your design set presets.</p>
<?php
$current_design_set = get_option('oxymade_default_designset_template', false);
$components_registered = get_option('oxymade_components_registered', false);
?>
<div style="display: inline-flex; align-items: center; gap: 12px;">
<?php if ($components_registered): ?>
<div style="display: inline-flex; align-items: center; gap: 6px;">
<span class="dashicons dashicons-yes-alt" style="color: #22c55e; font-size: 18px;"></span>
<span style="font-weight: 500; font-size: 14px;">Active</span>
</div>
<button type="button" class="button button-secondary" id="oxymade-import-components">
<span class="dashicons dashicons-layout"></span>
<?php \esc_html_e('Re-Import Components', 'oxymade'); ?>
</button>
<?php else: ?>
<div style="display: inline-flex; align-items: center; gap: 6px;">
<span class="dashicons dashicons-minus" style="color: #9ca3af; font-size: 18px;"></span>
<span style="color: #6b7280; font-weight: 500; font-size: 14px;">Inactive</span>
</div>
<button type="button" class="button button-secondary" id="oxymade-import-components">
<span class="dashicons dashicons-layout"></span>
<?php \esc_html_e('Import Components', 'oxymade'); ?>
</button>
<?php endif; ?>
</div>
<div id="oxymade-components-result" style="display: none;"></div>
</div>
</div>
</div> <!-- End of oxymade-steps-grid -->
<!-- Documentation Section -->
<div class="oxymade-documentation-section" style="margin-top: 40px; padding-top: 30px; border-top: 1px solid #e5e7eb;">
<h2 style="margin-bottom: 24px; color: #111827; font-size: 18px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.025em;">🚀 Ready to Build with OxyMade</h2>
<div style="display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 16px; margin-bottom: 30px;">
<!-- Blocks Documentation -->
<div class="oxymade-doc-box" style="background: rgba(255, 255, 255, 0.9); backdrop-filter: blur(8px); border: 1px solid rgba(229, 231, 235, 0.6); border-radius: 8px; padding: 24px; box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1), 0 1px 2px rgba(0, 0, 0, 0.06); transition: all 0.2s ease;">
<div style="display: flex; align-items: center; margin-bottom: 16px;">
<span style="background: #0ea5e9; color: white; padding: 6px 12px; border-radius: 6px; font-size: 12px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.025em; margin-right: 12px;">BLOCKS</span>
<h3 style="margin: 0; color: #111827; font-size: 16px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.025em;">Ready-to-Use Blocks</h3>
</div>
<div style="color: #6b7280; line-height: 1.6; font-size: 14px;">
<p style="margin: 0 0 12px 0; font-weight: 600; color: #374151; text-transform: uppercase; font-size: 12px; letter-spacing: 0.025em;">What's Included:</p>
<ul style="margin: 0 0 12px 0; padding-left: 20px;">
<li>✅ Hero Sections</li>
<li>✅ Feature Blocks</li>
<li>✅ Pricing Tables</li>
<li>✅ Testimonials</li>
</ul>
<p style="margin: 0 0 8px 0; font-weight: 600; color: #374151; text-transform: uppercase; font-size: 12px; letter-spacing: 0.025em;">Best For:</p>
<p style="margin: 0; font-size: 14px;">Building custom pages with professional components. Copy individual blocks and customize them for your needs.</p>
<p style="margin: 8px 0 0 0; font-size: 14px;"><strong>Copy Codes:</strong> Use OxyMade (native) framework blocks from <a href="https://oxymade.com/blocks" target="_blank" style="color: #0ea5e9; text-decoration: none; font-weight: 500;">oxymade.com/blocks</a></p>
</div>
</div>
<!-- Templates Documentation -->
<div class="oxymade-doc-box" style="background: rgba(255, 255, 255, 0.9); backdrop-filter: blur(8px); border: 1px solid rgba(229, 231, 235, 0.6); border-radius: 8px; padding: 24px; box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1), 0 1px 2px rgba(0, 0, 0, 0.06); transition: all 0.2s ease;">
<div style="display: flex; align-items: center; margin-bottom: 16px;">
<span style="background: #10b981; color: white; padding: 6px 12px; border-radius: 6px; font-size: 12px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.025em; margin-right: 12px;">TEMPLATES</span>
<h3 style="margin: 0; color: #111827; font-size: 16px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.025em;">Full Page Templates</h3>
</div>
<div style="color: #6b7280; line-height: 1.6; font-size: 14px;">
<p style="margin: 0 0 12px 0; font-weight: 600; color: #374151; text-transform: uppercase; font-size: 12px; letter-spacing: 0.025em;">What's Included:</p>
<ul style="margin: 0 0 12px 0; padding-left: 20px;">
<li>✅ Landing Pages</li>
<li>✅ About Pages</li>
<li>✅ Contact Pages</li>
<li>✅ Blog Layouts</li>
</ul>
<p style="margin: 0 0 8px 0; font-weight: 600; color: #374151; text-transform: uppercase; font-size: 12px; letter-spacing: 0.025em;">Best For:</p>
<p style="margin: 0; font-size: 14px;">Complete page designs ready to use. Perfect for quick website launches and professional layouts.</p>
<p style="margin: 8px 0 0 0; font-size: 14px;"><strong>Copy Codes:</strong> Use OxyMade (native) framework templates from <a href="https://oxymade.com/templates" target="_blank" style="color: #10b981; text-decoration: none; font-weight: 500;">oxymade.com/templates</a></p>
</div>
</div>
<!-- All Access Documentation -->
<div class="oxymade-doc-box" style="background: rgba(255, 255, 255, 0.9); backdrop-filter: blur(8px); border: 1px solid rgba(229, 231, 235, 0.6); border-radius: 8px; padding: 24px; box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1), 0 1px 2px rgba(0, 0, 0, 0.06); transition: all 0.2s ease;">
<div style="display: flex; align-items: center; margin-bottom: 16px;">
<span style="background: #f59e0b; color: white; padding: 6px 12px; border-radius: 6px; font-size: 12px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.025em; margin-right: 12px;">ALL ACCESS</span>
<h3 style="margin: 0; color: #111827; font-size: 16px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.025em;">Complete Library</h3>
</div>
<div style="color: #6b7280; line-height: 1.6; font-size: 14px;">
<p style="margin: 0 0 12px 0; font-weight: 600; color: #374151; text-transform: uppercase; font-size: 12px; letter-spacing: 0.025em;">What's Included:</p>
<ul style="margin: 0 0 12px 0; padding-left: 20px;">
<li>✅ All Blocks & Templates</li>
<li>✅ Future Updates</li>
<li>✅ Premium Support</li>
<li>✅ Exclusive Content</li>
</ul>
<p style="margin: 0 0 8px 0; font-weight: 600; color: #374151; text-transform: uppercase; font-size: 12px; letter-spacing: 0.025em;">Best For:</p>
<p style="margin: 0; font-size: 14px;">Unlimited access to everything. Perfect for agencies and developers who need the complete OxyMade experience.</p>
<p style="margin: 8px 0 0 0; font-size: 14px;"><strong>Copy Codes:</strong> Get unlimited access from <a href="https://oxymade.com/start" target="_blank" style="color: #f59e0b; text-decoration: none; font-weight: 500;">oxymade.com/start</a></p>
</div>
</div>
</div>
</div>
<!-- Typography Configuration Modal -->
<div id="typography-config-modal" style="display: none; position: fixed; z-index: 100001; left: 0; top: 0; width: 100%; height: 100%; overflow: auto; background-color: rgba(0,0,0,0.4);">
<div style="background-color: #ffffff; margin: 5% auto; padding: 30px; border: 1px solid #e5e7eb; width: 90%; max-width: 800px; box-shadow: 0 1px 3px rgba(0,0,0,0.1); border-radius: 8px;">
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px;">
<h2 style="margin: 0; font-size: 24px; color: #0284c7; position: relative; padding-bottom: 12px;">
Headings Configuration
<span style="display: block; width: 60px; height: 3px; background-color: #0284c7; position: absolute; bottom: 0; left: 0;"></span>
</h2>
<button type="button" class="close-typography-modal" style="background: none; border: none; font-size: 24px; cursor: pointer; color: #666;">×</button>
</div>
<div id="typography-config-content">
<!-- Configuration form will be loaded here -->
</div>
<div style="display: flex; justify-content: space-between; align-items: center; margin-top: 30px; padding-top: 20px; border-top: 1px solid #e5e7eb;">
<button type="button" class="button" id="reset-from-template" title="Re-import heading sizes from your design set template" style="font-size: 13px; padding: 6px 16px; height: auto; color: #9ca3af; border-color: #d1d5db;">
<span class="dashicons dashicons-image-rotate" style="font-size: 16px; width: 16px; height: 16px; margin-top: 2px;"></span>
Reset from Template
</button>
<div style="display: flex; gap: 12px;">
<button type="button" class="button button-secondary" id="cancel-typography-config" style="font-size: 13px; padding: 6px 16px; height: auto;">Cancel</button>
<button type="button" class="button button-primary" id="save-typography-config" style="font-size: 13px; padding: 6px 16px; height: auto;">Save Configuration</button>
</div>
</div>
</div>
</div>
<div id="design-set-modal" style="display: none; position: fixed; z-index: 100001; left: 0; top: 0; width: 100%; height: 100%; overflow: auto; background-color: rgba(0,0,0,0.4);">
<div style="background-color: #ffffff; margin: 5% auto; padding: 30px; border: 1px solid #e5e7eb; width: 90%; max-width: 1200px; box-shadow: 0 1px 3px rgba(0,0,0,0.1);">
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px;">
<h2 style="margin: 0; font-size: 24px; color: #0284c7; position: relative; padding-bottom: 12px;">
<?php _e('Available Design Sets & Templates', 'oxymade'); ?>
<span style="display: block; width: 60px; height: 3px; background-color: #0284c7; position: absolute; bottom: 0; left: 0;"></span>
</h2>
<button type="button" class="close-modal" style="background: none; border: none; font-size: 24px; cursor: pointer; color: #666;">×</button>
</div>
<p style="color: #666; margin-bottom: 25px;"><?php _e('Choose a design set to install its custom color palette and global settings specially designed for your Oxygen site', 'oxymade'); ?></p>
<!-- Search input -->
<div style="margin-bottom: 20px;">
<input type="text" id="design-set-search" placeholder="<?php esc_attr_e('Search design sets...', 'oxymade'); ?>" style="width: 100%; max-width: 400px; padding: 10px 15px; border: 1px solid #d1d5db; border-radius: 6px; font-size: 14px;">
</div>
<div id="design-sets-container" style="display: grid; grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); gap: 24px; max-height: 60vh; overflow-y: auto; padding: 5px;">
<div class="loading-spinner" style="grid-column: 1/-1; text-align: center; padding: 40px;">
<span class="dashicons dashicons-update" style="animation: spin 2s linear infinite; font-size: 30px; color: #0284c7;"></span>
<p><?php _e('Loading available design sets...', 'oxymade'); ?></p>
</div>
</div>
<!-- Design Set Preview Screen -->
<div id="design-set-preview" style="display: none;">
<div style="display: flex; align-items: center; gap: 12px; margin-bottom: 24px;">
<button type="button" id="preview-back-btn" style="background: none; border: none; cursor: pointer; padding: 4px; line-height: 1;">
<span class="dashicons dashicons-arrow-left-alt2" style="font-size: 22px; color: #6b7280;"></span>
</button>
<h2 id="preview-set-name" style="margin: 0; font-size: 22px; color: #111827; font-weight: 600;"></h2>
</div>
<div id="preview-content" style="max-height: 55vh; overflow-y: auto; padding-right: 8px;"></div>
<div style="margin-top: 20px; padding-top: 16px; border-top: 1px solid #f3f4f6; display: flex; justify-content: flex-end; gap: 10px;">
<button type="button" class="button button-secondary" id="preview-cancel-btn"><?php _e('Back to Templates', 'oxymade'); ?></button>
<button type="button" class="button button-primary" id="preview-confirm-btn" style="background: #0284c7; border-color: #0284c7;"><?php _e('Use This Template', 'oxymade'); ?></button>
</div>
</div>
</div>
</div>
<!-- Welcome Wizard Modal -->
<div id="oxymade-welcome-wizard" style="display: <?php echo $show_wizard ? 'block' : 'none'; ?>; position: fixed; z-index: 100002; left: 0; top: 0; width: 100%; height: 100%; overflow: auto; background-color: rgba(0,0,0,0.4);">
<div style="background-color: #ffffff; margin: 5% auto; padding: 30px; border: 1px solid #e5e7eb; width: 90%; max-width: 900px; box-shadow: 0 1px 3px rgba(0,0,0,0.1); border-radius: 8px;">
<!-- Screen 0: License Activation (only if not active) -->
<?php if (!$is_license_active): ?>
<div id="wizard-screen-0">
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 8px;">
<h2 style="margin: 0; font-size: 22px; color: #0284c7; position: relative; padding-bottom: 12px;">
Welcome to OxyMade
<span style="display: block; width: 60px; height: 3px; background-color: #0284c7; position: absolute; bottom: 0; left: 0;"></span>
</h2>
<a href="#" id="wizard-skip-license" style="color: #9ca3af; font-size: 13px; text-decoration: none;">Skip</a>
</div>
<p style="color: #6b7280; margin: 0 0 20px;">Activate your license to enable automatic updates. You can also skip and activate later.</p>
<div style="display: flex; gap: 12px; align-items: flex-end; margin-bottom: 20px;">
<div style="flex: 1; max-width: 400px;">
<label style="display: block; font-size: 13px; font-weight: 600; color: #374151; margin-bottom: 6px;">License Key</label>
<input type="text" id="wizard-license-key" placeholder="Enter your license key" style="width: 100%; padding: 8px 12px; border: 1px solid #d1d5db; border-radius: 6px; font-size: 14px;">
</div>
<button type="button" id="wizard-activate-license" class="button button-primary" style="font-size: 13px; padding: 6px 20px; height: auto; background: #0284c7; border-color: #0284c7;">Activate</button>
</div>
<div id="wizard-license-message" style="display: none; padding: 8px 12px; border-radius: 6px; font-size: 13px; margin-bottom: 16px;"></div>
<div style="border-top: 1px solid #f3f4f6; padding-top: 16px;">
<button type="button" id="wizard-continue-no-license" class="button button-secondary" style="font-size: 13px; padding: 6px 20px; height: auto;">Continue Without License</button>
</div>
</div>
<?php endif; ?>
<!-- Screen 1: Choose Design Set -->
<div id="wizard-screen-1" <?php echo !$is_license_active ? 'style="display: none;"' : ''; ?>>
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 8px;">
<h2 style="margin: 0; font-size: 22px; color: #0284c7; position: relative; padding-bottom: 12px;">
Welcome to OxyMade
<span style="display: block; width: 60px; height: 3px; background-color: #0284c7; position: absolute; bottom: 0; left: 0;"></span>
</h2>
<a href="#" id="wizard-skip" style="color: #9ca3af; font-size: 13px; text-decoration: none;">Skip</a>
</div>
<p style="color: #6b7280; margin: 0 0 16px;">Choose a design set to get started. This determines your color palette, typography, and global settings.</p>
<div style="margin-bottom: 16px;">
<input type="text" id="wizard-design-set-search" placeholder="<?php esc_attr_e('Search design sets...', 'oxymade'); ?>" style="width: 100%; max-width: 300px; padding: 8px 12px; border: 1px solid #d1d5db; border-radius: 6px; font-size: 14px;">
</div>
<div id="wizard-design-sets-container" style="display: grid; grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); gap: 20px; max-height: 55vh; overflow-y: auto; padding: 4px;">
<div class="loading-spinner" style="grid-column: 1/-1; text-align: center; padding: 40px;">
<span class="dashicons dashicons-update" style="animation: spin 2s linear infinite; font-size: 30px; color: #0284c7;"></span>
<p><?php _e('Loading design sets...', 'oxymade'); ?></p>
</div>
</div>
<!-- Wizard Design Set Preview Screen -->
<div id="wizard-design-set-preview" style="display: none;">
<div style="display: flex; align-items: center; gap: 10px; margin-bottom: 20px;">
<button type="button" id="wizard-preview-back-btn" style="background: none; border: none; cursor: pointer; padding: 4px; line-height: 1;">
<span class="dashicons dashicons-arrow-left-alt2" style="font-size: 20px; color: #6b7280;"></span>
</button>
<h2 id="wizard-preview-set-name" style="margin: 0; font-size: 20px; color: #111827; font-weight: 600;"></h2>
</div>
<div id="wizard-preview-content" style="max-height: 45vh; overflow-y: auto; padding-right: 8px;"></div>
<div style="margin-top: 16px; padding-top: 12px; border-top: 1px solid #f3f4f6; display: flex; justify-content: flex-end; gap: 10px;">
<button type="button" class="button button-secondary" id="wizard-preview-cancel-btn"><?php _e('Back', 'oxymade'); ?></button>
<button type="button" class="button button-primary" id="wizard-preview-confirm-btn" style="background: #0284c7; border-color: #0284c7;"><?php _e('Use This Template', 'oxymade'); ?></button>
</div>
</div>
</div>
<!-- Screen 2: Choose Setup Method -->
<div id="wizard-screen-2" style="display: none;">
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 8px;">
<h2 style="margin: 0; font-size: 22px; color: #0284c7; position: relative; padding-bottom: 12px;">
<span id="wizard-selected-set"></span> Selected
<span style="display: block; width: 60px; height: 3px; background-color: #0284c7; position: absolute; bottom: 0; left: 0;"></span>
</h2>
</div>
<p style="color: #6b7280; margin: 0 0 24px;">How would you like to set up your site?</p>
<div style="display: flex; gap: 16px;">
<div style="flex: 1; border: 2px solid #0284c7; border-radius: 8px; padding: 20px; background: #f8fbff;">
<h3 style="margin: 0 0 6px; font-size: 15px; font-weight: 600; color: #1f2937;">Complete Setup <span style="background: #dbeafe; color: #1d4ed8; padding: 2px 8px; border-radius: 10px; font-size: 11px; font-weight: 600; margin-left: 6px; vertical-align: middle;">Recommended</span></h3>
<p style="color: #6b7280; font-size: 13px; margin: 0 0 14px; line-height: 1.5;">Install colors, typography, global settings, and components in one click.</p>
<button type="button" id="wizard-complete-setup" class="button button-primary" style="font-size: 13px; padding: 6px 20px; height: auto; background: #0284c7; border-color: #0284c7;">Complete Setup Now</button>
</div>
<div style="flex: 1; border: 1px solid #e5e7eb; border-radius: 8px; padding: 20px;">
<h3 style="margin: 0 0 6px; font-size: 15px; font-weight: 600; color: #1f2937;">Step by Step</h3>
<p style="color: #6b7280; font-size: 13px; margin: 0 0 14px; line-height: 1.5;">Configure each step individually for full control over every setting.</p>
<button type="button" id="wizard-step-by-step" class="button button-secondary" style="font-size: 13px; padding: 6px 20px; height: auto;">Go Step by Step</button>
</div>
</div>
</div>
</div>
</div>
</div>
<style>
.oxymade-admin-selector-container {
display: flex;
flex-wrap: wrap;
gap: 24px;
}
/* Grid layout for steps 1-5 */
.oxymade-steps-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 24px;
margin-bottom: 30px;
}
.oxymade-steps-grid .oxymade-admin-card {
margin-bottom: 0;
}
/* Responsive grid adjustments */
@media (max-width: 1536px) {
.oxymade-steps-grid {
grid-template-columns: repeat(2, 1fr);
}
}
@media (max-width: 768px) {
.oxymade-steps-grid {
grid-template-columns: 1fr;
gap: 20px;
}
.oxymade-steps-grid .oxymade-admin-card[style*="grid-column"] {
grid-column: span 1 !important;
}
}
.step-badge {
display: inline-flex;
align-items: center;
justify-content: center;
background: #0284c7;
color: white;
width: auto;
padding-left: 12px;
padding-right: 12px;
height: 28px;
font-size: 15px;
font-weight: 600;
margin-right: 4px;
box-shadow: 0 1px 4px rgba(2, 132, 199, 0.08);
letter-spacing: 0.5px;
}
@keyframes spin {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
.oxymade-admin-card {
background: #fff;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
padding: 24px;
transition: all 0.3s ease;
border-left: 4px solid #0284c7;
border-radius: 8px;
}
.oxymade-admin-card:hover {
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.1);
transform: translateY(-2px);
}
.oxymade-admin-card h2 {
margin-top: 0;
/* color: #0284c7; */
font-size: 1.4rem;
margin-bottom: 16px;
}
.oxymade-admin-card-full {
flex: 1 1 100%;
}
.oxymade-selector-options {
display: flex;
gap: 24px;
margin-top: 20px;
}
.oxymade-selector-option {
flex: 1;
background: #fff;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
padding: 20px;
transition: all 0.2s ease;
border-top: 3px solid #0284c7;
border-radius: 8px;
cursor: pointer;
text-align: center;
}
.oxymade-selector-option:hover {
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
transform: translateY(-2px);
}
.oxymade-selector-option h3 {
margin-top: 0;
font-size: 1.1rem;
color: #3f3f46;
font-weight: 600;
}
/* Button Styling Improvements */
.oxymade-admin-card .button {
padding: 12px 24px;
font-weight: 600;
font-size: 12px;
border-radius: 6px;
transition: all 0.2s ease;
text-decoration: none;
display: inline-flex;
align-items: center;
gap: 8px;
margin-right: 12px;
margin-bottom: 8px;
}
.oxymade-admin-card .button-primary {
background: #0284c7;
color: white;
border: none;
box-shadow: 0 2px 4px rgba(2, 132, 199, 0.2);
}
.oxymade-admin-card .button-primary:hover {
background: #0369a1;
transform: translateY(-1px);
box-shadow: 0 4px 8px rgba(2, 132, 199, 0.3);
}
.oxymade-admin-card .button-secondary {
background: #f8fafc;
color: #64748b;
border: 1px solid #e2e8f0;
}
.oxymade-admin-card .button-secondary:hover {
background: #f1f5f9;
color: #475569;
transform: translateY(-1px);
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
.oxymade-license-status {
display: inline-flex;
align-items: center;
padding: 5px 16px;
margin: 12px 0;
font-weight: 500;
}
.oxymade-license-status.active {
background-color: #d1fae5;
color: #065f46;
}
.oxymade-license-status.inactive {
background-color: #fef2f2;
color: #b91c1c;
}
.oxymade-license-status .dashicons {
margin-right: 8px;
}
.notice.inline {
margin: 12px 0;
padding: 8px 12px;
}
.button.button-primary {
background: #0284c7;
border-color: #0284c7;
color: white;
padding: 6px 16px;
height: auto;
line-height: 1.5;
transition: all 0.2s ease;
}
.button.button-primary:hover {
background: #0369a1;
border-color: #0369a1;
transform: translateY(-1px);
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
}
.button.button-secondary {
border-color: #d4d4d8;
color: #52525b;
padding: 6px 16px;
height: auto;
line-height: 1.5;
transition: all 0.2s ease;
}
.button.button-secondary:hover {
background: #f4f4f5;
transform: translateY(-1px);
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
}
@media (max-width: 782px) {
.oxymade-selector-options {
flex-direction: column;
}
.oxymade-admin-card {
flex: 1 1 100%;
}
}
.oxymade-setup-steps {
margin: 20px 0;
}
.oxymade-step {
display: flex;
align-items: flex-start;
margin-bottom: 20px;
padding: 15px;
background: var(--surface-neutral);
transition: all 0.2s ease;
}
.oxymade-step:hover {
background: var(--subtle-neutral);
transform: translateY(-2px);
}
.oxymade-step-number {
width: 30px;
height: 30px;
background: var(--base-primary);
color: white;
display: flex;
align-items: center;
justify-content: center;
font-weight: 600;
margin-right: 15px;
flex-shrink: 0;
}
.oxymade-step-content {
flex: 1;
}
.oxymade-step-content h3 {
margin: 0 0 8px 0;
color: var(--heading-neutral);
font-size: 16px;
}
.oxymade-step-content p {
margin: 0;
color: var(--text-neutral);
font-size: 14px;
}
.oxymade-code-block {
display: flex;
align-items: center;
background: var(--bg-neutral);
padding: 8px 12px;
margin-top: 8px;
font-family: monospace;
font-size: 13px;
}
.oxymade-code-block code {
flex: 1;
color: var(--text-neutral);
}
.oxymade-copy-button {
background: none;
border: none;
cursor: pointer;
padding: 4px;
margin-left: 8px;
color: var(--muted-neutral);
transition: color 0.2s ease;
}
.oxymade-copy-button:hover {
color: var(--text-neutral);
}
.oxymade-setup-status {
display: flex;
align-items: center;
padding: 12px 15px;
margin-top: 20px;
font-weight: 500;
}
.oxymade-setup-status.active {
background: var(--surface-tertiary);
color: var(--text-tertiary);
}
.oxymade-setup-status.inactive {
background: var(--surface-neutral);
color: var(--text-neutral);
}
.oxymade-setup-status .dashicons {
margin-right: 8px;
}
@media (max-width: 782px) {
.oxymade-step {
flex-direction: column;
}
.oxymade-step-number {
margin-bottom: 10px;
}
}
.oxymade-license-header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 15px;
}
#refresh-license {
display: flex;
align-items: center;
gap: 5px;
}
#refresh-license .dashicons {
font-size: 16px;
width: 16px;
height: 16px;
}
#refresh-license.updating .dashicons {
animation: spin 1s linear infinite;
}
@keyframes spin {
100% {
transform: rotate(360deg);
}
}
/* Toggle Switch Styles */
.oxymade-toggle-switch {
position: relative;
display: inline-block;
width: 50px;
height: 24px;
}
.oxymade-toggle-switch input {
opacity: 0;
width: 0;
height: 0;
}
.oxymade-slider {
position: absolute;
cursor: pointer;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: #ccc;
transition: .4s;
}
.oxymade-slider:before {
position: absolute;
content: "";
height: 18px;
width: 18px;
left: 3px;
bottom: 3px;
background-color: white;
transition: .4s;
}
input:checked+.oxymade-slider {
background-color: #0284c7;
}
input:checked+.oxymade-slider:before {
transform: translateX(26px);
}
.oxymade-slider:hover {
box-shadow: 0 0 4px rgba(2, 132, 199, 0.2);
}
/* Toggle loading state */
.oxymade-toggle-switch.saving .oxymade-slider {
opacity: 0.7;
pointer-events: none;
}
.oxymade-toggle-switch.saving .oxymade-slider:before {
animation: pulse 1s infinite;
}
@keyframes pulse {
0% {
opacity: 1;
}
50% {
opacity: 0.5;
}
100% {
opacity: 1;
}
}
/* Disabled button styles */
button:disabled {
opacity: 0.5;
cursor: not-allowed;
}
</style>
<script>
(function($) {
'use strict';
$(document).ready(function() {
// Copy to clipboard functionality
$('.oxymade-copy-button').on('click', function() {
var text = $(this).data('clipboard-text');
navigator.clipboard.writeText(text).then(function() {
var button = $(this);
var originalText = button.html();
button.html('<span class="dashicons dashicons-yes"></span>');
setTimeout(function() {
button.html(originalText);
}, 2000);
}.bind(this));
});
// Handle selector option clicks
$('.oxymade-selector-option').on('click', function() {
const mode = $(this).data('mode');
$('.oxymade-selector-option').removeClass('selected');
$(this).addClass('selected');
// Update button states
// All selector buttons remain button-secondary for consistent styling
// Store the selected mode
updateSelectorMode(mode);
});
// Function to update selector mode
function updateSelectorMode(mode) {
$.ajax({
url: ajaxurl,
type: 'POST',
data: {
action: 'oxymade_update_selector_mode',
nonce: '<?php echo wp_create_nonce("oxymade-admin-nonce"); ?>',
mode: mode
},
success: function(response) {
if (response.success) {}
}
});
}
$('#refresh-license').on('click', function() {
const button = $(this);
if (button.hasClass('updating')) {
return;
}
button.addClass('updating');
$.ajax({
url: ajaxurl,
type: 'POST',
data: {
action: 'oxymade_refresh_license',
nonce: '<?php echo wp_create_nonce("oxymade-refresh-license"); ?>'
},
success: function(response) {
if (response.success) {
location.reload();
} else {
alert('Failed to refresh license: ' + (response.data || 'Unknown error'));
}
},
error: function() {
alert('Failed to refresh license. Please try again.');
},
complete: function() {
button.removeClass('updating');
}
});
});
// Domain-aware license functions
window.forceActivateDomain = function() {
if (!confirm('This will create a new domain activation for the current domain. Continue?')) {
return;
}
$.ajax({
url: ajaxurl,
type: 'POST',
data: {
action: 'domain_license_force_activate'
},
success: function(response) {
if (response.success) {
alert('Domain activated successfully!');
location.reload();
} else {
alert('Failed to activate domain: ' + (response.data || 'Unknown error'));
}
},
error: function() {
alert('Failed to activate domain. Please try again.');
}
});
};
window.refreshDomainLicense = function() {
$.ajax({
url: ajaxurl,
type: 'POST',
data: {
action: 'domain_license_refresh'
},
success: function(response) {
if (response.success) {
alert('License refreshed successfully!');
location.reload();
} else {
alert('Failed to refresh license: ' + (response.data || 'Unknown error'));
}
},
error: function() {
alert('Failed to refresh license. Please try again.');
}
});
};
}); // End document.ready
// Delete Selectors Modal - Event handlers outside document.ready
$(document).on('click', '#oxymade-delete-selectors', function() {
// First get selector count
$.ajax({
url: ajaxurl,
type: 'POST',
data: {
action: 'oxymade_get_selector_counts',
nonce: '<?php echo wp_create_nonce('oxymade_get_selector_counts'); ?>'
},
success: function(response) {
if (response.success) {
const data = response.data;
// Store counts for dynamic updates
modalCounts.total = data.total_selectors || 0;
modalCounts.oxymade = data.oxymade_count || 0;
modalCounts.custom = data.custom_count || 0;
modalCounts.component = data.component_count || 0;
modalCounts.other = data.other_count || 0;
// Update modal content with counts
$('#oxymade-modal-total-count').text(modalCounts.total);
$('#oxymade-modal-oxymade-count').text(modalCounts.oxymade);
$('#oxymade-modal-other-count').text(modalCounts.other);
$('#oxymade-modal-custom-count').text(modalCounts.custom);
$('#oxymade-modal-component-count').text(modalCounts.component);
// Update button counts
updateDeleteModal();
}
// Show modal regardless of success/failure
$('#oxymade-delete-modal').show();
},
error: function() {
// Show modal even if count fetch fails
$('#oxymade-delete-modal').show();
}
});
});
$(document).on('click', '#oxymade-cancel-delete-btn', function() {
$('#oxymade-delete-modal').hide();
});
// Fix Styling - scan all pages and restore used OxyMade classes
$(document).on('click', '#oxymade-fix-styling', function() {
var $btn = $(this);
var $result = $('#oxymade-selectors-result');
$btn.prop('disabled', true).html('<span class="dashicons dashicons-update" style="animation: spin 1s linear infinite;"></span> Scanning...');
$.ajax({
url: ajaxurl,
type: 'POST',
data: {
action: 'oxymade_fix_styling',
nonce: '<?php echo wp_create_nonce('oxymade_fix_styling'); ?>'
},
timeout: 300000,
success: function(response) {
if (response.success) {
var d = response.data;
$result.html(
'<div class="notice notice-success" style="margin-top: 12px;"><p><strong>Done!</strong> Scanned ' +
d.pages_scanned + ' pages. Found ' + d.classes_found +
' OxyMade classes. Added ' + d.classes_added +
', skipped ' + d.classes_skipped + ' (already loaded).</p></div>'
).show();
if (d.classes_added > 0) {
setTimeout(function() { location.reload(); }, 2000);
}
} else {
$result.html('<div class="notice notice-error" style="margin-top: 12px;"><p>' + response.data + '</p></div>').show();
}
},
error: function() {
$result.html('<div class="notice notice-error" style="margin-top: 12px;"><p>Request failed or timed out.</p></div>').show();
},
complete: function() {
$btn.prop('disabled', false).html('<span class="dashicons dashicons-admin-tools"></span> Fix Styling');
}
});
});
// Restore Selectors — same as Fix Styling, reuses the same AJAX handler
$(document).on('click', '#oxymade-restore-selectors', function() {
var $btn = $(this);
var $result = $('#oxymade-selectors-result');
$btn.prop('disabled', true).html('<span class="dashicons dashicons-update" style="animation: spin 1s linear infinite;"></span> Restoring...');
$.ajax({
url: ajaxurl, type: 'POST',
data: { action: 'oxymade_fix_styling', nonce: '<?php echo wp_create_nonce('oxymade_fix_styling'); ?>' },
timeout: 300000,
success: function(response) {
if (response.success) {
var d = response.data;
$result.html('<div class="notice notice-success" style="margin-top: 12px;"><p><strong>Restored!</strong> Scanned ' + d.pages_scanned + ' pages. Added ' + d.classes_added + ' OxyMade selectors.</p></div>').show();
setTimeout(function() { location.reload(); }, 2000);
} else {
$result.html('<div class="notice notice-error" style="margin-top: 12px;"><p>' + response.data + '</p></div>').show();
}
},
error: function() {
$result.html('<div class="notice notice-error" style="margin-top: 12px;"><p>Request failed or timed out.</p></div>').show();
},
complete: function() {
$btn.prop('disabled', false).html('<span class="dashicons dashicons-image-rotate"></span> Restore Selectors');
}
});
});
// Delete OxyMade selectors only
$(document).on('click', '#oxymade-delete-oxymade-only', function() {
const $button = $(this);
const $modal = $('#oxymade-delete-modal');
const savedCount = $('#oxymade-btn-oxymade-count').text();
$button.prop('disabled', true).html('<span class="dashicons dashicons-update" style="margin-right: 4px; animation: spin 1s linear infinite;"></span>Deleting...');
$.ajax({
url: ajaxurl,
type: 'POST',
data: {
action: 'oxymade_delete_oxymade_selectors',
nonce: '<?php echo wp_create_nonce('oxymade_delete_selectors'); ?>',
include_custom: $('#oxymade-delete-include-custom').is(':checked') ? '1' : '0',
keep_components: $('#oxymade-delete-keep-components').is(':checked') ? '1' : '0'
},
success: function(response) {
if (response.success) {
$modal.hide();
$('#oxymade-selectors-result').html('<div class="notice notice-success"><p><strong>Success!</strong> ' + response.data.message + '</p></div>').show();
setTimeout(function() { location.reload(); }, 1500);
} else {
$('#oxymade-selectors-result').html('<div class="notice notice-error"><p><strong>Error:</strong> ' + response.data + '</p></div>').show();
}
},
error: function() {
$('#oxymade-selectors-result').html('<div class="notice notice-error"><p><strong>Error:</strong> Failed to delete selectors. Please try again.</p></div>').show();
},
complete: function() {
$button.prop('disabled', false).html('<span class="dashicons dashicons-tag" style="margin-right: 6px; font-size: 16px;"></span>Delete OxyMade (<span id="oxymade-btn-oxymade-count">' + savedCount + '</span>)');
}
});
});
// Delete all selectors
$(document).on('click', '#oxymade-delete-all-selectors', function() {
const $button = $(this);
const $modal = $('#oxymade-delete-modal');
const savedCount = $('#oxymade-btn-all-count').text();
$button.prop('disabled', true).html('<span class="dashicons dashicons-update" style="margin-right: 4px; animation: spin 1s linear infinite;"></span>Deleting...');
$.ajax({
url: ajaxurl,
type: 'POST',
data: {
action: 'oxymade_delete_all_selectors',
nonce: '<?php echo wp_create_nonce('oxymade_delete_selectors'); ?>',
include_custom: $('#oxymade-delete-include-custom').is(':checked') ? '1' : '0',
keep_components: $('#oxymade-delete-keep-components').is(':checked') ? '1' : '0'
},
success: function(response) {
if (response.success) {
$modal.hide();
$('#oxymade-selectors-result').html('<div class="notice notice-success"><p><strong>Success!</strong> ' + response.data.message + '</p></div>').show();
setTimeout(function() { location.reload(); }, 1500);
} else {
$('#oxymade-selectors-result').html('<div class="notice notice-error"><p><strong>Error:</strong> ' + response.data + '</p></div>').show();
}
},
error: function() {
$('#oxymade-selectors-result').html('<div class="notice notice-error"><p><strong>Error:</strong> Failed to delete selectors. Please try again.</p></div>').show();
},
complete: function() {
$button.prop('disabled', false).html('<span class="dashicons dashicons-trash" style="margin-right: 6px; font-size: 16px;"></span>Delete All (<span id="oxymade-btn-all-count">' + savedCount + '</span>)');
}
});
});
// Store counts for dynamic button updates
var modalCounts = { total: 0, oxymade: 0, custom: 0, component: 0, other: 0 };
// Update modal descriptions and button counts when toggles change
function updateDeleteModal() {
var includeCustom = $('#oxymade-delete-include-custom').is(':checked');
var keepComponents = $('#oxymade-delete-keep-components').is(':checked');
// Calculate what gets deleted
var customKept = includeCustom ? 0 : modalCounts.custom;
var componentsKept = keepComponents ? modalCounts.component : 0;
var oxyDeleteCount = modalCounts.oxymade - customKept - componentsKept;
var allDeleteCount = modalCounts.total - customKept - componentsKept;
// Update button counts
$('#oxymade-btn-oxymade-count').text(Math.max(0, oxyDeleteCount));
$('#oxymade-btn-all-count').text(Math.max(0, allDeleteCount));
// Update descriptions
var keeps = [];
if (!includeCustom) keeps.push('custom*');
if (keepComponents) keeps.push('components');
if (keeps.length > 0) {
var keepStr = keeps.join(' and ') + ' selectors';
$('#oxymade-modal-desc-oxymade').text('Removes OxyMade utility selectors, keeps other selectors and ' + keepStr + ' intact.');
$('#oxymade-modal-desc-all').text('Removes ALL selectors from Oxygen except ' + keepStr + '.');
} else {
$('#oxymade-modal-desc-oxymade').text('Removes all OxyMade selectors including custom* and components, keeps other selectors intact.');
$('#oxymade-modal-desc-all').text('Removes ALL selectors from Oxygen with no exceptions.');
}
}
$(document).on('change', '#oxymade-delete-include-custom, #oxymade-delete-keep-components', updateDeleteModal);
// Close modal when clicking outside
$(document).on('click', '#oxymade-delete-modal', function(event) {
if (event.target.id === 'oxymade-delete-modal') {
$('#oxymade-delete-modal').hide();
}
});
// Tooltips are now handled by CSS only - no JavaScript needed
// Handle toggle auto-save
$('input[name="oxymade_settings[sync_with_oxygen]"]').on('change', function() {
const isChecked = $(this).is(':checked');
const value = isChecked ? '1' : '0';
// Show loading state on toggle
const $toggle = $(this).closest('.oxymade-toggle-switch');
$toggle.addClass('saving');
// Save via AJAX
$.ajax({
url: '<?php echo admin_url('admin-ajax.php'); ?>',
type: 'POST',
data: {
action: 'oxymade_save_sync_setting',
sync_with_oxygen: value,
nonce: '<?php echo wp_create_nonce('oxymade-sync-setting'); ?>'
},
success: function(response) {
$toggle.removeClass('saving');
// Show success message briefly
const $notice = $('<div class="notice notice-success is-dismissible" style="position: fixed; top: 32px; right: 20px; z-index: 9999;"><p>Sync setting saved!</p></div>');
$('body').append($notice);
setTimeout(() => $notice.fadeOut(), 2000);
},
error: function(xhr, status, error) {
$toggle.removeClass('saving');
// Show error message with more details
let errorMsg = 'Error saving sync setting!';
if (xhr.responseText) {
try {
const response = JSON.parse(xhr.responseText);
if (response.data && response.data.message) {
errorMsg = response.data.message;
}
} catch (e) {
errorMsg = 'Server error: ' + xhr.status;
}
}
const $notice = $('<div class="notice notice-error is-dismissible" style="position: fixed; top: 32px; right: 20px; z-index: 9999;"><p>' + errorMsg + '</p></div>');
$('body').append($notice);
setTimeout(() => $notice.fadeOut(), 5000);
}
});
});
// Autocomplete toggle auto-save
$('input[name="oxymade_settings[autocomplete_enabled]"]').on('change', function() {
const isChecked = $(this).is(':checked');
const value = isChecked ? '1' : '0';
const $toggle = $(this).closest('.oxymade-toggle-switch');
$toggle.addClass('saving');
$.ajax({
url: '<?php echo admin_url('admin-ajax.php'); ?>',
type: 'POST',
data: {
action: 'oxymade_save_autocomplete_setting',
autocomplete_enabled: value,
nonce: '<?php echo wp_create_nonce('oxymade-autocomplete-setting'); ?>'
},
success: function() {
$toggle.removeClass('saving');
const $notice = $('<div class="notice notice-success is-dismissible" style="position: fixed; top: 32px; right: 20px; z-index: 9999;"><p>Autocomplete setting saved!</p></div>');
$('body').append($notice);
setTimeout(() => $notice.fadeOut(), 2000);
},
error: function() {
$toggle.removeClass('saving');
const $notice = $('<div class="notice notice-error is-dismissible" style="position: fixed; top: 32px; right: 20px; z-index: 9999;"><p>Error saving autocomplete setting!</p></div>');
$('body').append($notice);
setTimeout(() => $notice.fadeOut(), 5000);
}
});
});
// Oxygen Sync JavaScript
$('#oxymade-sync-oxygen').on('click', function() {
const $button = $(this);
const $result = $('#oxygen-sync-result');
// Disable button and show loading
$button.prop('disabled', true).html('<span class="dashicons dashicons-update"></span> Syncing...');
$result.hide();
// Make AJAX request
$.ajax({
url: ajaxurl,
type: 'POST',
data: {
action: 'oxymade_sync_oxygen',
mode: 'add_new',
nonce: '<?php echo wp_create_nonce('oxymade-admin-nonce'); ?>'
},
success: function(response) {
if (response.success) {
$result.html('<div class="notice notice-success"><p>' + response.data.message + '</p></div>').show();
// Update sync status
setTimeout(() => location.reload(), 1500);
} else {
$result.html('<div class="notice notice-error"><p>' + response.data + '</p></div>').show();
}
},
error: function() {
$result.html('<div class="notice notice-error"><p>Failed to sync with Oxygen. Please try again.</p></div>').show();
},
complete: function() {
$button.prop('disabled', false).html('<span class="dashicons dashicons-update"></span> Sync with Oxygen');
}
});
});
// Auto-sync toggle
$('#oxygen-auto-sync').on('change', function() {
const autoSync = $(this).is(':checked');
$.ajax({
url: ajaxurl,
type: 'POST',
data: {
action: 'oxymade_toggle_auto_sync',
auto_sync: autoSync ? '1' : '0',
nonce: '<?php echo wp_create_nonce('oxymade-admin-nonce'); ?>'
},
success: function(response) {
if (response.success) {
// Show success message
const $notice = $('<div class="notice notice-success is-dismissible" style="position: fixed; top: 32px; right: 20px; z-index: 9999;"><p>Auto-sync setting updated successfully!</p></div>');
$('body').append($notice);
setTimeout(() => $notice.fadeOut(), 3000);
}
}
});
});
// Typography Configuration Modal
// Track if modal has been initialized
let typographyModalInitialized = false;
$('#oxymade-typography-config').on('click', function() {
if (!typographyModalInitialized) {
loadTypographyConfig();
typographyModalInitialized = true;
}
$('#typography-config-modal').show();
});
$('.close-typography-modal, #cancel-typography-config').on('click', function() {
$('#typography-config-modal').hide();
});
// Close modal when clicking outside
$('#typography-config-modal').on('click', function(e) {
if (e.target === this) {
$(this).hide();
}
});
// Load typography configuration
function loadTypographyConfig() {
// Generate the form HTML first
const formHTML = `
<div style="margin-bottom: 20px;">
<!-- Type Scale Toggle -->
<div style="display: flex; align-items: center; justify-content: space-between; margin-bottom: 20px; padding: 16px; background: #f8fafc; border-radius: 8px;">
<div style="display: flex; flex-direction: column; gap: 4px;">
<label style="font-weight: 600; color: #374151; font-size: 14px;">Type Scale</label>
<span style="font-size: 13px; color: #6b7280;">Automatically generate heading sizes based on mathematical ratios</span>
</div>
<label class="oxymade-toggle-switch">
<input type="checkbox" id="typescale-enabled">
<span class="oxymade-slider"></span>
</label>
</div>
<!-- Type Scale Configuration -->
<div id="typescale-config" style="display: none; margin-bottom: 20px; padding: 16px; background: #f0f9ff; border-radius: 8px; border: 1px solid #bae6fd;">
<h3 style="margin: 0 0 16px 0; font-size: 16px; color: #0284c7;">Type Scale Settings</h3>
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 16px;">
<div>
<label style="display: block; font-weight: 500; color: #374151; margin-bottom: 6px;">Base Font Size (Mobile)</label>
<input type="number" id="base-font-mobile" value="16" min="12" max="24" style="width: 100%; padding: 8px; border: 1px solid #d1d5db; border-radius: 4px;">
</div>
<div>
<label style="display: block; font-weight: 500; color: #374151; margin-bottom: 6px;">Base Font Size (Desktop)</label>
<input type="number" id="base-font-desktop" value="18" min="14" max="32" style="width: 100%; padding: 8px; border: 1px solid #d1d5db; border-radius: 4px;">
</div>
<div>
<label style="display: block; font-weight: 500; color: #374151; margin-bottom: 6px;">Type Scale (Mobile)</label>
<select id="typescale-mobile" style="width: 100%; padding: 8px; border: 1px solid #d1d5db; border-radius: 4px; background: #fff;">
<option value="1.067">1.067 – Minor Second</option>
<option value="1.125">1.125 – Major Second</option>
<option value="1.2">1.200 – Minor Third</option>
<option value="1.25" selected>1.250 – Major Third</option>
<option value="1.333">1.333 – Perfect Fourth</option>
<option value="1.414">1.414 – Augmented Fourth</option>
<option value="1.5">1.500 – Perfect Fifth</option>
<option value="1.618">1.618 – Golden Ratio</option>
</select>
</div>
<div>
<label style="display: block; font-weight: 500; color: #374151; margin-bottom: 6px;">Type Scale (Desktop)</label>
<select id="typescale-desktop" style="width: 100%; padding: 8px; border: 1px solid #d1d5db; border-radius: 4px; background: #fff;">
<option value="1.067">1.067 – Minor Second</option>
<option value="1.125">1.125 – Major Second</option>
<option value="1.2">1.200 – Minor Third</option>
<option value="1.25">1.250 – Major Third</option>
<option value="1.333" selected>1.333 – Perfect Fourth</option>
<option value="1.414">1.414 – Augmented Fourth</option>
<option value="1.5">1.500 – Perfect Fifth</option>
<option value="1.618">1.618 – Golden Ratio</option>
</select>
</div>
</div>
</div>
<!-- Fluid Section -->
<div id="fluid-section" style="display: none; margin-bottom: 20px;">
<div style="display: flex; align-items: center; justify-content: space-between; margin-bottom: 16px; padding: 16px; background: #f8fafc; border-radius: 8px;">
<div style="display: flex; flex-direction: column; gap: 4px;">
<label style="font-weight: 600; color: #374151; font-size: 14px;">Fluid Sizing</label>
<span style="font-size: 13px; color: #6b7280;">Use clamp() for responsive font sizes</span>
</div>
<label class="oxymade-toggle-switch">
<input type="checkbox" id="fluid-enabled">
<span class="oxymade-slider"></span>
</label>
</div>
<!-- Custom Sizes Configuration -->
<div id="custom-sizes-config" style="display: none; padding: 16px; background: #f9fafb; border-radius: 8px; border: 1px solid #e5e7eb;">
<h3 style="margin: 0 0 16px 0; font-size: 16px; color: #374151;">Custom Sizes</h3>
<p style="margin: 0 0 16px 0; font-size: 13px; color: #6b7280;">Enter custom CSS units (px, rem, em, vw, clamp(), etc.)</p>
<div id="custom-sizes-list">
<div style="display: grid; grid-template-columns: 100px 1fr; gap: 12px; align-items: center; margin-bottom: 12px; padding: 12px; background: #ffffff; border-radius: 6px; border: 1px solid #e5e7eb;">
<label style="font-weight: 600; color: #374151;">Hero</label>
<input type="text" data-key="hero" data-type="value" placeholder="e.g., 40px, 2.5rem, clamp(2rem, 4vw, 3rem)" style="padding: 8px; border: 1px solid #d1d5db; border-radius: 4px;">
</div>
<div style="display: grid; grid-template-columns: 100px 1fr; gap: 12px; align-items: center; margin-bottom: 12px; padding: 12px; background: #ffffff; border-radius: 6px; border: 1px solid #e5e7eb;">
<label style="font-weight: 600; color: #374151;">H1</label>
<input type="text" data-key="h1" data-type="value" placeholder="e.g., 32px, 2rem, clamp(1.5rem, 3vw, 2.5rem)" style="padding: 8px; border: 1px solid #d1d5db; border-radius: 4px;">
</div>
<div style="display: grid; grid-template-columns: 100px 1fr; gap: 12px; align-items: center; margin-bottom: 12px; padding: 12px; background: #ffffff; border-radius: 6px; border: 1px solid #e5e7eb;">
<label style="font-weight: 600; color: #374151;">H2</label>
<input type="text" data-key="h2" data-type="value" placeholder="e.g., 28px, 1.75rem, clamp(1.25rem, 2.5vw, 2rem)" style="padding: 8px; border: 1px solid #d1d5db; border-radius: 4px;">
</div>
<div style="display: grid; grid-template-columns: 100px 1fr; gap: 12px; align-items: center; margin-bottom: 12px; padding: 12px; background: #ffffff; border-radius: 6px; border: 1px solid #e5e7eb;">
<label style="font-weight: 600; color: #374151;">H3</label>
<input type="text" data-key="h3" data-type="value" placeholder="e.g., 24px, 1.5rem, clamp(1.125rem, 2vw, 1.75rem)" style="padding: 8px; border: 1px solid #d1d5db; border-radius: 4px;">
</div>
<div style="display: grid; grid-template-columns: 100px 1fr; gap: 12px; align-items: center; margin-bottom: 12px; padding: 12px; background: #ffffff; border-radius: 6px; border: 1px solid #e5e7eb;">
<label style="font-weight: 600; color: #374151;">H4</label>
<input type="text" data-key="h4" data-type="value" placeholder="e.g., 20px, 1.25rem, clamp(1rem, 1.5vw, 1.5rem)" style="padding: 8px; border: 1px solid #d1d5db; border-radius: 4px;">
</div>
<div style="display: grid; grid-template-columns: 100px 1fr; gap: 12px; align-items: center; margin-bottom: 12px; padding: 12px; background: #ffffff; border-radius: 6px; border: 1px solid #e5e7eb;">
<label style="font-weight: 600; color: #374151;">H5</label>
<input type="text" data-key="h5" data-type="value" placeholder="e.g., 18px, 1.125rem, clamp(0.875rem, 1vw, 1.25rem)" style="padding: 8px; border: 1px solid #d1d5db; border-radius: 4px;">
</div>
<div style="display: grid; grid-template-columns: 100px 1fr; gap: 12px; align-items: center; margin-bottom: 12px; padding: 12px; background: #ffffff; border-radius: 6px; border: 1px solid #e5e7eb;">
<label style="font-weight: 600; color: #374151;">H6</label>
<input type="text" data-key="h6" data-type="value" placeholder="e.g., 16px, 1rem, clamp(0.75rem, 0.5vw, 1rem)" style="padding: 8px; border: 1px solid #d1d5db; border-radius: 4px;">
</div>
</div>
</div>
<!-- Fluid Custom Sizes Configuration -->
<div id="fluid-custom-sizes-config" style="display: none; padding: 16px; background: #f0f9ff; border-radius: 8px; border: 1px solid #bae6fd;">
<h3 style="margin: 0 0 16px 0; font-size: 16px; color: #0284c7;">Fluid Custom Sizes</h3>
<p style="margin: 0 0 16px 0; font-size: 13px; color: #6b7280;">Enter mobile and desktop sizes to generate clamp() values</p>
<div id="fluid-custom-sizes-list">
<div style="display: grid; grid-template-columns: 100px 1fr 1fr; gap: 12px; align-items: center; margin-bottom: 12px; padding: 12px; background: #ffffff; border-radius: 6px; border: 1px solid #e5e7eb;">
<label style="font-weight: 600; color: #374151;">Hero</label>
<input type="number" data-key="hero" data-type="mobile" placeholder="Mobile" min="8" max="120" style="padding: 8px; border: 1px solid #d1d5db; border-radius: 4px;">
<input type="number" data-key="hero" data-type="desktop" placeholder="Desktop" min="8" max="200" style="padding: 8px; border: 1px solid #d1d5db; border-radius: 4px;">
</div>
<div style="display: grid; grid-template-columns: 100px 1fr 1fr; gap: 12px; align-items: center; margin-bottom: 12px; padding: 12px; background: #ffffff; border-radius: 6px; border: 1px solid #e5e7eb;">
<label style="font-weight: 600; color: #374151;">H1</label>
<input type="number" data-key="h1" data-type="mobile" placeholder="Mobile" min="8" max="120" style="padding: 8px; border: 1px solid #d1d5db; border-radius: 4px;">
<input type="number" data-key="h1" data-type="desktop" placeholder="Desktop" min="8" max="200" style="padding: 8px; border: 1px solid #d1d5db; border-radius: 4px;">
</div>
<div style="display: grid; grid-template-columns: 100px 1fr 1fr; gap: 12px; align-items: center; margin-bottom: 12px; padding: 12px; background: #ffffff; border-radius: 6px; border: 1px solid #e5e7eb;">
<label style="font-weight: 600; color: #374151;">H2</label>
<input type="number" data-key="h2" data-type="mobile" placeholder="Mobile" min="8" max="120" style="padding: 8px; border: 1px solid #d1d5db; border-radius: 4px;">
<input type="number" data-key="h2" data-type="desktop" placeholder="Desktop" min="8" max="200" style="padding: 8px; border: 1px solid #d1d5db; border-radius: 4px;">
</div>
<div style="display: grid; grid-template-columns: 100px 1fr 1fr; gap: 12px; align-items: center; margin-bottom: 12px; padding: 12px; background: #ffffff; border-radius: 6px; border: 1px solid #e5e7eb;">
<label style="font-weight: 600; color: #374151;">H3</label>
<input type="number" data-key="h3" data-type="mobile" placeholder="Mobile" min="8" max="120" style="padding: 8px; border: 1px solid #d1d5db; border-radius: 4px;">
<input type="number" data-key="h3" data-type="desktop" placeholder="Desktop" min="8" max="200" style="padding: 8px; border: 1px solid #d1d5db; border-radius: 4px;">
</div>
<div style="display: grid; grid-template-columns: 100px 1fr 1fr; gap: 12px; align-items: center; margin-bottom: 12px; padding: 12px; background: #ffffff; border-radius: 6px; border: 1px solid #e5e7eb;">
<label style="font-weight: 600; color: #374151;">H4</label>
<input type="number" data-key="h4" data-type="mobile" placeholder="Mobile" min="8" max="120" style="padding: 8px; border: 1px solid #d1d5db; border-radius: 4px;">
<input type="number" data-key="h4" data-type="desktop" placeholder="Desktop" min="8" max="200" style="padding: 8px; border: 1px solid #d1d5db; border-radius: 4px;">
</div>
<div style="display: grid; grid-template-columns: 100px 1fr 1fr; gap: 12px; align-items: center; margin-bottom: 12px; padding: 12px; background: #ffffff; border-radius: 6px; border: 1px solid #e5e7eb;">
<label style="font-weight: 600; color: #374151;">H5</label>
<input type="number" data-key="h5" data-type="mobile" placeholder="Mobile" min="8" max="120" style="padding: 8px; border: 1px solid #d1d5db; border-radius: 4px;">
<input type="number" data-key="h5" data-type="desktop" placeholder="Desktop" min="8" max="200" style="padding: 8px; border: 1px solid #d1d5db; border-radius: 4px;">
</div>
<div style="display: grid; grid-template-columns: 100px 1fr 1fr; gap: 12px; align-items: center; margin-bottom: 12px; padding: 12px; background: #ffffff; border-radius: 6px; border: 1px solid #e5e7eb;">
<label style="font-weight: 600; color: #374151;">H6</label>
<input type="number" data-key="h6" data-type="mobile" placeholder="Mobile" min="8" max="120" style="padding: 8px; border: 1px solid #d1d5db; border-radius: 4px;">
<input type="number" data-key="h6" data-type="desktop" placeholder="Desktop" min="8" max="200" style="padding: 8px; border: 1px solid #d1d5db; border-radius: 4px;">
</div>
</div>
</div>
</div>
</div>
`;
// Insert the form HTML into the modal
$('#typography-config-content').html(formHTML);
// Attach event handlers to the dynamically created form elements
attachTypographyEventHandlers();
// Now load the configuration and update the form
$.ajax({
url: ajaxurl,
type: 'POST',
data: {
action: 'oxymade_get_typography_config',
nonce: '<?php echo wp_create_nonce('oxymade-admin-nonce'); ?>'
},
success: function(response) {
if (response.success) {
renderTypographyConfig(response.data.config);
}
}
});
}
// Attach event handlers to typography form elements
var _typoSaveTimer = null;
function attachTypographyEventHandlers() {
// Toggle event handlers for conditional UI and auto-save
$('#typescale-enabled').off('change').on('change', function() {
updateCustomSizesVisibility();
autoSaveTypographyConfig();
});
$('#fluid-enabled').off('change').on('change', function() {
updateCustomSizesVisibility();
autoSaveTypographyConfig();
});
// Numeric input handlers (debounced) for base font, scale ratio, and custom sizes
$('#typography-config-modal').off('change.typo input.typo', 'input[type="number"], input[type="text"][data-key], select')
.on('change.typo input.typo', 'input[type="number"], input[type="text"][data-key], select', function() {
clearTimeout(_typoSaveTimer);
_typoSaveTimer = setTimeout(autoSaveTypographyConfig, 600);
});
}
// Auto-save typography configuration when toggles change
function autoSaveTypographyConfig() {
const typescaleChecked = $('#typescale-enabled').is(':checked');
const fluidChecked = $('#fluid-enabled').is(':checked');
const config = {
typescale_enabled: typescaleChecked,
fluid_enabled: fluidChecked,
base_font_size_mobile: parseInt($('#base-font-mobile').val()) || 16,
base_font_size_desktop: parseInt($('#base-font-desktop').val()) || 18,
typescale_mobile: parseFloat($('#typescale-mobile').val()) || 1.25,
typescale_desktop: parseFloat($('#typescale-desktop').val()) || 1.333,
custom_sizes: {}
};
// Collect ALL custom sizes data to preserve when switching modes
config.fluid_sizes = config.fluid_sizes || {};
config.custom_units_sizes = config.custom_units_sizes || {};
// Collect Fluid sizes
$('#fluid-custom-sizes-list input[data-key]').each(function() {
const key = $(this).data('key');
const type = $(this).data('type');
const value = $(this).val();
if (!config.fluid_sizes[key]) {
config.fluid_sizes[key] = {};
}
config.fluid_sizes[key][type] = parseInt(value) || 0;
});
// Collect Custom Units sizes
$('#custom-sizes-list input[data-key]').each(function() {
const key = $(this).data('key');
const type = $(this).data('type');
const value = $(this).val();
if (!config.custom_units_sizes[key]) {
config.custom_units_sizes[key] = {};
}
config.custom_units_sizes[key][type] = value;
});
// Set active custom_sizes based on current mode
if (typescaleChecked) {
config.custom_sizes = {};
} else if (fluidChecked) {
config.custom_sizes = config.fluid_sizes;
} else {
config.custom_sizes = config.custom_units_sizes;
}
// Save via AJAX
$.ajax({
url: ajaxurl,
type: 'POST',
data: {
action: 'oxymade_save_typography_config',
config: config,
nonce: '<?php echo wp_create_nonce('oxymade-admin-nonce'); ?>'
},
success: function(response) {},
error: function(xhr, status, error) {}
});
}
// Render typography configuration form
function renderTypographyConfig(config) {
const typescaleEnabled = config.typescale_enabled === true || config.typescale_enabled === 1 || config.typescale_enabled === '1';
const fluidEnabled = config.fluid_enabled === true || config.fluid_enabled === 1 || config.fluid_enabled === '1';
const typescaleToggle = $('#typescale-enabled');
const fluidToggle = $('#fluid-enabled');
if (typescaleToggle.length > 0) {
typescaleToggle.prop('checked', typescaleEnabled);
}
if (fluidToggle.length > 0) {
fluidToggle.prop('checked', fluidEnabled);
}
// Update visibility after setting toggle states
updateCustomSizesVisibility();
// Update input values
$('#base-font-mobile').val(config.base_font_size_mobile);
$('#base-font-desktop').val(config.base_font_size_desktop);
$('#typescale-mobile').val(config.typescale_mobile);
$('#typescale-desktop').val(config.typescale_desktop);
// Default placeholder values for each heading
const defaultSizes = {
hero: { mobile: 40, desktop: 72 },
h1: { mobile: 32, desktop: 48 },
h2: { mobile: 24, desktop: 32 },
h3: { mobile: 20, desktop: 28 },
h4: { mobile: 18, desktop: 24 },
h5: { mobile: 16, desktop: 20 },
h6: { mobile: 14, desktop: 18 }
};
// Update Fluid sizes with preserved data or defaults
Object.keys(defaultSizes).forEach(key => {
const fluidData = config.fluid_sizes && config.fluid_sizes[key];
const mobileValue = fluidData ? fluidData.mobile : defaultSizes[key].mobile;
const desktopValue = fluidData ? fluidData.desktop : defaultSizes[key].desktop;
$(`#fluid-custom-sizes-list input[data-key="${key}"][data-type="mobile"]`).val(mobileValue);
$(`#fluid-custom-sizes-list input[data-key="${key}"][data-type="desktop"]`).val(desktopValue);
});
// Update Custom Units sizes
if (config.custom_units_sizes) {
Object.keys(config.custom_units_sizes).forEach(key => {
const size = config.custom_units_sizes[key];
if (size.value !== undefined) {
$(`#custom-sizes-list input[data-key="${key}"][data-type="value"]`).val(size.value);
}
});
}
}
// Update custom sizes visibility based on toggles
function updateCustomSizesVisibility() {
const isTypeScaleEnabled = $('#typescale-enabled').is(':checked');
const isFluidEnabled = $('#fluid-enabled').is(':checked');
if (isTypeScaleEnabled) {
// Type scale is on - show type scale config, hide fluid and custom sections
$('#typescale-config').show();
$('#custom-sizes-config').hide();
$('#fluid-custom-sizes-config').hide();
$('#fluid-section').hide();
} else {
// Type scale is off - hide type scale config, show fluid toggle section
$('#typescale-config').hide();
$('#fluid-section').show();
if (isFluidEnabled) {
$('#custom-sizes-config').hide();
$('#fluid-custom-sizes-config').show();
} else {
$('#custom-sizes-config').show();
$('#fluid-custom-sizes-config').hide();
}
}
}
// Save typography configuration
$('#save-typography-config').on('click', function() {
const typescaleChecked = $('#typescale-enabled').is(':checked');
const fluidChecked = $('#fluid-enabled').is(':checked');
const config = {
typescale_enabled: typescaleChecked,
fluid_enabled: fluidChecked,
base_font_size_mobile: parseInt($('#base-font-mobile').val()) || 16,
base_font_size_desktop: parseInt($('#base-font-desktop').val()) || 18,
typescale_mobile: parseFloat($('#typescale-mobile').val()) || 1.25,
typescale_desktop: parseFloat($('#typescale-desktop').val()) || 1.333,
custom_sizes: {}
};
config.fluid_sizes = {};
config.custom_units_sizes = {};
// Collect Fluid sizes
$('#fluid-custom-sizes-list input[data-key]').each(function() {
const key = $(this).data('key');
const type = $(this).data('type');
const value = $(this).val();
if (!config.fluid_sizes[key]) config.fluid_sizes[key] = {};
config.fluid_sizes[key][type] = parseInt(value) || 0;
});
// Collect Custom Units sizes
$('#custom-sizes-list input[data-key]').each(function() {
const key = $(this).data('key');
const type = $(this).data('type');
const value = $(this).val();
if (!config.custom_units_sizes[key]) config.custom_units_sizes[key] = {};
config.custom_units_sizes[key][type] = value;
});
// Set active custom_sizes based on current mode
if (typescaleChecked) {
// Typescale mode - no custom sizes needed
config.custom_sizes = {};
} else if (fluidChecked) {
config.custom_sizes = config.fluid_sizes;
} else {
config.custom_sizes = config.custom_units_sizes;
}
$.ajax({
url: ajaxurl,
type: 'POST',
data: {
action: 'oxymade_save_typography_config',
config: config,
nonce: '<?php echo wp_create_nonce('oxymade-admin-nonce'); ?>'
},
success: function(response) {
if (response.success) {
$('#typography-config-modal').hide();
}
},
error: function(xhr, status, error) {}
});
});
// Reset from Template button
$('#reset-from-template').on('click', function() {
if (!confirm('This will overwrite your current heading sizes with values from your design set template. Continue?')) {
return;
}
const $btn = $(this);
$btn.prop('disabled', true).text('Resetting...');
$.ajax({
url: ajaxurl,
type: 'POST',
data: {
action: 'oxymade_reset_typography_from_template',
nonce: '<?php echo wp_create_nonce('oxymade-admin-nonce'); ?>'
},
success: function(response) {
$btn.prop('disabled', false).html('<span class="dashicons dashicons-image-rotate" style="font-size: 16px; width: 16px; height: 16px; margin-top: 2px;"></span> Reset from Template');
if (response.success && response.data.config) {
renderTypographyConfig(response.data.config);
const $notice = $('<div class="notice notice-success" style="position: fixed; top: 32px; right: 20px; z-index: 100002; padding: 8px 16px;"><p>Heading sizes reset from template!</p></div>');
$('body').append($notice);
setTimeout(() => $notice.fadeOut(300, function() { $(this).remove(); }), 2000);
} else if (response.data) {
alert(response.data);
}
},
error: function() {
$btn.prop('disabled', false).html('<span class="dashicons dashicons-image-rotate" style="font-size: 16px; width: 16px; height: 16px; margin-top: 2px;"></span> Reset from Template');
}
});
});
// Complete Setup — run all remaining steps sequentially
$('#oxymade-complete-setup').on('click', function() {
const $btn = $(this);
const nonce = '<?php echo wp_create_nonce('oxymade-admin-nonce'); ?>';
$btn.prop('disabled', true);
const steps = [
{ action: 'oxymade_install_palette', label: 'Colors & Spacing' },
{ action: 'oxymade_install_typography', label: 'Typography' },
{ action: 'oxymade_install_global_settings', label: 'Global Settings' },
{ action: 'oxymade_sync_components', label: 'Components', data: { mode: 'update' } }
];
let current = 0;
const failedSteps = [];
function runNext() {
if (current >= steps.length) {
if (failedSteps.length > 0) {
$btn.text('⚠ Completed with errors');
alert('Setup completed but these steps failed: ' + failedSteps.join(', ') + '. Try re-installing them individually.');
} else {
$btn.text('Setup Complete!');
}
$('#oxymade-setup-banner').delay(800).fadeOut(400);
setTimeout(() => location.reload(), 1500);
return;
}
const step = steps[current];
$btn.text('Installing ' + step.label + '...');
const data = Object.assign({ action: step.action, nonce: nonce }, step.data || {});
$.ajax({
url: ajaxurl,
type: 'POST',
data: data,
success: function(response) {
if (!response.success) { failedSteps.push(step.label); }
current++; runNext();
},
error: function() { failedSteps.push(step.label); current++; runNext(); }
});
}
runNext();
});
// ── Welcome Wizard ──
$(document).ready(function() {
const $wizard = $('#oxymade-welcome-wizard');
if (!$wizard.length || $wizard.css('display') === 'none') return;
// License screen handlers (Screen 0)
function goToScreen1() {
$('#wizard-screen-0').fadeOut(200, function() {
$('#wizard-screen-1').fadeIn(200);
});
initWizardSets();
}
$('#wizard-skip-license, #wizard-continue-no-license').on('click', function(e) {
e.preventDefault();
goToScreen1();
});
$('#wizard-activate-license').on('click', function() {
var $btn = $(this);
var key = $('#wizard-license-key').val().trim();
var $msg = $('#wizard-license-message');
if (!key) { $msg.show().css({'background': '#fef2f2', 'color': '#dc2626'}).text('Please enter a license key.'); return; }
$btn.prop('disabled', true).text('Activating...');
$.ajax({
url: ajaxurl, type: 'POST',
data: { action: 'oxymade_license_action', license_action: 'activate', license_key: key, nonce: '<?php echo wp_create_nonce("oxymade-license-action"); ?>' },
success: function(r) {
if (r.success) {
$msg.show().css({'background': '#f0fdf4', 'color': '#16a34a'}).text('License activated! Continuing...');
setTimeout(goToScreen1, 1000);
} else {
$msg.show().css({'background': '#fef2f2', 'color': '#dc2626'}).text(r.data || 'Activation failed.');
$btn.prop('disabled', false).text('Activate');
}
},
error: function() {
$msg.show().css({'background': '#fef2f2', 'color': '#dc2626'}).text('Connection error. Try again.');
$btn.prop('disabled', false).text('Activate');
}
});
});
// Wait for admin.js to expose OxyMadeAdmin (it loads in footer, after this inline script)
function initWizardSets() {
if (window.OxyMadeAdmin && window.OxyMadeAdmin.fetchDesignSets) {
window.OxyMadeAdmin.fetchDesignSets('#wizard-design-sets-container', function(setName) {
$('#wizard-selected-set').text(setName);
$('#wizard-screen-1').fadeOut(200, function() {
$('#wizard-screen-2').fadeIn(200);
});
});
} else {
setTimeout(initWizardSets, 50);
}
}
// Only fetch immediately if Screen 1 is visible (license already active)
if ($('#wizard-screen-1').css('display') !== 'none') {
initWizardSets();
}
// Wizard search filter
var wizardAllSets = [];
var wizardOnSelect = null;
var origFetch = initWizardSets;
$('#wizard-design-set-search').on('input', function() {
var term = $(this).val().toLowerCase();
if (!wizardAllSets.length) return;
var filtered = wizardAllSets.filter(function(s) {
return s.title.toLowerCase().indexOf(term) !== -1 ||
(s.description && s.description.toLowerCase().indexOf(term) !== -1);
});
if (window.OxyMadeAdmin && window.OxyMadeAdmin.fetchDesignSets) {
// Re-render filtered sets — need access to renderDesignSets
var container = $('#wizard-design-sets-container');
container.empty();
if (!filtered.length) {
container.html('<p style="grid-column: 1/-1; text-align: center; padding: 20px; color: #666;">No design sets match your search.</p>');
return;
}
filtered.forEach(function(set) {
var card = $('<div class="design-set-card"></div>').css({
'border': '1px solid #e0e0e0', 'border-radius': '8px', 'overflow': 'hidden', 'transition': 'transform 0.2s, box-shadow 0.2s', 'cursor': 'pointer'
}).hover(
function() { $(this).css({'transform': 'translateY(-3px)', 'box-shadow': '0 4px 12px rgba(0,0,0,0.1)'}); },
function() { $(this).css({'transform': 'translateY(0)', 'box-shadow': 'none'}); }
);
if (set.screenshot) {
card.append('<img src="' + set.screenshot + '" alt="' + set.title + '" style="width: 100%; height: 180px; object-fit: cover;">');
} else {
card.append('<div style="width: 100%; height: 180px; background-color: #f5f5f5; display: flex; align-items: center; justify-content: center;"><span class="dashicons dashicons-format-image" style="font-size: 48px; opacity: 0.3;"></span></div>');
}
var content = $('<div></div>').css('padding', '15px');
content.append('<h3 style="margin-top: 0; margin-bottom: 10px;">' + set.title + '</h3>');
if (set.description) content.append('<p style="margin-bottom: 15px;">' + set.description.substring(0, 100) + '</p>');
content.append('<button class="button button-primary install-set" data-id="' + set.id + '">Set as Default</button>');
card.append(content);
container.append(card);
});
container.find('.install-set').on('click', function(e) {
e.preventDefault();
var setId = $(this).data('id');
var btn = $(this);
var setName = btn.closest('.design-set-card').find('h3').text();
btn.html('<span class="dashicons dashicons-update" style="animation: spin 1s linear infinite;"></span> Setting...').prop('disabled', true);
$.ajax({
url: ajaxurl, type: 'POST',
data: { action: 'oxymade_install_design_set', nonce: '<?php echo wp_create_nonce("oxymade-admin-nonce"); ?>', set_name: setName },
success: function(r) {
if (r.success) {
btn.html('<span class="dashicons dashicons-yes"></span> Default Set!');
$('#wizard-selected-set').text(setName);
$('#wizard-screen-1').fadeOut(200, function() { $('#wizard-screen-2').fadeIn(200); });
} else { btn.text('Set as Default').prop('disabled', false); }
},
error: function() { btn.text('Set as Default').prop('disabled', false); }
});
});
}
});
// Hook into fetch completion to cache sets for search
var _origAjax = $.ajax;
$(document).ajaxComplete(function(e, xhr, settings) {
if (settings.data && typeof settings.data === 'string' && settings.data.indexOf('oxymade_fetch_design_sets') !== -1) {
try {
var resp = JSON.parse(xhr.responseText);
if (resp.success && resp.data) {
wizardAllSets = Array.isArray(resp.data) ? resp.data : (resp.data.data ? resp.data.data : [resp.data]);
}
} catch(ex) {}
}
});
// "Complete Setup Now" — run all steps sequentially
$('#wizard-complete-setup').on('click', function() {
const $btn = $(this);
const nonce = '<?php echo wp_create_nonce('oxymade-admin-nonce'); ?>';
$btn.prop('disabled', true);
const steps = [
{ action: 'oxymade_install_palette', label: 'Colors & Spacing' },
{ action: 'oxymade_install_typography', label: 'Typography' },
{ action: 'oxymade_install_global_settings', label: 'Global Settings' },
{ action: 'oxymade_sync_components', label: 'Components', data: { mode: 'update' } }
];
let current = 0;
const failedSteps = [];
function runNext() {
if (current >= steps.length) {
if (failedSteps.length > 0) {
$btn.text('Completed with errors');
alert('Setup completed but these steps failed: ' + failedSteps.join(', '));
} else {
$btn.text('Setup Complete!');
}
$.post(ajaxurl, { action: 'oxymade_dismiss_wizard', nonce: nonce });
setTimeout(function() { location.reload(); }, 1200);
return;
}
const step = steps[current];
$btn.text('Installing ' + step.label + '...');
const data = Object.assign({ action: step.action, nonce: nonce }, step.data || {});
$.ajax({
url: ajaxurl,
type: 'POST',
data: data,
success: function(response) {
if (!response.success) failedSteps.push(step.label);
current++; runNext();
},
error: function() { failedSteps.push(step.label); current++; runNext(); }
});
}
runNext();
});
// "Go Step by Step" / "Skip" — dismiss wizard
$('#wizard-step-by-step, #wizard-skip').on('click', function(e) {
e.preventDefault();
$.post(ajaxurl, {
action: 'oxymade_dismiss_wizard',
nonce: '<?php echo wp_create_nonce('oxymade-admin-nonce'); ?>'
});
$wizard.fadeOut(200);
});
});
// Fluid Text Sizing Toggle
$('input[name="oxymade_settings[fluid_text_sizing]"]').on('change', function() {
const isChecked = $(this).is(':checked');
const value = isChecked ? '1' : '0';
// Show loading state on toggle
const $toggle = $(this).closest('.oxymade-toggle-switch');
$toggle.addClass('saving');
// Save via AJAX
$.ajax({
url: '<?php echo admin_url('admin-ajax.php'); ?>',
type: 'POST',
data: {
action: 'oxymade_toggle_fluid_text_sizing',
fluid_text_sizing: value,
nonce: '<?php echo wp_create_nonce('oxymade-admin-nonce'); ?>'
},
success: function(response) {
$toggle.removeClass('saving');
// Show success message briefly
const $notice = $('<div class="notice notice-success is-dismissible" style="position: fixed; top: 32px; right: 20px; z-index: 9999;"><p>Fluid text sizing setting saved!</p></div>');
$('body').append($notice);
setTimeout(() => $notice.fadeOut(), 2000);
},
error: function(xhr, status, error) {
$toggle.removeClass('saving');
// Show error message with more details
let errorMsg = 'Error saving fluid text sizing setting!';
if (xhr.responseText) {
try {
const response = JSON.parse(xhr.responseText);
if (response.data && response.data.message) {
errorMsg = response.data.message;
}
} catch (e) {
errorMsg = 'Server error: ' + xhr.status;
}
}
const $notice = $('<div class="notice notice-error is-dismissible" style="position: fixed; top: 32px; right: 20px; z-index: 9999;"><p>' + errorMsg + '</p></div>');
$('body').append($notice);
setTimeout(() => $notice.fadeOut(), 5000);
}
});
});
})(jQuery); // End IIFE
</script>
<!-- Delete Selectors Warning Modal -->
<div id="oxymade-delete-modal" style="display: none; position: fixed; z-index: 100001; left: 0; top: 0; width: 100%; height: 100%; overflow: auto; background-color: rgba(0,0,0,0.4);">
<div style="background-color: #ffffff; margin: 10% auto; padding: 30px; border: 1px solid #e5e7eb; width: 90%; max-width: 500px; box-shadow: 0 4px 6px rgba(0,0,0,0.1); border-radius: 8px;">
<div style="display: flex; align-items: center; justify-content: space-between; margin-bottom: 20px;">
<div style="display: flex; align-items: center;">
<span class="dashicons dashicons-warning" style="color: #dc2626; font-size: 24px; margin-right: 12px;"></span>
<h3 style="margin: 0; color: #dc2626; font-size: 18px;">Delete Selectors</h3>
</div>
<div style="display: flex; align-items: center; gap: 12px;">
<div style="display: flex; align-items: center; gap: 6px;">
<span style="font-size: 12px; color: #6b7280;">Keep components</span>
<label class="oxymade-toggle-switch" style="display: flex; align-items: center;">
<input type="checkbox" id="oxymade-delete-keep-components" value="1" checked>
<span class="oxymade-slider"></span>
</label>
</div>
<div style="display: flex; align-items: center; gap: 6px;">
<span style="font-size: 12px; color: #6b7280;">Include custom*</span>
<label class="oxymade-toggle-switch" style="display: flex; align-items: center;">
<input type="checkbox" id="oxymade-delete-include-custom" value="1">
<span class="oxymade-slider"></span>
</label>
</div>
</div>
</div>
<p style="margin: 0 0 20px 0; color: #6b7280; line-height: 1.5;">
Choose what to delete from your Oxygen selectors list. This action cannot be undone.
</p>
<!-- Selector Count Display -->
<div style="background: #f8fafc; border: 1px solid #e2e8f0; border-radius: 6px; padding: 16px; margin-bottom: 20px;">
<h4 style="margin: 0 0 12px 0; color: #374151; font-size: 14px;">Current Selectors in Database:</h4>
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 12px; font-size: 13px;">
<div>
<strong>Total Selectors:</strong> <span id="oxymade-modal-total-count">Loading...</span>
</div>
<div>
<strong>OxyMade Selectors:</strong> <span id="oxymade-modal-oxymade-count">Loading...</span>
</div>
<div>
<strong>Other Selectors:</strong> <span id="oxymade-modal-other-count">Loading...</span>
</div>
<div>
<strong>Custom Selectors:</strong> <span id="oxymade-modal-custom-count">Loading...</span>
</div>
<div>
<strong>Components:</strong> <span id="oxymade-modal-component-count">Loading...</span>
</div>
</div>
</div>
<div style="background: #f9fafb; border: 1px solid #e5e7eb; border-radius: 6px; padding: 16px; margin-bottom: 20px;">
<div style="display: flex; align-items: center; margin-bottom: 8px;">
<span class="dashicons dashicons-tag" style="color: #f59e0b; margin-right: 8px;"></span>
<strong style="color: #374151;">Delete OxyMade Only</strong>
</div>
<p id="oxymade-modal-desc-oxymade" style="margin: 0; font-size: 14px; color: #6b7280;">Removes OxyMade utility selectors, keeps other selectors and custom* and components selectors intact.</p>
<div style="display: flex; align-items: center; margin-top: 12px;">
<span class="dashicons dashicons-trash" style="color: #dc2626; margin-right: 8px;"></span>
<strong style="color: #374151;">Delete All Selectors</strong>
</div>
<p id="oxymade-modal-desc-all" style="margin: 0; font-size: 14px; color: #6b7280;">Removes ALL selectors from Oxygen except custom* and components selectors.</p>
</div>
<div style="display: flex; justify-content: flex-end; gap: 12px;">
<button type="button" id="oxymade-cancel-delete-btn" class="button button-secondary" style="font-size: 13px; padding: 6px 16px; height: auto;">
Cancel
</button>
<button type="button" id="oxymade-delete-oxymade-only" class="button button-primary" style="background: #f59e0b; border-color: #f59e0b; font-size: 13px; padding: 6px 16px; height: auto;">
<span class="dashicons dashicons-tag" style="margin-right: 6px; font-size: 16px;"></span>
Delete OxyMade (<span id="oxymade-btn-oxymade-count">0</span>)
</button>
<button type="button" id="oxymade-delete-all-selectors" class="button button-primary" style="background: #dc2626; border-color: #dc2626; font-size: 13px; padding: 6px 16px; height: auto;">
<span class="dashicons dashicons-trash" style="margin-right: 6px; font-size: 16px;"></span>
Delete All (<span id="oxymade-btn-all-count">0</span>)
</button>
</div>
</div>
</div>
<!-- License Modal -->
<div id="oxymade-license-modal" class="oxymade-modal" style="display: none;">
<div class="oxymade-modal-overlay"></div>
<div class="oxymade-modal-content">
<div class="oxymade-modal-header">
<h2><?php echo $is_license_active ? __('Manage License', 'oxymade') : __('Activate License', 'oxymade'); ?></h2>
<button type="button" class="oxymade-modal-close" aria-label="<?php _e('Close', 'oxymade'); ?>">
<span class="dashicons dashicons-no-alt"></span>
</button>
</div>
<div class="oxymade-modal-body">
<?php
global $oxymade_license_client;
if (isset($oxymade_license_client)) {
$settings = $oxymade_license_client->settings();
$activation = $settings->get_activation();
$action = !empty($activation->id) ? 'deactivate' : 'activate';
?>
<form method="post" id="oxymade-license-form" action="">
<input type="hidden" name="_action" value="<?php echo esc_attr($action); ?>">
<input type="hidden" name="_nonce" value="<?php echo esc_attr(wp_create_nonce('OxyMade')); ?>">
<input type="hidden" name="activation_id" value="<?php echo esc_attr($settings->activation_id ?? ''); ?>">
<?php if ('activate' === $action) : ?>
<div class="form-group">
<label for="license_key"><?php _e('License Key', 'oxymade'); ?></label>
<p class="description">
<?php _e('Enter your license key to activate OxyMade and receive updates.', 'oxymade'); ?>
</p>
<input type="password"
class="widefat"
autocomplete="off"
name="license_key"
id="license_key"
placeholder="<?php _e('Enter your license key...', 'oxymade'); ?>"
required>
</div>
<div class="oxymade-modal-footer">
<button type="submit" class="button button-primary" id="oxymade-activate-license" style="font-size: 13px; padding: 8px 16px; height: auto;">
<span class="dashicons dashicons-yes-alt" style="font-size: 16px;"></span>
<?php _e('Activate License', 'oxymade'); ?>
</button>
<button type="button" class="button button-secondary oxymade-modal-close" style="font-size: 13px; padding: 8px 16px; height: auto;">
<?php _e('Cancel', 'oxymade'); ?>
</button>
</div>
<?php else : ?>
<div class="license-active-info">
<div class="success-message">
<span class="dashicons dashicons-yes-alt"></span>
<p><?php _e('Your license is successfully activated for this site.', 'oxymade'); ?></p>
</div>
<div class="license-details">
<p><strong><?php _e('Status:', 'oxymade'); ?></strong> <?php echo esc_html($license_status_display); ?></p>
</div>
</div>
<div class="oxymade-modal-footer">
<button type="submit" class="button button-danger" id="oxymade-deactivate-license" style="font-size: 13px; padding: 8px 16px; height: auto;">
<span class="dashicons dashicons-dismiss" style="font-size: 16px;"></span>
<?php _e('Deactivate License', 'oxymade'); ?>
</button>
<button type="button" class="button button-secondary oxymade-modal-close" style="font-size: 13px; padding: 8px 16px; height: auto;">
<?php _e('Close', 'oxymade'); ?>
</button>
</div>
<?php endif; ?>
</form>
<?php
} else {
echo '<p class="error-message">' . __('License client not initialized. Please check your configuration.', 'oxymade') . '</p>';
}
?>
</div>
</div>
</div>
<style>
.wrap.oxymade-admin-wrapper {
max-width: 1440px;
}
/* License Modal Styles */
.oxymade-modal {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
z-index: 999999;
}
.oxymade-modal-overlay {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.5);
cursor: pointer;
}
.oxymade-modal-content {
position: relative;
background: white;
max-width: 550px;
margin: 100px auto;
border-radius: 8px;
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.15);
}
.oxymade-modal-header {
padding: 20px 24px;
border-bottom: 1px solid #e5e7eb;
display: flex;
justify-content: space-between;
align-items: center;
}
.oxymade-modal-header h2 {
margin: 0;
font-size: 18px;
font-weight: 600;
color: #1f2937;
}
.oxymade-modal-close {
background: none;
border: none;
cursor: pointer;
padding: 4px;
color: #9ca3af;
transition: color 0.15s;
}
.oxymade-modal-close:hover {
color: #4b5563;
}
.oxymade-modal-close .dashicons {
font-size: 20px;
width: 20px;
height: 20px;
}
.oxymade-modal-body .form-group {
margin-bottom: 20px;
padding: 16px;
}
.oxymade-modal-body label {
display: block;
margin-bottom: 8px;
font-weight: 600;
color: #374151;
font-size: 14px;
}
.oxymade-modal-body .description {
margin: -4px 0 12px 0;
color: #6b7280;
font-size: 13px;
}
.oxymade-modal-body input[type="password"],
.oxymade-modal-body input[type="text"] {
width: 100%;
padding: 12px 16px;
border: 1px solid #d1d5db;
border-radius: 6px;
font-size: 14px;
transition: border-color 0.2s, box-shadow 0.2s;
}
.oxymade-modal-body input:focus {
outline: none;
border-color: #3b82f6;
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
}
.oxymade-modal-footer {
padding: 16px 24px;
border-top: 1px solid #e5e7eb;
display: flex;
gap: 10px;
justify-content: flex-end;
background: white;
border-radius: 0 0 8px 8px;
}
.oxymade-modal-footer .button {
padding: 8px 16px;
font-weight: 500;
font-size: 13px;
border: 1px solid;
border-radius: 6px;
cursor: pointer;
transition: all 0.15s ease;
display: inline-flex;
align-items: center;
gap: 6px;
}
.oxymade-modal-footer .button-primary {
background: #22c55e;
border-color: #22c55e;
color: white;
}
.oxymade-modal-footer .button-primary:hover {
background: #16a34a;
border-color: #16a34a;
}
.oxymade-modal-footer .button-danger {
background: white;
border-color: #e5e7eb;
color: #ef4444;
}
.oxymade-modal-footer .button-danger:hover {
background: #fef2f2;
border-color: #ef4444;
}
.oxymade-modal-footer .button-secondary {
background: white;
color: #6b7280;
border-color: #e5e7eb;
}
.oxymade-modal-footer .button-secondary:hover {
background: #f9fafb;
border-color: #d1d5db;
}
.license-active-info {
margin-bottom: 20px;
padding: 16px;
}
.success-message {
background: #f0fdf4;
border: 1px solid #22c55e;
border-radius: 8px;
padding: 16px;
display: flex;
align-items: center;
gap: 12px;
margin-bottom: 20px;
}
.success-message .dashicons {
color: #22c55e;
font-size: 24px;
width: 24px;
height: 24px;
}
.success-message p {
margin: 0;
color: #166534;
font-weight: 500;
}
.license-details {
background: #f9fafb;
border-radius: 6px;
padding: 16px;
}
.license-details p {
margin: 0;
color: #374151;
font-size: 14px;
}
.error-message {
color: #dc2626;
background: #fef2f2;
border: 1px solid #ef4444;
border-radius: 6px;
padding: 12px;
margin: 0;
}
.oxymade-modal-body #oxymade-license-form button[type="submit"] {
min-width: 120px;
}
.oxymade-modal-body #oxymade-license-form button .dashicons {
font-size: 16px;
}
</style>
<script>
jQuery(document).ready(function($) {
// Open modal when clicking license status
$('#oxymade-license-trigger').on('click', function(e) {
e.preventDefault();
e.stopPropagation();
$('#oxymade-license-modal').fadeIn(150);
$('body').css('overflow', 'hidden');
});
// Close modal function
function closeModal() {
$('#oxymade-license-modal').fadeOut(150);
$('body').css('overflow', '');
}
// Close modal when clicking close button
$('.oxymade-modal-close').on('click', function(e) {
e.preventDefault();
e.stopPropagation();
closeModal();
});
// Close modal when clicking overlay (outside the modal content)
$('.oxymade-modal-overlay').on('click', function(e) {
e.preventDefault();
e.stopPropagation();
closeModal();
});
// Prevent closing when clicking inside modal content
$('.oxymade-modal-content').on('click', function(e) {
e.stopPropagation();
});
// Close modal with Escape key
$(document).on('keydown', function(e) {
if (e.key === 'Escape' && $('#oxymade-license-modal').is(':visible')) {
closeModal();
}
});
// Handle license form submission
$('#oxymade-license-form').on('submit', function(e) {
e.preventDefault();
var $form = $(this);
var $submitBtn = $form.find('button[type="submit"]');
var originalText = $submitBtn.html();
var action = $form.find('input[name="_action"]').val();
// Disable button and show loading state
$submitBtn.prop('disabled', true).html(
'<span class="dashicons dashicons-update" style="animation: rotation 1s infinite linear;"></span> ' +
(action === 'activate' ? '<?php _e("Activating...", "oxymade"); ?>' : '<?php _e("Deactivating...", "oxymade"); ?>')
);
// Add rotation animation style if not exists
if (!$('style#rotation-animation').length) {
$('<style id="rotation-animation">@keyframes rotation { from { transform: rotate(0deg); } to { transform: rotate(360deg); } }</style>').appendTo('head');
}
// Submit via AJAX
$.ajax({
url: ajaxurl,
type: 'POST',
data: {
action: 'oxymade_license_action',
nonce: '<?php echo wp_create_nonce("oxymade-license-action"); ?>',
license_action: action,
license_key: $form.find('input[name="license_key"]').val(),
activation_id: $form.find('input[name="activation_id"]').val(),
_nonce: $form.find('input[name="_nonce"]').val()
},
success: function(response) {
if (response.success) {
// Show success message
alert(response.data.message || (action === 'activate' ? '<?php _e("License activated successfully!", "oxymade"); ?>' : '<?php _e("License deactivated successfully!", "oxymade"); ?>'));
// Reload page to reflect changes
window.location.reload();
} else {
alert(response.data || '<?php _e("An error occurred. Please try again.", "oxymade"); ?>');
$submitBtn.prop('disabled', false).html(originalText);
}
},
error: function(xhr, status, error) {
alert('<?php _e("Connection error. Please try again.", "oxymade"); ?>');
$submitBtn.prop('disabled', false).html(originalText);
}
});
});
});
// Add rotation animation for loading spinner
const style = document.createElement('style');
style.textContent = `
@keyframes rotation {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
`;
document.head.appendChild(style);
</script>
<?php
}
/**
* Enqueue admin scripts and styles
*/
function enqueue_admin_assets($hook)
{
// Always enqueue admin styles for menu icon styling
wp_enqueue_style(
'oxymade-admin-styles',
OXYMADE_PLUGIN_URL . 'assets/css/admin.css',
[],
OXYMADE_VERSION
);
// Only enqueue scripts on the settings page
if ($hook !== 'toplevel_page_oxymade-settings') {
return;
}
wp_enqueue_script(
'oxymade-admin-scripts',
OXYMADE_PLUGIN_URL . 'assets/js/admin.js',
['jquery'],
OXYMADE_VERSION,
true
);
wp_localize_script(
'oxymade-admin-scripts',
'oxymadeAdmin',
[
'ajaxUrl' => admin_url('admin-ajax.php'),
'nonce' => wp_create_nonce('oxymade-admin-nonce'),
'installPaletteConfirm' => 'Are you sure you want to install the default color palette? This will override any existing color settings.',
'installPaletteText' => 'Installing color palette...',
'currentDesignSet' => get_option('oxymade_default_designset_template', false)
]
);
}
add_action('admin_enqueue_scripts', __NAMESPACE__ . '\\enqueue_admin_assets');
/**
* AJAX handler for installing default color palette
*/
function install_default_palette()
{
// Check nonce for security
if (!isset($_POST['nonce']) || !wp_verify_nonce($_POST['nonce'], 'oxymade-admin-nonce')) {
wp_send_json_error(['message' => 'Invalid security token']);
exit;
}
// Check if user has permission
if (!current_user_can('manage_options')) {
wp_send_json_error(['message' => 'You do not have permission to perform this action']);
exit;
}
// Check if sync is enabled
$oxymade_settings = get_option('oxymade_settings', []);
$sync_with_oxygen = isset($oxymade_settings['sync_with_oxygen']) ? $oxymade_settings['sync_with_oxygen'] : true;
// Get design set info
$design_set = get_option('oxymade_default_designset_template', 'Layers');
$design_set_lower = strtolower($design_set);
// Always fetch fresh from design set source
// Try remote first, fall back to local bundled file
$palette_data = null;
$remote_url = "https://breakmade.com/assets/colors/{$design_set_lower}.json";
$remote_args = [
'timeout' => 30,
'headers' => ['Accept' => 'application/json'],
];
$response = wp_remote_get($remote_url, $remote_args);
if (!is_wp_error($response) && wp_remote_retrieve_response_code($response) === 200) {
$palette_data = json_decode(wp_remote_retrieve_body($response), true);
}
// Fall back to local bundled file if remote failed
if (empty($palette_data)) {
$local_data_file = OXYMADE_PLUGIN_DIR . "data/{$design_set_lower}.json";
if (file_exists($local_data_file)) {
$palette_data = json_decode(file_get_contents($local_data_file), true);
}
}
if (empty($palette_data)) {
wp_send_json_error(['message' => "Failed to fetch color palette for '{$design_set}' from remote and local sources."]);
exit;
}
// Extract the structured and flat palettes from the JSON
$structured_palette = isset($palette_data['structured_palette']) ? $palette_data['structured_palette'] : [];
$flat_palette = isset($palette_data['flat_palette']) ? $palette_data['flat_palette'] : [];
// Verify we have valid palette data
if (empty($structured_palette) || empty($flat_palette)) {
wp_send_json_error(['message' => 'Color palette data is incomplete or invalid']);
exit;
}
// Save fresh palette data to database (overwrites any user modifications)
update_option('oxymade_color_palette', json_encode($structured_palette));
update_option('oxymade_color_palette_flat', json_encode($flat_palette));
// Generate CSS file with color variables (including alt-shade)
if (!empty($flat_palette)) {
\OxyMade\ColorPalette\generate_css_file($flat_palette, $structured_palette);
}
// Only sync with Breakdance if sync toggle is ON
if ($sync_with_oxygen) {
// Update Oxygen global settings if Oxygen is active
if (function_exists('\\Breakdance\\Data\\get_global_settings_array')) {
try {
// Sync colors with Breakdance
\OxyMade\ColorPalette\update_breakdance_colors($flat_palette);
} catch (\Exception $e) {
wp_send_json_error(['message' => 'Error updating Oxygen colors: ' . $e->getMessage()]);
exit;
}
}
// Clear Oxygen cache to apply new colors
if (function_exists('\\Breakdance\\Render\\generateCacheForGlobalSettings')) {
try {
\Breakdance\Render\generateCacheForGlobalSettings();
} catch (\Exception $e) {
wp_send_json_error(['message' => 'Error clearing Oxygen cache: ' . $e->getMessage()]);
exit;
}
}
// Sync color and spacing variables with Oxygen (Step 2)
if (class_exists('\\OxyMade\\Variables\\VariableManager')) {
$result = \OxyMade\Variables\VariableManager::sync_colors_and_spacing_with_oxygen('add_new');
if (is_array($result) && !$result['success']) {
wp_send_json_error(['message' => 'Colors saved but failed to sync variables: ' . ($result['error'] ?? 'Unknown error')]);
exit;
}
}
wp_send_json_success([
'message' => 'Colors and spacing variables installed and synced successfully!'
]);
} else {
// Sync is OFF - only sync spacing variables, don't touch colors in Breakdance
if (class_exists('\\OxyMade\\Variables\\VariableManager')) {
$result = \OxyMade\Variables\VariableManager::sync_spacing_only_with_oxygen('add_new');
if (is_array($result) && !$result['success']) {
wp_send_json_error(['message' => 'Failed to sync spacing variables: ' . ($result['error'] ?? 'Unknown error')]);
exit;
}
}
wp_send_json_success([
'message' => 'Spacing variables synced (sync disabled - colors not updated in Breakdance)'
]);
}
exit;
}
add_action('wp_ajax_oxymade_install_palette', __NAMESPACE__ . '\\install_default_palette');
/**
* AJAX handler for installing design sets
*/
function install_design_set()
{
// Check nonce for security
if (!isset($_POST['nonce']) || !wp_verify_nonce($_POST['nonce'], 'oxymade-admin-nonce')) {
wp_send_json_error('Invalid security token');
exit;
}
// Check if user has permission
if (!current_user_can('manage_options')) {
wp_send_json_error('You do not have permission to perform this action');
exit;
}
// Get the design set name
$set_name = isset($_POST['set_name']) ? sanitize_text_field($_POST['set_name']) : '';
if (empty($set_name)) {
wp_send_json_error(['message' => 'No design set name provided']);
exit;
}
// Get previous design set for comparison
$previous_set = get_option('oxymade_default_designset_template', 'Not set');
// Check if design set actually changed
$design_set_changed = ($previous_set !== $set_name);
// Save the design set name
update_option('oxymade_default_designset_template', $set_name);
// If design set changed, clear cached data so Steps 2-4 fetch fresh values
if ($design_set_changed) {
delete_option('oxymade_color_palette');
delete_option('oxymade_color_palette_flat');
delete_option('oxymade_heading_presets');
delete_option('oxymade_typography_config');
delete_option('oxymade_typography_installed');
// Set flag to show warning that steps need to be redone
update_option('oxymade_design_set_needs_reinstall', true);
// Reset step completion tracking
delete_option('oxymade_reinstall_step2_done');
delete_option('oxymade_reinstall_step3_done');
delete_option('oxymade_reinstall_step4_done');
}
// Extract and cache heading presets from design set JSON
// Only fetch if design set changed or no presets exist yet
$existing_presets = get_option('oxymade_heading_presets', []);
$heading_presets = $existing_presets;
if ($design_set_changed || empty($existing_presets)) {
$heading_presets = [];
$set_name_lower = strtolower($set_name);
$json = null;
// Try remote first for all design sets
$remote_url = "https://breakmade.com/assets/{$set_name_lower}.json";
$remote_args = ['timeout' => 15, 'headers' => ['Accept' => 'application/json']];
$response = wp_remote_get($remote_url, $remote_args);
if (!is_wp_error($response) && wp_remote_retrieve_response_code($response) === 200) {
$json = json_decode(wp_remote_retrieve_body($response), true);
}
// Fall back to local globals.json if remote failed
if (!$json) {
$local_globals_file = plugin_dir_path(__FILE__) . '../data/globals.json';
if (file_exists($local_globals_file)) {
$json = json_decode(file_get_contents($local_globals_file), true);
}
}
if ($json && isset($json['settings']['typography'])) {
$heading_presets = \OxyMade\Typography\TypographyManager::extract_heading_presets_from_globals($json['settings']['typography']);
}
if (!empty($heading_presets)) {
update_option('oxymade_heading_presets', $heading_presets);
}
}
wp_send_json_success([
'message' => 'Design set installed successfully',
'heading_presets' => $heading_presets
]);
}
add_action('wp_ajax_oxymade_install_design_set', __NAMESPACE__ . '\\install_design_set');
/**
* AJAX handler for fetching design sets from remote API (PHP proxy to avoid CORS)
*/
function fetch_design_sets()
{
// Check nonce for security
if (!isset($_POST['nonce']) || !wp_verify_nonce($_POST['nonce'], 'oxymade-admin-nonce')) {
wp_send_json_error('Invalid security token');
exit;
}
// Check if user has permission
if (!current_user_can('manage_options')) {
wp_send_json_error('You do not have permission to perform this action');
exit;
}
// Fetch design sets from Cloudflare Workers API
$api_url = 'https://breakmade.anvesh-24d.workers.dev/wp-json/madetheme/v1/templates';
$request_args = [
'timeout' => 15,
'headers' => ['Accept' => 'application/json'],
];
$response = wp_remote_get($api_url, $request_args);
$data = null;
if (!is_wp_error($response)) {
$body = wp_remote_retrieve_body($response);
$data = json_decode($body, true);
}
// Fallback: if remote fails, return bundled "Layers" design set
if (empty($data)) {
$data = [[
'id' => 'layers',
'title' => 'Layers',
'description' => 'A clean, modern design set with a balanced color palette and refined typography. Bundled with OxyMade.',
'categories' => [['name' => 'Starter'], ['name' => 'Bundled']],
]];
}
wp_send_json_success($data);
}
add_action('wp_ajax_oxymade_fetch_design_sets', __NAMESPACE__ . '\\fetch_design_sets');
/**
* AJAX handler for previewing a design set (fetches color + typography data without installing)
*/
function preview_design_set()
{
if (!isset($_POST['nonce']) || !wp_verify_nonce($_POST['nonce'], 'oxymade-admin-nonce')) {
wp_send_json_error('Invalid security token');
exit;
}
if (!current_user_can('manage_options')) {
wp_send_json_error('You do not have permission to perform this action');
exit;
}
$set_name = isset($_POST['set_name']) ? sanitize_text_field($_POST['set_name']) : '';
if (empty($set_name)) {
wp_send_json_error('No design set name provided');
exit;
}
$set_name_lower = strtolower($set_name);
$preview = [
'colors' => [],
'headings' => [],
'fonts' => ['heading' => '', 'body' => ''],
'body' => [],
'theme' => [],
'container' => [],
];
$remote_args = [
'timeout' => 15,
'headers' => ['Accept' => 'application/json'],
];
// 1. Fetch color palette
$color_url = "https://breakmade.com/assets/colors/{$set_name_lower}.json";
$response = wp_remote_get($color_url, $remote_args);
if (!is_wp_error($response) && wp_remote_retrieve_response_code($response) === 200) {
$palette_data = json_decode(wp_remote_retrieve_body($response), true);
if (!empty($palette_data['structured_palette'])) {
$preview['colors'] = $palette_data['structured_palette'];
}
}
// Fallback to local file
if (empty($preview['colors'])) {
$local_file = OXYMADE_PLUGIN_DIR . "data/{$set_name_lower}.json";
if (file_exists($local_file)) {
$local_data = json_decode(file_get_contents($local_file), true);
if (!empty($local_data['structured_palette'])) {
$preview['colors'] = $local_data['structured_palette'];
}
}
}
// 2. Fetch typography/globals
$globals_url = "https://breakmade.com/assets/{$set_name_lower}.json";
$response = wp_remote_get($globals_url, $remote_args);
$json = null;
if (!is_wp_error($response) && wp_remote_retrieve_response_code($response) === 200) {
$json = json_decode(wp_remote_retrieve_body($response), true);
}
// Fallback to local globals.json
if (!$json) {
$local_globals = plugin_dir_path(__FILE__) . '../data/globals.json';
if (file_exists($local_globals)) {
$json = json_decode(file_get_contents($local_globals), true);
}
}
if ($json && isset($json['settings'])) {
$settings = $json['settings'];
// Helper: convert "gfont-inter" → "Inter", "gfont-playfairdisplay" → "Playfair Display"
$parse_gfont = function ($val) {
if (empty($val) || !is_string($val)) return '';
// Strip "gfont-" prefix
$name = preg_replace('/^gfont-/', '', $val);
// Insert spaces before uppercase runs: "playfairdisplay" → split by known patterns
// Use a lookup for common multi-word fonts, otherwise capitalize
$known = [
'playfairdisplay' => 'Playfair Display',
'plusjakartasans' => 'Plus Jakarta Sans',
'ibmplexsans' => 'IBM Plex Sans',
'opensans' => 'Open Sans',
'publicsans' => 'Public Sans',
'spacegrotesk' => 'Space Grotesk',
'schibstedgrotesk' => 'Schibsted Grotesk',
'hankengrotesk' => 'Hanken Grotesk',
'wixmadefordisplay' => 'Wix Madefor Display',
'portlligatsans' => 'Port Lligat Sans',
'dmsans' => 'DM Sans',
];
$lower = strtolower($name);
return $known[$lower] ?? ucfirst($name);
};
// Typography
if (isset($settings['typography'])) {
$typography = $settings['typography'];
// Extract font families from typography-level fields (newer template format)
// Path: settings.typography.heading_font / settings.typography.body_font
if (!empty($typography['heading_font'])) {
$preview['fonts']['heading'] = $parse_gfont($typography['heading_font']);
}
if (!empty($typography['body_font'])) {
$preview['fonts']['body'] = $parse_gfont($typography['body_font']);
}
// Extract heading presets
$preview['headings'] = \OxyMade\Typography\TypographyManager::extract_heading_presets_from_globals($typography);
// Fallback: extract font families from typography presets if top-level fields missing
if (empty($preview['fonts']['heading']) && !empty($typography['global_typography']['typography_presets'])) {
foreach ($typography['global_typography']['typography_presets'] as $preset_entry) {
$font = $preset_entry['custom']['customTypography']['fontFamily'] ?? null;
if ($font && !empty(trim($font))) {
$preview['fonts']['heading'] = $font;
break;
}
}
}
// Extract body typography
$body_typo = $typography['advanced']['body']['typography']['custom']['customTypography'] ?? null;
if ($body_typo) {
if (empty($preview['fonts']['body']) && !empty($body_typo['fontFamily'])) {
$preview['fonts']['body'] = $body_typo['fontFamily'];
}
$preview['body'] = [
'font_size' => $body_typo['fontSize']['breakpoint_base']['style'] ?? '',
'line_height' => $body_typo['advanced']['lineHeight']['breakpoint_base']['style'] ?? '',
'letter_spacing' => $body_typo['advanced']['letterSpacing']['breakpoint_base']['style'] ?? '',
];
}
}
// Global theme color assignments
if (isset($settings['colors'])) {
$preview['theme'] = $settings['colors'];
}
// Container settings
if (isset($settings['containers']['sections'])) {
$sections = $settings['containers']['sections'];
$preview['container'] = [
'width' => $sections['container_width']['style'] ?? '',
'padding' => $sections['horizontal_padding']['breakpoint_base']['style'] ?? '',
];
}
}
wp_send_json_success($preview);
}
add_action('wp_ajax_oxymade_preview_design_set', __NAMESPACE__ . '\\preview_design_set');
/**
* Helper function to sync global settings with Oxygen
*/
function sync_global_settings_with_oxygen($settings_data, $option_name)
{
if (!function_exists('\Breakdance\Data\get_global_settings_array')) {
wp_send_json_error('Oxygen is not active.');
return false;
}
try {
$settings = \Breakdance\Data\get_global_settings_array();
if (!isset($settings['settings'])) {
$settings['settings'] = [];
}
// COMPLETELY REPLACE the settings sections (not merge)
// This ensures existing settings are overwritten
if (isset($settings_data['typography'])) {
$settings['settings']['typography'] = $settings_data['typography'];
}
if (isset($settings_data['containers'])) {
$settings['settings']['containers'] = $settings_data['containers'];
}
if (isset($settings_data['code'])) {
$settings['settings']['code'] = $settings_data['code'];
}
// Force save with complete replacement
$result = \Breakdance\Data\save_global_settings(json_encode($settings));
// Clear Oxygen cache
if (function_exists('\Breakdance\Render\generateCacheForGlobalSettings')) {
\Breakdance\Render\generateCacheForGlobalSettings();
}
// Set tracking option
update_option($option_name, true);
return true;
} catch (\Exception $e) {
wp_send_json_error('Error updating Oxygen global settings: ' . $e->getMessage());
return false;
}
}
/**
* AJAX handler for installing global settings
* - Layers (default): Load from local globals.json
* - Other design sets: Fetch from remote breakmade.com/assets/{design_set}.json
*/
function install_global_settings()
{
// Security check
if (!isset($_POST['nonce']) || !wp_verify_nonce($_POST['nonce'], 'oxymade-admin-nonce')) {
wp_send_json_error('Invalid security token');
exit;
}
if (!current_user_can('manage_options')) {
wp_send_json_error('You do not have permission to perform this action');
exit;
}
// Get the selected design set
$design_set = get_option('oxymade_default_designset_template', 'Layers');
$design_set_lower = strtolower($design_set);
// Try remote first, fall back to local bundled file
$json = null;
$remote_url = "https://breakmade.com/assets/{$design_set_lower}.json";
$remote_args = [
'timeout' => 30,
'headers' => ['Accept' => 'application/json'],
];
$response = wp_remote_get($remote_url, $remote_args);
if (!is_wp_error($response) && wp_remote_retrieve_response_code($response) === 200) {
$json = json_decode(wp_remote_retrieve_body($response), true);
}
// Fall back to local globals.json if remote failed
if (!$json) {
$local_globals_file = plugin_dir_path(__FILE__) . '../data/globals.json';
if (file_exists($local_globals_file)) {
$json = json_decode(file_get_contents($local_globals_file), true);
}
}
if (!$json || !isset($json['settings'])) {
wp_send_json_error(['message' => "Failed to fetch global settings for '{$design_set}' from remote and local sources."]);
exit;
}
// Use more aggressive replacement approach
if (function_exists('\Breakdance\Data\get_global_settings_array')) {
try {
$settings = \Breakdance\Data\get_global_settings_array();
// COMPLETELY REPLACE the entire settings structure
$settings['settings'] = $json['settings'];
// Force save with complete replacement
\Breakdance\Data\save_global_settings(json_encode($settings));
// Clear Oxygen cache
if (function_exists('\Breakdance\Render\generateCacheForGlobalSettings')) {
\Breakdance\Render\generateCacheForGlobalSettings();
}
// Set tracking option
update_option('oxymade_global_settings_installed', true);
// Mark Step 4 as done for design set reinstall warning
$needs_reinstall = get_option('oxymade_design_set_needs_reinstall', false);
if ($needs_reinstall) {
update_option('oxymade_reinstall_step4_done', true);
// Check if all steps are done — if so, clear the warning completely
$step2_done = get_option('oxymade_reinstall_step2_done', false);
$step3_done = get_option('oxymade_reinstall_step3_done', false);
if ($step2_done && $step3_done) {
delete_option('oxymade_design_set_needs_reinstall');
delete_option('oxymade_reinstall_step2_done');
delete_option('oxymade_reinstall_step3_done');
delete_option('oxymade_reinstall_step4_done');
}
}
wp_send_json_success([
'message' => 'Global settings completely replaced and synced with Oxygen successfully.'
]);
} catch (\Exception $e) {
wp_send_json_error(['message' => 'Error completely replacing Oxygen global settings: ' . $e->getMessage()]);
exit;
}
} else {
wp_send_json_error(['message' => 'Oxygen is not active.']);
exit;
}
}
add_action('wp_ajax_oxymade_install_global_settings', __NAMESPACE__ . '\\install_global_settings');
/**
* AJAX handler for installing typography variables
*/
function install_typography_variables()
{
// Security check
if (!isset($_POST['nonce']) || !wp_verify_nonce($_POST['nonce'], 'oxymade-admin-nonce')) {
wp_send_json_error('Invalid security token');
exit;
}
if (!current_user_can('manage_options')) {
wp_send_json_error('You do not have permission to perform this action');
exit;
}
// Ensure heading presets are cached (for components and template reset)
$existing_presets = get_option('oxymade_heading_presets', []);
if (empty($existing_presets)) {
$set_name = get_option('oxymade_default_designset_template', '');
if (!empty($set_name)) {
$set_name_lower = strtolower($set_name);
$json = null;
// Try remote first for all design sets
$remote_url = "https://breakmade.com/assets/{$set_name_lower}.json";
$remote_args = ['timeout' => 15, 'headers' => ['Accept' => 'application/json']];
$response = wp_remote_get($remote_url, $remote_args);
if (!is_wp_error($response) && wp_remote_retrieve_response_code($response) === 200) {
$json = json_decode(wp_remote_retrieve_body($response), true);
}
// Fall back to local globals.json if remote failed
if (!$json) {
$local_globals_file = plugin_dir_path(__FILE__) . '../data/globals.json';
if (file_exists($local_globals_file)) {
$json = json_decode(file_get_contents($local_globals_file), true);
}
}
if ($json && isset($json['settings']['typography']) && class_exists('\\OxyMade\\Typography\\TypographyManager')) {
$heading_presets = \OxyMade\Typography\TypographyManager::extract_heading_presets_from_globals($json['settings']['typography']);
if (!empty($heading_presets)) {
update_option('oxymade_heading_presets', $heading_presets);
$existing_presets = $heading_presets;
}
}
}
}
// Import preset values into typography config on first install (or after design set change)
// so the config modal shows template values and generate_headings_custom() uses them.
// NOTE: Must check raw DB option, NOT get_config() — get_config() has a read-time
// fallback that populates fluid_sizes from presets, masking the empty state.
if (!empty($existing_presets) && class_exists('\\OxyMade\\Typography\\TypographyManager')) {
$raw_config = get_option('oxymade_typography_config', []);
if (empty($raw_config) || empty($raw_config['fluid_sizes'])) {
$fluid_sizes = [];
foreach ($existing_presets as $key => $preset) {
if (isset($preset['mobile']) && isset($preset['desktop'])) {
$fluid_sizes[$key] = [
'mobile' => (int) $preset['mobile'],
'desktop' => (int) $preset['desktop'],
];
}
}
if (!empty($fluid_sizes)) {
$current_config = \OxyMade\Typography\TypographyManager::get_config();
$current_config['fluid_sizes'] = $fluid_sizes;
$current_config['custom_sizes'] = $fluid_sizes;
$current_config['fluid_enabled'] = true;
\OxyMade\Typography\TypographyManager::save_config($current_config);
}
}
}
// Sync ONLY typography variables with Oxygen (Step 3)
if (class_exists('\\OxyMade\\Variables\\VariableManager')) {
$result = \OxyMade\Variables\VariableManager::sync_typography_with_oxygen('add_new');
if (is_array($result) && $result['success']) {
// Mark typography as installed
update_option('oxymade_typography_installed', true);
// Mark Step 3 as done for design set reinstall warning
$needs_reinstall = get_option('oxymade_design_set_needs_reinstall', false);
if ($needs_reinstall) {
update_option('oxymade_reinstall_step3_done', true);
// Check if all steps are done — if so, clear the warning completely
$step2_done = get_option('oxymade_reinstall_step2_done', false);
$step4_done = get_option('oxymade_reinstall_step4_done', false);
if ($step2_done && $step4_done) {
delete_option('oxymade_design_set_needs_reinstall');
delete_option('oxymade_reinstall_step2_done');
delete_option('oxymade_reinstall_step3_done');
delete_option('oxymade_reinstall_step4_done');
}
}
wp_send_json_success(['message' => 'Typography variables synced successfully!']);
} else {
$error_message = 'Failed to install typography variables';
if (is_array($result) && isset($result['error'])) {
$error_message = $result['error'];
}
wp_send_json_error($error_message);
}
} else {
wp_send_json_error('Variable manager not available');
}
}
add_action('wp_ajax_oxymade_install_typography', __NAMESPACE__ . '\\install_typography_variables');
/**
* AJAX handler for dismissing notices
*/
function dismiss_notice()
{
// Security check
if (!isset($_POST['nonce']) || !wp_verify_nonce($_POST['nonce'], 'oxymade-admin-nonce')) {
wp_send_json_error('Invalid security token');
exit;
}
if (!current_user_can('manage_options')) {
wp_send_json_error('You do not have permission to perform this action');
exit;
}
$notice_type = isset($_POST['notice_type']) ? sanitize_text_field($_POST['notice_type']) : '';
$current_user_id = get_current_user_id();
wp_send_json_error('Invalid notice type');
exit;
wp_send_json_success(['message' => 'Notice dismissed successfully']);
}
add_action('wp_ajax_oxymade_dismiss_notice', __NAMESPACE__ . '\\dismiss_notice');
/**
* AJAX handler for hiding progress tracker
*/
function hide_progress_tracker()
{
// Security check
if (!isset($_POST['nonce']) || !wp_verify_nonce($_POST['nonce'], 'oxymade-admin-nonce')) {
wp_send_json_error('Invalid security token');
exit;
}
if (!current_user_can('manage_options')) {
wp_send_json_error('You do not have permission to perform this action');
exit;
}
// Save user preference to hide progress tracker
$current_user_id = get_current_user_id();
update_user_meta($current_user_id, 'oxymade_hide_progress_tracker', true);
wp_send_json_success(['message' => 'Progress tracker hidden']);
}
add_action('wp_ajax_oxymade_hide_progress_tracker', __NAMESPACE__ . '\\hide_progress_tracker');
/**
* Reset notice dismissals (for testing)
*/
function reset_notice_dismissals()
{
if (!current_user_can('manage_options')) {
wp_die('Unauthorized');
}
$current_user_id = get_current_user_id();
wp_redirect(admin_url('admin.php?page=oxymade-settings¬ices_reset=1'));
exit;
}
// Add reset action (for testing)
if (isset($_GET['reset_notices']) && current_user_can('manage_options')) {
add_action('admin_init', __NAMESPACE__ . '\\reset_notice_dismissals');
}
/**
* AJAX handler for updating selector mode
*/
function update_selector_mode()
{
// Check capabilities
if (!current_user_can('manage_options')) {
wp_die('Unauthorized');
}
// Check nonce for security
if (!isset($_POST['nonce']) || !wp_verify_nonce($_POST['nonce'], 'oxymade-admin-nonce')) {
wp_send_json_error('Invalid security token');
exit;
}
$mode = isset($_POST['mode']) ? sanitize_text_field($_POST['mode']) : 'add_new';
// Validate mode
if (!in_array($mode, ['add_new', 'update', 'replace'])) {
wp_send_json_error('Invalid mode');
exit;
}
// Update the selector mode
update_option('oxymade_selectors_mode', $mode);
wp_send_json_success(['message' => 'Selector mode updated successfully', 'mode' => $mode]);
}
add_action('wp_ajax_oxymade_update_selector_mode', __NAMESPACE__ . '\\update_selector_mode');
/**
* AJAX handler for saving sync setting
*/
function save_sync_setting()
{
try {
// Check capabilities
if (!current_user_can('manage_options')) {
wp_die('Unauthorized');
}
// Check nonce for security
if (!isset($_POST['nonce']) || !wp_verify_nonce($_POST['nonce'], 'oxymade-sync-setting')) {
wp_send_json_error('Invalid security token');
exit;
}
$sync_with_oxygen = isset($_POST['sync_with_oxygen']) ? sanitize_text_field($_POST['sync_with_oxygen']) : '0';
$sync_with_oxygen = ($sync_with_oxygen === '1') ? true : false;
// Get current settings
$current_settings = get_option('oxymade_settings', []);
// Ensure it's an array (in case it was saved as string previously)
if (!is_array($current_settings)) {
$current_settings = [];
}
// Update sync setting
$current_settings['sync_with_oxygen'] = $sync_with_oxygen;
// Save to database
$result = update_option('oxymade_settings', $current_settings);
if ($result) {
wp_send_json_success([
'message' => 'Sync setting saved successfully',
'sync_with_oxygen' => $sync_with_oxygen
]);
} else {
wp_send_json_error('Failed to save sync setting');
}
} catch (Exception $e) {
wp_send_json_error('Server error: ' . $e->getMessage());
}
}
add_action('wp_ajax_oxymade_save_sync_setting', __NAMESPACE__ . '\\save_sync_setting');
/**
* AJAX handler for toggling autocomplete
*/
function save_autocomplete_setting() {
if (!current_user_can('manage_options')) {
wp_send_json_error('Unauthorized');
return;
}
if (!isset($_POST['nonce']) || !wp_verify_nonce($_POST['nonce'], 'oxymade-autocomplete-setting')) {
wp_send_json_error('Invalid security token');
return;
}
$enabled = isset($_POST['autocomplete_enabled']) && $_POST['autocomplete_enabled'] === '1';
// Store as '1'/'0' string to avoid WP's boolean false → empty string ambiguity
update_option('oxymade_autocomplete_enabled', $enabled ? '1' : '0');
wp_send_json_success(['autocomplete_enabled' => $enabled]);
}
add_action('wp_ajax_oxymade_save_autocomplete_setting', __NAMESPACE__ . '\\save_autocomplete_setting');
/**
* AJAX handler for toggling auto-sync
*/
function toggle_auto_sync()
{
// Check capabilities
if (!current_user_can('manage_options')) {
wp_die('Unauthorized');
}
// Check nonce for security
if (!isset($_POST['nonce']) || !wp_verify_nonce($_POST['nonce'], 'oxymade-admin-nonce')) {
wp_send_json_error('Invalid security token');
exit;
}
$auto_sync = isset($_POST['auto_sync']) ? sanitize_text_field($_POST['auto_sync']) : '0';
$auto_sync = ($auto_sync === '1') ? true : false;
// Update auto-sync setting
update_option('oxymade_oxygen_auto_sync', $auto_sync);
wp_send_json_success(['message' => 'Auto-sync setting updated successfully']);
}
add_action('wp_ajax_oxymade_toggle_auto_sync', __NAMESPACE__ . '\\toggle_auto_sync');
/**
* AJAX handler for toggling fluid spacing
*/
function toggle_fluid_spacing()
{
// Check capabilities
if (!current_user_can('manage_options')) {
wp_die('Unauthorized');
}
// Check nonce for security
if (!isset($_POST['nonce']) || !wp_verify_nonce($_POST['nonce'], 'oxymade-admin-nonce')) {
wp_send_json_error('Invalid security token');
exit;
}
$fluid_spacing = isset($_POST['fluid_spacing']) ? sanitize_text_field($_POST['fluid_spacing']) : '0';
$fluid_spacing = ($fluid_spacing === '1') ? true : false;
// Get current settings
$current_settings = get_option('oxymade_settings', []);
if (!is_array($current_settings)) {
$current_settings = [];
}
// Update fluid spacing setting
$current_settings['fluid_spacing'] = $fluid_spacing;
update_option('oxymade_settings', $current_settings);
wp_send_json_success(['message' => 'Fluid spacing setting updated successfully']);
}
add_action('wp_ajax_oxymade_toggle_fluid_spacing', __NAMESPACE__ . '\\toggle_fluid_spacing');
// Components AJAX handlers
add_action('wp_ajax_oxymade_sync_components', __NAMESPACE__ . '\\sync_components');
add_action('wp_ajax_oxymade_toggle_update_existing_components', __NAMESPACE__ . '\\toggle_update_existing_components');
/**
* AJAX handler for toggling fluid text sizing
*/
function toggle_fluid_text_sizing()
{
// Check capabilities
if (!current_user_can('manage_options')) {
wp_die('Unauthorized');
}
// Check nonce for security
if (!isset($_POST['nonce']) || !wp_verify_nonce($_POST['nonce'], 'oxymade-admin-nonce')) {
wp_send_json_error('Invalid security token');
exit;
}
// Get the fluid text sizing value
$fluid_text_sizing = isset($_POST['fluid_text_sizing']) ? sanitize_text_field($_POST['fluid_text_sizing']) : '0';
$fluid_text_sizing = ($fluid_text_sizing === '1') ? true : false;
// Get current settings
$current_settings = get_option('oxymade_settings', []);
if (!is_array($current_settings)) {
$current_settings = [];
}
// Update fluid text sizing setting
$current_settings['fluid_text_sizing'] = $fluid_text_sizing;
update_option('oxymade_settings', $current_settings);
// Auto-sync to Oxygen so text size variables update immediately
if (class_exists('\\OxyMade\\Variables\\VariableManager')) {
\OxyMade\Variables\VariableManager::sync_typography_with_oxygen('add_new');
}
wp_send_json_success(['message' => 'Fluid text sizing setting updated successfully']);
}
add_action('wp_ajax_oxymade_toggle_fluid_text_sizing', __NAMESPACE__ . '\\toggle_fluid_text_sizing');
/**
* AJAX handler for resetting typography config from design set template presets
*/
function reset_typography_from_template()
{
if (!current_user_can('manage_options')) {
wp_die('Unauthorized');
}
if (!isset($_POST['nonce']) || !wp_verify_nonce($_POST['nonce'], 'oxymade-admin-nonce')) {
wp_send_json_error('Invalid security token');
exit;
}
$presets = get_option('oxymade_heading_presets', []);
if (empty($presets)) {
wp_send_json_error('No heading presets available. Please install a design set first (Step 1).');
exit;
}
// Build fluid_sizes from presets
$fluid_sizes = [];
foreach ($presets as $key => $preset) {
if (isset($preset['mobile']) && isset($preset['desktop'])) {
$fluid_sizes[$key] = [
'mobile' => (int) $preset['mobile'],
'desktop' => (int) $preset['desktop'],
];
}
}
// Get current config and overwrite heading-related sizes with template values
$config = \OxyMade\Typography\TypographyManager::get_config();
$config['fluid_sizes'] = $fluid_sizes;
$config['custom_sizes'] = $fluid_sizes;
$config['typescale_enabled'] = false;
$config['fluid_enabled'] = true;
\OxyMade\Typography\TypographyManager::save_config($config);
// Auto-apply to Oxygen
if (class_exists('\\OxyMade\\Variables\\VariableManager')) {
\OxyMade\Variables\VariableManager::sync_typography_with_oxygen('add_new');
}
// Return updated config so JS can re-render form
wp_send_json_success([
'message' => 'Typography reset from template successfully!',
'config' => \OxyMade\Typography\TypographyManager::get_config()
]);
}
add_action('wp_ajax_oxymade_reset_typography_from_template', __NAMESPACE__ . '\\reset_typography_from_template');
/**
* AJAX handler for dismissing the welcome wizard
*/
function dismiss_wizard()
{
if (!isset($_POST['nonce']) || !wp_verify_nonce($_POST['nonce'], 'oxymade-admin-nonce')) {
wp_send_json_error('Invalid security token');
exit;
}
update_user_meta(get_current_user_id(), 'oxymade_wizard_dismissed', true);
wp_send_json_success();
}
add_action('wp_ajax_oxymade_dismiss_wizard', __NAMESPACE__ . '\\dismiss_wizard');
/**
* AJAX handler for toggling full selectors - REMOVED
* Always use lightweight mode (selectors-only)
*/
/**
* AJAX handler for getting oxymade settings
*/
function get_oxymade_settings()
{
// Security check
if (!isset($_POST['nonce']) || !wp_verify_nonce($_POST['nonce'], 'oxymade-admin-nonce')) {
wp_send_json_error('Invalid security token');
exit;
}
// Check capabilities
if (!current_user_can('manage_options')) {
wp_send_json_error('You do not have permission to perform this action');
exit;
}
$settings = get_option('oxymade_settings', []);
wp_send_json_success([
'settings' => $settings
]);
}
add_action('wp_ajax_oxymade_get_oxymade_settings', __NAMESPACE__ . '\\get_oxymade_settings');
/**
* AJAX handler for syncing components
*/
function sync_components()
{
\OxyMade\Components::ajax_sync_components();
}
/**
* AJAX handler for toggling update existing components
*/
function toggle_update_existing_components()
{
// Check capabilities
if (!current_user_can('manage_options')) {
wp_die('Unauthorized');
}
// Check nonce for security
if (!isset($_POST['nonce']) || !wp_verify_nonce($_POST['nonce'], 'oxymade-admin-nonce')) {
wp_send_json_error('Invalid security token');
exit;
}
$update_existing_components = isset($_POST['update_existing_components']) ? sanitize_text_field($_POST['update_existing_components']) : '0';
$update_existing_components = ($update_existing_components === '1') ? true : false;
// Get current settings
$current_settings = get_option('oxymade_settings', []);
if (!is_array($current_settings)) {
$current_settings = [];
}
// Update the setting
$current_settings['update_existing_components'] = $update_existing_components;
// Save settings
$result = update_option('oxymade_settings', $current_settings);
if ($result) {
wp_send_json_success([
'message' => 'Update existing components setting saved successfully',
'update_existing_components' => $update_existing_components
]);
} else {
wp_send_json_error('Failed to save update existing components setting');
}
}
/**
* Invalidate all caches related to the selectors option.
* Must be called BEFORE and AFTER update_option to ensure:
* - Before: update_option reads real DB value (not stale transient from pre_option filter)
* - After: subsequent reads see the updated value (filter re-caches old value during update)
*/
function _invalidate_selectors_cache() {
delete_transient('oxymade_selectors_option_cache');
wp_cache_delete('oxygen_oxy_selectors_json_string', 'options');
wp_cache_delete('notoptions', 'options');
wp_cache_delete('alloptions', 'options');
}
/**
* OxyMade component selector IDs (headings, buttons, cards, etc.)
* These have actual CSS properties unlike utility class stubs.
*/
function get_component_ids() {
return [
'598d7449-2571-572f-aeac-0773ea558490' => true, // hero
'88c9e023-14db-5806-be06-43ff96eea490' => true, // h1
'3b44d1bb-e9bc-5e7a-8e99-7b09bdbedce3' => true, // h2
'24f816bc-7420-5590-8aa3-deda5f9a5bae' => true, // h3
'4d009ad7-98b0-5c2b-ad13-cc1b2a934b8e' => true, // h4
'd80772b9-3f56-5152-88aa-c8f9823da92d' => true, // h5
'7c80764c-7d60-5b90-8af4-eb2288c723a4' => true, // h6
'218a73c1-4998-49cd-952e-be3eda8cd76c' => true, // .btn-s, .btn-m, .btn-l
'f53b48f2-55c6-4ba0-9241-05a421cb5e09' => true, // .btn-s:hover, .btn-m:hover, .btn-l:hover
'8c26d1f5-edc5-5afa-be8b-f9353a84826e' => true, // btn-s
'9cf49b5d-1023-5847-abaf-d8fd17a9fa78' => true, // btn-m
'2671c6f2-ef56-587a-a64c-927127ce20e4' => true, // btn-l
'ed8547db-b2dd-5f44-a83f-0d0d3f5a5d3d' => true, // btn-black
'7167ab38-6980-528e-9ece-c521034cc9f0' => true, // btn-primary
'0eb286af-33ad-5067-882c-2d9ace9a8ed8' => true, // btn-secondary
'f0fcde18-fa3d-5681-a329-0ee2d19e2ad7' => true, // btn-accent
'0227532d-c06e-56f6-9fd1-cb930ea4d065' => true, // btn-tertiary
'02802b7c-5aa6-5d8d-a85d-0f52918233e0' => true, // btn-white
'50c7ab01-b710-568e-a12f-d772bb8068ed' => true, // btn-light
'd7077905-008f-5e54-a2a7-c596704b5301' => true, // btn-link
'64a744ec-40e5-42a1-8592-94a20f4f7050' => true, // .btn-outline
'b5984b06-f644-572a-add2-3720ffd0a74d' => true, // card-normal
'4d8e5557-e9e1-554d-ae7b-1e15ea89ccc3' => true, // card-loose
'00dd929e-ab5b-567b-b755-7d5ec3023471' => true, // card-tight
'0ea4bc15-40f5-58b0-a092-db6dfb52c8c8' => true, // card-snug
'e584f30e-f20f-5391-aa29-d6ce676c1051' => true, // card-relaxed
'be4a4e76-daaa-50f7-8c32-4939af277da3' => true, // card-none
'c69bf05e-8b46-52f4-98ed-5df31d6bd8ca' => true, // button-pair
'91e0ce1c-454d-4eac-8a44-48b8db8bd70f' => true, // button-pair children
];
}
/**
* AJAX handler for deleting all OxyMade selectors
*/
function delete_all_selectors()
{
// Check capabilities
if (!current_user_can('manage_options')) {
wp_die('Unauthorized');
}
// Verify nonce
if (!wp_verify_nonce($_POST['nonce'], 'oxymade_delete_selectors')) {
wp_send_json_error('Invalid nonce');
}
// Check if Oxygen is active
if (!function_exists('\\Breakdance\\Data\\get_global_option')) {
wp_send_json_error('Oxygen is not active - get_global_option function not found');
}
if (!function_exists('\\Breakdance\\Data\\set_global_option')) {
wp_send_json_error('Oxygen is not active - set_global_option function not found');
}
try {
// Get all selectors from global option
// Use get_option instead of Oxygen API since it works
$all_selectors_raw = get_option('oxygen_oxy_selectors_json_string', []);
// Decode JSON string if it's a string
if (is_string($all_selectors_raw)) {
$all_selectors = json_decode($all_selectors_raw, true);
} else {
$all_selectors = $all_selectors_raw;
}
// Try alternative option names if main one is empty
if (empty($all_selectors)) {
$alt_options = [
'oxy_selectors_json_string',
'oxygen_selectors_json_string',
'breakdance_oxy_selectors_json_string',
'breakdance_selectors_json_string'
];
foreach ($alt_options as $alt_option) {
$alt_result = get_option($alt_option, []);
if (!empty($alt_result)) {
// Decode JSON string if it's a string
if (is_string($alt_result)) {
$all_selectors = json_decode($alt_result, true);
} else {
$all_selectors = $alt_result;
}
break;
}
}
}
// Count selectors before deletion
$total_count = is_array($all_selectors) ? count($all_selectors) : 0;
if ($total_count === 0) {
wp_send_json_success(['message' => 'No selectors found to delete']);
}
// Check toggle flags
$include_custom = isset($_POST['include_custom']) && $_POST['include_custom'] === '1';
$keep_components = isset($_POST['keep_components']) && $_POST['keep_components'] === '1';
$component_ids = $keep_components ? get_component_ids() : [];
$remaining_selectors = [];
$custom_kept = 0;
$components_kept = 0;
if (is_array($all_selectors)) {
foreach ($all_selectors as $selector) {
$is_custom = isset($selector['name']) && stripos($selector['name'], 'custom') === 0;
$is_component = isset($selector['id']) && isset($component_ids[$selector['id']]);
if ((!$include_custom && $is_custom) || ($keep_components && $is_component)) {
$remaining_selectors[] = $selector;
if ($is_custom) $custom_kept++;
if ($is_component) $components_kept++;
}
}
}
$deleted_count = $total_count - count($remaining_selectors);
if ($deleted_count === 0) {
wp_send_json_success(['message' => 'No selectors to delete (all protected by current toggle settings)']);
}
// Invalidate caches before AND after write:
// Before: so update_option reads real DB value (not stale transient)
// After: so subsequent reads see the new value (filter re-caches during update)
_invalidate_selectors_cache();
// Store as JSON string to match Oxygen's expected format
update_option('oxygen_oxy_selectors_json_string', wp_json_encode(array_values($remaining_selectors)));
_invalidate_selectors_cache();
$preserved = [];
if ($custom_kept > 0) $preserved[] = "{$custom_kept} custom";
if ($components_kept > 0) $preserved[] = "{$components_kept} components";
$message = "Successfully deleted {$deleted_count} selectors";
if (!empty($preserved)) {
$message .= " (" . implode(', ', $preserved) . " preserved)";
}
wp_send_json_success([
'message' => $message
]);
} catch (Exception $e) {
wp_send_json_error('Error deleting selectors: ' . $e->getMessage());
}
}
add_action('wp_ajax_oxymade_delete_all_selectors', __NAMESPACE__ . '\\delete_all_selectors');
/**
* AJAX handler for deleting only OxyMade selectors
*/
function delete_oxymade_selectors()
{
// Check capabilities
if (!current_user_can('manage_options')) {
wp_die('Unauthorized');
}
// Verify nonce
if (!wp_verify_nonce($_POST['nonce'], 'oxymade_delete_selectors')) {
wp_send_json_error('Invalid nonce');
}
// Check if Oxygen is active
if (!function_exists('\\Breakdance\\Data\\get_global_option')) {
wp_send_json_error('Oxygen is not active - get_global_option function not found');
}
if (!function_exists('\\Breakdance\\Data\\set_global_option')) {
wp_send_json_error('Oxygen is not active - set_global_option function not found');
}
try {
// Get all selectors from global option
// Use get_option instead of Oxygen API since it works
$all_selectors_raw = get_option('oxygen_oxy_selectors_json_string', []);
// Decode JSON string if it's a string
if (is_string($all_selectors_raw)) {
$all_selectors = json_decode($all_selectors_raw, true);
} else {
$all_selectors = $all_selectors_raw;
}
// Try alternative option names if main one is empty
if (empty($all_selectors)) {
$alt_options = [
'oxy_selectors_json_string',
'oxygen_selectors_json_string',
'breakdance_oxy_selectors_json_string',
'breakdance_selectors_json_string'
];
foreach ($alt_options as $alt_option) {
$alt_result = get_option($alt_option, []);
if (!empty($alt_result)) {
// Decode JSON string if it's a string
if (is_string($alt_result)) {
$all_selectors = json_decode($alt_result, true);
} else {
$all_selectors = $alt_result;
}
break;
}
}
}
// Count total selectors before deletion
$total_count = is_array($all_selectors) ? count($all_selectors) : 0;
if ($total_count === 0) {
wp_send_json_success(['message' => 'No selectors found to delete']);
}
// Filter out OxyMade selectors, respect toggle flags
$include_custom = isset($_POST['include_custom']) && $_POST['include_custom'] === '1';
$keep_components = isset($_POST['keep_components']) && $_POST['keep_components'] === '1';
$component_ids = $keep_components ? get_component_ids() : [];
$remaining_selectors = [];
$deleted_count = 0;
$custom_kept = 0;
$components_kept = 0;
foreach ($all_selectors as $selector) {
$is_oxymade = isset($selector['collection']) && strpos($selector['collection'], 'OxyMade') === 0;
$is_custom = isset($selector['name']) && stripos($selector['name'], 'custom') === 0;
$is_component = isset($selector['id']) && isset($component_ids[$selector['id']]);
if ($is_oxymade) {
// Keep if custom* and not including custom, or if component and keeping components
if ((!$include_custom && $is_custom) || ($keep_components && $is_component)) {
$remaining_selectors[] = $selector;
if ($is_custom) $custom_kept++;
if ($is_component) $components_kept++;
} else {
$deleted_count++;
}
} else {
$remaining_selectors[] = $selector;
}
}
if ($deleted_count === 0) {
wp_send_json_success(['message' => 'No OxyMade selectors found to delete']);
}
_invalidate_selectors_cache();
// Store as JSON string to match Oxygen's expected format
update_option('oxygen_oxy_selectors_json_string', wp_json_encode(array_values($remaining_selectors)));
_invalidate_selectors_cache();
$preserved = [];
if ($custom_kept > 0) $preserved[] = "{$custom_kept} custom";
if ($components_kept > 0) $preserved[] = "{$components_kept} components";
$message = "Successfully deleted {$deleted_count} OxyMade selectors";
if (!empty($preserved)) {
$message .= " (" . implode(', ', $preserved) . " preserved)";
}
wp_send_json_success([
'message' => $message
]);
} catch (Exception $e) {
wp_send_json_error('Error deleting OxyMade selectors: ' . $e->getMessage());
}
}
add_action('wp_ajax_oxymade_delete_oxymade_selectors', __NAMESPACE__ . '\\delete_oxymade_selectors');
// Get selector counts for modal display
function get_selector_counts()
{
if (!current_user_can('manage_options')) {
wp_send_json_error('Insufficient permissions');
}
try {
// Use get_option to get selectors
$all_selectors_raw = get_option('oxygen_oxy_selectors_json_string', []);
// Decode JSON string if it's a string
if (is_string($all_selectors_raw)) {
$all_selectors = json_decode($all_selectors_raw, true);
} else {
$all_selectors = $all_selectors_raw;
}
$total_count = is_array($all_selectors) ? count($all_selectors) : 0;
// Try alternative option names if main one is empty
if (empty($all_selectors)) {
$alt_options = [
'oxy_selectors_json_string',
'oxygen_selectors_json_string',
'breakdance_oxy_selectors_json_string',
'breakdance_selectors_json_string'
];
foreach ($alt_options as $alt_option) {
$alt_result = get_option($alt_option, []);
if (!empty($alt_result)) {
// Decode JSON string if it's a string
if (is_string($alt_result)) {
$all_selectors = json_decode($alt_result, true);
} else {
$all_selectors = $alt_result;
}
$total_count = is_array($all_selectors) ? count($all_selectors) : 0;
break;
}
}
}
$oxymade_selectors = [];
$other_selectors = [];
$custom_count = 0;
$component_ids = get_component_ids();
$component_count = 0;
if (is_array($all_selectors)) {
foreach ($all_selectors as $selector) {
if (isset($selector['collection']) && strpos($selector['collection'], 'OxyMade') === 0) {
$oxymade_selectors[] = $selector;
if (isset($selector['name']) && stripos($selector['name'], 'custom') === 0) {
$custom_count++;
}
if (isset($selector['id']) && isset($component_ids[$selector['id']])) {
$component_count++;
}
} else {
$other_selectors[] = $selector;
}
}
}
// Get unique collections safely
$oxymade_collections = [];
$other_collections = [];
if (!empty($oxymade_selectors)) {
$oxymade_collections = array_unique(array_column($oxymade_selectors, 'collection'));
}
if (!empty($other_selectors)) {
$other_collections = array_unique(array_column($other_selectors, 'collection'));
}
wp_send_json_success([
'total_selectors' => $total_count,
'oxymade_count' => count($oxymade_selectors),
'custom_count' => $custom_count,
'component_count' => $component_count,
'other_count' => count($other_selectors),
'oxymade_collections' => $oxymade_collections,
'other_collections' => $other_collections
]);
} catch (Exception $e) {
wp_send_json_error('Error getting selector counts: ' . $e->getMessage());
}
}
add_action('wp_ajax_oxymade_get_selector_counts', __NAMESPACE__ . '\\get_selector_counts');
/**
* AJAX handler for Fix Styling — scans all pages/templates for OxyMade class
* UUIDs and injects only the used selectors back into Oxygen's store.
*/
function fix_styling()
{
if (!current_user_can('manage_options')) {
wp_send_json_error('Insufficient permissions');
}
if (!wp_verify_nonce($_POST['nonce'], 'oxymade_fix_styling')) {
wp_send_json_error('Invalid nonce');
}
@set_time_limit(300);
// 1. Build OxyMade UUID registry from selectors-only.json
$selectors_file = OXYMADE_PLUGIN_DIR . 'data/selectors-only.json';
if (!file_exists($selectors_file)) {
wp_send_json_error('Selectors data file not found');
}
$json = file_get_contents($selectors_file);
$selectors = json_decode($json, true);
if (!is_array($selectors)) {
wp_send_json_error('Failed to parse selectors data');
}
$registry = [];
foreach ($selectors as $sel) {
if (isset($sel['id'])) {
$registry[$sel['id']] = $sel;
}
}
// 2. Determine meta key
$meta_key = '_oxygen_data';
if (function_exists('\\Breakdance\\BreakdanceOxygen\\Strings\\__bdox')) {
$prefix = \Breakdance\BreakdanceOxygen\Strings\__bdox('_meta_prefix');
$meta_key = $prefix . 'data';
}
// 3. Query all posts with builder data + Oxygen CPTs
$regular_posts = get_posts([
'post_type' => 'any',
'posts_per_page' => -1,
'post_status' => ['publish', 'draft', 'pending', 'future', 'private'],
'meta_query' => [['key' => $meta_key, 'compare' => 'EXISTS']],
'fields' => 'ids',
]);
$cpt_posts = get_posts([
'post_type' => ['oxygen_template', 'oxygen_header', 'oxygen_footer', 'oxygen_block'],
'posts_per_page' => -1,
'post_status' => ['publish', 'draft', 'pending', 'future', 'private'],
'fields' => 'ids',
]);
$all_post_ids = array_unique(array_merge($regular_posts, $cpt_posts));
// 4. Walk each post's tree, collect class UUIDs
$found_ids = [];
foreach ($all_post_ids as $post_id) {
$raw = get_post_meta($post_id, $meta_key, true);
if (empty($raw)) continue;
// Meta is stored as JSON string: {"tree_json_string":"<nested json>"}
// First decode gives us the wrapper, then decode tree_json_string for actual tree
if (is_string($raw)) {
$wrapper = json_decode($raw, true);
if (is_array($wrapper) && isset($wrapper['tree_json_string'])) {
$tree = json_decode($wrapper['tree_json_string'], true);
} else {
// Fallback: maybe raw is the tree directly
$tree = $wrapper;
}
} elseif (is_array($raw) && isset($raw['tree_json_string'])) {
$tree = json_decode($raw['tree_json_string'], true);
} else {
continue;
}
if (!is_array($tree)) continue;
// Use exportedLookupTable for flat iteration (faster)
if (isset($tree['exportedLookupTable']) && is_array($tree['exportedLookupTable'])) {
foreach ($tree['exportedLookupTable'] as $node) {
$classes = $node['data']['properties']['meta']['classes'] ?? null;
if (is_array($classes)) {
foreach ($classes as $class_id) {
if (is_string($class_id)) {
$found_ids[$class_id] = true;
}
}
}
}
} elseif (isset($tree['root'])) {
_collect_class_ids($tree['root'], $found_ids);
}
}
// 5. Filter to only OxyMade UUIDs
$oxymade_ids = array_intersect_key($found_ids, $registry);
// 6. Read current selectors, build existing ID set
$all_selectors_raw = get_option('oxygen_oxy_selectors_json_string', []);
if (is_string($all_selectors_raw)) {
$all_selectors = json_decode($all_selectors_raw, true);
} else {
$all_selectors = $all_selectors_raw;
}
if (!is_array($all_selectors)) {
$all_selectors = [];
}
$existing_ids = [];
foreach ($all_selectors as $sel) {
if (isset($sel['id'])) {
$existing_ids[$sel['id']] = true;
}
}
// 7. Add missing OxyMade selectors
$added = 0;
$skipped = 0;
$new_collections = [];
// Get existing collections
$collections_raw = get_option('oxygen_oxy_selectors_collections_json_string', []);
if (is_string($collections_raw)) {
$collections = json_decode($collections_raw, true);
} else {
$collections = $collections_raw;
}
if (!is_array($collections)) {
$collections = [];
}
foreach ($oxymade_ids as $uuid => $_) {
if (isset($existing_ids[$uuid])) {
$skipped++;
continue;
}
$sel = $registry[$uuid];
// Build stub selector (no CSS properties, just metadata)
$stub = [
'id' => $sel['id'],
'name' => $sel['name'],
'children' => $sel['children'] ?? [],
'locked' => true,
'collection' => $sel['collection'] ?? '',
'type' => 'class',
];
$all_selectors[] = $stub;
$added++;
// Track new collections
$col = $sel['collection'] ?? '';
if ($col && !in_array($col, $collections)) {
$collections[] = $col;
$new_collections[] = $col;
}
}
// 8. Save if anything changed (store as JSON string to match Oxygen's format)
if ($added > 0) {
_invalidate_selectors_cache();
update_option('oxygen_oxy_selectors_json_string', wp_json_encode($all_selectors));
if (!empty($new_collections)) {
update_option('oxygen_oxy_selectors_collections_json_string', wp_json_encode($collections));
}
_invalidate_selectors_cache();
}
wp_send_json_success([
'pages_scanned' => count($all_post_ids),
'classes_found' => count($oxymade_ids),
'classes_added' => $added,
'classes_skipped' => $skipped,
]);
}
/**
* Recursively collect class IDs from a document tree node.
*/
function _collect_class_ids($node, &$ids)
{
if (!is_array($node)) return;
$classes = $node['data']['properties']['meta']['classes'] ?? null;
if (is_array($classes)) {
foreach ($classes as $class_id) {
if (is_string($class_id)) {
$ids[$class_id] = true;
}
}
}
if (isset($node['children']) && is_array($node['children'])) {
foreach ($node['children'] as $child) {
_collect_class_ids($child, $ids);
}
}
}
add_action('wp_ajax_oxymade_fix_styling', __NAMESPACE__ . '\\fix_styling');
/**
* AJAX handler for license activation/deactivation
*/
function handle_license_action()
{
// Check nonce for security
if (!isset($_POST['nonce']) || !wp_verify_nonce($_POST['nonce'], 'oxymade-license-action')) {
wp_send_json_error('Invalid security token');
exit;
}
// Check if user has permission
if (!current_user_can('manage_options')) {
wp_send_json_error('You do not have permission to perform this action');
exit;
}
global $oxymade_license_client;
if (!isset($oxymade_license_client)) {
wp_send_json_error('License client not initialized');
exit;
}
// Get the action type
$license_action = isset($_POST['license_action']) ? sanitize_text_field($_POST['license_action']) : '';
if (empty($license_action)) {
wp_send_json_error('No action specified');
exit;
}
try {
if ($license_action === 'activate') {
// Activate license
$license_key = isset($_POST['license_key']) ? sanitize_text_field($_POST['license_key']) : '';
if (empty($license_key)) {
wp_send_json_error('License key is required');
exit;
}
$activated = $oxymade_license_client->license()->activate($license_key);
if (is_wp_error($activated)) {
wp_send_json_error($activated->get_error_message());
exit;
}
// Refresh license data cache
if (class_exists('\\OxyMade\\License\\LicenseCheck')) {
\OxyMade\License\LicenseCheck::refresh_license_data();
}
wp_send_json_success([
'message' => __('License activated successfully!', 'oxymade')
]);
} elseif ($license_action === 'deactivate') {
// Deactivate license
$activation_id = isset($_POST['activation_id']) ? sanitize_text_field($_POST['activation_id']) : '';
if (empty($activation_id)) {
wp_send_json_error('Activation ID is required');
exit;
}
$deactivated = $oxymade_license_client->license()->deactivate($activation_id);
if (is_wp_error($deactivated)) {
wp_send_json_error($deactivated->get_error_message());
exit;
}
// Clear custom license cache on deactivation
if (class_exists('\\OxyMade\\License\\LicenseCheck')) {
\OxyMade\License\LicenseCheck::clear_license_data();
}
wp_send_json_success([
'message' => __('License deactivated successfully!', 'oxymade')
]);
} else {
wp_send_json_error('Invalid action');
}
} catch (\Exception $e) {
wp_send_json_error('An error occurred: ' . $e->getMessage());
}
exit;
}
add_action('wp_ajax_oxymade_license_action', __NAMESPACE__ . '\\handle_license_action');