文章目录[隐藏]
WordPress开发教程:集成网站自动化社媒舆情监测与预警通知系统
引言:WordPress的无限可能
在当今数字化时代,企业网站已不再仅仅是展示信息的静态页面,而是需要具备智能化、自动化功能的综合平台。WordPress作为全球最受欢迎的内容管理系统,其真正的强大之处在于其高度的可扩展性和灵活性。通过代码二次开发,我们可以将WordPress从一个简单的博客平台转变为功能强大的业务工具。本教程将深入探讨如何通过WordPress程序开发,集成网站自动化社交媒体舆情监测与预警通知系统,同时实现常用互联网小工具功能,为您的网站增添智能化翅膀。
第一部分:WordPress开发环境搭建与准备
1.1 开发环境配置
在进行WordPress二次开发前,首先需要搭建合适的开发环境。推荐使用本地开发环境如XAMPP、MAMP或Local by Flywheel,这些工具可以快速搭建包含Apache、MySQL和PHP的完整环境。对于代码编辑器,Visual Studio Code或PHPStorm都是优秀的选择,它们提供了强大的代码提示、调试和版本控制集成功能。
确保您的开发环境满足以下要求:
- PHP版本7.4或更高(推荐8.0+)
- MySQL 5.6+或MariaDB 10.1+
- Apache或Nginx服务器
- 启用必要的PHP扩展(curl、json、mbstring等)
1.2 子主题创建与结构规划
为避免直接修改主题文件导致更新时丢失更改,我们强烈建议创建子主题。在wp-content/themes目录下创建新文件夹,命名为您的主题名加“-child”,例如“twentytwentyone-child”。
子主题至少需要包含以下文件:
- style.css:包含主题元数据
- functions.php:用于添加自定义功能
- 可选的模板文件
在style.css中添加:
/*
Theme Name: Twenty Twenty-One Child
Template: twentytwentyone
Version: 1.0
*/
1.3 必备开发工具与插件
安装以下开发辅助插件:
- Query Monitor:数据库查询和性能分析
- Debug Bar:PHP错误和警告显示
- Show Current Template:显示当前使用的模板文件
- Advanced Custom Fields:自定义字段管理(可选但推荐)
第二部分:社交媒体舆情监测系统开发
2.1 系统架构设计
社交媒体舆情监测系统需要包含以下核心模块:
- 数据采集模块:从各社交媒体平台获取数据
- 数据处理模块:清洗、分析和分类数据
- 存储模块:将处理后的数据存入数据库
- 展示模块:在WordPress后台和前端展示数据
- 预警模块:根据设定规则触发通知
2.2 社交媒体API集成
首先,我们需要集成主流社交媒体的API。以下是一个基础类,用于处理多个平台的API连接:
class SocialMediaMonitor {
private $platforms = [];
private $api_keys = [];
public function __construct() {
$this->init_platforms();
}
private function init_platforms() {
// 从数据库或配置文件中读取API密钥
$this->api_keys = get_option('social_media_api_keys', []);
// 初始化各平台连接
$this->platforms = [
'twitter' => new TwitterAPI($this->api_keys['twitter'] ?? ''),
'facebook' => new FacebookAPI($this->api_keys['facebook'] ?? ''),
'instagram' => new InstagramAPI($this->api_keys['instagram'] ?? ''),
'weibo' => new WeiboAPI($this->api_keys['weibo'] ?? ''),
];
}
public function fetch_posts($platform, $keywords, $limit = 100) {
if (!isset($this->platforms[$platform])) {
return new WP_Error('invalid_platform', '不支持的社交媒体平台');
}
try {
return $this->platforms[$platform]->search($keywords, $limit);
} catch (Exception $e) {
error_log('社交媒体数据获取失败: ' . $e->getMessage());
return [];
}
}
}
2.3 数据采集与定时任务
使用WordPress的Cron系统定时采集数据:
class SocialMediaCrawler {
public function __construct() {
add_action('init', [$this, 'schedule_crawling']);
add_action('social_media_crawl_hook', [$this, 'crawl_all_platforms']);
}
public function schedule_crawling() {
if (!wp_next_scheduled('social_media_crawl_hook')) {
wp_schedule_event(time(), 'hourly', 'social_media_crawl_hook');
}
}
public function crawl_all_platforms() {
$monitor = new SocialMediaMonitor();
$keywords = get_option('monitoring_keywords', []);
foreach (['twitter', 'facebook', 'instagram'] as $platform) {
$posts = $monitor->fetch_posts($platform, $keywords, 50);
$this->process_and_store($posts, $platform);
}
// 记录最后一次爬取时间
update_option('last_crawl_time', current_time('mysql'));
}
private function process_and_store($posts, $platform) {
global $wpdb;
$table_name = $wpdb->prefix . 'social_media_posts';
foreach ($posts as $post) {
$data = [
'platform' => $platform,
'post_id' => $post['id'],
'author' => sanitize_text_field($post['author']),
'content' => wp_kses_post($post['content']),
'url' => esc_url_raw($post['url']),
'likes' => intval($post['likes']),
'shares' => intval($post['shares']),
'comments' => intval($post['comments']),
'post_time' => $post['created_at'],
'sentiment' => $this->analyze_sentiment($post['content']),
'created_at' => current_time('mysql')
];
$wpdb->insert($table_name, $data);
}
}
}
2.4 情感分析与关键词提取
集成自然语言处理功能,分析文本情感和提取关键词:
class SentimentAnalyzer {
private $positive_words = [];
private $negative_words = [];
public function __construct() {
// 加载情感词典
$this->load_sentiment_dictionaries();
}
public function analyze($text) {
$words = $this->tokenize($text);
$positive_score = 0;
$negative_score = 0;
foreach ($words as $word) {
if (in_array($word, $this->positive_words)) {
$positive_score++;
}
if (in_array($word, $this->negative_words)) {
$negative_score++;
}
}
$total = $positive_score + $negative_score;
if ($total == 0) {
return 'neutral';
}
$score = ($positive_score - $negative_score) / $total;
if ($score > 0.2) {
return 'positive';
} elseif ($score < -0.2) {
return 'negative';
} else {
return 'neutral';
}
}
public function extract_keywords($text, $limit = 5) {
// 使用TF-IDF算法或简单词频统计
$words = $this->tokenize($text);
$stop_words = $this->get_stop_words();
// 过滤停用词
$filtered_words = array_diff($words, $stop_words);
// 统计词频
$word_freq = array_count_values($filtered_words);
// 按频率排序
arsort($word_freq);
// 返回前N个关键词
return array_slice(array_keys($word_freq), 0, $limit);
}
}
第三部分:预警通知系统实现
3.1 预警规则配置系统
创建灵活可配置的预警规则系统:
class AlertSystem {
private $rules = [];
public function __construct() {
$this->load_rules();
add_action('new_social_media_post', [$this, 'check_alerts'], 10, 2);
}
private function load_rules() {
$this->rules = get_option('alert_rules', [
[
'id' => 1,
'name' => '负面舆情预警',
'conditions' => [
'sentiment' => 'negative',
'engagement' => '>100',
'keywords' => ['投诉', '问题', '故障']
],
'channels' => ['email', 'slack'],
'recipients' => ['admin@example.com'],
'cooldown' => 300 // 5分钟内不重复报警
]
]);
}
public function check_alerts($post_data, $platform) {
foreach ($this->rules as $rule) {
if ($this->matches_rule($post_data, $rule)) {
$this->trigger_alert($rule, $post_data);
}
}
}
private function matches_rule($post_data, $rule) {
foreach ($rule['conditions'] as $key => $value) {
if (!$this->check_condition($post_data, $key, $value)) {
return false;
}
}
return true;
}
private function trigger_alert($rule, $post_data) {
$last_alert = get_transient('alert_' . $rule['id']);
if ($last_alert) {
return; // 还在冷却期内
}
// 发送通知到各个渠道
foreach ($rule['channels'] as $channel) {
switch ($channel) {
case 'email':
$this->send_email_alert($rule, $post_data);
break;
case 'slack':
$this->send_slack_alert($rule, $post_data);
break;
case 'webhook':
$this->send_webhook_alert($rule, $post_data);
break;
}
}
// 设置冷却期
set_transient('alert_' . $rule['id'], true, $rule['cooldown']);
// 记录报警历史
$this->log_alert($rule, $post_data);
}
}
3.2 多渠道通知集成
实现多种通知渠道:
class NotificationChannels {
public function send_email($to, $subject, $message, $headers = '') {
if (empty($headers)) {
$headers = [
'Content-Type: text/html; charset=UTF-8',
'From: 舆情监测系统 <alerts@yourdomain.com>'
];
}
return wp_mail($to, $subject, $message, $headers);
}
public function send_slack($webhook_url, $message, $channel = '#alerts') {
$payload = [
'channel' => $channel,
'username' => '舆情监测机器人',
'text' => $message,
'icon_emoji' => ':warning:'
];
$args = [
'body' => json_encode($payload),
'headers' => ['Content-Type' => 'application/json'],
'timeout' => 30
];
return wp_remote_post($webhook_url, $args);
}
public function send_webhook($url, $data) {
$args = [
'body' => json_encode($data),
'headers' => ['Content-Type' => 'application/json'],
'timeout' => 30
];
return wp_remote_post($url, $args);
}
public function send_sms($phone, $message) {
// 集成短信服务商API
$api_key = get_option('sms_api_key');
$api_url = 'https://api.sms-provider.com/send';
$data = [
'apikey' => $api_key,
'mobile' => $phone,
'text' => $message
];
return wp_remote_post($api_url, [
'body' => $data,
'timeout' => 30
]);
}
}
3.3 实时仪表盘与数据可视化
创建实时监控仪表盘:
class MonitoringDashboard {
public function __construct() {
add_action('admin_menu', [$this, 'add_admin_pages']);
add_action('wp_dashboard_setup', [$this, 'add_dashboard_widget']);
add_shortcode('social_media_monitor', [$this, 'shortcode_display']);
}
public function add_admin_pages() {
add_menu_page(
'舆情监测系统',
'舆情监测',
'manage_options',
'social-media-monitor',
[$this, 'render_dashboard'],
'dashicons-chart-line',
30
);
// 添加子菜单
add_submenu_page(
'social-media-monitor',
'预警设置',
'预警设置',
'manage_options',
'alert-settings',
[$this, 'render_alert_settings']
);
}
public function render_dashboard() {
?>
<div class="wrap">
<h1>社交媒体舆情监测仪表盘</h1>
<div class="dashboard-grid">
<div class="dashboard-card">
<h3>今日舆情概览</h3>
<div id="sentiment-chart"></div>
</div>
<div class="dashboard-card">
<h3>平台分布</h3>
<div id="platform-chart"></div>
</div>
<div class="dashboard-card full-width">
<h3>实时动态</h3>
<div id="realtime-feed"></div>
</div>
</div>
<script>
// 使用Chart.js或ECharts渲染图表
</script>
</div>
<?php
}
public function add_dashboard_widget() {
wp_add_dashboard_widget(
'social_media_widget',
'社交媒体舆情监控',
[$this, 'render_dashboard_widget']
);
}
public function shortcode_display($atts) {
$atts = shortcode_atts([
'platform' => 'all',
'limit' => 10,
'show_sentiment' => true
], $atts);
ob_start();
$this->render_public_display($atts);
return ob_get_clean();
}
}
第四部分:常用互联网小工具集成
4.1 多功能工具类开发
创建通用工具类,集成常用功能:
class WordPressTools {
// URL缩短功能
public static function shorten_url($url, $service = 'bitly') {
$api_keys = get_option('url_shortener_keys', []);
switch ($service) {
case 'bitly':
return $this->bitly_shorten($url, $api_keys['bitly'] ?? '');
case 'tinyurl':
return $this->tinyurl_shorten($url);
default:
return $url;
}
}
// 二维码生成
public static function generate_qrcode($data, $size = 200) {
$api_url = 'https://api.qrserver.com/v1/create-qr-code/';
return add_query_arg([
'data' => urlencode($data),
'size' => $size . 'x' . $size
], $api_url);
}
// 内容摘要生成
public static function generate_excerpt($content, $length = 200) {
$content = strip_tags($content);
$content = preg_replace('/s+/', ' ', $content);
if (mb_strlen($content) <= $length) {
return $content;
}
return mb_substr($content, 0, $length) . '...';
}
// 图片压缩与优化
public static function optimize_image($image_url, $quality = 80) {
// 使用WordPress图像处理API
$upload_dir = wp_upload_dir();
$image_path = str_replace($upload_dir['baseurl'], $upload_dir['basedir'], $image_url);
if (file_exists($image_path)) {
$editor = wp_get_image_editor($image_path);
if (!is_wp_error($editor)) {
$editor->set_quality($quality);
$result = $editor->save();
if (!is_wp_error($result)) {
return str_replace($upload_dir['basedir'], $upload_dir['baseurl'], $result['path']);
}
}
}
return $image_url;
}
}
4.2 短代码与小工具集成
创建易于使用的短代码和小工具:
class ToolShortcodes {
public function __construct() {
// 注册短代码
add_shortcode('qrcode', [$this, 'qrcode_shortcode']);
add_shortcode('countdown', [$this, 'countdown_shortcode']);
add_shortcode('weather', [$this, 'weather_shortcode']);
add_shortcode('calculator', [$this, 'calculator_shortcode']);
// 注册小工具
add_action('widgets_init', [$this, 'register_widgets']);
}
public function qrcode_shortcode($atts) {
$atts = shortcode_atts([
'data' => get_permalink(),
'size' => '150',
'color' => '000000',
'bgcolor' => 'ffffff'
], $atts);
$url = 'https://api.qrserver.com/v1/create-qr-code/';
$params = [
'data' => $atts['data'],
'size' => $atts['size'] . 'x' . $atts['size'],
'color' => $atts['color'],
'bgcolor' => $atts['bgcolor']
];
$src = add_query_arg($params, $url);
return sprintf(
4.3 实用小工具示例:实时天气与计算器
// 天气小工具类
class WeatherWidget extends WP_Widget {
public function __construct() {
parent::__construct(
'weather_widget',
'实时天气',
['description' => '显示当前位置的实时天气信息']
);
}
public function widget($args, $instance) {
echo $args['before_widget'];
if (!empty($instance['title'])) {
echo $args['before_title'] . apply_filters('widget_title', $instance['title']) . $args['after_title'];
}
$location = !empty($instance['location']) ? $instance['location'] : 'auto';
$units = !empty($instance['units']) ? $instance['units'] : 'metric';
$weather_data = $this->get_weather_data($location, $units);
if ($weather_data) {
?>
<div class="weather-widget">
<div class="weather-current">
<div class="weather-icon">
<img src="<?php echo esc_url($weather_data['icon']); ?>"
alt="<?php echo esc_attr($weather_data['description']); ?>">
</div>
<div class="weather-temp">
<span class="temp-number"><?php echo round($weather_data['temp']); ?></span>
<span class="temp-unit">°<?php echo $units === 'metric' ? 'C' : 'F'; ?></span>
</div>
<div class="weather-details">
<p class="weather-desc"><?php echo esc_html($weather_data['description']); ?></p>
<p class="weather-location"><?php echo esc_html($weather_data['location']); ?></p>
<div class="weather-extra">
<span>湿度: <?php echo $weather_data['humidity']; ?>%</span>
<span>风速: <?php echo $weather_data['wind_speed']; ?> km/h</span>
</div>
</div>
</div>
<?php if (!empty($weather_data['forecast'])) : ?>
<div class="weather-forecast">
<?php foreach (array_slice($weather_data['forecast'], 0, 5) as $day) : ?>
<div class="forecast-day">
<span class="day-name"><?php echo $day['day']; ?></span>
<img src="<?php echo esc_url($day['icon']); ?>" alt="<?php echo esc_attr($day['desc']); ?>">
<span class="day-temp"><?php echo round($day['temp_max']); ?>°</span>
</div>
<?php endforeach; ?>
</div>
<?php endif; ?>
</div>
<?php
} else {
echo '<p>无法获取天气数据</p>';
}
echo $args['after_widget'];
}
private function get_weather_data($location, $units) {
$api_key = get_option('weather_api_key', '');
$cache_key = 'weather_data_' . md5($location . $units);
$cached_data = get_transient($cache_key);
if ($cached_data !== false) {
return $cached_data;
}
if (empty($api_key)) {
return false;
}
$api_url = 'https://api.openweathermap.org/data/2.5/weather';
if ($location === 'auto') {
// 尝试获取用户IP位置
$ip = $_SERVER['REMOTE_ADDR'];
$geo_data = wp_remote_get("http://ip-api.com/json/{$ip}");
if (!is_wp_error($geo_data)) {
$geo = json_decode(wp_remote_retrieve_body($geo_data), true);
if ($geo && $geo['status'] === 'success') {
$location = "{$geo['lat']},{$geo['lon']}";
}
}
}
$params = [
'q' => $location,
'appid' => $api_key,
'units' => $units,
'lang' => 'zh_cn'
];
$response = wp_remote_get(add_query_arg($params, $api_url));
if (is_wp_error($response)) {
return false;
}
$data = json_decode(wp_remote_retrieve_body($response), true);
if ($data && $data['cod'] == 200) {
$weather_data = [
'location' => $data['name'] . ', ' . $data['sys']['country'],
'temp' => $data['main']['temp'],
'humidity' => $data['main']['humidity'],
'pressure' => $data['main']['pressure'],
'wind_speed' => $data['wind']['speed'],
'description' => $data['weather'][0]['description'],
'icon' => "https://openweathermap.org/img/wn/{$data['weather'][0]['icon']}@2x.png",
'forecast' => $this->get_forecast($data['coord']['lat'], $data['coord']['lon'], $api_key, $units)
];
// 缓存1小时
set_transient($cache_key, $weather_data, HOUR_IN_SECONDS);
return $weather_data;
}
return false;
}
}
// 计算器短代码实现
public function calculator_shortcode($atts) {
$atts = shortcode_atts([
'type' => 'basic',
'theme' => 'light',
'currency' => 'CNY'
], $atts);
ob_start();
?>
<div class="calculator-container" data-theme="<?php echo esc_attr($atts['theme']); ?>">
<?php if ($atts['type'] === 'basic') : ?>
<div class="calculator basic-calculator">
<div class="calculator-display">
<input type="text" readonly value="0" class="calc-display">
</div>
<div class="calculator-buttons">
<button class="calc-btn operator" data-action="clear">C</button>
<button class="calc-btn operator" data-action="backspace">⌫</button>
<button class="calc-btn operator" data-action="percentage">%</button>
<button class="calc-btn operator" data-action="divide">÷</button>
<button class="calc-btn number" data-number="7">7</button>
<button class="calc-btn number" data-number="8">8</button>
<button class="calc-btn number" data-number="9">9</button>
<button class="calc-btn operator" data-action="multiply">×</button>
<button class="calc-btn number" data-number="4">4</button>
<button class="calc-btn number" data-number="5">5</button>
<button class="calc-btn number" data-number="6">6</button>
<button class="calc-btn operator" data-action="subtract">-</button>
<button class="calc-btn number" data-number="1">1</button>
<button class="calc-btn number" data-number="2">2</button>
<button class="calc-btn number" data-number="3">3</button>
<button class="calc-btn operator" data-action="add">+</button>
<button class="calc-btn number zero" data-number="0">0</button>
<button class="calc-btn number" data-number=".">.</button>
<button class="calc-btn operator equals" data-action="equals">=</button>
</div>
</div>
<?php elseif ($atts['type'] === 'currency') : ?>
<div class="calculator currency-converter">
<div class="converter-row">
<input type="number" class="amount-input" value="1" min="0" step="0.01">
<select class="currency-select from-currency">
<option value="CNY">人民币 (CNY)</option>
<option value="USD">美元 (USD)</option>
<option value="EUR">欧元 (EUR)</option>
<option value="JPY">日元 (JPY)</option>
<option value="GBP">英镑 (GBP)</option>
</select>
</div>
<div class="converter-swap">
<button class="swap-btn">⇅</button>
</div>
<div class="converter-row">
<input type="number" class="amount-output" readonly>
<select class="currency-select to-currency">
<option value="USD">美元 (USD)</option>
<option value="CNY">人民币 (CNY)</option>
<option value="EUR">欧元 (EUR)</option>
<option value="JPY">日元 (JPY)</option>
<option value="GBP">英镑 (GBP)</option>
</select>
</div>
<div class="converter-rate">
汇率: <span class="rate-value">1 CNY = 0.14 USD</span>
<span class="rate-update">更新时间: <span class="update-time"></span></span>
</div>
</div>
<?php endif; ?>
</div>
<script>
(function($) {
'use strict';
<?php if ($atts['type'] === 'basic') : ?>
// 基础计算器逻辑
$(document).ready(function() {
let currentInput = '0';
let previousInput = '';
let operation = null;
let resetScreen = false;
$('.basic-calculator .calc-display').val(currentInput);
$('.calc-btn.number').on('click', function() {
const number = $(this).data('number');
if (currentInput === '0' || resetScreen) {
currentInput = number;
resetScreen = false;
} else {
currentInput += number;
}
$('.calc-display').val(currentInput);
});
$('.calc-btn.operator').on('click', function() {
const action = $(this).data('action');
switch(action) {
case 'clear':
currentInput = '0';
previousInput = '';
operation = null;
break;
case 'backspace':
if (currentInput.length > 1) {
currentInput = currentInput.slice(0, -1);
} else {
currentInput = '0';
}
break;
case 'percentage':
currentInput = (parseFloat(currentInput) / 100).toString();
break;
case 'add':
case 'subtract':
case 'multiply':
case 'divide':
if (previousInput !== '') {
calculate();
}
operation = action;
previousInput = currentInput;
resetScreen = true;
break;
case 'equals':
if (previousInput !== '' && operation !== null) {
calculate();
operation = null;
previousInput = '';
}
break;
}
$('.calc-display').val(currentInput);
});
function calculate() {
let prev = parseFloat(previousInput);
let current = parseFloat(currentInput);
let result = 0;
switch(operation) {
case 'add':
result = prev + current;
break;
case 'subtract':
result = prev - current;
break;
case 'multiply':
result = prev * current;
break;
case 'divide':
result = current !== 0 ? prev / current : '错误';
break;
}
currentInput = result.toString();
resetScreen = true;
}
});
<?php elseif ($atts['type'] === 'currency') : ?>
// 货币转换器逻辑
$(document).ready(function() {
const apiKey = '<?php echo get_option('currency_api_key', ''); ?>';
let exchangeRates = {};
let lastUpdate = null;
function updateExchangeRates() {
if (!apiKey) {
// 使用免费API
$.getJSON('https://api.exchangerate-api.com/v4/latest/CNY')
.done(function(data) {
exchangeRates = data.rates;
lastUpdate = new Date(data.date);
updateDisplay();
})
.fail(function() {
// 备用数据源
loadFallbackRates();
});
} else {
// 使用付费API
$.ajax({
url: 'https://api.currencyapi.com/v3/latest',
headers: { 'apikey': apiKey },
success: function(data) {
exchangeRates = data.data;
lastUpdate = new Date(data.meta.last_updated_at);
updateDisplay();
}
});
}
}
function loadFallbackRates() {
// 硬编码的汇率(示例数据)
exchangeRates = {
'CNY': 1,
'USD': 0.14,
'EUR': 0.13,
'JPY': 15.5,
'GBP': 0.11
};
lastUpdate = new Date();
updateDisplay();
}
function updateDisplay() {
const fromCurrency = $('.from-currency').val();
const toCurrency = $('.to-currency').val();
const amount = parseFloat($('.amount-input').val());
if (exchangeRates[fromCurrency] && exchangeRates[toCurrency]) {
const rate = exchangeRates[toCurrency] / exchangeRates[fromCurrency];
const converted = amount * rate;
$('.amount-output').val(converted.toFixed(2));
$('.rate-value').text(`1 ${fromCurrency} = ${rate.toFixed(4)} ${toCurrency}`);
$('.update-time').text(lastUpdate.toLocaleTimeString());
}
}
// 初始化
updateExchangeRates();
// 事件监听
$('.amount-input, .from-currency, .to-currency').on('input change', updateDisplay);
$('.swap-btn').on('click', function() {
const fromVal = $('.from-currency').val();
const toVal = $('.to-currency').val();
$('.from-currency').val(toVal);
$('.to-currency').val(fromVal);
updateDisplay();
});
// 每30分钟更新一次汇率
setInterval(updateExchangeRates, 30 * 60 * 1000);
});
<?php endif; ?>
})(jQuery);
</script>
<style>
.calculator-container {
max-width: 400px;
margin: 20px auto;
border-radius: 10px;
overflow: hidden;
box-shadow: 0 4px 12px rgba(0,0,0,0.1);
}
.basic-calculator {
background: #f5f5f5;
padding: 20px;
}
.calculator-display {
margin-bottom: 20px;
}
.calc-display {
width: 100%;
height: 60px;
font-size: 24px;
text-align: right;
padding: 10px;
border: none;
background: white;
border-radius: 5px;
box-shadow: inset 0 2px 5px rgba(0,0,0,0.1);
}
.calculator-buttons {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 10px;
}
.calc-btn {
height: 50px;
font-size: 18px;
border: none;
border-radius: 5px;
cursor: pointer;
transition: all 0.2s;
}
.calc-btn.number {
background: white;
color: #333;
}
.calc-btn.operator {
background: #4a90e2;
color: white;
}
.calc-btn.equals {
background: #f39c12;
color: white;
}
.calc-btn.zero {
grid-column: span 2;
}
.calc-btn:hover {
opacity: 0.9;
transform: translateY(-2px);
}
.currency-converter {
background: white;
padding: 20px;
}
.converter-row {
display: flex;
gap: 10px;
margin-bottom: 15px;
}
.amount-input, .amount-output {
flex: 1;
height: 50px;
font-size: 18px;
padding: 10px;
border: 1px solid #ddd;
border-radius: 5px;
}
.currency-select {
width: 150px;
height: 50px;
font-size: 16px;
padding: 10px;
border: 1px solid #ddd;
border-radius: 5px;
}
.converter-swap {
text-align: center;
margin: 10px 0;
}
.swap-btn {
width: 40px;
height: 40px;
border-radius: 50%;
border: 2px solid #4a90e2;
background: white;
color: #4a90e2;
font-size: 18px;
cursor: pointer;
transition: all 0.2s;
}
.swap-btn:hover {
background: #4a90e2;
color: white;
}
.converter-rate {
margin-top: 20px;
padding-top: 15px;
border-top: 1px solid #eee;
font-size: 14px;
color: #666;
}
.rate-value {
font-weight: bold;
color: #333;
}
.rate-update {
float: right;
}
</style>
<?php
return ob_get_clean();
}
