diff --git a/mc-player-history.php b/mc-player-history.php index 7664f9c..2d11557 100644 --- a/mc-player-history.php +++ b/mc-player-history.php @@ -1,492 +1,492 @@ - Prefix -> Name -> Status (unten). -Version: 1.1.0 -Author: Dein Name -*/ - -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' => '', '&1' => '', - '&2' => '', '&3' => '', - '&4' => '', '&5' => '', - '&6' => '', '&7' => '', - '&8' => '', '&9' => '', - '&a' => '', '&b' => '', - '&c' => '', '&d' => '', - '&e' => '', '&f' => '', - '&l' => '', '&m' => '', - '&n' => '', '&o' => '', - '&r' => '', - ); - foreach ( $map as $code => $html ) { - if ( $code === '&r' ) { - $text = str_replace( $code, $html . '', $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 = 'legacy-' . md5( strtolower( trim( $name ) ) ); - if ( empty( $name ) ) continue; - - $exists = $wpdb->get_var( $wpdb->prepare( "SELECT id FROM $table_name WHERE uuid = %s", $uuid ) ); - $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 = '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 '

Keine Spieler gefunden.

'; - } - - ob_start(); - echo '
'; - - $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 = 'https://minotar.net/avatar/' . $username . '/80'; - - $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 = 'Online'; - } 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 = 'Zuletzt: ' . $date_str . ''; - } - - // STRUKTUR: Bild -> Prefix -> Name -> Spacer -> Status - echo '
'; - echo '' . $username . ''; - - echo '
'; - - // 1. PREFIX - if ( ! empty( $row->prefix ) ) { - echo '' . $prefix . ''; - } - - // 2. NAME - echo '' . $username . ''; - - // 3. SPACER (Nimmt den verbleibenden Platz ein) - echo '
'; - - // 4. STATUS - echo '
' . $status_html . '
'; - - echo '
'; - echo '
'; - } - echo '
'; - - echo '
Aktualisiert: ' . current_time('H:i:s') . '
'; - - 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() { - if ( isset( $_POST['mcph_manual_sync'] ) && check_admin_referer( 'mcph_manual_sync_action' ) ) { - mcph_sync_from_statusapi(); - delete_transient( 'mcph_api_cache_data' ); - echo '

Manueller Sync ausgeführt.

'; - } - ?> -
-

MC Player History Einstellungen

-
- - - - - - -
StatusAPI URL - -

Bitte unbedingt mit http:// angeben.

-
- -
-

Manueller Sync

