607 lines
21 KiB
PHP
607 lines
21 KiB
PHP
<?php
|
|
/*
|
|
Plugin Name: MC Player History
|
|
Description: Spielerverlauf deines Minecraft Servers.
|
|
Version: 1.1.1
|
|
Author: M_Viper
|
|
*/
|
|
|
|
if ( ! defined( 'ABSPATH' ) ) {
|
|
exit;
|
|
}
|
|
|
|
global $mc_player_history_db_version;
|
|
$mc_player_history_db_version = '1.15.0';
|
|
|
|
// Hilfsfunktion für Logging
|
|
function mcph_log( $message ) {
|
|
if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
|
|
if ( defined( 'WP_DEBUG_LOG' ) && WP_DEBUG_LOG ) {
|
|
error_log( '[MC Player History] ' . $message );
|
|
} else {
|
|
error_log( '[MC Player History] ' . $message );
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Hilfsfunktion: Minecraft Color Codes umwandeln
|
|
*/
|
|
function mcph_parse_minecraft_colors( $text ) {
|
|
if ( empty( $text ) ) return '';
|
|
$map = array(
|
|
'&0' => '<span style="color: #000000">', '&1' => '<span style="color: #0000AA">',
|
|
'&2' => '<span style="color: #00AA00">', '&3' => '<span style="color: #00AAAA">',
|
|
'&4' => '<span style="color: #AA0000">', '&5' => '<span style="color: #AA00AA">',
|
|
'&6' => '<span style="color: #FFAA00">', '&7' => '<span style="color: #AAAAAA">',
|
|
'&8' => '<span style="color: #555555">', '&9' => '<span style="color: #5555FF">',
|
|
'&a' => '<span style="color: #55FF55">', '&b' => '<span style="color: #55FFFF">',
|
|
'&c' => '<span style="color: #FF5555">', '&d' => '<span style="color: #FF55FF">',
|
|
'&e' => '<span style="color: #FFFF55">', '&f' => '<span style="color: #FFFFFF">',
|
|
'&l' => '<span style="font-weight: bold">', '&m' => '<span style="text-decoration: line-through">',
|
|
'&n' => '<span style="text-decoration: underline">', '&o' => '<span style="font-style: italic">',
|
|
'&r' => '</span>',
|
|
);
|
|
foreach ( $map as $code => $html ) {
|
|
if ( $code === '&r' ) {
|
|
$text = str_replace( $code, $html . '</span>', $text );
|
|
} else {
|
|
$text = str_replace( $code, $html, $text );
|
|
}
|
|
}
|
|
return $text;
|
|
}
|
|
|
|
/**
|
|
* 1. Tabelle beim Aktivieren anlegen
|
|
*/
|
|
register_activation_hook( __FILE__, 'mcph_install' );
|
|
function mcph_install() {
|
|
global $wpdb;
|
|
$table_name = $wpdb->prefix . 'mc_players';
|
|
$charset_collate = $wpdb->get_charset_collate();
|
|
$sql = "CREATE TABLE $table_name (
|
|
id bigint(20) NOT NULL AUTO_INCREMENT,
|
|
uuid varchar(64) NOT NULL,
|
|
username varchar(64) NOT NULL,
|
|
prefix varchar(255) DEFAULT NULL,
|
|
first_seen datetime NOT NULL,
|
|
last_seen datetime NOT NULL,
|
|
is_online tinyint(1) NOT NULL DEFAULT 0,
|
|
PRIMARY KEY (id),
|
|
UNIQUE KEY uuid (uuid),
|
|
KEY username (username),
|
|
KEY is_online (is_online)
|
|
) $charset_collate;";
|
|
require_once( ABSPATH . 'wp-admin/includes/upgrade.php' );
|
|
dbDelta( $sql );
|
|
update_option( 'mc_player_history_db_version', $mc_player_history_db_version );
|
|
}
|
|
|
|
register_deactivation_hook( __FILE__, 'mcph_deactivate' );
|
|
function mcph_deactivate() {
|
|
wp_clear_scheduled_hook( 'mcph_sync_event' );
|
|
}
|
|
|
|
/**
|
|
* 2. Cron-Job
|
|
*/
|
|
add_action( 'init', 'mcph_setup_schedule' );
|
|
function mcph_setup_schedule() {
|
|
if ( ! wp_next_scheduled( 'mcph_sync_event' ) ) {
|
|
wp_schedule_event( time(), 'mcph_2min', 'mcph_sync_event' );
|
|
}
|
|
}
|
|
add_filter( 'cron_schedules', 'mcph_add_cron_interval' );
|
|
function mcph_add_cron_interval( $schedules ) {
|
|
$schedules['mcph_2min'] = array( 'interval' => 2 * MINUTE_IN_SECONDS, 'display' => 'Alle 2 Minuten' );
|
|
return $schedules;
|
|
}
|
|
|
|
add_action( 'mcph_sync_event', 'mcph_sync_from_statusapi' );
|
|
function mcph_sync_from_statusapi() {
|
|
$api_url = get_option( 'mcph_statusapi_url' );
|
|
if ( ! empty( $api_url ) && strpos( $api_url, 'http' ) !== 0 ) { $api_url = 'http://' . $api_url; }
|
|
if ( empty( $api_url ) ) { $api_url = 'http://localhost:9191'; }
|
|
|
|
$response = wp_remote_get( $api_url, array( 'timeout' => 5 ) );
|
|
if ( is_wp_error( $response ) ) { return; }
|
|
$json = json_decode( wp_remote_retrieve_body( $response ) );
|
|
if ( ! $json ) { return; }
|
|
|
|
global $wpdb;
|
|
$table_name = $wpdb->prefix . 'mc_players';
|
|
if ( $wpdb->get_var( "SHOW TABLES LIKE '$table_name'" ) != $table_name ) { return; }
|
|
|
|
$wpdb->query( "UPDATE $table_name SET is_online = 0" );
|
|
$players = isset( $json->players ) && is_array( $json->players ) ? $json->players : array();
|
|
|
|
foreach ( $players as $p ) {
|
|
$name = isset( $p->name ) ? sanitize_text_field( $p->name ) : '';
|
|
$prefix = isset( $p->prefix ) ? sanitize_text_field( $p->prefix ) : '';
|
|
// UUID aus API übernehmen, falls vorhanden, sonst legacy-Hash
|
|
if ( isset( $p->uuid ) && !empty( $p->uuid ) ) {
|
|
$uuid = sanitize_text_field( $p->uuid );
|
|
} else {
|
|
$uuid = 'legacy-' . md5( strtolower( trim( $name ) ) );
|
|
}
|
|
if ( empty( $name ) ) continue;
|
|
|
|
// Prüfe ob Spieler bereits existiert (nach UUID ODER Username)
|
|
$exists = $wpdb->get_var( $wpdb->prepare( "SELECT id FROM $table_name WHERE uuid = %s", $uuid ) );
|
|
|
|
// Falls nicht per UUID gefunden, prüfe nach Username (für Migration alter Einträge)
|
|
if ( ! $exists ) {
|
|
$old_entry = $wpdb->get_row( $wpdb->prepare( "SELECT id, uuid FROM $table_name WHERE username = %s", $name ) );
|
|
if ( $old_entry ) {
|
|
// Alter Eintrag gefunden - UUID aktualisieren statt neuen Eintrag
|
|
$wpdb->update(
|
|
$table_name,
|
|
array( 'uuid' => $uuid, 'prefix' => $prefix, 'last_seen' => current_time( 'mysql' ), 'is_online' => 1 ),
|
|
array( 'id' => $old_entry->id ),
|
|
array( '%s', '%s', '%s', '%d' ),
|
|
array( '%d' )
|
|
);
|
|
continue;
|
|
}
|
|
}
|
|
|
|
$now = current_time( 'mysql' );
|
|
|
|
if ( $exists ) {
|
|
$wpdb->update( $table_name, array( 'username' => $name, 'prefix' => $prefix, 'last_seen' => $now, 'is_online' => 1 ), array( 'uuid' => $uuid ), array( '%s', '%s', '%s', '%d' ), array( '%s' ) );
|
|
} else {
|
|
$wpdb->insert( $table_name, array( 'uuid' => $uuid, 'username' => $name, 'prefix' => $prefix, 'first_seen' => $now, 'last_seen' => $now, 'is_online' => 1 ), array( '%s', '%s', '%s', '%s', '%s', '%d' ) );
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 3. AJAX Handler
|
|
*/
|
|
add_action( 'wp_ajax_mcph_refresh', 'mcph_ajax_refresh' );
|
|
add_action( 'wp_ajax_nopriv_mcph_refresh', 'mcph_ajax_refresh' );
|
|
|
|
function mcph_ajax_refresh() {
|
|
$limit = isset( $_POST['limit'] ) ? intval( $_POST['limit'] ) : 500;
|
|
$only_online = isset( $_POST['only_online'] ) && $_POST['only_online'] === 'true';
|
|
$html = mcph_generate_player_html( $limit, $only_online, true );
|
|
wp_send_json_success( array( 'html' => $html ) );
|
|
}
|
|
|
|
/**
|
|
* 4. HTML Generator (Strikte Reihenfolge)
|
|
*/
|
|
function mcph_generate_player_html( $limit = 500, $only_online = false, $is_ajax = false ) {
|
|
global $wpdb;
|
|
$table_name = $wpdb->prefix . 'mc_players';
|
|
|
|
// --- LIVE STATUS CHECK MIT CACHE ---
|
|
$cache_key = 'mcph_api_cache_data';
|
|
$api_response = get_transient( $cache_key );
|
|
|
|
if ( false === $api_response ) {
|
|
$api_url = get_option( 'mcph_statusapi_url' );
|
|
if ( ! empty( $api_url ) && strpos( $api_url, 'http' ) !== 0 ) { $api_url = 'http://' . $api_url; }
|
|
if ( empty( $api_url ) ) { $api_url = 'http://localhost:9191'; }
|
|
|
|
$response = wp_remote_get( $api_url, array( 'timeout' => 2 ) );
|
|
|
|
if ( ! is_wp_error( $response ) ) {
|
|
$body = wp_remote_retrieve_body( $response );
|
|
$json = json_decode( $body );
|
|
if ( $json && isset( $json->players ) && is_array( $json->players ) ) {
|
|
set_transient( $cache_key, $json->players, 5 );
|
|
$api_response = $json->players;
|
|
}
|
|
}
|
|
}
|
|
|
|
$live_online_uuids = array();
|
|
$has_live_data = false;
|
|
|
|
if ( is_array( $api_response ) && ! empty( $api_response ) ) {
|
|
$has_live_data = true;
|
|
foreach ( $api_response as $p ) {
|
|
if ( isset( $p->name ) ) {
|
|
// UUID aus API übernehmen, falls vorhanden, sonst legacy-Hash
|
|
if ( isset( $p->uuid ) && !empty( $p->uuid ) ) {
|
|
$uuid = sanitize_text_field( $p->uuid );
|
|
} else {
|
|
$uuid = 'legacy-' . md5( strtolower( trim( $p->name ) ) );
|
|
}
|
|
$live_online_uuids[ $uuid ] = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
$sql_limit = $only_online ? ($limit * 2) : $limit;
|
|
$rows = $wpdb->get_results( $wpdb->prepare( "SELECT * FROM $table_name ORDER BY last_seen DESC LIMIT %d", $sql_limit ) );
|
|
|
|
if ( empty( $rows ) ) {
|
|
return '<p style="text-align:center; color:#777;">Keine Spieler gefunden.</p>';
|
|
}
|
|
|
|
ob_start();
|
|
echo '<div class="mc-grid">';
|
|
|
|
$displayed_count = 0;
|
|
|
|
foreach ( $rows as $row ) {
|
|
$is_online = false;
|
|
if ( $has_live_data ) {
|
|
$is_online = isset( $live_online_uuids[ $row->uuid ] );
|
|
} else {
|
|
$is_online = ( $row->is_online == 1 );
|
|
}
|
|
|
|
if ( $only_online && ! $is_online ) {
|
|
continue;
|
|
}
|
|
|
|
if ( $displayed_count >= $limit ) {
|
|
break;
|
|
}
|
|
$displayed_count++;
|
|
|
|
$username = esc_html( $row->username );
|
|
$prefix = mcph_parse_minecraft_colors( $row->prefix );
|
|
|
|
// Avatar-Logik: Moderne 3D-Köpfe (alle in gleiche Richtung)
|
|
$avatar = '';
|
|
if ( strpos( $username, '.' ) !== false || ( isset($row->uuid) && strpos($row->uuid, 'xuid') === 0 ) ) {
|
|
// Bedrock: mc-heads.net mit 3D Head
|
|
$avatar = isset($row->uuid) && !empty($row->uuid)
|
|
? 'https://mc-heads.net/head/' . esc_attr($row->uuid) . '/100'
|
|
: 'https://mc-heads.net/head/' . $username . '/100';
|
|
} else {
|
|
// Java: mc-heads.net mit 3D Head (gleiche Richtung wie Bedrock)
|
|
$avatar = 'https://mc-heads.net/head/' . $username . '/100';
|
|
}
|
|
|
|
$anim_style = '';
|
|
if ( ! $is_ajax ) {
|
|
$anim_style = 'animation: fadeInUp 0.5s ease forwards; opacity: 0; animation-delay: ' . ($displayed_count * 0.05) . 's;';
|
|
}
|
|
|
|
if ( $is_online ) {
|
|
$status_html = '<span class="mc-status mc-online">Online</span>';
|
|
} else {
|
|
$time = strtotime( $row->last_seen );
|
|
$date_str = ( date('d.m.Y', $time) == date('d.m.Y') ) ? date('H:i', $time) : date('d.m.Y H:i', $time);
|
|
$status_html = '<span class="mc-status mc-offline">Zuletzt: ' . $date_str . '</span>';
|
|
}
|
|
|
|
// STRUKTUR: Bild -> Prefix -> Name -> Spacer -> Status
|
|
echo '<div class="mc-player-card" style="' . $anim_style . '">';
|
|
echo '<img src="' . $avatar . '" class="mc-avatar" alt="' . $username . '" loading="lazy">';
|
|
|
|
echo '<div class="mc-info-stack">';
|
|
|
|
// 1. PREFIX
|
|
if ( ! empty( $row->prefix ) ) {
|
|
echo '<span class="mc-prefix">' . $prefix . '</span>';
|
|
}
|
|
|
|
// 2. NAME
|
|
echo '<span class="mc-name">' . $username . '</span>';
|
|
|
|
// 3. SPACER (Nimmt den verbleibenden Platz ein)
|
|
echo '<div class="mc-spacer"></div>';
|
|
|
|
// 4. STATUS
|
|
echo '<div class="mc-status-line">' . $status_html . '</div>';
|
|
|
|
echo '</div>';
|
|
echo '</div>';
|
|
}
|
|
echo '</div>';
|
|
|
|
echo '<div class="mc-update-time">Aktualisiert: ' . current_time('H:i:s') . '</div>';
|
|
|
|
return ob_get_clean();
|
|
}
|
|
|
|
/**
|
|
* 5. Admin Menü
|
|
*/
|
|
add_action( 'admin_menu', 'mcph_admin_menu' );
|
|
function mcph_admin_menu() {
|
|
add_options_page( 'MC Player History', 'MC Player History', 'manage_options', 'mc_player_history', 'mcph_options_page' );
|
|
}
|
|
add_action( 'admin_init', 'mcph_settings_init' );
|
|
function mcph_settings_init() {
|
|
register_setting( 'mcph_plugin_options', 'mcph_statusapi_url' );
|
|
}
|
|
function mcph_options_page() {
|
|
// Duplikate bereinigen
|
|
if ( isset( $_POST['mcph_cleanup_duplicates'] ) && check_admin_referer( 'mcph_cleanup_action' ) ) {
|
|
global $wpdb;
|
|
$table_name = $wpdb->prefix . 'mc_players';
|
|
|
|
// Finde Duplikate (gleicher Username, verschiedene UUIDs)
|
|
$duplicates = $wpdb->get_results(
|
|
"SELECT username, COUNT(*) as count, GROUP_CONCAT(id ORDER BY last_seen DESC) as ids
|
|
FROM $table_name
|
|
GROUP BY username
|
|
HAVING count > 1"
|
|
);
|
|
|
|
$cleaned = 0;
|
|
foreach ( $duplicates as $dup ) {
|
|
$ids = explode( ',', $dup->ids );
|
|
// Behalte den neuesten (ersten), lösche die anderen
|
|
array_shift( $ids );
|
|
foreach ( $ids as $id ) {
|
|
$wpdb->delete( $table_name, array( 'id' => $id ), array( '%d' ) );
|
|
$cleaned++;
|
|
}
|
|
}
|
|
|
|
echo '<div class="notice notice-success"><p>' . $cleaned . ' Duplikate wurden entfernt.</p></div>';
|
|
}
|
|
|
|
if ( isset( $_POST['mcph_manual_sync'] ) && check_admin_referer( 'mcph_manual_sync_action' ) ) {
|
|
mcph_sync_from_statusapi();
|
|
delete_transient( 'mcph_api_cache_data' );
|
|
echo '<div class="notice notice-success"><p>Manueller Sync ausgeführt.</p></div>';
|
|
}
|
|
?>
|
|
<div class="wrap">
|
|
<h1>MC Player History Einstellungen</h1>
|
|
<form method="post" action="options.php">
|
|
<?php settings_fields( 'mcph_plugin_options' ); do_settings_sections( 'mcph_plugin_options' ); ?>
|
|
<table class="form-table">
|
|
<tr valign="top">
|
|
<th scope="row">StatusAPI URL</th>
|
|
<td>
|
|
<input type="text" name="mcph_statusapi_url" value="<?php echo esc_attr( get_option( 'mcph_statusapi_url' ) ); ?>" class="regular-text" placeholder="http://localhost:9191" />
|
|
<p class="description">Bitte unbedingt mit http:// angeben.</p>
|
|
</td>
|
|
</tr>
|
|
</table>
|
|
<?php submit_button(); ?>
|
|
</form>
|
|
|
|
<h2>Duplikate bereinigen</h2>
|
|
<form method="post">
|
|
<?php wp_nonce_field( 'mcph_cleanup_action' ); ?>
|
|
<p class="description">Entfernt doppelte Spieler-Einträge aus der Datenbank.</p>
|
|
<input type="submit" name="mcph_cleanup_duplicates" class="button button-secondary" value="Duplikate jetzt entfernen" />
|
|
</form>
|
|
|
|
<h2>Manueller Sync</h2>
|
|
<form method="post">
|
|
<?php wp_nonce_field( 'mcph_manual_sync_action' ); ?>
|
|
<input type="submit" name="mcph_manual_sync" class="button" value="Jetzt synchronisieren" />
|
|
</form>
|
|
</div>
|
|
<?php
|
|
}
|
|
|
|
/**
|
|
* 6. Shortcode + CSS
|
|
*/
|
|
add_shortcode( 'mc_player_history', 'mcph_shortcode' );
|
|
|
|
function mcph_shortcode( $atts ) {
|
|
$atts = shortcode_atts( array(
|
|
'limit' => 500,
|
|
'interval' => 2,
|
|
'only_online' => 'false'
|
|
), $atts );
|
|
|
|
$container_id = 'mc-player-wrapper-' . uniqid();
|
|
|
|
ob_start();
|
|
|
|
echo '<div id="' . $container_id . '" class="mc-player-list">';
|
|
echo mcph_generate_player_html( $atts['limit'], $atts['only_online'] === 'true', false );
|
|
echo '</div>';
|
|
|
|
?>
|
|
<script>
|
|
(function() {
|
|
const intervalSeconds = <?php echo intval($atts['interval']); ?>;
|
|
const wrapperId = "<?php echo $container_id; ?>";
|
|
const limit = <?php echo intval($atts['limit']); ?>;
|
|
const onlyOnline = "<?php echo ($atts['only_online'] === 'true') ? 'true' : 'false'; ?>";
|
|
|
|
function refreshList() {
|
|
fetch('<?php echo admin_url('admin-ajax.php'); ?>', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
|
|
},
|
|
body: 'action=mcph_refresh&limit=' + limit + '&only_online=' + onlyOnline
|
|
})
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
if (data.success) {
|
|
const wrapper = document.getElementById(wrapperId);
|
|
if (wrapper) {
|
|
wrapper.innerHTML = data.data.html;
|
|
}
|
|
}
|
|
})
|
|
.catch(error => console.error('MC Player History Error:', error));
|
|
}
|
|
|
|
setInterval(refreshList, intervalSeconds * 1000);
|
|
})();
|
|
</script>
|
|
<?php
|
|
|
|
echo '<style>
|
|
.mc-player-list { width: 100%; }
|
|
|
|
.mc-grid {
|
|
display: grid;
|
|
gap: 20px;
|
|
grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
|
|
justify-content: center;
|
|
align-items: stretch;
|
|
}
|
|
|
|
@keyframes fadeInUp {
|
|
from { opacity: 0; transform: translateY(10px); }
|
|
to { opacity: 1; transform: translateY(0); }
|
|
}
|
|
|
|
/* KARTE */
|
|
.mc-player-card {
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
gap: 8px;
|
|
padding: 20px 10px;
|
|
background: linear-gradient(145deg, #ffffff 0%, #f8f9fa 100%);
|
|
border: 1px solid #e0e0e0;
|
|
border-radius: 12px;
|
|
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
|
cursor: default;
|
|
position: relative;
|
|
box-shadow: 0 2px 8px rgba(0,0,0,0.08);
|
|
}
|
|
|
|
.mc-player-card:hover {
|
|
transform: translateY(-8px) scale(1.02);
|
|
box-shadow: 0 12px 24px rgba(0,0,0,0.15);
|
|
border-color: #667eea;
|
|
background: linear-gradient(145deg, #ffffff 0%, #f0f2ff 100%);
|
|
}
|
|
|
|
.mc-avatar {
|
|
border-radius: 8px;
|
|
border: 3px solid #e0e0e0;
|
|
background: transparent;
|
|
width: 80px;
|
|
height: 80px;
|
|
transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1);
|
|
flex-shrink: 0;
|
|
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
|
|
object-fit: contain;
|
|
padding: 0;
|
|
}
|
|
|
|
.mc-player-card:hover .mc-avatar {
|
|
border-color: #667eea;
|
|
transform: scale(1.15) rotateY(10deg);
|
|
box-shadow: 0 8px 20px rgba(102, 126, 234, 0.4);
|
|
}
|
|
|
|
/* INFOS BEREICH */
|
|
.mc-info-stack {
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
width: 100%;
|
|
flex-grow: 1;
|
|
position: relative;
|
|
padding-bottom: 35px;
|
|
justify-content: flex-start;
|
|
}
|
|
|
|
/* 1. PREFIX */
|
|
.mc-prefix {
|
|
font-size: 0.9em;
|
|
display: block;
|
|
width: 100%;
|
|
text-align: center;
|
|
margin-bottom: 4px;
|
|
line-height: 1.2;
|
|
text-shadow: 0 1px 2px rgba(0,0,0,0.1);
|
|
}
|
|
|
|
/* 2. NAME */
|
|
.mc-name {
|
|
font-weight: bold;
|
|
font-size: 1.1em;
|
|
color: #2d3748;
|
|
word-break: break-word;
|
|
display: block;
|
|
width: 100%;
|
|
text-align: center;
|
|
text-shadow: 0 1px 2px rgba(0,0,0,0.05);
|
|
}
|
|
|
|
/* 3. SPACER */
|
|
.mc-spacer {
|
|
flex-grow: 1;
|
|
}
|
|
|
|
/* 4. STATUS (UNTEN) */
|
|
.mc-status-line {
|
|
position: absolute;
|
|
bottom: 0;
|
|
left: 0;
|
|
width: 100%;
|
|
text-align: center;
|
|
margin: 0;
|
|
padding-bottom: 5px;
|
|
}
|
|
|
|
.mc-status {
|
|
display: inline-block;
|
|
padding: 5px 14px;
|
|
border-radius: 20px;
|
|
font-size: 0.8em;
|
|
font-weight: 600;
|
|
transition: all 0.3s ease;
|
|
}
|
|
|
|
.mc-online {
|
|
background: linear-gradient(135deg, #d4f8e8 0%, #b8f2d9 100%);
|
|
color: #0a7340;
|
|
border: 1px solid #81e6b8;
|
|
box-shadow: 0 2px 4px rgba(10, 115, 64, 0.1);
|
|
}
|
|
|
|
.mc-offline {
|
|
background: linear-gradient(135deg, #fff5f5 0%, #ffe5e5 100%);
|
|
color: #c53030;
|
|
border: 1px solid #fbb6b6;
|
|
box-shadow: 0 2px 4px rgba(197, 48, 48, 0.1);
|
|
}
|
|
|
|
.mc-player-card:hover .mc-status {
|
|
transform: scale(1.05);
|
|
}
|
|
|
|
.mc-update-time {
|
|
font-size: 0.85em;
|
|
color: #718096;
|
|
text-align: center;
|
|
margin-top: 20px;
|
|
font-style: italic;
|
|
padding: 8px;
|
|
background: rgba(102, 126, 234, 0.05);
|
|
border-radius: 6px;
|
|
display: inline-block;
|
|
width: 100%;
|
|
}
|
|
|
|
@media (min-width: 1200px) {
|
|
.mc-grid { grid-template-columns: repeat(6, 1fr); }
|
|
}
|
|
|
|
@media (max-width: 600px) {
|
|
.mc-grid {
|
|
grid-template-columns: repeat(2, 1fr);
|
|
gap: 12px;
|
|
}
|
|
.mc-avatar {
|
|
width: 70px;
|
|
height: 70px;
|
|
}
|
|
.mc-name {
|
|
font-size: 1em;
|
|
}
|
|
.mc-player-card {
|
|
padding: 15px 8px;
|
|
}
|
|
}
|
|
</style>';
|
|
|
|
return ob_get_clean();
|
|
}
|