Files
beep/includes/class-settings.php

1119 lines
44 KiB
PHP
Executable File
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
/**
* Beep Settings
*
* Handles all customizable settings for the Beep plugin.
* Settings are stored in wp_options and injected as CSS variables.
*/
if (!defined("ABSPATH")) {
exit;
}
class Beep_Settings {
/**
* Option key for storing settings
*/
const OPTION_KEY = 'beep_settings';
/**
* Default settings
*/
private static $defaults = [
// Colors - Backgrounds
'bg_main' => '#FFFFFF', // Twitter Light: white
'bg_card' => '#F5F8FA', // Twitter Light: card background
'bg_card_hover' => '#EBEEF0', // Twitter Light: hover state
'bg_input' => '#F5F8FA', // Twitter Light: input fields
// Colors - Text
'text_primary' => '#0F1419', // Twitter Light: main text (near black)
'text_secondary' => '#536471', // Twitter Light: muted text
'text_link' => '#1D9BF0', // Twitter Light: links (Twitter blue)
// Colors - Borders & Dividers
'border_color' => '#EFF3F4', // Twitter Light: borders and dividers
'border_hover' => '#CBD0D4', // Twitter Light: hover border
// Colors - Interactive
'color_like' => '#F91880', // Like heart (Twitter pink)
'color_repost' => '#00BA7C', // Repost green
'color_reply' => '#1D9BF0', // Reply blue
'color_quote' => '#1D9BF0', // Quote blue
'color_poll' => '#1D9BF0', // Poll blue
// Colors - Buttons
'btn_primary_bg' => '#1D9BF0', // Primary button background
'btn_primary_text' => '#FFFFFF', // Primary button text
'btn_secondary_bg' => '#2F3336', // Secondary button background
'btn_secondary_text' => '#E7E9EA', // Secondary button text
// Colors - Avatar Ring
'avatar_ring' => '#2F3336', // Avatar ring color
// Branding
'logo_url' => '', // Custom logo URL
'icon_url' => '', // Custom icon URL (favicon)
'site_name' => 'Beep', // Or 'X' or 'Twitter'
'tagline' => 'Messages from',
// Layout
'feed_max_width' => '600px', // Max width of feed
'feed_padding' => '16px', // Feed padding
'post_padding' => '14px 20px', // Post padding
'avatar_size' => '48px', // Avatar dimensions
'gap_between_posts' => '12px', // Gap between posts
// Border Radius
'radius_card' => '16px', // Card border radius
'radius_avatar' => '9999px', // Avatar circle
'radius_button' => '9999px', // Buttons/pills
'radius_input' => '4px', // Input fields
// Typography
'font_family' => '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif',
'font_size_base' => '15px', // Base font size
'font_size_small' => '13px', // Small text (timestamps)
'font_size_large' => '20px', // Large text (names)
// Features (on/off)
'enable_likes' => '1',
'enable_reposts' => '1',
'enable_quotes' => '1',
'enable_polls' => '1',
'enable_follows' => '1',
'enable_media' => '1',
'enable_replies' => '1',
// Behavior
'character_limit' => '280',
'posts_per_page' => '20',
'auto_refresh' => '0', // Auto refresh interval (0 = off)
'public_posting' => '0', // Allow public posting without login
// Content
'show_avatar' => '1',
'show_stats' => '1', // Show reply/like counts
'show_timestamps' => '1', // Show relative time
'show_gravatar' => '1', // Show Gravatar
'show_public_notice' => '1', // Show "sign in to post" notice
// Animation
'animation_speed' => '150', // Transition speed in ms
];
/**
* Initialize settings
*/
public static function init() {
add_action('admin_init', [__CLASS__, 'register_settings']);
add_action('admin_menu', [__CLASS__, 'add_settings_page']);
add_filter('plugin_action_links_' . plugin_basename(BEEP_FILE), [__CLASS__, 'add_settings_link']);
add_action('wp_head', [__CLASS__, 'inject_css_variables'], 100);
}
/**
* Get all settings with defaults
*/
public static function get_settings() {
$saved = get_option(self::OPTION_KEY, []);
return wp_parse_args($saved, self::$defaults);
}
/**
* Get a single setting
*/
public static function get($key, $default = null) {
$settings = self::get_settings();
return isset($settings[$key]) ? $settings[$key] : ($default ?? (self::$defaults[$key] ?? null));
}
/**
* Register settings with WordPress
*/
public static function register_settings() {
register_setting(
self::OPTION_KEY,
self::OPTION_KEY,
[
'type' => 'array',
'sanitize_callback' => [__CLASS__, 'sanitize_settings'],
'default' => [],
]
);
// === Section: Colors - Backgrounds ===
add_settings_section(
'beep_colors_bg',
'Colors - Backgrounds',
[__CLASS__, 'section_colors_bg_description'],
'beep-settings'
);
add_settings_field(
'bg_main',
'Main Background',
[__CLASS__, 'field_color_picker'],
'beep-settings',
'beep_colors_bg',
['key' => 'bg_main', 'label' => 'Feed/Page background']
);
add_settings_field(
'bg_card',
'Card Background',
[__CLASS__, 'field_color_picker'],
'beep-settings',
'beep_colors_bg',
['key' => 'bg_card', 'label' => 'Post cards & containers']
);
add_settings_field(
'bg_card_hover',
'Card Hover Background',
[__CLASS__, 'field_color_picker'],
'beep-settings',
'beep_colors_bg',
['key' => 'bg_card_hover', 'label' => 'Hover state for cards']
);
add_settings_field(
'bg_input',
'Input Background',
[__CLASS__, 'field_color_picker'],
'beep-settings',
'beep_colors_bg',
['key' => 'bg_input', 'label' => 'Textarea/input fields']
);
// === Section: Colors - Text ===
add_settings_section(
'beep_colors_text',
'Colors - Text',
[__CLASS__, 'section_colors_text_description'],
'beep-settings'
);
add_settings_field(
'text_primary',
'Primary Text',
[__CLASS__, 'field_color_picker'],
'beep-settings',
'beep_colors_text',
['key' => 'text_primary', 'label' => 'Main text color']
);
add_settings_field(
'text_secondary',
'Secondary Text',
[__CLASS__, 'field_color_picker'],
'beep-settings',
'beep_colors_text',
['key' => 'text_secondary', 'label' => 'Muted/timestamp text']
);
add_settings_field(
'text_link',
'Link Color',
[__CLASS__, 'field_color_picker'],
'beep-settings',
'beep_colors_text',
['key' => 'text_link', 'label' => 'Links & buttons']
);
// === Section: Colors - Interactive ===
add_settings_section(
'beep_colors_interactive',
'Colors - Interactive Elements',
[__CLASS__, 'section_colors_interactive_description'],
'beep-settings'
);
add_settings_field(
'color_like',
'Like Heart Color',
[__CLASS__, 'field_color_picker'],
'beep-settings',
'beep_colors_interactive',
['key' => 'color_like', 'label' => 'Heart/like button']
);
add_settings_field(
'color_repost',
'Repost Color',
[__CLASS__, 'field_color_picker'],
'beep-settings',
'beep_colors_interactive',
['key' => 'color_repost', 'label' => 'Repost button']
);
add_settings_field(
'color_reply',
'Reply Color',
[__CLASS__, 'field_color_picker'],
'beep-settings',
'beep_colors_interactive',
['key' => 'color_reply', 'label' => 'Reply button']
);
add_settings_field(
'border_color',
'Border Color',
[__CLASS__, 'field_color_picker'],
'beep-settings',
'beep_colors_interactive',
['key' => 'border_color', 'label' => 'Dividers & borders']
);
// === Section: Layout ===
add_settings_section(
'beep_layout',
'Layout & Spacing',
[__CLASS__, 'section_layout_description'],
'beep-settings'
);
add_settings_field(
'feed_max_width',
'Feed Max Width',
[__CLASS__, 'field_text'],
'beep-settings',
'beep_layout',
['key' => 'feed_max_width', 'label' => 'e.g. 600px or 100%']
);
add_settings_field(
'feed_padding',
'Feed Padding',
[__CLASS__, 'field_text'],
'beep-settings',
'beep_layout',
['key' => 'feed_padding', 'label' => 'e.g. 16px']
);
add_settings_field(
'post_padding',
'Post Padding',
[__CLASS__, 'field_text'],
'beep-settings',
'beep_layout',
['key' => 'post_padding', 'label' => 'e.g. 14px 20px']
);
add_settings_field(
'avatar_size',
'Avatar Size',
[__CLASS__, 'field_text'],
'beep-settings',
'beep_layout',
['key' => 'avatar_size', 'label' => 'e.g. 48px']
);
// === Section: Border Radius ===
add_settings_section(
'beep_radius',
'Border Radius',
[__CLASS__, 'section_radius_description'],
'beep-settings'
);
add_settings_field(
'radius_card',
'Card Radius',
[__CLASS__, 'field_text'],
'beep-settings',
'beep_radius',
['key' => 'radius_card', 'label' => 'e.g. 16px for cards, 0 for sharp edges']
);
add_settings_field(
'radius_avatar',
'Avatar Radius',
[__CLASS__, 'field_text'],
'beep-settings',
'beep_radius',
['key' => 'radius_avatar', 'label' => 'e.g. 9999px for circle']
);
// === Section: Typography ===
add_settings_section(
'beep_typography',
'Typography',
[__CLASS__, 'section_typography_description'],
'beep-settings'
);
add_settings_field(
'font_family',
'Font Family',
[__CLASS__, 'field_text'],
'beep-settings',
'beep_typography',
['key' => 'font_family', 'label' => 'CSS font-family value']
);
add_settings_field(
'font_size_base',
'Base Font Size',
[__CLASS__, 'field_text'],
'beep-settings',
'beep_typography',
['key' => 'font_size_base', 'label' => 'e.g. 15px']
);
// === Section: Branding & Media ===
add_settings_section(
'beep_branding',
'Branding & Media',
[__CLASS__, 'section_branding_description'],
'beep-settings'
);
add_settings_field(
'logo_url',
'Logo Image',
[__CLASS__, 'field_media_upload'],
'beep-settings',
'beep_branding',
['key' => 'logo_url', 'label' => 'Upload a logo image. Leave empty to use text.']
);
add_settings_field(
'icon_url',
'Icon Image',
[__CLASS__, 'field_media_upload'],
'beep-settings',
'beep_branding',
['key' => 'icon_url', 'label' => 'Upload an icon/favicon image.']
);
add_settings_field(
'site_name',
'Site Name',
[__CLASS__, 'field_text'],
'beep-settings',
'beep_branding',
['key' => 'site_name', 'label' => 'e.g. Beep, X, or Twitter']
);
add_settings_field(
'tagline',
'Tagline',
[__CLASS__, 'field_text'],
'beep-settings',
'beep_branding',
['key' => 'tagline', 'label' => 'Text above logo, e.g. "Messages from"']
);
// === Section: Features ===
add_settings_section(
'beep_features',
'Features',
[__CLASS__, 'section_features_description'],
'beep-settings'
);
add_settings_field(
'enable_likes',
'Enable Likes',
[__CLASS__, 'field_checkbox'],
'beep-settings',
'beep_features',
['key' => 'enable_likes', 'label' => 'Show like button on posts']
);
add_settings_field(
'enable_reposts',
'Enable Reposts',
[__CLASS__, 'field_checkbox'],
'beep-settings',
'beep_features',
['key' => 'enable_reposts', 'label' => 'Show repost button']
);
add_settings_field(
'enable_quotes',
'Enable Quotes',
[__CLASS__, 'field_checkbox'],
'beep-settings',
'beep_features',
['key' => 'enable_quotes', 'label' => 'Show quote button']
);
add_settings_field(
'enable_replies',
'Enable Replies',
[__CLASS__, 'field_checkbox'],
'beep-settings',
'beep_features',
['key' => 'enable_replies', 'label' => 'Allow replying to posts']
);
add_settings_field(
'enable_media',
'Enable Media',
[__CLASS__, 'field_checkbox'],
'beep-settings',
'beep_features',
['key' => 'enable_media', 'label' => 'Allow image attachments']
);
add_settings_field(
'enable_polls',
'Enable Polls',
[__CLASS__, 'field_checkbox'],
'beep-settings',
'beep_features',
['key' => 'enable_polls', 'label' => 'Allow poll creation']
);
// === Section: Behavior ===
add_settings_section(
'beep_behavior',
'Behavior',
[__CLASS__, 'section_behavior_description'],
'beep-settings'
);
add_settings_field(
'character_limit',
'Character Limit',
[__CLASS__, 'field_text'],
'beep-settings',
'beep_behavior',
['key' => 'character_limit', 'label' => 'Max characters per post (default 280)']
);
add_settings_field(
'posts_per_page',
'Posts Per Page',
[__CLASS__, 'field_text'],
'beep-settings',
'beep_behavior',
['key' => 'posts_per_page', 'label' => 'Number of posts to load']
);
add_settings_field(
'public_posting',
'Allow Public Posting',
[__CLASS__, 'field_checkbox'],
'beep-settings',
'beep_behavior',
['key' => 'public_posting', 'label' => 'Allow posting without logging in']
);
// === Section: Content Display ===
add_settings_section(
'beep_content',
'Content Display',
[__CLASS__, 'section_content_description'],
'beep-settings'
);
add_settings_field(
'show_avatar',
'Show Avatars',
[__CLASS__, 'field_checkbox'],
'beep-settings',
'beep_content',
['key' => 'show_avatar', 'label' => 'Display user profile pictures']
);
add_settings_field(
'show_stats',
'Show Statistics',
[__CLASS__, 'field_checkbox'],
'beep-settings',
'beep_content',
['key' => 'show_stats', 'label' => 'Display reply/like counts']
);
add_settings_field(
'show_timestamps',
'Show Timestamps',
[__CLASS__, 'field_checkbox'],
'beep-settings',
'beep_content',
['key' => 'show_timestamps', 'label' => 'Display relative time']
);
add_settings_field(
'show_public_notice',
'Show Public Notice',
[__CLASS__, 'field_checkbox'],
'beep-settings',
'beep_content',
['key' => 'show_public_notice', 'label' => 'Show "Sign in to post" notice']
);
}
/**
* Add settings page to admin menu
*/
public static function add_settings_page() {
add_options_page(
'Beep Settings',
'Beep',
'manage_options',
'beep-settings',
[__CLASS__, 'render_settings_page']
);
}
/**
* Add settings link on Plugins page
*/
public static function add_settings_link($links) {
$settings_link = '<a href="options-general.php?page=beep-settings">' . __('Settings') . '</a>';
array_unshift($links, $settings_link);
return $links;
}
/**
* Sanitize settings on save
*/
public static function sanitize_settings($input) {
$sanitized = [];
foreach (self::$defaults as $key => $default) {
if (is_bool($default)) {
$sanitized[$key] = !empty($input[$key]) ? '1' : '0';
} elseif (is_int($default)) {
$sanitized[$key] = isset($input[$key]) ? intval($input[$key]) : $default;
} else {
$sanitized[$key] = isset($input[$key]) ? sanitize_text_field($input[$key]) : $default;
}
}
return $sanitized;
}
/**
* Render the settings page
*/
public static function render_settings_page() {
if (!current_user_can('manage_options')) {
return;
}
?>
<div class="wrap beep-settings-wrap">
<h1>Beep Settings</h1>
<div class="beep-settings-header">
<p>Customize the appearance and behavior of your Beep feed. Changes take effect immediately.</p>
<p>
<strong>Quick Presets:</strong>
<button type="button" class="button beep-preset-twitter" data-preset="twitter">Twitter Dark</button>
<button type="button" class="button beep-preset-light" data-preset="light">Twitter Light</button>
<button type="button" class="button beep-preset-minimal" data-preset="minimal">Minimal</button>
</p>
</div>
<form method="post" action="options.php">
<?php
settings_fields(self::OPTION_KEY);
do_settings_sections('beep-settings');
submit_button('Save Settings');
?>
</form>
<hr>
<div class="beep-settings-footer">
<h2>Need Help?</h2>
<p>Use your browser's Inspector tool to find colors you want to match. Click any element and look at the computed styles.</p>
<p><strong>Want to go fully Twitter/X dark mode?</strong> Use these values:</p>
<ul style="list-style: disc; margin-left: 20px;">
<li>Main Background: <code>#15202B</code></li>
<li>Card Background: <code>#192734</code></li>
<li>Primary Text: <code>#E7E9EA</code></li>
<li>Secondary Text: <code>#71767B</code></li>
<li>Link Color: <code>#1D9BF0</code></li>
<li>Like Heart: <code>#F91880</code></li>
<li>Border Color: <code>#2F3336</code></li>
<li>Card Radius: <code>0px</code> (for sharp Twitter-like edges)</li>
</ul>
</div>
</div>
<style>
.beep-settings-wrap { max-width: 800px; }
.beep-settings-wrap .form-table th { font-weight: 600; }
.beep-settings-wrap input[type="text"] { width: 200px; }
.beep-settings-wrap .beep-color-preview { display: inline-block; width: 20px; height: 20px; border: 1px solid #ccc; vertical-align: middle; margin-right: 8px; border-radius: 3px; }
.beep-settings-header { background: #f0f0f1; padding: 15px 20px; border-radius: 8px; margin-bottom: 20px; }
.beep-settings-footer { background: #f0f0f1; padding: 15px 20px; border-radius: 8px; margin-top: 30px; }
.beep-settings-footer code { background: #fff; padding: 2px 6px; border-radius: 3px; }
</style>
<script>
jQuery(document).ready(function($) {
// Color picker enhancement
$('.beep-color-picker').each(function() {
var $input = $(this);
var $preview = $input.siblings('.beep-color-preview');
$input.on('input', function() {
$preview.css('background-color', $(this).val());
});
// Initialize preview
$preview.css('background-color', $input.val());
});
// Preset buttons
$('.beep-preset-twitter').on('click', function() {
var preset = <?php echo json_encode([
'bg_main' => '#15202B', 'bg_card' => '#192734', 'bg_card_hover' => '#1C2732', 'bg_input' => '#253341',
'text_primary' => '#E7E9EA', 'text_secondary' => '#71767B', 'text_link' => '#1D9BF0',
'color_like' => '#F91880', 'color_repost' => '#00BA7C', 'color_reply' => '#1D9BF0',
'border_color' => '#2F3336', 'radius_card' => '0px', 'radius_avatar' => '9999px',
]); ?>;
applyPreset(preset);
});
$('.beep-preset-light').on('click', function() {
var preset = <?php echo json_encode([
'bg_main' => '#FFFFFF', 'bg_card' => '#F7F9F9', 'bg_card_hover' => '#EFF3F4', 'bg_input' => '#F7F9F9',
'text_primary' => '#0F1419', 'text_secondary' => '#536471', 'text_link' => '#1D9BF0',
'color_like' => '#F91880', 'color_repost' => '#00BA7C', 'color_reply' => '#1D9BF0',
'border_color' => '#EFF3F4', 'radius_card' => '16px', 'radius_avatar' => '9999px',
]); ?>;
applyPreset(preset);
});
$('.beep-preset-minimal').on('click', function() {
var preset = <?php echo json_encode([
'bg_main' => '#1a1a1a', 'bg_card' => '#1a1a1a', 'bg_card_hover' => '#262626', 'bg_input' => '#262626',
'text_primary' => '#e0e0e0', 'text_secondary' => '#888888', 'text_link' => '#ffffff',
'color_like' => '#ff4444', 'color_repost' => '#44ff44', 'color_reply' => '#4444ff',
'border_color' => '#333333', 'radius_card' => '0px', 'radius_avatar' => '0px',
]); ?>;
applyPreset(preset);
});
function applyPreset(preset) {
Object.keys(preset).forEach(function(key) {
var $input = $('input[name="beep_settings[' + key + ']"]');
if ($input.length) {
$input.val(preset[key]).trigger('input');
}
});
}
// Media upload handling
$('.beep-media-upload-btn').on('click', function() {
var $btn = $(this);
var targetId = $btn.data('target');
var type = $btn.data('type') || 'icon';
var $input = $('#' + targetId);
var $wrapper = $btn.closest('[data-beep-upload-wrapper]');
var $previewArea = $wrapper.find('[data-beep-preview-area]');
var mediaFrame = wp.media({
title: 'Choose ' + (type === 'logo' ? 'Logo Image' : 'Icon Image'),
button: { text: 'Use as ' + (type === 'logo' ? 'Logo' : 'Icon') },
multiple: false,
library: { type: 'image' }
});
mediaFrame.on('select', function() {
var attachment = mediaFrame.state().get('selection').first().toJSON();
$input.val(attachment.url);
renderPreview(attachment.url, type, $previewArea);
});
mediaFrame.open();
});
// Live preview on URL paste/type
$('.beep-media-input').on('input', function() {
var $input = $(this);
var $wrapper = $input.closest('[data-beep-upload-wrapper]');
var $previewArea = $wrapper.find('[data-beep-preview-area]');
var type = $input.attr('name').includes('logo') ? 'logo' : 'icon';
var url = $input.val().trim();
if (url && (url.startsWith('http://') || url.startsWith('https://'))) {
renderPreview(url, type, $previewArea);
} else {
renderEmptyPreview($previewArea);
}
});
// Render preview helper
function renderPreview(url, type, $area) {
var recommended = type === 'logo'
? '300×80 px minimum'
: '32×32 px';
var displayW = type === 'logo' ? 280 : 80;
var displayH = type === 'logo' ? 80 : 80;
var $img = $('<img/>', {
src: url,
alt: 'Preview',
crossOrigin: 'anonymous',
css: {
maxWidth: displayW + 'px',
maxHeight: displayH + 'px',
border: '1px solid #ccc',
borderRadius: '6px',
background: '#fff',
display: 'block'
}
});
var $imgWrapper = $('<div/>', {
css: {
display: 'inline-block',
padding: '12px',
background: '#fff',
border: '1px solid #EFF3F4',
borderRadius: '8px'
}
}).append($img);
var $badge = $('<span/>', {
text: '⚖ Pending size check…',
css: { fontSize: '11px', color: '#71767B', fontFamily: 'monospace', display: 'block', marginTop: '6px' }
});
$img.on('load', function() {
var w = this.naturalWidth || this.width;
var h = this.naturalHeight || this.height;
var ok = type === 'logo' ? (w >= 150 && h >= 40) : (w >= 16 && h >= 16);
var label = type === 'logo' ? 'Logo' : 'Icon';
var $sizeInfo = $('<div/>', {
css: { marginTop: '8px', fontSize: '12px', fontFamily: 'monospace' }
});
$sizeInfo.append(
$('<span/>', {
text: label + ' size: ' + w + '×' + h + 'px',
css: { color: ok ? '#00BA7C' : '#F91880', fontWeight: '600' }
})
);
$sizeInfo.append(
$('<br><span/>', {
text: 'Recommended: ' + recommended,
css: { color: '#71767B' }
})
);
if (!ok) {
$sizeInfo.append(
$('<br><span/>', {
text: '⚠ May appear blurry — larger source recommended',
css: { color: '#F91880', fontWeight: '500' }
})
);
}
// Render the full preview area
$area.html('').append(
$('<div/>', { css: { display: 'flex', alignItems: 'flex-start', gap: '16px' } }).append(
$imgWrapper,
$('<div/>').append(
$('<div/>', {
text: '✓ Live preview',
css: { fontSize: '12px', fontWeight: '600', color: '#00BA7C', marginBottom: '4px' }
}),
$sizeInfo,
$('<div/>', {
text: 'In feed header (wordmark):',
css: { fontSize: '11px', color: '#71767B', marginTop: '8px', marginBottom: '4px', textTransform: 'uppercase', letterSpacing: '.5px' }
}),
$('<img/>', {
src: url,
alt: label + ' in header',
css: {
maxWidth: '200px',
maxHeight: '48px',
border: '1px solid #EFF3F4',
borderRadius: '4px',
display: 'block'
}
}),
$('<div/>', {
text: 'In post (badge):',
css: { fontSize: '11px', color: '#71767B', marginTop: '8px', marginBottom: '4px', textTransform: 'uppercase', letterSpacing: '.5px' }
}),
$('<img/>', {
src: url,
alt: label + ' badge',
css: {
maxWidth: '32px',
maxHeight: '32px',
border: '1px solid #EFF3F4',
borderRadius: '4px',
display: 'block'
}
}),
$('<img/>', {
src: url,
alt: label + ' badge (reply)',
css: {
maxWidth: '24px',
maxHeight: '24px',
border: '1px solid #EFF3F4',
borderRadius: '4px',
display: 'block',
marginTop: '4px'
}
})
)
)
);
}).on('error', function() {
$area.html(
'<div style="color:#b32d2e;font-size:13px;padding:8px;">Could not load image — check the URL</div>'
);
});
}
function renderEmptyPreview($area) {
$area.html(
'<div class="beep-upload-empty" style="display:flex;align-items:center;gap:12px;padding:10px 14px;background:#F5F8FA;border:1px dashed #CBD0D4;border-radius:8px;color:#536471;font-size:13px;">' +
'<svg viewBox="0 0 24 24" width="20" height="20" fill="#536471"><path d="M21 19V5c0-1.1-.9-2-2-2H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2zM8.5 13.5l2.5 3.01L14.5 12l4.5 6H5l3.5-4.5z"/></svg>' +
'No image chosen yet — click "Choose Image" or paste a URL above' +
'</div>'
);
}
// Initialize previews on page load
$('.beep-media-input').each(function() {
var $input = $(this);
var $wrapper = $input.closest('[data-beep-upload-wrapper]');
var $previewArea = $wrapper.find('[data-beep-preview-area]');
var type = $input.attr('name').includes('logo') ? 'logo' : 'icon';
var url = $input.val().trim();
if (url && (url.startsWith('http://') || url.startsWith('https://'))) {
renderPreview(url, type, $previewArea);
}
});
// Remove media button
$(document).on('click', '.beep-media-remove', function() {
var targetId = $(this).data('target');
var previewId = $(this).data('preview');
$('#' + targetId).val('');
var $wrapper = $('#' + targetId).closest('[data-beep-upload-wrapper]');
var $previewArea = $wrapper.find('[data-beep-preview-area]');
renderEmptyPreview($previewArea);
// Remove the "Remove" button itself
$(this).remove();
});
});
</script>
<?php
}
/**
* Inject CSS variables into <head>
*/
public static function inject_css_variables() {
$settings = self::get_settings();
?>
<style id="beep-custom-css-vars">
:root {
--beep-bg: <?php echo esc_attr($settings['bg_main']); ?>;
--beep-card-bg: <?php echo esc_attr($settings['bg_card']); ?>;
--beep-card-hover: <?php echo esc_attr($settings['bg_card_hover']); ?>;
--beep-input-bg: <?php echo esc_attr($settings['bg_input']); ?>;
--beep-text: <?php echo esc_attr($settings['text_primary']); ?>;
--beep-muted: <?php echo esc_attr($settings['text_secondary']); ?>;
--beep-accent: <?php echo esc_attr($settings['text_link']); ?>;
--beep-border: <?php echo esc_attr($settings['border_color']); ?>;
--beep-border-hover: <?php echo esc_attr($settings['border_hover']); ?>;
--beep-like: <?php echo esc_attr($settings['color_like']); ?>;
--beep-repost: <?php echo esc_attr($settings['color_repost']); ?>;
--beep-reply: <?php echo esc_attr($settings['color_reply']); ?>;
--beep-quote: <?php echo esc_attr($settings['color_quote']); ?>;
--beep-poll: <?php echo esc_attr($settings['color_poll']); ?>;
--beep-btn-primary-bg: <?php echo esc_attr($settings['btn_primary_bg']); ?>;
--beep-btn-primary-text: <?php echo esc_attr($settings['btn_primary_text']); ?>;
--beep-btn-secondary-bg: <?php echo esc_attr($settings['btn_secondary_bg']); ?>;
--beep-btn-secondary-text: <?php echo esc_attr($settings['btn_secondary_text']); ?>;
--beep-avatar-ring: <?php echo esc_attr($settings['avatar_ring']); ?>;
--beep-feed-max-width: <?php echo esc_attr($settings['feed_max_width']); ?>;
--beep-feed-padding: <?php echo esc_attr($settings['feed_padding']); ?>;
--beep-post-padding: <?php echo esc_attr($settings['post_padding']); ?>;
--beep-avatar-size: <?php echo esc_attr($settings['avatar_size']); ?>;
--beep-gap: <?php echo esc_attr($settings['gap_between_posts']); ?>;
--beep-radius-card: <?php echo esc_attr($settings['radius_card']); ?>;
--beep-radius-avatar: <?php echo esc_attr($settings['radius_avatar']); ?>;
--beep-radius-button: <?php echo esc_attr($settings['radius_button']); ?>;
--beep-radius-input: <?php echo esc_attr($settings['radius_input']); ?>;
--beep-font-family: <?php echo esc_attr($settings['font_family']); ?>;
--beep-font-size: <?php echo esc_attr($settings['font_size_base']); ?>;
--beep-font-size-small: <?php echo esc_attr($settings['font_size_small']); ?>;
--beep-font-size-large: <?php echo esc_attr($settings['font_size_large']); ?>;
--beep-animation: <?php echo esc_attr($settings['animation_speed']); ?>ms ease;
}
/* Override page-level backgrounds (fixes Elementor purple gradient) */
body, .elementor, .elementor-section-wrap, .elementor-page, .elementor-editor-active body {
background: var(--beep-bg) !important;
}
</style>
<?php
}
// === Section Descriptions ===
public static function section_colors_bg_description() {
echo '<p>Background colors for the feed and cards.</p>';
}
public static function section_colors_text_description() {
echo '<p>Text colors throughout the feed.</p>';
}
public static function section_colors_interactive_description() {
echo '<p>Colors for interactive elements like buttons and icons.</p>';
}
public static function section_layout_description() {
echo '<p>Spacing and layout dimensions.</p>';
}
public static function section_radius_description() {
echo '<p>Border radius for rounded corners. Use <code>0px</code> for sharp edges (Twitter style), <code>16px</code> for rounded cards.</p>';
}
public static function section_typography_description() {
echo '<p>Font settings.</p>';
}
public static function section_branding_description() {
echo '<p>Logo, icon, and site name settings.</p>';
}
public static function section_features_description() {
echo '<p>Toggle features on or off.</p>';
}
public static function section_behavior_description() {
echo '<p>How the feed behaves.</p>';
}
public static function section_content_description() {
echo '<p>What to show/hide in the feed.</p>';
}
// === Field Renderers ===
public static function field_color_picker($args) {
$key = $args['key'];
$label = $args['label'];
$value = self::get($key);
?>
<span class="beep-color-preview" style="background-color: <?php echo esc_attr($value); ?>"></span>
<input type="text"
name="beep_settings[<?php echo esc_attr($key); ?>]"
id="beep_settings_<?php echo esc_attr($key); ?>"
value="<?php echo esc_attr($value); ?>"
class="beep-color-picker regular-text"
placeholder="#000000"
data-default-color="<?php echo esc_attr(self::$defaults[$key]); ?>">
<p class="description"><?php echo esc_html($label); ?></p>
<?php
}
public static function field_text($args) {
$key = $args['key'];
$label = $args['label'];
$value = self::get($key);
?>
<input type="text"
name="beep_settings[<?php echo esc_attr($key); ?>]"
id="beep_settings_<?php echo esc_attr($key); ?>"
value="<?php echo esc_attr($value); ?>"
class="regular-text">
<p class="description"><?php echo esc_html($label); ?></p>
<?php
}
public static function field_checkbox($args) {
$key = $args['key'];
$label = $args['label'];
$value = self::get($key);
?>
<label for="beep_settings_<?php echo esc_attr($key); ?>">
<input type="checkbox"
name="beep_settings[<?php echo esc_attr($key); ?>]"
id="beep_settings_<?php echo esc_attr($key); ?>"
value="1"
<?php checked($value, '1'); ?>>
<?php echo esc_html($label); ?>
</label>
<?php
}
public static function field_media_upload($args) {
$key = $args['key'];
$label = $args['label'];
$value = self::get($key);
$is_logo = ($key === 'logo_url');
$spec_label = $is_logo ? 'Logo (Wordmark)' : 'Icon (Badge)';
$recommended = $is_logo ? '300×80 px minimum, transparent PNG recommended' : '32×32 px (shown at 32px and 24px)';
?>
<!-- Spec callout -->
<div class="beep-upload-spec" style="background:#f0f6ff;border-left:3px solid #1D9BF0;padding:8px 12px;margin-bottom:10px;border-radius:4px;font-size:13px;color:#0F1419;">
<strong style="font-size:12px;text-transform:uppercase;letter-spacing:.5px;color:#536471;">Recommended for <?php echo esc_html($spec_label); ?>:</strong><br>
<span style="font-family:monospace;font-size:12px;"><?php echo esc_html($recommended); ?></span>
<?php if ($is_logo) : ?>
<br><span style="color:#71767B;">Transparent PNG/SVG — the header has a blurred backdrop</span>
<?php else : ?>
<br><span style="color:#71767B;">Will be displayed at 32×32px in posts, 24×24px in replies</span>
<?php endif; ?>
</div>
<div class="beep-media-upload-wrapper" data-beep-upload-wrapper style="display:flex;flex-direction:column;gap:12px;">
<!-- Upload row -->
<div style="display:flex;align-items:center;gap:10px;">
<input type="text"
name="beep_settings[<?php echo esc_attr($key); ?>]"
id="beep_settings_<?php echo esc_attr($key); ?>"
value="<?php echo esc_attr($value); ?>"
class="regular-text beep-media-input"
placeholder="https://... or leave empty for default"
style="flex:1;">
<button type="button" class="button beep-media-upload-btn"
data-target="beep_settings_<?php echo esc_attr($key); ?>"
data-type="<?php echo esc_attr($is_logo ? 'logo' : 'icon'); ?>">
Choose Image
</button>
</div>
<!-- Preview area -->
<div class="beep-upload-preview-area" data-beep-preview-area="<?php echo esc_attr($key); ?>">
<?php if (!empty($value)) : ?>
<!-- Live preview (populated by JS on load + on change) -->
<?php else : ?>
<div class="beep-upload-empty" style="display:flex;align-items:center;gap:12px;padding:10px 14px;background:#F5F8FA;border:1px dashed #CBD0D4;border-radius:8px;color:#536471;font-size:13px;">
<svg viewBox="0 0 24 24" width="20" height="20" fill="#536471"><path d="M21 19V5c0-1.1-.9-2-2-2H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2zM8.5 13.5l2.5 3.01L14.5 12l4.5 6H5l3.5-4.5z"/></svg>
No image chosen yet — click "Choose Image" or paste a URL above
</div>
<?php endif; ?>
</div>
<?php if (!empty($value)) : ?>
<button type="button" class="button-link beep-media-remove"
data-target="beep_settings_<?php echo esc_attr($key); ?>"
data-preview="beep_upload_preview_<?php echo esc_attr($key); ?>"
style="color:#b32d2e;text-decoration:none;font-size:13px;align-self:flex-start;">
Remove image
</button>
<?php endif; ?>
</div>
<p class="description" style="margin-top:0;"><?php echo esc_html($label); ?></p>
<?php
}
}
// Initialize settings
Beep_Settings::init();