-
- - -
-
- 500, - 'interval' => 2, - 'only_online' => 'false' - ), $atts ); - - $container_id = 'mc-player-wrapper-' . uniqid(); - - ob_start(); - - echo '
'; - echo mcph_generate_player_html( $atts['limit'], $atts['only_online'] === 'true', false ); - echo '
'; - - ?> - - - .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; /* Weniger Abstand dazwischen */ - padding: 20px 10px; - background: #fff; - border: 1px solid #eee; - border-radius: 8px; - transition: all 0.3s ease; - cursor: default; - position: relative; /* Fallback */ - } - - .mc-player-card:hover { - transform: translateY(-5px); - box-shadow: 0 10px 20px rgba(0,0,0,0.1); - border-color: #ddd; - } - - .mc-avatar { - border-radius: 4px; - border: 3px solid #ddd; - background: #fff; - width: 60px; - height: 60px; - transition: border-color 0.3s; - flex-shrink: 0; - } - - .mc-player-card:hover .mc-avatar { - border-color: #bbb; - } - - /* INFOS BEREICH */ - .mc-info-stack { - display: flex; - flex-direction: column; - align-items: center; - width: 100%; - flex-grow: 1; /* Füllt den Rest der Karte */ - position: relative; /* Referenz für absoluten Status */ - padding-bottom: 35px; /* Platz für Status unten */ - justify-content: flex-start; /* Oben ausrichten */ - } - - /* 1. PREFIX */ - .mc-prefix { - font-size: 0.9em; - display: block; - width: 100%; - text-align: center; - margin-bottom: 4px; - line-height: 1.2; - } - - /* 2. NAME */ - .mc-name { - font-weight: bold; - font-size: 1.1em; - color: #333; - word-break: break-word; - display: block; - width: 100%; - text-align: center; - } - - /* 3. SPACER */ - .mc-spacer { - flex-grow: 1; /* Nimmt ALLEN leeren Platz zwischen Name und Status ein */ - } - - /* 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: 4px 12px; - border-radius: 20px; - font-size: 0.8em; - font-weight: 600; - } - .mc-online { background-color: #e6fffa; color: #28a745; border: 1px solid #b2f5ea; } - .mc-offline { background-color: #fff5f5; color: #e53e3e; border: 1px solid #feb2b2; } - - .mc-update-time { - font-size: 0.8em; - color: #999; - text-align: center; - margin-top: 15px; - font-style: italic; - } - - @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: 10px; } - .mc-avatar { width: 50px; height: 50px; } - .mc-name { font-size: 1em; } - .mc-player-card { padding: 10px 5px; } - } - '; - - return ob_get_clean(); + '', '&1' => '', + '&2' => '', '&3' => '', + '&4' => '', '&5' => '', + '&6' => '', '&7' => '', + '&8' => '', '&9' => '', + '&a' => '', '&b' => '', + '&c' => '', '&d' => '', + '&e' => '', '&f' => '', + '&l' => '', '&m' => '', + '&n' => '', '&o' => '', + '&r' => '', + ); + foreach ( $map as $code => $html ) { + if ( $code === '&r' ) { + $text = str_replace( $code, $html . '', $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 = 'legacy-' . md5( strtolower( trim( $name ) ) ); + if ( empty( $name ) ) continue; + + $exists = $wpdb->get_var( $wpdb->prepare( "SELECT id FROM $table_name WHERE uuid = %s", $uuid ) ); + $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 = '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 '

Keine Spieler gefunden.

'; + } + + ob_start(); + echo '
'; + + $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 = 'https://minotar.net/avatar/' . $username . '/80'; + + $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 = 'Online'; + } 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 = 'Zuletzt: ' . $date_str . ''; + } + + // STRUKTUR: Bild -> Prefix -> Name -> Spacer -> Status + echo '
'; + echo '' . $username . ''; + + echo '
'; + + // 1. PREFIX + if ( ! empty( $row->prefix ) ) { + echo '' . $prefix . ''; + } + + // 2. NAME + echo '' . $username . ''; + + // 3. SPACER (Nimmt den verbleibenden Platz ein) + echo '
'; + + // 4. STATUS + echo '
' . $status_html . '
'; + + echo '
'; + echo '
'; + } + echo '
'; + + echo '
Aktualisiert: ' . current_time('H:i:s') . '
'; + + 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() { + if ( isset( $_POST['mcph_manual_sync'] ) && check_admin_referer( 'mcph_manual_sync_action' ) ) { + mcph_sync_from_statusapi(); + delete_transient( 'mcph_api_cache_data' ); + echo '

Manueller Sync ausgeführt.

'; + } + ?> +
+

MC Player History Einstellungen

+
+ + + + + + +
StatusAPI URL + +

Bitte unbedingt mit http:// angeben.

+
+ +
+

Manueller Sync

+
+ + +
+
+ 500, + 'interval' => 2, + 'only_online' => 'false' + ), $atts ); + + $container_id = 'mc-player-wrapper-' . uniqid(); + + ob_start(); + + echo '
'; + echo mcph_generate_player_html( $atts['limit'], $atts['only_online'] === 'true', false ); + echo '
'; + + ?> + + + .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; /* Weniger Abstand dazwischen */ + padding: 20px 10px; + background: #fff; + border: 1px solid #eee; + border-radius: 8px; + transition: all 0.3s ease; + cursor: default; + position: relative; /* Fallback */ + } + + .mc-player-card:hover { + transform: translateY(-5px); + box-shadow: 0 10px 20px rgba(0,0,0,0.1); + border-color: #ddd; + } + + .mc-avatar { + border-radius: 4px; + border: 3px solid #ddd; + background: #fff; + width: 60px; + height: 60px; + transition: border-color 0.3s; + flex-shrink: 0; + } + + .mc-player-card:hover .mc-avatar { + border-color: #bbb; + } + + /* INFOS BEREICH */ + .mc-info-stack { + display: flex; + flex-direction: column; + align-items: center; + width: 100%; + flex-grow: 1; /* Füllt den Rest der Karte */ + position: relative; /* Referenz für absoluten Status */ + padding-bottom: 35px; /* Platz für Status unten */ + justify-content: flex-start; /* Oben ausrichten */ + } + + /* 1. PREFIX */ + .mc-prefix { + font-size: 0.9em; + display: block; + width: 100%; + text-align: center; + margin-bottom: 4px; + line-height: 1.2; + } + + /* 2. NAME */ + .mc-name { + font-weight: bold; + font-size: 1.1em; + color: #333; + word-break: break-word; + display: block; + width: 100%; + text-align: center; + } + + /* 3. SPACER */ + .mc-spacer { + flex-grow: 1; /* Nimmt ALLEN leeren Platz zwischen Name und Status ein */ + } + + /* 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: 4px 12px; + border-radius: 20px; + font-size: 0.8em; + font-weight: 600; + } + .mc-online { background-color: #e6fffa; color: #28a745; border: 1px solid #b2f5ea; } + .mc-offline { background-color: #fff5f5; color: #e53e3e; border: 1px solid #feb2b2; } + + .mc-update-time { + font-size: 0.8em; + color: #999; + text-align: center; + margin-top: 15px; + font-style: italic; + } + + @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: 10px; } + .mc-avatar { width: 50px; height: 50px; } + .mc-name { font-size: 1em; } + .mc-player-card { padding: 10px 5px; } + } + '; + + return ob_get_clean(); } \ No newline at end of file