Compare commits
	
		
			8 Commits
		
	
	
		
			d10fcdf15e
			...
			main
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 1bdcbc8fb9 | |||
| d8982ead59 | |||
| 2df1b5a560 | |||
| a17146fee9 | |||
| 38ee14524f | |||
| b6300bcb0b | |||
| e9d197229a | |||
| 4846e045bb | 
							
								
								
									
										10
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										10
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							| @@ -1902,6 +1902,7 @@ dependencies = [ | |||||||
|  "tokio", |  "tokio", | ||||||
|  "tower 0.5.1", |  "tower 0.5.1", | ||||||
|  "tower-http", |  "tower-http", | ||||||
|  |  "uuid", | ||||||
|  "vergen-gitcl", |  "vergen-gitcl", | ||||||
| ] | ] | ||||||
|  |  | ||||||
| @@ -2876,6 +2877,15 @@ version = "0.2.1" | |||||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
| checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" | checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" | ||||||
|  |  | ||||||
|  | [[package]] | ||||||
|  | name = "uuid" | ||||||
|  | version = "1.12.1" | ||||||
|  | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
|  | checksum = "b3758f5e68192bb96cc8f9b7e2c2cfdabb435499a28499a42f8f984092adad4b" | ||||||
|  | dependencies = [ | ||||||
|  |  "getrandom", | ||||||
|  | ] | ||||||
|  |  | ||||||
| [[package]] | [[package]] | ||||||
| name = "vcpkg" | name = "vcpkg" | ||||||
| version = "0.2.15" | version = "0.2.15" | ||||||
|   | |||||||
| @@ -43,6 +43,8 @@ rand = "0.8" | |||||||
|  |  | ||||||
| lazy_static = "1.4" | lazy_static = "1.4" | ||||||
|  |  | ||||||
|  | uuid = { version = "1.3", features = ["v4"] } | ||||||
|  |  | ||||||
| [build-dependencies] | [build-dependencies] | ||||||
| reqwest = { version = "0.12.9", features = ["blocking"] } | reqwest = { version = "0.12.9", features = ["blocking"] } | ||||||
| serde = { version = "1.0.215", features = ["derive"] } | serde = { version = "1.0.215", features = ["derive"] } | ||||||
|   | |||||||
							
								
								
									
										28
									
								
								src/comms.rs
									
									
									
									
									
								
							
							
						
						
									
										28
									
								
								src/comms.rs
									
									
									
									
									
								
							| @@ -33,22 +33,6 @@ pub struct PlayerData { | |||||||
|  |  | ||||||
| impl PlayerData { | impl PlayerData { | ||||||
|     pub fn new(pos: Vec3, yaw: f32, player_type: PlayerType, has_bomb: bool, has_awp: bool, |     pub fn new(pos: Vec3, yaw: f32, player_type: PlayerType, has_bomb: bool, has_awp: bool, | ||||||
|                is_scoped: bool, player_name: String, weapon_id: i16, health: u32) -> PlayerData { |  | ||||||
|         PlayerData { |  | ||||||
|             pos, |  | ||||||
|             yaw, |  | ||||||
|             player_type, |  | ||||||
|             has_bomb, |  | ||||||
|             has_awp, |  | ||||||
|             is_scoped, |  | ||||||
|             player_name, |  | ||||||
|             weapon_id, |  | ||||||
|             money: 0, |  | ||||||
|             health |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     pub fn new_with_money(pos: Vec3, yaw: f32, player_type: PlayerType, has_bomb: bool, has_awp: bool, |  | ||||||
|                     is_scoped: bool, player_name: String, weapon_id: i16, money: i32, health: u32) -> PlayerData { |                     is_scoped: bool, player_name: String, weapon_id: i16, money: i32, health: u32) -> PlayerData { | ||||||
|         PlayerData { |         PlayerData { | ||||||
|             pos, |             pos, | ||||||
| @@ -63,14 +47,6 @@ impl PlayerData { | |||||||
|             health |             health | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     pub fn get_pos(&self) -> &Vec3 { |  | ||||||
|         &self.pos |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     pub fn get_player_name(&self) -> &str { |  | ||||||
|         &self.player_name |  | ||||||
|     } |  | ||||||
| } | } | ||||||
|  |  | ||||||
| #[derive(Debug, Clone, Serialize, Deserialize)] | #[derive(Debug, Clone, Serialize, Deserialize)] | ||||||
| @@ -191,10 +167,6 @@ impl RadarData { | |||||||
|     pub fn get_entities(&self) -> &Vec<EntityData> { |     pub fn get_entities(&self) -> &Vec<EntityData> { | ||||||
|         &self.player_data |         &self.player_data | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     pub fn set_entities(&mut self, entities: Vec<EntityData>) { |  | ||||||
|         self.player_data = entities; |  | ||||||
|     } |  | ||||||
| } | } | ||||||
|  |  | ||||||
| unsafe impl Send for RadarData {} | unsafe impl Send for RadarData {} | ||||||
|   | |||||||
| @@ -196,7 +196,7 @@ pub async fn run(radar_data: ArcRwlockRadarData, connector: Connector, pcileech_ | |||||||
|  |  | ||||||
|                 entity_data.push( |                 entity_data.push( | ||||||
|                     EntityData::Player( |                     EntityData::Player( | ||||||
|                         PlayerData::new_with_money( |                         PlayerData::new( | ||||||
|                             local_data.pos, |                             local_data.pos, | ||||||
|                             local_data.yaw, |                             local_data.yaw, | ||||||
|                             PlayerType::Local, |                             PlayerType::Local, | ||||||
| @@ -210,7 +210,6 @@ pub async fn run(radar_data: ArcRwlockRadarData, connector: Connector, pcileech_ | |||||||
|                         ) |                         ) | ||||||
|                     ) |                     ) | ||||||
|                 ); |                 ); | ||||||
|                 log::debug!("Added local player with money: {}", local_data.money); |  | ||||||
|             } |             } | ||||||
|  |  | ||||||
|             // Other players |             // Other players | ||||||
| @@ -238,7 +237,7 @@ pub async fn run(radar_data: ArcRwlockRadarData, connector: Connector, pcileech_ | |||||||
|  |  | ||||||
|                         entity_data.push( |                         entity_data.push( | ||||||
|                             EntityData::Player( |                             EntityData::Player( | ||||||
|                                 PlayerData::new_with_money( |                                 PlayerData::new( | ||||||
|                                     player_data.pos, |                                     player_data.pos, | ||||||
|                                     player_data.yaw, |                                     player_data.yaw, | ||||||
|                                     player_type, |                                     player_type, | ||||||
|   | |||||||
							
								
								
									
										118
									
								
								src/websocket.rs
									
									
									
									
									
								
							
							
						
						
									
										118
									
								
								src/websocket.rs
									
									
									
									
									
								
							| @@ -1,4 +1,4 @@ | |||||||
| use std::{sync::Arc, path::PathBuf}; | use std::{sync::Arc, path::PathBuf, collections::HashMap}; | ||||||
| use axum::{ | use axum::{ | ||||||
|     extract::{ws::{WebSocketUpgrade, WebSocket, Message}, State}, |     extract::{ws::{WebSocketUpgrade, WebSocket, Message}, State}, | ||||||
|     response::Response, |     response::Response, | ||||||
| @@ -7,14 +7,21 @@ use axum::{ | |||||||
| }; | }; | ||||||
| use flate2::{write::GzEncoder, Compression}; | use flate2::{write::GzEncoder, Compression}; | ||||||
| use std::io::Write; | use std::io::Write; | ||||||
| use tokio::sync::RwLock; | use tokio::sync::{RwLock, Mutex}; | ||||||
| use tower_http::services::ServeDir; | use tower_http::services::ServeDir; | ||||||
|  |  | ||||||
| use crate::comms::{RadarData, ArcRwlockRadarData}; | use crate::comms::{RadarData}; | ||||||
|  |  | ||||||
|  | struct ClientState { | ||||||
|  |     last_entity_count: usize, | ||||||
|  |     ping_ms: u32, | ||||||
|  |     high_latency: bool, | ||||||
|  | } | ||||||
|  |  | ||||||
| #[derive(Clone)] | #[derive(Clone)] | ||||||
| struct AppState { | struct AppState { | ||||||
|     data_lock: Arc<RwLock<RadarData>> |     data_lock: Arc<RwLock<RadarData>>, | ||||||
|  |     clients: Arc<Mutex<HashMap<String, ClientState>>>, | ||||||
| } | } | ||||||
|  |  | ||||||
| async fn ws_handler(ws: WebSocketUpgrade, State(state): State<AppState>) -> Response { | async fn ws_handler(ws: WebSocketUpgrade, State(state): State<AppState>) -> Response { | ||||||
| @@ -23,48 +30,81 @@ async fn ws_handler(ws: WebSocketUpgrade, State(state): State<AppState>) -> Resp | |||||||
| } | } | ||||||
|  |  | ||||||
| async fn handle_socket(mut socket: WebSocket, state: AppState) { | async fn handle_socket(mut socket: WebSocket, state: AppState) { | ||||||
|  |     let client_id = uuid::Uuid::new_v4().to_string(); | ||||||
|  |  | ||||||
|  |     { | ||||||
|  |         let mut clients = state.clients.lock().await; | ||||||
|  |         clients.insert(client_id.clone(), ClientState { | ||||||
|  |             last_entity_count: 0, | ||||||
|  |             ping_ms: 0, | ||||||
|  |             high_latency: false, | ||||||
|  |         }); | ||||||
|  |     } | ||||||
|  |  | ||||||
|     let mut compression_buffer: Vec<u8> = Vec::with_capacity(65536); |     let mut compression_buffer: Vec<u8> = Vec::with_capacity(65536); | ||||||
|  |     let mut frame_counter = 0; | ||||||
|  |     let mut skip_frames = false; | ||||||
|  |  | ||||||
|     while let Some(msg) = socket.recv().await { |     while let Some(msg) = socket.recv().await { | ||||||
|         if let Ok(msg) = msg { |         if let Ok(msg) = msg { | ||||||
|             if let Ok(text) = msg.to_text() { |             if let Ok(text) = msg.to_text() { | ||||||
|                 if text == "requestInfo" { |                 if text == "requestInfo" { | ||||||
|  |                     frame_counter += 1; | ||||||
|  |                     if skip_frames && frame_counter % 2 != 0 { | ||||||
|  |                         continue; | ||||||
|  |                     } | ||||||
|  |  | ||||||
|                     let radar_data = state.data_lock.read().await; |                     let radar_data = state.data_lock.read().await; | ||||||
|  |                     let mut clients = state.clients.lock().await; | ||||||
|  |                     let client_state = clients.get_mut(&client_id).unwrap(); | ||||||
|  |  | ||||||
|                     if let Ok(json) = serde_json::to_string(&*radar_data) { |                     let entity_count = radar_data.get_entities().len(); | ||||||
|                         compression_buffer.clear(); |  | ||||||
|  |  | ||||||
|                         let compression_level = if json.len() > 10000 { |                     if entity_count > 5 && !skip_frames && client_state.ping_ms > 100 { | ||||||
|                             Compression::best() |                         skip_frames = true; | ||||||
|                         } else { |                         log::info!("Enabling frame skipping for high latency client"); | ||||||
|                             Compression::fast() |                     } | ||||||
|                         }; |  | ||||||
|  |  | ||||||
|                         let mut encoder = GzEncoder::new(Vec::new(), compression_level); |                     client_state.last_entity_count = entity_count; | ||||||
|                         if encoder.write_all(json.as_bytes()).is_ok() { |  | ||||||
|                             match encoder.finish() { |                     let Ok(json) = serde_json::to_string(&*radar_data) else { | ||||||
|                                 Ok(compressed) => { |                         continue; | ||||||
|                                     if compressed.len() < json.len() { |                     }; | ||||||
|                                         let mut message = vec![0x01]; |  | ||||||
|                                         message.extend_from_slice(&compressed); |                     compression_buffer.clear(); | ||||||
|                                         let _ = socket.send(Message::Binary(message)).await; |  | ||||||
|                                     } else { |                     let compression_level = if json.len() > 20000 || client_state.high_latency { | ||||||
|                                         let mut uncompressed = vec![0x00]; |                         Compression::best() | ||||||
|                                         uncompressed.extend_from_slice(json.as_bytes()); |                     } else if json.len() > 5000 { | ||||||
|                                         let _ = socket.send(Message::Binary(uncompressed)).await; |                         Compression::default() | ||||||
|                                     } |                     } else { | ||||||
|                                 }, |                         Compression::fast() | ||||||
|                                 Err(_) => { |                     }; | ||||||
|  |  | ||||||
|  |                     let mut encoder = GzEncoder::new(Vec::new(), compression_level); | ||||||
|  |                     if encoder.write_all(json.as_bytes()).is_ok() { | ||||||
|  |                         match encoder.finish() { | ||||||
|  |                             Ok(compressed) => { | ||||||
|  |                                 if compressed.len() < json.len() { | ||||||
|  |                                     let mut message = vec![0x01]; | ||||||
|  |                                     message.extend_from_slice(&compressed); | ||||||
|  |                                     let _ = socket.send(Message::Binary(message)).await; | ||||||
|  |                                 } else { | ||||||
|                                     let mut uncompressed = vec![0x00]; |                                     let mut uncompressed = vec![0x00]; | ||||||
|                                     uncompressed.extend_from_slice(json.as_bytes()); |                                     uncompressed.extend_from_slice(json.as_bytes()); | ||||||
|                                     let _ = socket.send(Message::Binary(uncompressed)).await; |                                     let _ = socket.send(Message::Binary(uncompressed)).await; | ||||||
|                                 } |                                 } | ||||||
|  |                             }, | ||||||
|  |                             Err(_) => { | ||||||
|  |                                 let mut uncompressed = vec![0x00]; | ||||||
|  |                                 uncompressed.extend_from_slice(json.as_bytes()); | ||||||
|  |                                 let _ = socket.send(Message::Binary(uncompressed)).await; | ||||||
|                             } |                             } | ||||||
|                         } else { |  | ||||||
|                             let mut uncompressed = vec![0x00]; |  | ||||||
|                             uncompressed.extend_from_slice(json.as_bytes()); |  | ||||||
|                             let _ = socket.send(Message::Binary(uncompressed)).await; |  | ||||||
|                         } |                         } | ||||||
|  |                     } else { | ||||||
|  |                         let mut uncompressed = vec![0x00]; | ||||||
|  |                         uncompressed.extend_from_slice(json.as_bytes()); | ||||||
|  |                         let _ = socket.send(Message::Binary(uncompressed)).await; | ||||||
|                     } |                     } | ||||||
|                 } else if text == "toggleMoneyReveal" { |                 } else if text == "toggleMoneyReveal" { | ||||||
|                     let new_value = { |                     let new_value = { | ||||||
| @@ -80,19 +120,35 @@ async fn handle_socket(mut socket: WebSocket, state: AppState) { | |||||||
|                     }); |                     }); | ||||||
|  |  | ||||||
|                     let _ = socket.send(Message::Text(response.to_string())).await; |                     let _ = socket.send(Message::Text(response.to_string())).await; | ||||||
|  |                 } else if text.starts_with("ping:") { | ||||||
|  |                     if let Some(ping_str) = text.strip_prefix("ping:") { | ||||||
|  |                         if let Ok(ping_ms) = ping_str.parse::<u32>() { | ||||||
|  |                             let mut clients = state.clients.lock().await; | ||||||
|  |                             if let Some(client) = clients.get_mut(&client_id) { | ||||||
|  |                                 client.ping_ms = ping_ms; | ||||||
|  |                                 client.high_latency = ping_ms > 100; | ||||||
|  |                             } | ||||||
|  |                         } | ||||||
|  |                     } | ||||||
|  |                     let _ = socket.send(Message::Text("pong".to_string())).await; | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|         } else { |         } else { | ||||||
|             break; |             break; | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |     let mut clients = state.clients.lock().await; | ||||||
|  |     clients.remove(&client_id); | ||||||
| } | } | ||||||
|  |  | ||||||
| pub async fn run(path: PathBuf, port: u16, data_lock: Arc<RwLock<RadarData>>) -> anyhow::Result<()> { | pub async fn run(path: PathBuf, port: u16, data_lock: Arc<RwLock<RadarData>>) -> anyhow::Result<()> { | ||||||
|     let app = Router::new() |     let app = Router::new() | ||||||
|         .nest_service("/", ServeDir::new(path)) |         .nest_service("/", ServeDir::new(path)) | ||||||
|         .route("/ws", get(ws_handler)) |         .route("/ws", get(ws_handler)) | ||||||
|         .with_state(AppState { data_lock }); |         .with_state(AppState { | ||||||
|  |             data_lock, | ||||||
|  |             clients: Arc::new(Mutex::new(HashMap::new())) | ||||||
|  |         }); | ||||||
|  |  | ||||||
|     let address = format!("0.0.0.0:{}", port); |     let address = format!("0.0.0.0:{}", port); | ||||||
|     log::info!("Starting WebSocket server on {}", address); |     log::info!("Starting WebSocket server on {}", address); | ||||||
|   | |||||||
| @@ -4,7 +4,7 @@ | |||||||
| <head> | <head> | ||||||
|     <meta charset="UTF-8"> |     <meta charset="UTF-8"> | ||||||
|     <meta name="viewport" content="width=device-width, initial-scale=1.0"> |     <meta name="viewport" content="width=device-width, initial-scale=1.0"> | ||||||
|     <title>radarflow</title> |     <title>radar</title> | ||||||
|     <link href="styles.css" rel="stylesheet" type="text/css" /> |     <link href="styles.css" rel="stylesheet" type="text/css" /> | ||||||
|     <script src="https://cdnjs.cloudflare.com/ajax/libs/pako/2.1.0/pako.min.js"></script> |     <script src="https://cdnjs.cloudflare.com/ajax/libs/pako/2.1.0/pako.min.js"></script> | ||||||
| </head> | </head> | ||||||
| @@ -43,6 +43,10 @@ | |||||||
|                     <input type="checkbox" onclick="toggleHealth()" id="healthCheck" name="health" checked /> |                     <input type="checkbox" onclick="toggleHealth()" id="healthCheck" name="health" checked /> | ||||||
|                     <label for="healthCheck">Display Health</label> |                     <label for="healthCheck">Display Health</label> | ||||||
|                 </div> |                 </div> | ||||||
|  |                 <div> | ||||||
|  |                     <input type="checkbox" onclick="toggleOffscreenIndicators()" id="offscreenCheck" name="offscreen" checked /> | ||||||
|  |                     <label for="offscreenCheck">Off-screen Indicators</label> | ||||||
|  |                 </div> | ||||||
|                 <div> |                 <div> | ||||||
|                     <input type="checkbox" onclick="toggleRotate()" id="rotateCheck" name="rotate" checked /> |                     <input type="checkbox" onclick="toggleRotate()" id="rotateCheck" name="rotate" checked /> | ||||||
|                     <label for="rotateCheck">Rotate Map</label> |                     <label for="rotateCheck">Rotate Map</label> | ||||||
| @@ -50,6 +54,12 @@ | |||||||
|                 <div> |                 <div> | ||||||
|                     <input type="checkbox" onclick="toggleCentered()" id="centerCheck" name="center" checked /> |                     <input type="checkbox" onclick="toggleCentered()" id="centerCheck" name="center" checked /> | ||||||
|                     <label for="centerCheck">Player Centered</label> |                     <label for="centerCheck">Player Centered</label> | ||||||
|  |                     <div id="zoomLevelContainer" style="margin-top: 5px; margin-left: 20px; display: none;"> | ||||||
|  |                         <label for="zoomLevelSlider">Zoom Level: </label> | ||||||
|  |                         <span id="zoomLevelValue">1.0</span><br> | ||||||
|  |                         <input type="range" id="zoomLevelSlider" min="1.0" max="5.0" step="0.1" value="1.0" | ||||||
|  |                             style="width: 100%; margin: 5px 0;" oninput="updateZoomLevel(this.value)"> | ||||||
|  |                     </div> | ||||||
|                 </div> |                 </div> | ||||||
|                 <div class="player-focus"> |                 <div class="player-focus"> | ||||||
|                     <label for="playerSelect">Focus Player:</label> |                     <label for="playerSelect">Focus Player:</label> | ||||||
| @@ -57,6 +67,27 @@ | |||||||
|                         <option value="local">YOU</option> |                         <option value="local">YOU</option> | ||||||
|                     </select> |                     </select> | ||||||
|                 </div> |                 </div> | ||||||
|  |                 <div id="sizeControlsContainer" | ||||||
|  |                     style="margin-top: 10px; padding: 5px; border-top: 1px solid rgba(255, 255, 255, 0.2);"> | ||||||
|  |                     <div class="size-control" style="margin-bottom: 8px;"> | ||||||
|  |                         <label for="textSizeSlider">Text Size: </label> | ||||||
|  |                         <span id="textSizeValue">1.0</span><br> | ||||||
|  |                         <input type="range" id="textSizeSlider" min="0.1" max="2.0" step="0.1" value="1.0" | ||||||
|  |                             style="width: 100%; margin: 5px 0;" oninput="updateTextSize(this.value)"> | ||||||
|  |                     </div> | ||||||
|  |                     <div class="size-control"> | ||||||
|  |                         <label for="entitySizeSlider">Player Size: </label> | ||||||
|  |                         <span id="entitySizeValue">1.0</span><br> | ||||||
|  |                         <input type="range" id="entitySizeSlider" min="0.5" max="2.0" step="0.1" value="1.0" | ||||||
|  |                             style="width: 100%; margin: 5px 0;" oninput="updateEntitySize(this.value)"> | ||||||
|  |                     </div> | ||||||
|  |                     <div class="size-control" style="margin-top: 8px;"> | ||||||
|  |                         <label for="healthBarSizeSlider">Health Bar Size: </label> | ||||||
|  |                         <span id="healthBarSizeValue">1.0</span><br> | ||||||
|  |                         <input type="range" id="healthBarSizeSlider" min="0.5" max="2.5" step="0.1" value="1.0" | ||||||
|  |                             style="width: 100%; margin: 5px 0;" oninput="updateHealthBarSize(this.value)"> | ||||||
|  |                     </div> | ||||||
|  |                 </div> | ||||||
|                 <button id="showDangerousBtn" onclick="toggleDangerousOptions()">Show Dangerous Options</button> |                 <button id="showDangerousBtn" onclick="toggleDangerousOptions()">Show Dangerous Options</button> | ||||||
|                 <div class="dangerous-options" id="dangerousOptions"> |                 <div class="dangerous-options" id="dangerousOptions"> | ||||||
|                     <div> |                     <div> | ||||||
| @@ -71,6 +102,6 @@ | |||||||
|     </div> |     </div> | ||||||
|     <script src="script.js"></script> |     <script src="script.js"></script> | ||||||
|     <script src="webstuff.js"></script> |     <script src="webstuff.js"></script> | ||||||
|  |  | ||||||
| </body> | </body> | ||||||
|  |  | ||||||
| </html> | </html> | ||||||
							
								
								
									
										1168
									
								
								webradar/script.js
									
									
									
									
									
								
							
							
						
						
									
										1168
									
								
								webradar/script.js
									
									
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @@ -23,6 +23,8 @@ body { | |||||||
| canvas { | canvas { | ||||||
|     width: 100%; |     width: 100%; | ||||||
|     height: 100%; |     height: 100%; | ||||||
|  |     image-rendering: -webkit-optimize-contrast; | ||||||
|  |     image-rendering: crisp-edges; | ||||||
| } | } | ||||||
|  |  | ||||||
| #settingsHolder { | #settingsHolder { | ||||||
| @@ -47,6 +49,25 @@ canvas { | |||||||
|     background-color: rgba(25, 25, 25, 0.7); |     background-color: rgba(25, 25, 25, 0.7); | ||||||
|     border-radius: 5px; |     border-radius: 5px; | ||||||
|     transition: opacity 0.3s ease; |     transition: opacity 0.3s ease; | ||||||
|  |     font-size: 14px; | ||||||
|  |     max-height: 90vh; | ||||||
|  |     overflow-y: auto; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @media (max-width: 768px) { | ||||||
|  |     #settingsHolder .settings { | ||||||
|  |         font-size: 16px; | ||||||
|  |         padding: 12px; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     #settingsHolder .settings input[type="checkbox"] { | ||||||
|  |         transform: scale(1.2); | ||||||
|  |         margin-right: 8px; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     #settingsHolder .settings>div { | ||||||
|  |         padding: 6px 0; | ||||||
|  |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| #settingsHolder:hover .settings { | #settingsHolder:hover .settings { | ||||||
| @@ -92,31 +113,67 @@ canvas { | |||||||
|     background-color: #8a0000; |     background-color: #8a0000; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | .size-control { | ||||||
|  |     margin-bottom: 10px; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .size-control label { | ||||||
|  |     display: inline-block; | ||||||
|  |     margin-bottom: 3px; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | input[type="range"] { | ||||||
|  |     width: 100%; | ||||||
|  |     margin: 5px 0; | ||||||
|  |     -webkit-appearance: none; | ||||||
|  |     height: 6px; | ||||||
|  |     background: #555; | ||||||
|  |     border-radius: 3px; | ||||||
|  |     outline: none; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | input[type="range"]::-webkit-slider-thumb { | ||||||
|  |     -webkit-appearance: none; | ||||||
|  |     appearance: none; | ||||||
|  |     width: 16px; | ||||||
|  |     height: 16px; | ||||||
|  |     border-radius: 50%; | ||||||
|  |     background: #68a3e5; | ||||||
|  |     cursor: pointer; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | input[type="range"]::-moz-range-thumb { | ||||||
|  |     width: 16px; | ||||||
|  |     height: 16px; | ||||||
|  |     border-radius: 50%; | ||||||
|  |     background: #68a3e5; | ||||||
|  |     cursor: pointer; | ||||||
|  |     border: none; | ||||||
|  | } | ||||||
|  |  | ||||||
| @media (max-width: 600px), | @media (max-width: 600px), | ||||||
| (max-height: 600px) { | (max-height: 600px) { | ||||||
|     #settingsHolder { |     #settingsHolder { | ||||||
|         display: none; |         display: none; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     #showMenuBtn { | ||||||
|  |         display: block !important; | ||||||
|  |         font-size: 16px !important; | ||||||
|  |         padding: 8px 12px !important; | ||||||
|  |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| @media (max-width: 400px), | @media (max-width: 400px), | ||||||
| (max-height: 400px) { | (max-height: 400px) { | ||||||
|     #canvasContainer::before { |     #canvasContainer::before { | ||||||
|         content: 'settings'; |         content: ''; | ||||||
|         position: fixed; |         display: none; | ||||||
|         top: 10px; |     } | ||||||
|         left: 10px; |  | ||||||
|         background-color: rgba(25, 25, 25, 0.7); |     #showMenuBtn { | ||||||
|         color: white; |         padding: 6px 10px !important; | ||||||
|         width: 30px; |         font-size: 14px !important; | ||||||
|         height: 30px; |  | ||||||
|         border-radius: 15px; |  | ||||||
|         display: flex; |  | ||||||
|         align-items: center; |  | ||||||
|         justify-content: center; |  | ||||||
|         font-size: 16px; |  | ||||||
|         cursor: pointer; |  | ||||||
|         z-index: 101; |  | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -129,6 +186,7 @@ canvas { | |||||||
|     margin-left: 5px; |     margin-left: 5px; | ||||||
|     cursor: pointer; |     cursor: pointer; | ||||||
|     min-width: 150px; |     min-width: 150px; | ||||||
|  |     font-size: inherit; | ||||||
| } | } | ||||||
|  |  | ||||||
| #playerSelect:hover { | #playerSelect:hover { | ||||||
| @@ -147,19 +205,23 @@ canvas { | |||||||
|  |  | ||||||
| .player-focus { | .player-focus { | ||||||
|     margin-top: 10px; |     margin-top: 10px; | ||||||
|  |     display: flex; | ||||||
|  |     align-items: center; | ||||||
|  |     flex-wrap: wrap; | ||||||
| } | } | ||||||
|  |  | ||||||
| #hideMenuBtn { | #hideMenuBtn { | ||||||
|     background-color: #333; |     background-color: #333; | ||||||
|     color: white; |     color: white; | ||||||
|     border: none; |     border: none; | ||||||
|     padding: 5px 10px; |     padding: 8px 10px; | ||||||
|     margin-top: 10px; |     margin-top: 10px; | ||||||
|     border-radius: 3px; |     border-radius: 3px; | ||||||
|     cursor: pointer; |     cursor: pointer; | ||||||
|     font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; |     font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; | ||||||
|     transition: background-color 0.3s; |     transition: background-color 0.3s; | ||||||
|     width: 100%; |     width: 100%; | ||||||
|  |     font-size: inherit; | ||||||
| } | } | ||||||
|  |  | ||||||
| #hideMenuBtn:hover { | #hideMenuBtn:hover { | ||||||
| @@ -170,19 +232,64 @@ canvas { | |||||||
|     position: fixed; |     position: fixed; | ||||||
|     top: 10px; |     top: 10px; | ||||||
|     left: 10px; |     left: 10px; | ||||||
|     background-color: rgba(25, 25, 25, 0.7); |     background-color: rgba(25, 25, 25, 0.9); | ||||||
|     color: white; |     color: white; | ||||||
|     border: none; |     border: none; | ||||||
|     padding: 5px 10px; |     padding: 8px 12px; | ||||||
|     border-radius: 15px; |     border-radius: 15px; | ||||||
|     font-size: 14px; |     font-size: 16px; | ||||||
|     cursor: pointer; |     cursor: pointer; | ||||||
|     z-index: 101; |     z-index: 101; | ||||||
|     font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; |     font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; | ||||||
|     transition: opacity 0.3s; |     transition: opacity 0.3s; | ||||||
|     opacity: 0.6; |     opacity: 0.8; | ||||||
|  |     box-shadow: 0 2px 5px rgba(0, 0, 0, 0.3); | ||||||
| } | } | ||||||
|  |  | ||||||
| #showMenuBtn:hover { | #showMenuBtn:hover { | ||||||
|     opacity: 1; |     opacity: 1; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | input[type="checkbox"] { | ||||||
|  |     margin-right: 8px; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | label { | ||||||
|  |     cursor: pointer; | ||||||
|  |     user-select: none; | ||||||
|  |     margin-bottom: 2px; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .settings>div { | ||||||
|  |     margin-bottom: 5px; | ||||||
|  |     padding: 3px 0; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .touch-device input[type="checkbox"] { | ||||||
|  |     transform: scale(1.3); | ||||||
|  |     margin: 2px 10px 2px 2px; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .touch-device input[type="range"]::-webkit-slider-thumb { | ||||||
|  |     width: 20px; | ||||||
|  |     height: 20px; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .touch-device input[type="range"]::-moz-range-thumb { | ||||||
|  |     width: 20px; | ||||||
|  |     height: 20px; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .touch-device .settings>div { | ||||||
|  |     padding: 6px 0; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @media (prefers-color-scheme: dark) { | ||||||
|  |     #settingsHolder .settings { | ||||||
|  |         background-color: rgba(15, 15, 15, 0.85); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     #showMenuBtn { | ||||||
|  |         background-color: rgba(15, 15, 15, 0.9); | ||||||
|  |     } | ||||||
|  | } | ||||||
		Reference in New Issue
	
	Block a user