From 839b9f6c0866015369cd76e2407702464c0ee870 Mon Sep 17 00:00:00 2001 From: markmental Date: Tue, 10 Feb 2026 19:11:28 -0500 Subject: [PATCH 01/10] National Team Expansion (50+ Countries + PRC China) --- DEVLOG.md | 8 +++ src/data.rs | 159 +++++++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 166 insertions(+), 1 deletion(-) diff --git a/DEVLOG.md b/DEVLOG.md index 7f5a802..66ab8c6 100644 --- a/DEVLOG.md +++ b/DEVLOG.md @@ -78,3 +78,11 @@ - Added modal scrolling controls (`j/k` or up/down) and close controls (`Esc` or `q`). - Simplified detail view to focus on scoreboard, logs, and instance info. - Added detail-panel hint bar to direct users to the new dedicated modals. + +## 2026-02-10 - National team expansion + +### Scope completed +- Expanded team database to include 50+ national teams in addition to existing clubs. +- Added national-team flag mappings, including `PRC China`. +- Added tactic/formation profile mappings for the new national teams. +- Verified with `list` and deterministic `quick` simulation using national teams. diff --git a/src/data.rs b/src/data.rs index 1dcd7b8..258d6d4 100644 --- a/src/data.rs +++ b/src/data.rs @@ -59,7 +59,7 @@ pub const TACTICS: [Tactic; 4] = [ }, ]; -pub const TEAMS: [&str; 29] = [ +pub const TEAMS: [&str; 85] = [ "Kashima Antlers", "Urawa Red Diamonds", "Gamba Osaka", @@ -89,6 +89,62 @@ pub const TEAMS: [&str; 29] = [ "Benfica", "Porto", "Celtic", + "England", + "France", + "Spain", + "Germany", + "Italy", + "Portugal", + "Netherlands", + "Belgium", + "Croatia", + "Denmark", + "Switzerland", + "Austria", + "Sweden", + "Norway", + "Poland", + "Serbia", + "Turkey", + "Ukraine", + "Czech Republic", + "Scotland", + "Argentina", + "Brazil", + "Uruguay", + "Colombia", + "Chile", + "Peru", + "Ecuador", + "Paraguay", + "Bolivia", + "Venezuela", + "United States", + "Mexico", + "Canada", + "Costa Rica", + "Panama", + "Jamaica", + "Honduras", + "Japan", + "South Korea", + "Australia", + "Iran", + "Saudi Arabia", + "Qatar", + "Iraq", + "United Arab Emirates", + "PRC China", + "Morocco", + "Senegal", + "Nigeria", + "Egypt", + "Algeria", + "Tunisia", + "Ghana", + "Cameroon", + "Ivory Coast", + "South Africa", ]; pub fn team_flag(team: &str) -> &'static str { @@ -113,6 +169,61 @@ pub fn team_flag(team: &str) -> &'static str { "Juventus" | "Inter" | "AC Milan" => "๐Ÿ‡ฎ๐Ÿ‡น", "Ajax" => "๐Ÿ‡ณ๐Ÿ‡ฑ", "Benfica" | "Porto" => "๐Ÿ‡ต๐Ÿ‡น", + "England" | "Scotland" => "๐Ÿ‡ฌ๐Ÿ‡ง", + "France" => "๐Ÿ‡ซ๐Ÿ‡ท", + "Spain" => "๐Ÿ‡ช๐Ÿ‡ธ", + "Germany" => "๐Ÿ‡ฉ๐Ÿ‡ช", + "Italy" => "๐Ÿ‡ฎ๐Ÿ‡น", + "Portugal" => "๐Ÿ‡ต๐Ÿ‡น", + "Netherlands" => "๐Ÿ‡ณ๐Ÿ‡ฑ", + "Belgium" => "๐Ÿ‡ง๐Ÿ‡ช", + "Croatia" => "๐Ÿ‡ญ๐Ÿ‡ท", + "Denmark" => "๐Ÿ‡ฉ๐Ÿ‡ฐ", + "Switzerland" => "๐Ÿ‡จ๐Ÿ‡ญ", + "Austria" => "๐Ÿ‡ฆ๐Ÿ‡น", + "Sweden" => "๐Ÿ‡ธ๐Ÿ‡ช", + "Norway" => "๐Ÿ‡ณ๐Ÿ‡ด", + "Poland" => "๐Ÿ‡ต๐Ÿ‡ฑ", + "Serbia" => "๐Ÿ‡ท๐Ÿ‡ธ", + "Turkey" => "๐Ÿ‡น๐Ÿ‡ท", + "Ukraine" => "๐Ÿ‡บ๐Ÿ‡ฆ", + "Czech Republic" => "๐Ÿ‡จ๐Ÿ‡ฟ", + "Argentina" => "๐Ÿ‡ฆ๐Ÿ‡ท", + "Brazil" => "๐Ÿ‡ง๐Ÿ‡ท", + "Uruguay" => "๐Ÿ‡บ๐Ÿ‡พ", + "Colombia" => "๐Ÿ‡จ๐Ÿ‡ด", + "Chile" => "๐Ÿ‡จ๐Ÿ‡ฑ", + "Peru" => "๐Ÿ‡ต๐Ÿ‡ช", + "Ecuador" => "๐Ÿ‡ช๐Ÿ‡จ", + "Paraguay" => "๐Ÿ‡ต๐Ÿ‡พ", + "Bolivia" => "๐Ÿ‡ง๐Ÿ‡ด", + "Venezuela" => "๐Ÿ‡ป๐Ÿ‡ช", + "United States" => "๐Ÿ‡บ๐Ÿ‡ธ", + "Mexico" => "๐Ÿ‡ฒ๐Ÿ‡ฝ", + "Canada" => "๐Ÿ‡จ๐Ÿ‡ฆ", + "Costa Rica" => "๐Ÿ‡จ๐Ÿ‡ท", + "Panama" => "๐Ÿ‡ต๐Ÿ‡ฆ", + "Jamaica" => "๐Ÿ‡ฏ๐Ÿ‡ฒ", + "Honduras" => "๐Ÿ‡ญ๐Ÿ‡ณ", + "Japan" => "๐Ÿ‡ฏ๐Ÿ‡ต", + "South Korea" => "๐Ÿ‡ฐ๐Ÿ‡ท", + "Australia" => "๐Ÿ‡ฆ๐Ÿ‡บ", + "Iran" => "๐Ÿ‡ฎ๐Ÿ‡ท", + "Saudi Arabia" => "๐Ÿ‡ธ๐Ÿ‡ฆ", + "Qatar" => "๐Ÿ‡ถ๐Ÿ‡ฆ", + "Iraq" => "๐Ÿ‡ฎ๐Ÿ‡ถ", + "United Arab Emirates" => "๐Ÿ‡ฆ๐Ÿ‡ช", + "PRC China" => "๐Ÿ‡จ๐Ÿ‡ณ", + "Morocco" => "๐Ÿ‡ฒ๐Ÿ‡ฆ", + "Senegal" => "๐Ÿ‡ธ๐Ÿ‡ณ", + "Nigeria" => "๐Ÿ‡ณ๐Ÿ‡ฌ", + "Egypt" => "๐Ÿ‡ช๐Ÿ‡ฌ", + "Algeria" => "๐Ÿ‡ฉ๐Ÿ‡ฟ", + "Tunisia" => "๐Ÿ‡น๐Ÿ‡ณ", + "Ghana" => "๐Ÿ‡ฌ๐Ÿ‡ญ", + "Cameroon" => "๐Ÿ‡จ๐Ÿ‡ฒ", + "Ivory Coast" => "๐Ÿ‡จ๐Ÿ‡ฎ", + "South Africa" => "๐Ÿ‡ฟ๐Ÿ‡ฆ", _ => "๐Ÿณ๏ธ", } } @@ -247,6 +358,52 @@ pub fn profile_for(team: &str) -> TeamProfile { formation: "4-3-3", tactic: "counter", }, + "Spain" | "Netherlands" | "Portugal" | "Japan" | "PRC China" => TeamProfile { + formation: "4-3-3", + tactic: "possession", + }, + "England" | "Germany" | "France" | "Brazil" | "Argentina" | "Belgium" | "United States" + | "South Korea" | "Morocco" | "Nigeria" => TeamProfile { + formation: "4-2-3-1", + tactic: "high_press", + }, + "Italy" | "Croatia" | "Denmark" | "Switzerland" | "Uruguay" | "Mexico" | "Canada" + | "Iran" | "Saudi Arabia" | "Senegal" | "Algeria" | "Tunisia" => TeamProfile { + formation: "4-4-2", + tactic: "counter", + }, + "Austria" + | "Sweden" + | "Norway" + | "Poland" + | "Serbia" + | "Turkey" + | "Ukraine" + | "Czech Republic" + | "Scotland" + | "Colombia" + | "Chile" + | "Peru" + | "Ecuador" + | "Paraguay" + | "Bolivia" + | "Venezuela" + | "Costa Rica" + | "Panama" + | "Jamaica" + | "Honduras" + | "Australia" + | "Qatar" + | "Iraq" + | "United Arab Emirates" + | "Egypt" + | "Ghana" + | "Cameroon" + | "Ivory Coast" + | "South Africa" => TeamProfile { + formation: "4-2-3-1", + tactic: "counter", + }, _ => TeamProfile { formation: "4-4-2", tactic: "counter", From 2e191dea036c1f656c61784fbb948eafe136e0f8 Mon Sep 17 00:00:00 2001 From: markmental Date: Tue, 10 Feb 2026 20:40:10 -0500 Subject: [PATCH 02/10] Add new README --- README.md | 234 ++++++++++++++++++++++++++++++++---------------------- 1 file changed, 138 insertions(+), 96 deletions(-) diff --git a/README.md b/README.md index 7eb6e09..6dc9d5e 100644 --- a/README.md +++ b/README.md @@ -1,126 +1,168 @@ -# โšฝ SoccerCloud โ€” Cloudified Soccer Simulation Environment +# SoccerCloud CLI (Rust) -**Live Demo:** [https://mentalnet.xyz/soccercloud/](https://mentalnet.xyz/soccercloud/) -**Author:** [markmental / MentalNet.xyz](https://mentalnet.xyz) -**License:** MIT +Terminal-native rebuild of MentalNet SoccerCloud with a cloud-dashboard feel. ---- +## Overview -## ๐Ÿง  Overview +This project is a Rust TUI/CLI soccer simulator with: -**SoccerCloud** is a browser-based soccer simulator that reimagines match simulations through the aesthetic and structure of a **cloud orchestration dashboard** โ€” think *OpenStack meets Football Manager*. +- Single Match, 4-Team League, and 4-Team Knockout modes +- Live match logs, scoreboard, and instance lifecycle controls +- Seeded deterministic runs (`--seed`) for reproducible results +- CSV export for single, league, and knockout outputs +- Expanded team pool (clubs + 50+ national teams, including `PRC China`) -Each match, league, or knockout bracket behaves like a **โ€œvirtual instanceโ€**, complete with lifecycle controls: -- **Create / Start / View / Delete / Clone / Export** -- Real-time logs, xG data, formations, and tactical analytics -- **Dynamic UI** styled like a cloud console with per-match telemetry +## Requirements -SoccerCloud is written entirely in **HTML, CSS, and vanilla JavaScript**, with no external backend dependencies. It runs fully client-side and is suitable for static hosting. +- Rust toolchain (stable) +- Cargo +- A terminal that supports UTF-8 and colors ---- - -## ๐ŸŒ Live Deployment - -> [https://mentalnet.xyz/soccercloud/](https://mentalnet.xyz/soccercloud/) - -Hosted on **MentalNet.xyz**, the current deployment showcases all features including: -- Match instance dashboard -- 4-team League and Knockout modes -- CSV export of results and tables -- Auto-team picker with J-League and European clubs -- Cloud-inspired modal configuration UI - ---- - -## ๐Ÿ—๏ธ Features - -| Category | Description | -|-----------|-------------| -| **Simulation Types** | Single Match, 4-Team League, 4-Team Knockout | -| **Team Database** | Includes J-League + top European clubs with realistic formations/tactics | -| **UI Design** | Styled like a lightweight OpenStack/Proxmox console | -| **Export Options** | Download match or league data as CSV | -| **Logging & Recaps** | Live xG updates, goal commentary, and tactical analysis | -| **Client-Only** | Runs directly in browser โ€” no backend needed | - ---- - -## ๐Ÿ—‚๏ธ Project Structure - -``` - -soccercloud/ -โ”œโ”€โ”€ index.html # Main web dashboard and simulation logic -โ”œโ”€โ”€ data.js # Team definitions, flags, formations, and tactics -โ””โ”€โ”€ assets/ # (Optional) icons, logos, or future expansion files - -```` - ---- - -## ๐Ÿš€ Getting Started (Local) - -You can run SoccerCloud locally with **no build process** โ€” just open it in a browser. - -### Option 1: Double-click -```bash -open index.html -```` - -### Option 2: Local dev server +Install Rust (if needed): ```bash -python3 -m http.server 8080 +curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh ``` -Then visit: -๐Ÿ‘‰ `http://localhost:8080` +## Setup ---- +Clone and build: -## ๐Ÿงฉ Technical Notes +```bash +git clone +cd soccercloud-cli +cargo check +``` -* Written in **vanilla JavaScript** for speed and transparency. -* Each simulation instance is handled via a `SimulationInstance` class. -* Data persistence is session-based; future versions may support saving instance states. -* CSS uses retro **UnifrakturCook + Press Start 2P** fonts for a distinct MentalNet look. +Run in debug mode: ---- +```bash +cargo run +``` -## ๐Ÿ–ฅ๏ธ Upcoming: CLI Edition +Build optimized binary: -> โšก **tuxsoccercloud** *(Coming soon!)* +```bash +cargo build --release +./target/release/soccercloud +``` -A simplified **terminal version** of the simulator is in development โ€” ideal for users who prefer a command-line workflow or want to integrate match simulations into scripts or data pipelines. +## CLI Usage -Planned features: +Default (interactive TUI): -* Text-only match recaps and league tables -* Randomized or argument-based team selection -* Fully offline operation +```bash +soccercloud +``` ---- +or with Cargo: -## ๐Ÿค Contributing +```bash +cargo run -- +``` -Pull requests are welcome (when I get signups up)! -To contribute: +Use a global seed for reproducibility: -1. Fork this repository -2. Make your edits in a feature branch -3. Submit a pull request with a clear description +```bash +cargo run -- --seed 42 +``` ---- +### Quick match (headless) -## ๐Ÿ’ก Credits +```bash +cargo run -- quick --home "Arsenal" --away "Real Madrid" --seed 42 +``` -* Built and designed by **markmental** -* Hosted under **MentalNet.xyz** -* Inspired by *OpenStack Horizon* dashboards and *Football Manager*-style simulations -* Font assets via [Google Fonts](https://fonts.google.com) -* Icons via [Font Awesome](https://fontawesome.com) +CPU auto-fill for missing team(s): ---- +```bash +cargo run -- quick --home "England" --seed 42 +cargo run -- quick --seed 42 +``` -### โšฝ *"Deploy your next match like a VM โ€” welcome to SoccerCloud."* +### List teams +```bash +cargo run -- list +``` + +### Export CSV + +Single: + +```bash +cargo run -- export --mode single --team "Arsenal" --team "PRC China" --out match.csv --seed 42 +``` + +League: + +```bash +cargo run -- export --mode league4 --team "England" --team "Brazil" --team "Japan" --team "Germany" --out league.csv --seed 42 +``` + +Knockout: + +```bash +cargo run -- export --mode knockout4 --team "France" --team "Argentina" --team "Morocco" --team "PRC China" --out knockout.csv --seed 42 +``` + +## TUI Controls + +Global: + +- `n` create Single instance +- `l` create League4 instance +- `o` create Knockout4 instance +- `s` start selected instance +- `c` clone selected instance +- `d` delete selected instance +- `e` export selected instance CSV +- `v` or `Enter` toggle dashboard/detail +- `j/k` or `Up/Down` navigate instances +- `1/2/4/0` speed control (1x/2x/4x/instant) +- `q` quit + +Create modal: + +- `m` set selected slot to manual team +- `p` set selected slot to CPU auto-fill +- `[` / `]` or `Left/Right` cycle manual team +- `Enter` create +- `Esc` cancel + +Readable fullscreen data panels: + +- `t` stats modal +- `g` standings/bracket modal +- `h` history modal +- `j/k` or `Up/Down` scroll inside modal +- `Esc` or `q` close modal + +## Project Structure + +```text +src/ +โ”œโ”€โ”€ main.rs # CLI entrypoint and commands +โ”œโ”€โ”€ app.rs # App state and event loop +โ”œโ”€โ”€ data.rs # Teams, flags, tactics, profiles +โ”œโ”€โ”€ sim.rs # Match/league/knockout simulation engine +โ”œโ”€โ”€ instance.rs # Simulation instance lifecycle and state +โ”œโ”€โ”€ export.rs # CSV export +โ”œโ”€โ”€ utils.rs # RNG + helper utilities +โ””โ”€โ”€ ui/ + โ”œโ”€โ”€ mod.rs + โ”œโ”€โ”€ dashboard.rs + โ”œโ”€โ”€ detail.rs + โ”œโ”€โ”€ modal.rs + โ””โ”€โ”€ widgets.rs +``` + +## Notes + +- Dependency policy is intentionally strict (minimal crates). +- Team data is embedded in the binary (no external runtime data files). +- Use `--seed` for deterministic comparisons and debugging. + +## License + +MIT From cc75f370fb59a0fb70a87b3e1aa15cd0153a027a Mon Sep 17 00:00:00 2001 From: markmental Date: Tue, 10 Feb 2026 20:56:18 -0500 Subject: [PATCH 03/10] Dashboard/Detail UI Cleanup (Remove Status + Panels Footers) --- src/ui/detail.rs | 17 +---------------- src/ui/mod.rs | 15 +-------------- 2 files changed, 2 insertions(+), 30 deletions(-) diff --git a/src/ui/detail.rs b/src/ui/detail.rs index e696c1c..87d7a57 100644 --- a/src/ui/detail.rs +++ b/src/ui/detail.rs @@ -3,7 +3,6 @@ use ratatui::prelude::*; use ratatui::widgets::{Block, Borders, List, ListItem, Paragraph}; use crate::app::App; -use crate::instance::SimStatus; pub fn render(f: &mut Frame<'_>, area: Rect, app: &App) { let Some(inst) = app.selected_instance() else { @@ -15,11 +14,7 @@ pub fn render(f: &mut Frame<'_>, area: Rect, app: &App) { let chunks = Layout::default() .direction(Direction::Vertical) - .constraints([ - Constraint::Length(3), - Constraint::Min(8), - Constraint::Length(2), - ]) + .constraints([Constraint::Length(3), Constraint::Min(8)]) .split(area); let score = Paragraph::new(inst.scoreboard.clone()) @@ -49,12 +44,6 @@ pub fn render(f: &mut Frame<'_>, area: Rect, app: &App) { f.render_widget(log_widget, middle[0]); let mut right_lines: Vec = Vec::new(); - let status = match &inst.status { - SimStatus::Pending => "pending", - SimStatus::Running { .. } => "running", - SimStatus::Completed => "completed", - }; - right_lines.push(ListItem::new(format!("Status: {}", status))); right_lines.push(ListItem::new(format!("Seed: {}", inst.seed))); right_lines.push(ListItem::new(format!("Mode: {}", inst.sim_type.as_str()))); right_lines.push(ListItem::new("")); @@ -68,8 +57,4 @@ pub fn render(f: &mut Frame<'_>, area: Rect, app: &App) { .borders(Borders::ALL), ); f.render_widget(side, middle[1]); - - let help = Paragraph::new("Open readable panels: t=Stats, g=Standings/Bracket, h=History") - .block(Block::default().borders(Borders::ALL).title("Panels")); - f.render_widget(help, chunks[2]); } diff --git a/src/ui/mod.rs b/src/ui/mod.rs index 598591e..708ea8c 100644 --- a/src/ui/mod.rs +++ b/src/ui/mod.rs @@ -12,11 +12,7 @@ use crate::app::App; pub fn draw(f: &mut Frame<'_>, app: &App) { let areas = Layout::default() .direction(Direction::Vertical) - .constraints([ - Constraint::Length(3), - Constraint::Min(10), - Constraint::Length(2), - ]) + .constraints([Constraint::Length(3), Constraint::Min(10)]) .split(f.area()); let header = Paragraph::new("MentalNet SoccerCloud | n/l/o create | s start | c clone | d delete | e export | v detail | t stats | g standings | h history | q quit") @@ -30,15 +26,6 @@ pub fn draw(f: &mut Frame<'_>, app: &App) { dashboard::render(f, areas[1], app); } - let footer = Paragraph::new(format!( - "{} | speed={} (1/2/4/0) | create modal: m=manual p=cpu [ ] team Enter=create Esc=cancel | view modal: j/k scroll Esc/q close", - app.status_line, - app.speed.label() - )) - .block(Block::default().borders(Borders::ALL).title("Status")) - .style(Style::default().fg(Color::Green)); - f.render_widget(footer, areas[2]); - if let Some(draft) = &app.create_draft { modal::render_create(f, f.area(), app, draft); } From 591ab5ac4d4d1b02a8fea0234bdaf797bfb53d8e Mon Sep 17 00:00:00 2001 From: markmental Date: Tue, 10 Feb 2026 21:45:45 -0500 Subject: [PATCH 04/10] Dynamic TEAMS Array Auto-Derived from TEAMS_DATA --- src/data.rs | 894 ++++++++++++++++++++++++++++++++-------------------- 1 file changed, 558 insertions(+), 336 deletions(-) diff --git a/src/data.rs b/src/data.rs index 258d6d4..38adfe0 100644 --- a/src/data.rs +++ b/src/data.rs @@ -10,12 +10,6 @@ pub struct Tactic { pub press_mult: f64, } -#[derive(Debug, Clone, Copy)] -pub struct TeamProfile { - pub formation: &'static str, - pub tactic: &'static str, -} - pub const TACTICS: [Tactic; 4] = [ Tactic { key: "counter", @@ -59,179 +53,566 @@ pub const TACTICS: [Tactic; 4] = [ }, ]; -pub const TEAMS: [&str; 85] = [ - "Kashima Antlers", - "Urawa Red Diamonds", - "Gamba Osaka", - "Cerezo Osaka", - "Kawasaki Frontale", - "Yokohama F. Marinos", - "Nagoya Grampus", - "Shimizu S-Pulse", - "Sanfrecce Hiroshima", - "Consadole Sapporo", - "Ventforet Kofu", - "Tokyo Verdy", - "JEF United Chiba", - "Arsenal", - "FC Barcelona", - "Real Madrid", - "Manchester City", - "Manchester United", - "Liverpool", - "Bayern Munich", - "Borussia Dortmund", - "Paris Saint-Germain", - "Juventus", - "Inter", - "AC Milan", - "Ajax", - "Benfica", - "Porto", - "Celtic", - "England", - "France", - "Spain", - "Germany", - "Italy", - "Portugal", - "Netherlands", - "Belgium", - "Croatia", - "Denmark", - "Switzerland", - "Austria", - "Sweden", - "Norway", - "Poland", - "Serbia", - "Turkey", - "Ukraine", - "Czech Republic", - "Scotland", - "Argentina", - "Brazil", - "Uruguay", - "Colombia", - "Chile", - "Peru", - "Ecuador", - "Paraguay", - "Bolivia", - "Venezuela", - "United States", - "Mexico", - "Canada", - "Costa Rica", - "Panama", - "Jamaica", - "Honduras", - "Japan", - "South Korea", - "Australia", - "Iran", - "Saudi Arabia", - "Qatar", - "Iraq", - "United Arab Emirates", - "PRC China", - "Morocco", - "Senegal", - "Nigeria", - "Egypt", - "Algeria", - "Tunisia", - "Ghana", - "Cameroon", - "Ivory Coast", - "South Africa", +#[derive(Debug, Clone, Copy)] +pub struct Team { + pub name: &'static str, + pub flag: &'static str, + pub formation: &'static str, + pub tactic: &'static str, +} + +pub const TEAMS_DATA: [Team; 85] = [ + // J-League Clubs + Team { + name: "Kashima Antlers", + flag: "๐Ÿ‡ฏ๐Ÿ‡ต", + formation: "4-4-2", + tactic: "counter", + }, + Team { + name: "Urawa Red Diamonds", + flag: "๐Ÿ‡ฏ๐Ÿ‡ต", + formation: "4-2-3-1", + tactic: "possession", + }, + Team { + name: "Gamba Osaka", + flag: "๐Ÿ‡ฏ๐Ÿ‡ต", + formation: "4-4-2", + tactic: "counter", + }, + Team { + name: "Cerezo Osaka", + flag: "๐Ÿ‡ฏ๐Ÿ‡ต", + formation: "4-4-2", + tactic: "counter", + }, + Team { + name: "Kawasaki Frontale", + flag: "๐Ÿ‡ฏ๐Ÿ‡ต", + formation: "4-3-3", + tactic: "possession", + }, + Team { + name: "Yokohama F. Marinos", + flag: "๐Ÿ‡ฏ๐Ÿ‡ต", + formation: "4-3-3", + tactic: "high_press", + }, + Team { + name: "Nagoya Grampus", + flag: "๐Ÿ‡ฏ๐Ÿ‡ต", + formation: "4-2-3-1", + tactic: "low_block", + }, + Team { + name: "Shimizu S-Pulse", + flag: "๐Ÿ‡ฏ๐Ÿ‡ต", + formation: "4-4-2", + tactic: "counter", + }, + Team { + name: "Sanfrecce Hiroshima", + flag: "๐Ÿ‡ฏ๐Ÿ‡ต", + formation: "3-5-2", + tactic: "possession", + }, + Team { + name: "Consadole Sapporo", + flag: "๐Ÿ‡ฏ๐Ÿ‡ต", + formation: "3-5-2", + tactic: "high_press", + }, + Team { + name: "Ventforet Kofu", + flag: "๐Ÿ‡ฏ๐Ÿ‡ต", + formation: "4-4-2", + tactic: "counter", + }, + Team { + name: "Tokyo Verdy", + flag: "๐Ÿ‡ฏ๐Ÿ‡ต", + formation: "4-3-3", + tactic: "possession", + }, + Team { + name: "JEF United Chiba", + flag: "๐Ÿ‡ฏ๐Ÿ‡ต", + formation: "4-3-3", + tactic: "counter", + }, + // European Clubs + Team { + name: "Arsenal", + flag: "๐Ÿ‡ฌ๐Ÿ‡ง", + formation: "4-3-3", + tactic: "possession", + }, + Team { + name: "FC Barcelona", + flag: "๐Ÿ‡ช๐Ÿ‡ธ", + formation: "4-3-3", + tactic: "possession", + }, + Team { + name: "Real Madrid", + flag: "๐Ÿ‡ช๐Ÿ‡ธ", + formation: "4-3-3", + tactic: "counter", + }, + Team { + name: "Manchester City", + flag: "๐Ÿ‡ฌ๐Ÿ‡ง", + formation: "4-3-3", + tactic: "possession", + }, + Team { + name: "Manchester United", + flag: "๐Ÿ‡ฌ๐Ÿ‡ง", + formation: "4-2-3-1", + tactic: "high_press", + }, + Team { + name: "Liverpool", + flag: "๐Ÿ‡ฌ๐Ÿ‡ง", + formation: "4-3-3", + tactic: "high_press", + }, + Team { + name: "Bayern Munich", + flag: "๐Ÿ‡ฉ๐Ÿ‡ช", + formation: "4-2-3-1", + tactic: "high_press", + }, + Team { + name: "Borussia Dortmund", + flag: "๐Ÿ‡ฉ๐Ÿ‡ช", + formation: "4-2-3-1", + tactic: "high_press", + }, + Team { + name: "Paris Saint-Germain", + flag: "๐Ÿ‡ซ๐Ÿ‡ท", + formation: "4-3-3", + tactic: "possession", + }, + Team { + name: "Juventus", + flag: "๐Ÿ‡ฎ๐Ÿ‡น", + formation: "3-5-2", + tactic: "low_block", + }, + Team { + name: "Inter", + flag: "๐Ÿ‡ฎ๐Ÿ‡น", + formation: "3-5-2", + tactic: "low_block", + }, + Team { + name: "AC Milan", + flag: "๐Ÿ‡ฎ๐Ÿ‡น", + formation: "4-2-3-1", + tactic: "possession", + }, + Team { + name: "Ajax", + flag: "๐Ÿ‡ณ๐Ÿ‡ฑ", + formation: "4-3-3", + tactic: "possession", + }, + Team { + name: "Benfica", + flag: "๐Ÿ‡ต๐Ÿ‡น", + formation: "4-2-3-1", + tactic: "possession", + }, + Team { + name: "Porto", + flag: "๐Ÿ‡ต๐Ÿ‡น", + formation: "4-4-2", + tactic: "counter", + }, + Team { + name: "Celtic", + flag: "๐Ÿ‡ฌ๐Ÿ‡ง", + formation: "4-3-3", + tactic: "possession", + }, + // UEFA National Teams + Team { + name: "England", + flag: "๐Ÿ‡ฌ๐Ÿ‡ง", + formation: "4-2-3-1", + tactic: "high_press", + }, + Team { + name: "France", + flag: "๐Ÿ‡ซ๐Ÿ‡ท", + formation: "4-2-3-1", + tactic: "high_press", + }, + Team { + name: "Spain", + flag: "๐Ÿ‡ช๐Ÿ‡ธ", + formation: "4-3-3", + tactic: "possession", + }, + Team { + name: "Germany", + flag: "๐Ÿ‡ฉ๐Ÿ‡ช", + formation: "4-2-3-1", + tactic: "high_press", + }, + Team { + name: "Italy", + flag: "๐Ÿ‡ฎ๐Ÿ‡น", + formation: "4-4-2", + tactic: "counter", + }, + Team { + name: "Portugal", + flag: "๐Ÿ‡ต๐Ÿ‡น", + formation: "4-3-3", + tactic: "possession", + }, + Team { + name: "Netherlands", + flag: "๐Ÿ‡ณ๐Ÿ‡ฑ", + formation: "4-3-3", + tactic: "possession", + }, + Team { + name: "Belgium", + flag: "๐Ÿ‡ง๐Ÿ‡ช", + formation: "4-2-3-1", + tactic: "high_press", + }, + Team { + name: "Croatia", + flag: "๐Ÿ‡ญ๐Ÿ‡ท", + formation: "4-4-2", + tactic: "counter", + }, + Team { + name: "Denmark", + flag: "๐Ÿ‡ฉ๐Ÿ‡ฐ", + formation: "4-4-2", + tactic: "counter", + }, + Team { + name: "Switzerland", + flag: "๐Ÿ‡จ๐Ÿ‡ญ", + formation: "4-4-2", + tactic: "counter", + }, + Team { + name: "Austria", + flag: "๐Ÿ‡ฆ๐Ÿ‡น", + formation: "4-2-3-1", + tactic: "counter", + }, + Team { + name: "Sweden", + flag: "๐Ÿ‡ธ๐Ÿ‡ช", + formation: "4-2-3-1", + tactic: "counter", + }, + Team { + name: "Norway", + flag: "๐Ÿ‡ณ๐Ÿ‡ด", + formation: "4-2-3-1", + tactic: "counter", + }, + Team { + name: "Poland", + flag: "๐Ÿ‡ต๐Ÿ‡ฑ", + formation: "4-2-3-1", + tactic: "counter", + }, + Team { + name: "Serbia", + flag: "๐Ÿ‡ท๐Ÿ‡ธ", + formation: "4-2-3-1", + tactic: "counter", + }, + Team { + name: "Turkey", + flag: "๐Ÿ‡น๐Ÿ‡ท", + formation: "4-2-3-1", + tactic: "counter", + }, + Team { + name: "Ukraine", + flag: "๐Ÿ‡บ๐Ÿ‡ฆ", + formation: "4-2-3-1", + tactic: "counter", + }, + Team { + name: "Czech Republic", + flag: "๐Ÿ‡จ๐Ÿ‡ฟ", + formation: "4-2-3-1", + tactic: "counter", + }, + Team { + name: "Scotland", + flag: "๐Ÿ‡ฌ๐Ÿ‡ง", + formation: "4-2-3-1", + tactic: "counter", + }, + // CONMEBOL National Teams + Team { + name: "Argentina", + flag: "๐Ÿ‡ฆ๐Ÿ‡ท", + formation: "4-2-3-1", + tactic: "high_press", + }, + Team { + name: "Brazil", + flag: "๐Ÿ‡ง๐Ÿ‡ท", + formation: "4-2-3-1", + tactic: "high_press", + }, + Team { + name: "Uruguay", + flag: "๐Ÿ‡บ๐Ÿ‡พ", + formation: "4-4-2", + tactic: "counter", + }, + Team { + name: "Colombia", + flag: "๐Ÿ‡จ๐Ÿ‡ด", + formation: "4-2-3-1", + tactic: "counter", + }, + Team { + name: "Chile", + flag: "๐Ÿ‡จ๐Ÿ‡ฑ", + formation: "4-2-3-1", + tactic: "counter", + }, + Team { + name: "Peru", + flag: "๐Ÿ‡ต๐Ÿ‡ช", + formation: "4-2-3-1", + tactic: "counter", + }, + Team { + name: "Ecuador", + flag: "๐Ÿ‡ช๐Ÿ‡จ", + formation: "4-2-3-1", + tactic: "counter", + }, + Team { + name: "Paraguay", + flag: "๐Ÿ‡ต๐Ÿ‡พ", + formation: "4-2-3-1", + tactic: "counter", + }, + Team { + name: "Bolivia", + flag: "๐Ÿ‡ง๐Ÿ‡ด", + formation: "4-2-3-1", + tactic: "counter", + }, + Team { + name: "Venezuela", + flag: "๐Ÿ‡ป๐Ÿ‡ช", + formation: "4-2-3-1", + tactic: "counter", + }, + // CONCACAF National Teams + Team { + name: "United States", + flag: "๐Ÿ‡บ๐Ÿ‡ธ", + formation: "4-2-3-1", + tactic: "high_press", + }, + Team { + name: "Mexico", + flag: "๐Ÿ‡ฒ๐Ÿ‡ฝ", + formation: "4-4-2", + tactic: "counter", + }, + Team { + name: "Canada", + flag: "๐Ÿ‡จ๐Ÿ‡ฆ", + formation: "4-4-2", + tactic: "counter", + }, + Team { + name: "Costa Rica", + flag: "๐Ÿ‡จ๐Ÿ‡ท", + formation: "4-2-3-1", + tactic: "counter", + }, + Team { + name: "Panama", + flag: "๐Ÿ‡ต๐Ÿ‡ฆ", + formation: "4-2-3-1", + tactic: "counter", + }, + Team { + name: "Jamaica", + flag: "๐Ÿ‡ฏ๐Ÿ‡ฒ", + formation: "4-2-3-1", + tactic: "counter", + }, + Team { + name: "Honduras", + flag: "๐Ÿ‡ญ๐Ÿ‡ณ", + formation: "4-2-3-1", + tactic: "counter", + }, + // AFC/OFC National Teams + Team { + name: "Japan", + flag: "๐Ÿ‡ฏ๐Ÿ‡ต", + formation: "4-3-3", + tactic: "possession", + }, + Team { + name: "South Korea", + flag: "๐Ÿ‡ฐ๐Ÿ‡ท", + formation: "4-2-3-1", + tactic: "high_press", + }, + Team { + name: "Australia", + flag: "๐Ÿ‡ฆ๐Ÿ‡บ", + formation: "4-2-3-1", + tactic: "counter", + }, + Team { + name: "Iran", + flag: "๐Ÿ‡ฎ๐Ÿ‡ท", + formation: "4-4-2", + tactic: "counter", + }, + Team { + name: "Saudi Arabia", + flag: "๐Ÿ‡ธ๐Ÿ‡ฆ", + formation: "4-4-2", + tactic: "counter", + }, + Team { + name: "Qatar", + flag: "๐Ÿ‡ถ๐Ÿ‡ฆ", + formation: "4-2-3-1", + tactic: "counter", + }, + Team { + name: "Iraq", + flag: "๐Ÿ‡ฎ๐Ÿ‡ถ", + formation: "4-2-3-1", + tactic: "counter", + }, + Team { + name: "United Arab Emirates", + flag: "๐Ÿ‡ฆ๐Ÿ‡ช", + formation: "4-2-3-1", + tactic: "counter", + }, + Team { + name: "PRC China", + flag: "๐Ÿ‡จ๐Ÿ‡ณ", + formation: "4-3-3", + tactic: "possession", + }, + // CAF National Teams + Team { + name: "Morocco", + flag: "๐Ÿ‡ฒ๐Ÿ‡ฆ", + formation: "4-2-3-1", + tactic: "high_press", + }, + Team { + name: "Senegal", + flag: "๐Ÿ‡ธ๐Ÿ‡ณ", + formation: "4-4-2", + tactic: "counter", + }, + Team { + name: "Nigeria", + flag: "๐Ÿ‡ณ๐Ÿ‡ฌ", + formation: "4-2-3-1", + tactic: "high_press", + }, + Team { + name: "Egypt", + flag: "๐Ÿ‡ช๐Ÿ‡ฌ", + formation: "4-2-3-1", + tactic: "counter", + }, + Team { + name: "Algeria", + flag: "๐Ÿ‡ฉ๐Ÿ‡ฟ", + formation: "4-4-2", + tactic: "counter", + }, + Team { + name: "Tunisia", + flag: "๐Ÿ‡น๐Ÿ‡ณ", + formation: "4-4-2", + tactic: "counter", + }, + Team { + name: "Ghana", + flag: "๐Ÿ‡ฌ๐Ÿ‡ญ", + formation: "4-2-3-1", + tactic: "counter", + }, + Team { + name: "Cameroon", + flag: "๐Ÿ‡จ๐Ÿ‡ฒ", + formation: "4-2-3-1", + tactic: "counter", + }, + Team { + name: "Ivory Coast", + flag: "๐Ÿ‡จ๐Ÿ‡ฎ", + formation: "4-2-3-1", + tactic: "counter", + }, + Team { + name: "South Africa", + flag: "๐Ÿ‡ฟ๐Ÿ‡ฆ", + formation: "4-2-3-1", + tactic: "counter", + }, ]; -pub fn team_flag(team: &str) -> &'static str { - match team { - "Kashima Antlers" - | "Urawa Red Diamonds" - | "Gamba Osaka" - | "Cerezo Osaka" - | "Kawasaki Frontale" - | "Yokohama F. Marinos" - | "Nagoya Grampus" - | "Shimizu S-Pulse" - | "Sanfrecce Hiroshima" - | "Consadole Sapporo" - | "Ventforet Kofu" - | "Tokyo Verdy" - | "JEF United Chiba" => "๐Ÿ‡ฏ๐Ÿ‡ต", - "Arsenal" | "Manchester City" | "Manchester United" | "Liverpool" | "Celtic" => "๐Ÿ‡ฌ๐Ÿ‡ง", - "FC Barcelona" | "Real Madrid" => "๐Ÿ‡ช๐Ÿ‡ธ", - "Bayern Munich" | "Borussia Dortmund" => "๐Ÿ‡ฉ๐Ÿ‡ช", - "Paris Saint-Germain" => "๐Ÿ‡ซ๐Ÿ‡ท", - "Juventus" | "Inter" | "AC Milan" => "๐Ÿ‡ฎ๐Ÿ‡น", - "Ajax" => "๐Ÿ‡ณ๐Ÿ‡ฑ", - "Benfica" | "Porto" => "๐Ÿ‡ต๐Ÿ‡น", - "England" | "Scotland" => "๐Ÿ‡ฌ๐Ÿ‡ง", - "France" => "๐Ÿ‡ซ๐Ÿ‡ท", - "Spain" => "๐Ÿ‡ช๐Ÿ‡ธ", - "Germany" => "๐Ÿ‡ฉ๐Ÿ‡ช", - "Italy" => "๐Ÿ‡ฎ๐Ÿ‡น", - "Portugal" => "๐Ÿ‡ต๐Ÿ‡น", - "Netherlands" => "๐Ÿ‡ณ๐Ÿ‡ฑ", - "Belgium" => "๐Ÿ‡ง๐Ÿ‡ช", - "Croatia" => "๐Ÿ‡ญ๐Ÿ‡ท", - "Denmark" => "๐Ÿ‡ฉ๐Ÿ‡ฐ", - "Switzerland" => "๐Ÿ‡จ๐Ÿ‡ญ", - "Austria" => "๐Ÿ‡ฆ๐Ÿ‡น", - "Sweden" => "๐Ÿ‡ธ๐Ÿ‡ช", - "Norway" => "๐Ÿ‡ณ๐Ÿ‡ด", - "Poland" => "๐Ÿ‡ต๐Ÿ‡ฑ", - "Serbia" => "๐Ÿ‡ท๐Ÿ‡ธ", - "Turkey" => "๐Ÿ‡น๐Ÿ‡ท", - "Ukraine" => "๐Ÿ‡บ๐Ÿ‡ฆ", - "Czech Republic" => "๐Ÿ‡จ๐Ÿ‡ฟ", - "Argentina" => "๐Ÿ‡ฆ๐Ÿ‡ท", - "Brazil" => "๐Ÿ‡ง๐Ÿ‡ท", - "Uruguay" => "๐Ÿ‡บ๐Ÿ‡พ", - "Colombia" => "๐Ÿ‡จ๐Ÿ‡ด", - "Chile" => "๐Ÿ‡จ๐Ÿ‡ฑ", - "Peru" => "๐Ÿ‡ต๐Ÿ‡ช", - "Ecuador" => "๐Ÿ‡ช๐Ÿ‡จ", - "Paraguay" => "๐Ÿ‡ต๐Ÿ‡พ", - "Bolivia" => "๐Ÿ‡ง๐Ÿ‡ด", - "Venezuela" => "๐Ÿ‡ป๐Ÿ‡ช", - "United States" => "๐Ÿ‡บ๐Ÿ‡ธ", - "Mexico" => "๐Ÿ‡ฒ๐Ÿ‡ฝ", - "Canada" => "๐Ÿ‡จ๐Ÿ‡ฆ", - "Costa Rica" => "๐Ÿ‡จ๐Ÿ‡ท", - "Panama" => "๐Ÿ‡ต๐Ÿ‡ฆ", - "Jamaica" => "๐Ÿ‡ฏ๐Ÿ‡ฒ", - "Honduras" => "๐Ÿ‡ญ๐Ÿ‡ณ", - "Japan" => "๐Ÿ‡ฏ๐Ÿ‡ต", - "South Korea" => "๐Ÿ‡ฐ๐Ÿ‡ท", - "Australia" => "๐Ÿ‡ฆ๐Ÿ‡บ", - "Iran" => "๐Ÿ‡ฎ๐Ÿ‡ท", - "Saudi Arabia" => "๐Ÿ‡ธ๐Ÿ‡ฆ", - "Qatar" => "๐Ÿ‡ถ๐Ÿ‡ฆ", - "Iraq" => "๐Ÿ‡ฎ๐Ÿ‡ถ", - "United Arab Emirates" => "๐Ÿ‡ฆ๐Ÿ‡ช", - "PRC China" => "๐Ÿ‡จ๐Ÿ‡ณ", - "Morocco" => "๐Ÿ‡ฒ๐Ÿ‡ฆ", - "Senegal" => "๐Ÿ‡ธ๐Ÿ‡ณ", - "Nigeria" => "๐Ÿ‡ณ๐Ÿ‡ฌ", - "Egypt" => "๐Ÿ‡ช๐Ÿ‡ฌ", - "Algeria" => "๐Ÿ‡ฉ๐Ÿ‡ฟ", - "Tunisia" => "๐Ÿ‡น๐Ÿ‡ณ", - "Ghana" => "๐Ÿ‡ฌ๐Ÿ‡ญ", - "Cameroon" => "๐Ÿ‡จ๐Ÿ‡ฒ", - "Ivory Coast" => "๐Ÿ‡จ๐Ÿ‡ฎ", - "South Africa" => "๐Ÿ‡ฟ๐Ÿ‡ฆ", - _ => "๐Ÿณ๏ธ", +/// Generate team names array dynamically from TEAMS_DATA at compile time +const fn extract_team_names(data: &[Team; N]) -> [&str; N] { + let mut result = [""; N]; + let mut i = 0; + while i < N { + result[i] = data[i].name; + i += 1; } + result +} + +/// Team names array automatically derived from TEAMS_DATA +pub const TEAMS: [&str; TEAMS_DATA.len()] = extract_team_names(&TEAMS_DATA); + +pub fn team_by_name(name: &str) -> Option<&'static Team> { + TEAMS_DATA.iter().find(|t| t.name == name) +} + +pub fn team_flag(team: &str) -> &'static str { + team_by_name(team).map(|t| t.flag).unwrap_or("๐Ÿณ๏ธ") } pub fn display_name(team: &str) -> String { format!("{} {}", team_flag(team), team) } +#[derive(Debug, Clone, Copy)] +pub struct TeamProfile { + pub formation: &'static str, + pub tactic: &'static str, +} + pub fn tactic_by_key(key: &str) -> Tactic { TACTICS .iter() @@ -241,172 +622,13 @@ pub fn tactic_by_key(key: &str) -> Tactic { } pub fn profile_for(team: &str) -> TeamProfile { - match team { - "Arsenal" => TeamProfile { - formation: "4-3-3", - tactic: "possession", - }, - "FC Barcelona" => TeamProfile { - formation: "4-3-3", - tactic: "possession", - }, - "Real Madrid" => TeamProfile { - formation: "4-3-3", - tactic: "counter", - }, - "Manchester City" => TeamProfile { - formation: "4-3-3", - tactic: "possession", - }, - "Manchester United" => TeamProfile { - formation: "4-2-3-1", - tactic: "high_press", - }, - "Liverpool" => TeamProfile { - formation: "4-3-3", - tactic: "high_press", - }, - "Bayern Munich" => TeamProfile { - formation: "4-2-3-1", - tactic: "high_press", - }, - "Borussia Dortmund" => TeamProfile { - formation: "4-2-3-1", - tactic: "high_press", - }, - "Paris Saint-Germain" => TeamProfile { - formation: "4-3-3", - tactic: "possession", - }, - "Juventus" => TeamProfile { - formation: "3-5-2", - tactic: "low_block", - }, - "Inter" => TeamProfile { - formation: "3-5-2", - tactic: "low_block", - }, - "AC Milan" => TeamProfile { - formation: "4-2-3-1", - tactic: "possession", - }, - "Ajax" => TeamProfile { - formation: "4-3-3", - tactic: "possession", - }, - "Benfica" => TeamProfile { - formation: "4-2-3-1", - tactic: "possession", - }, - "Porto" => TeamProfile { + team_by_name(team) + .map(|t| TeamProfile { + formation: t.formation, + tactic: t.tactic, + }) + .unwrap_or(TeamProfile { formation: "4-4-2", tactic: "counter", - }, - "Celtic" => TeamProfile { - formation: "4-3-3", - tactic: "possession", - }, - "Kawasaki Frontale" => TeamProfile { - formation: "4-3-3", - tactic: "possession", - }, - "Yokohama F. Marinos" => TeamProfile { - formation: "4-3-3", - tactic: "high_press", - }, - "Kashima Antlers" => TeamProfile { - formation: "4-4-2", - tactic: "counter", - }, - "Urawa Red Diamonds" => TeamProfile { - formation: "4-2-3-1", - tactic: "possession", - }, - "Gamba Osaka" => TeamProfile { - formation: "4-4-2", - tactic: "counter", - }, - "Cerezo Osaka" => TeamProfile { - formation: "4-4-2", - tactic: "counter", - }, - "Nagoya Grampus" => TeamProfile { - formation: "4-2-3-1", - tactic: "low_block", - }, - "Sanfrecce Hiroshima" => TeamProfile { - formation: "3-5-2", - tactic: "possession", - }, - "Consadole Sapporo" => TeamProfile { - formation: "3-5-2", - tactic: "high_press", - }, - "Shimizu S-Pulse" => TeamProfile { - formation: "4-4-2", - tactic: "counter", - }, - "Ventforet Kofu" => TeamProfile { - formation: "4-4-2", - tactic: "counter", - }, - "Tokyo Verdy" => TeamProfile { - formation: "4-3-3", - tactic: "possession", - }, - "JEF United Chiba" => TeamProfile { - formation: "4-3-3", - tactic: "counter", - }, - "Spain" | "Netherlands" | "Portugal" | "Japan" | "PRC China" => TeamProfile { - formation: "4-3-3", - tactic: "possession", - }, - "England" | "Germany" | "France" | "Brazil" | "Argentina" | "Belgium" | "United States" - | "South Korea" | "Morocco" | "Nigeria" => TeamProfile { - formation: "4-2-3-1", - tactic: "high_press", - }, - "Italy" | "Croatia" | "Denmark" | "Switzerland" | "Uruguay" | "Mexico" | "Canada" - | "Iran" | "Saudi Arabia" | "Senegal" | "Algeria" | "Tunisia" => TeamProfile { - formation: "4-4-2", - tactic: "counter", - }, - "Austria" - | "Sweden" - | "Norway" - | "Poland" - | "Serbia" - | "Turkey" - | "Ukraine" - | "Czech Republic" - | "Scotland" - | "Colombia" - | "Chile" - | "Peru" - | "Ecuador" - | "Paraguay" - | "Bolivia" - | "Venezuela" - | "Costa Rica" - | "Panama" - | "Jamaica" - | "Honduras" - | "Australia" - | "Qatar" - | "Iraq" - | "United Arab Emirates" - | "Egypt" - | "Ghana" - | "Cameroon" - | "Ivory Coast" - | "South Africa" => TeamProfile { - formation: "4-2-3-1", - tactic: "counter", - }, - _ => TeamProfile { - formation: "4-4-2", - tactic: "counter", - }, - } + }) } From 20568d2b3eb03985f2b886e7a7d84841b03d0e96 Mon Sep 17 00:00:00 2001 From: markmental Date: Wed, 11 Feb 2026 11:57:27 -0500 Subject: [PATCH 05/10] web version initial implementation --- Cargo.lock | 1357 ++++++++++++++++++++++++++++++++++++++++++++++++++- Cargo.toml | 2 + DEVLOG.md | 10 + README.md | 19 + data.js | 446 ++++++++++++++--- index.html | 1082 ++++++++++++++++++---------------------- src/main.rs | 15 + src/web.rs | 514 +++++++++++++++++++ 8 files changed, 2747 insertions(+), 698 deletions(-) create mode 100644 src/web.rs diff --git a/Cargo.lock b/Cargo.lock index cd2cdd6..ef61a65 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,219 @@ # It is not intended for manual editing. version = 4 +[[package]] +name = "actix-codec" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f7b0a21988c1bf877cf4759ef5ddaac04c1c9fe808c9142ecb78ba97d97a28a" +dependencies = [ + "bitflags", + "bytes", + "futures-core", + "futures-sink", + "memchr", + "pin-project-lite", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "actix-http" +version = "3.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7926860314cbe2fb5d1f13731e387ab43bd32bca224e82e6e2db85de0a3dba49" +dependencies = [ + "actix-codec", + "actix-rt", + "actix-service", + "actix-utils", + "base64", + "bitflags", + "brotli", + "bytes", + "bytestring", + "derive_more", + "encoding_rs", + "flate2", + "foldhash", + "futures-core", + "h2", + "http", + "httparse", + "httpdate", + "itoa", + "language-tags", + "local-channel", + "mime", + "percent-encoding", + "pin-project-lite", + "rand", + "sha1", + "smallvec", + "tokio", + "tokio-util", + "tracing", + "zstd", +] + +[[package]] +name = "actix-macros" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e01ed3140b2f8d422c68afa1ed2e85d996ea619c988ac834d255db32138655cb" +dependencies = [ + "quote", + "syn", +] + +[[package]] +name = "actix-router" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13d324164c51f63867b57e73ba5936ea151b8a41a1d23d1031eeb9f70d0236f8" +dependencies = [ + "bytestring", + "cfg-if", + "http", + "regex", + "regex-lite", + "serde", + "tracing", +] + +[[package]] +name = "actix-rt" +version = "2.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92589714878ca59a7626ea19734f0e07a6a875197eec751bb5d3f99e64998c63" +dependencies = [ + "futures-core", + "tokio", +] + +[[package]] +name = "actix-server" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a65064ea4a457eaf07f2fba30b4c695bf43b721790e9530d26cb6f9019ff7502" +dependencies = [ + "actix-rt", + "actix-service", + "actix-utils", + "futures-core", + "futures-util", + "mio", + "socket2 0.5.10", + "tokio", + "tracing", +] + +[[package]] +name = "actix-service" +version = "2.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e46f36bf0e5af44bdc4bdb36fbbd421aa98c79a9bce724e1edeb3894e10dc7f" +dependencies = [ + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "actix-utils" +version = "3.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88a1dcdff1466e3c2488e1cb5c36a71822750ad43839937f85d2f4d9f8b705d8" +dependencies = [ + "local-waker", + "pin-project-lite", +] + +[[package]] +name = "actix-web" +version = "4.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1654a77ba142e37f049637a3e5685f864514af11fcbc51cb51eb6596afe5b8d6" +dependencies = [ + "actix-codec", + "actix-http", + "actix-macros", + "actix-router", + "actix-rt", + "actix-server", + "actix-service", + "actix-utils", + "actix-web-codegen", + "bytes", + "bytestring", + "cfg-if", + "cookie", + "derive_more", + "encoding_rs", + "foldhash", + "futures-core", + "futures-util", + "impl-more", + "itoa", + "language-tags", + "log", + "mime", + "once_cell", + "pin-project-lite", + "regex", + "regex-lite", + "serde", + "serde_json", + "serde_urlencoded", + "smallvec", + "socket2 0.6.2", + "time", + "tracing", + "url", +] + +[[package]] +name = "actix-web-codegen" +version = "4.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f591380e2e68490b5dfaf1dd1aa0ebe78d84ba7067078512b4ea6e4492d622b8" +dependencies = [ + "actix-router", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "adler2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" + +[[package]] +name = "aho-corasick" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" +dependencies = [ + "memchr", +] + +[[package]] +name = "alloc-no-stdlib" +version = "2.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc7bb162ec39d46ab1ca8c77bf72e890535becd1751bb45f64c597edb4c8c6b3" + +[[package]] +name = "alloc-stdlib" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94fb8275041c72129eb51b7d0322c29b8387a0386127718b096429201a5d6ece" +dependencies = [ + "alloc-no-stdlib", +] + [[package]] name = "allocator-api2" version = "0.2.21" @@ -58,12 +271,63 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + [[package]] name = "bitflags" version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "brotli" +version = "8.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bd8b9603c7aa97359dbd97ecf258968c95f3adddd6db2f7e7a5bef101c84560" +dependencies = [ + "alloc-no-stdlib", + "alloc-stdlib", + "brotli-decompressor", +] + +[[package]] +name = "brotli-decompressor" +version = "5.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "874bb8112abecc98cbd6d81ea4fa7e94fb9449648c93cc89aa40c81c24d7de03" +dependencies = [ + "alloc-no-stdlib", + "alloc-stdlib", +] + +[[package]] +name = "bytes" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" + +[[package]] +name = "bytestring" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "113b4343b5f6617e7ad401ced8de3cc8b012e73a594347c307b90db3e9271289" +dependencies = [ + "bytes", +] + [[package]] name = "cassowary" version = "0.3.0" @@ -79,6 +343,18 @@ dependencies = [ "rustversion", ] +[[package]] +name = "cc" +version = "1.2.55" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47b26a0954ae34af09b50f0de26458fa95369a0d478d8236d3f93082b219bd29" +dependencies = [ + "find-msvc-tools", + "jobserver", + "libc", + "shlex", +] + [[package]] name = "cfg-if" version = "1.0.4" @@ -145,6 +421,44 @@ dependencies = [ "static_assertions", ] +[[package]] +name = "convert_case" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "633458d4ef8c78b72454de2d54fd6ab2e60f9e02be22f3c6104cdc8a4e0fceb9" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "cookie" +version = "0.16.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e859cd57d0710d9e06c381b550c06e76992472a8c6d527aecd2fc673dcc231fb" +dependencies = [ + "percent-encoding", + "time", + "version_check", +] + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "crc32fast" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" +dependencies = [ + "cfg-if", +] + [[package]] name = "crossterm" version = "0.28.1" @@ -170,6 +484,16 @@ dependencies = [ "winapi", ] +[[package]] +name = "crypto-common" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" +dependencies = [ + "generic-array", + "typenum", +] + [[package]] name = "darling" version = "0.23.0" @@ -204,12 +528,74 @@ dependencies = [ "syn", ] +[[package]] +name = "deranged" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc3dc5ad92c2e2d1c193bbbbdf2ea477cb81331de4f3103f267ca18368b988c4" +dependencies = [ + "powerfmt", +] + +[[package]] +name = "derive_more" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d751e9e49156b02b44f9c1815bcb94b984cdcc4396ecc32521c739452808b134" +dependencies = [ + "derive_more-impl", +] + +[[package]] +name = "derive_more-impl" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "799a97264921d8623a957f6c3b9011f3b5492f557bbb7a5a19b7fa6d06ba8dcb" +dependencies = [ + "convert_case", + "proc-macro2", + "quote", + "rustc_version", + "syn", + "unicode-xid", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] + +[[package]] +name = "displaydoc" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "either" version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" +[[package]] +name = "encoding_rs" +version = "0.8.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" +dependencies = [ + "cfg-if", +] + [[package]] name = "equivalent" version = "1.0.2" @@ -226,12 +612,114 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "find-msvc-tools" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" + +[[package]] +name = "flate2" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + [[package]] name = "foldhash" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" +[[package]] +name = "form_urlencoded" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "futures-core" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" + +[[package]] +name = "futures-sink" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" + +[[package]] +name = "futures-task" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" + +[[package]] +name = "futures-util" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +dependencies = [ + "futures-core", + "futures-task", + "pin-project-lite", + "pin-utils", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +dependencies = [ + "cfg-if", + "libc", + "r-efi", + "wasip2", +] + +[[package]] +name = "h2" +version = "0.3.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0beca50380b1fc32983fc1cb4587bfa4bb9e78fc259aad4a0032d2080309222d" +dependencies = [ + "bytes", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + [[package]] name = "hashbrown" version = "0.15.5" @@ -243,18 +731,165 @@ dependencies = [ "foldhash", ] +[[package]] +name = "hashbrown" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" + [[package]] name = "heck" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" +[[package]] +name = "http" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "httparse" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" + +[[package]] +name = "httpdate" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" + +[[package]] +name = "icu_collections" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c6b649701667bbe825c3b7e6388cb521c23d88644678e83c0c4d0a621a34b43" +dependencies = [ + "displaydoc", + "potential_utf", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locale_core" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edba7861004dd3714265b4db54a3c390e880ab658fec5f7db895fae2046b5bb6" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_normalizer" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f6c8828b67bf8908d82127b2054ea1b4427ff0230ee9141c54251934ab1b599" +dependencies = [ + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7aedcccd01fc5fe81e6b489c15b247b8b0690feb23304303a9e560f37efc560a" + +[[package]] +name = "icu_properties" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "020bfc02fe870ec3a66d93e677ccca0562506e5872c650f893269e08615d74ec" +dependencies = [ + "icu_collections", + "icu_locale_core", + "icu_properties_data", + "icu_provider", + "zerotrie", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "616c294cf8d725c6afcd8f55abc17c56464ef6211f9ed59cccffe534129c77af" + +[[package]] +name = "icu_provider" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85962cf0ce02e1e0a629cc34e7ca3e373ce20dda4c4d7294bbd0bf1fdb59e614" +dependencies = [ + "displaydoc", + "icu_locale_core", + "writeable", + "yoke", + "zerofrom", + "zerotrie", + "zerovec", +] + [[package]] name = "ident_case" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" +[[package]] +name = "idna" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + +[[package]] +name = "impl-more" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8a5a9a0ff0086c7a148acb942baaabeadf9504d10400b5a05645853729b9cd2" + +[[package]] +name = "indexmap" +version = "2.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" +dependencies = [ + "equivalent", + "hashbrown 0.16.1", +] + [[package]] name = "indoc" version = "2.0.7" @@ -298,6 +933,22 @@ version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" +[[package]] +name = "jobserver" +version = "0.1.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" +dependencies = [ + "getrandom", + "libc", +] + +[[package]] +name = "language-tags" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4345964bb142484797b161f473a503a434de77149dd8c7427788c6e13379388" + [[package]] name = "libc" version = "0.2.181" @@ -310,6 +961,29 @@ version = "0.4.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" +[[package]] +name = "litemap" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6373607a59f0be73a39b6fe456b8192fcc3585f602af20751600e974dd455e77" + +[[package]] +name = "local-channel" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6cbc85e69b8df4b8bb8b89ec634e7189099cea8927a276b7384ce5488e53ec8" +dependencies = [ + "futures-core", + "futures-sink", + "local-waker", +] + +[[package]] +name = "local-waker" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d873d7c67ce09b42110d801813efbc9364414e356be9935700d368351657487" + [[package]] name = "lock_api" version = "0.4.14" @@ -331,7 +1005,29 @@ version = "0.12.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "234cf4f4a04dc1f57e24b96cc0cd600cf2af460d4161ac5ecdd0af8e1f3b2a38" dependencies = [ - "hashbrown", + "hashbrown 0.15.5", +] + +[[package]] +name = "memchr" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" + +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "miniz_oxide" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" +dependencies = [ + "adler2", + "simd-adler32", ] [[package]] @@ -346,6 +1042,18 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "num-conv" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf97ec579c3c42f953ef76dbf8d55ac91fb219dde70e49aa4a6b7d74e9919050" + +[[package]] +name = "once_cell" +version = "1.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" + [[package]] name = "once_cell_polyfill" version = "1.70.2" @@ -381,6 +1089,54 @@ version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" +[[package]] +name = "percent-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" + +[[package]] +name = "pin-project-lite" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "pkg-config" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" + +[[package]] +name = "potential_utf" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b73949432f5e2a09657003c25bca5e19a0e9c84f8058ca374f49e0ebe605af77" +dependencies = [ + "zerovec", +] + +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + [[package]] name = "proc-macro2" version = "1.0.106" @@ -399,6 +1155,41 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + +[[package]] +name = "rand" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" +dependencies = [ + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" +dependencies = [ + "getrandom", +] + [[package]] name = "ratatui" version = "0.29.0" @@ -429,6 +1220,50 @@ dependencies = [ "bitflags", ] +[[package]] +name = "regex" +version = "1.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-lite" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cab834c73d247e67f4fae452806d17d3c7501756d98c8808d7c9c7aa7d18f973" + +[[package]] +name = "regex-syntax" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a96887878f22d7bad8a3b6dc5b7440e0ada9a245242924394987b21cf2210a4c" + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + [[package]] name = "rustix" version = "0.38.44" @@ -460,6 +1295,84 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[package]] +name = "semver" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" + +[[package]] +name = "serde" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.149" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" +dependencies = [ + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "sha1" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + [[package]] name = "signal-hook" version = "0.3.18" @@ -491,6 +1404,18 @@ dependencies = [ "libc", ] +[[package]] +name = "simd-adler32" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e320a6c5ad31d271ad523dcf3ad13e2767ad8b1cb8f047f75a8aeaf8da139da2" + +[[package]] +name = "slab" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" + [[package]] name = "smallvec" version = "1.15.1" @@ -501,11 +1426,39 @@ checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" name = "soccercloud" version = "0.1.0" dependencies = [ + "actix-web", "clap", "crossterm", "ratatui", + "serde", ] +[[package]] +name = "socket2" +version = "0.5.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "socket2" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86f4aa3ad99f2088c990dfa82d367e19cb29268ed67c574d10d0a4bfe71f07e0" +dependencies = [ + "libc", + "windows-sys 0.60.2", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" + [[package]] name = "static_assertions" version = "1.1.0" @@ -551,6 +1504,125 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "synstructure" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "time" +version = "0.3.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "743bd48c283afc0388f9b8827b976905fb217ad9e647fae3a379a9283c4def2c" +dependencies = [ + "deranged", + "itoa", + "num-conv", + "powerfmt", + "serde_core", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca" + +[[package]] +name = "time-macros" +version = "0.2.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e70e4c5a0e0a8a4823ad65dfe1a6930e4f4d756dcd9dd7939022b5e8c501215" +dependencies = [ + "num-conv", + "time-core", +] + +[[package]] +name = "tinystr" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42d3e9c45c09de15d06dd8acf5f4e0e399e85927b7f00711024eb7ae10fa4869" +dependencies = [ + "displaydoc", + "zerovec", +] + +[[package]] +name = "tokio" +version = "1.49.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72a2903cd7736441aac9df9d7688bd0ce48edccaadf181c3b90be801e81d3d86" +dependencies = [ + "bytes", + "libc", + "mio", + "parking_lot", + "pin-project-lite", + "signal-hook-registry", + "socket2 0.6.2", + "windows-sys 0.61.2", +] + +[[package]] +name = "tokio-util" +version = "0.7.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tracing" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" +dependencies = [ + "log", + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tracing-core" +version = "0.1.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" +dependencies = [ + "once_cell", +] + +[[package]] +name = "typenum" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" + [[package]] name = "unicode-ident" version = "1.0.23" @@ -586,18 +1658,57 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd" +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + +[[package]] +name = "url" +version = "2.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", + "serde", +] + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + [[package]] name = "utf8parse" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + [[package]] name = "wasi" version = "0.11.1+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" +[[package]] +name = "wasip2" +version = "1.0.2+wasi-0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5" +dependencies = [ + "wit-bindgen", +] + [[package]] name = "winapi" version = "0.3.9" @@ -626,13 +1737,31 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.6", +] + [[package]] name = "windows-sys" version = "0.59.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" dependencies = [ - "windows-targets", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" +dependencies = [ + "windows-targets 0.53.5", ] [[package]] @@ -650,14 +1779,31 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_gnullvm", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm 0.52.6", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", +] + +[[package]] +name = "windows-targets" +version = "0.53.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" +dependencies = [ + "windows-link", + "windows_aarch64_gnullvm 0.53.1", + "windows_aarch64_msvc 0.53.1", + "windows_i686_gnu 0.53.1", + "windows_i686_gnullvm 0.53.1", + "windows_i686_msvc 0.53.1", + "windows_x86_64_gnu 0.53.1", + "windows_x86_64_gnullvm 0.53.1", + "windows_x86_64_msvc 0.53.1", ] [[package]] @@ -666,44 +1812,235 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" + [[package]] name = "windows_aarch64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" +[[package]] +name = "windows_aarch64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" + [[package]] name = "windows_i686_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" +[[package]] +name = "windows_i686_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" + [[package]] name = "windows_i686_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" +[[package]] +name = "windows_i686_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" + [[package]] name = "windows_i686_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" +[[package]] +name = "windows_i686_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" + [[package]] name = "windows_x86_64_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" +[[package]] +name = "windows_x86_64_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" + [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" + [[package]] name = "windows_x86_64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" + +[[package]] +name = "wit-bindgen" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" + +[[package]] +name = "writeable" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9" + +[[package]] +name = "yoke" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72d6e5c6afb84d73944e5cedb052c4680d5657337201555f9f2a16b7406d4954" +dependencies = [ + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "zerocopy" +version = "0.8.39" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db6d35d663eadb6c932438e763b262fe1a70987f9ae936e60158176d710cae4a" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.39" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4122cd3169e94605190e77839c9a40d40ed048d305bfdc146e7df40ab0f3e517" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "zerofrom" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "zerotrie" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a59c17a5562d507e4b54960e8569ebee33bee890c70aa3fe7b97e85a9fd7851" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", +] + +[[package]] +name = "zerovec" +version = "0.11.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c28719294829477f525be0186d13efa9a3c602f7ec202ca9e353d310fb9a002" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "zmij" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4de98dfa5d5b7fef4ee834d0073d560c9ca7b6c46a71d058c48db7960f8cfaf7" + +[[package]] +name = "zstd" +version = "0.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e91ee311a569c327171651566e07972200e76fcfe2242a4fa446149a3881c08a" +dependencies = [ + "zstd-safe", +] + +[[package]] +name = "zstd-safe" +version = "7.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f49c4d5f0abb602a93fb8736af2a4f4dd9512e36f7f570d66e65ff867ed3b9d" +dependencies = [ + "zstd-sys", +] + +[[package]] +name = "zstd-sys" +version = "2.0.16+zstd.1.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e19ebc2adc8f83e43039e79776e3fda8ca919132d68a1fed6a5faca2683748" +dependencies = [ + "cc", + "pkg-config", +] diff --git a/Cargo.toml b/Cargo.toml index 0b14ec8..881ccd2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,6 +7,8 @@ edition = "2021" ratatui = "0.29" crossterm = "0.28" clap = { version = "4.5", features = ["derive"] } +actix-web = "4.11" +serde = { version = "1.0", features = ["derive"] } [profile.release] opt-level = 3 diff --git a/DEVLOG.md b/DEVLOG.md index 66ab8c6..f01ed97 100644 --- a/DEVLOG.md +++ b/DEVLOG.md @@ -86,3 +86,13 @@ - Added national-team flag mappings, including `PRC China`. - Added tactic/formation profile mappings for the new national teams. - Verified with `list` and deterministic `quick` simulation using national teams. + +## 2026-02-11 - Web mode with Actix and Rust-backed frontend + +### Scope completed +- Added `--web` launch mode to start an Actix server at `127.0.0.1:9009`. +- Implemented web APIs for team listing and simulation lifecycle: + - create/list/detail/start/clone/delete/export CSV +- Reused Rust simulation engine and instance lifecycle for backend execution. +- Rebuilt `index.html` and `data.js` as a modern web dashboard UI backed by Rust APIs. +- Removed legacy client-side simulation engine from the browser path. diff --git a/README.md b/README.md index 6dc9d5e..ca9a6e1 100644 --- a/README.md +++ b/README.md @@ -67,6 +67,24 @@ Use a global seed for reproducibility: cargo run -- --seed 42 ``` +### Web mode (Actix) + +Launch the web UI on port `9009`: + +```bash +cargo run -- --web +``` + +Then open: + +```text +http://127.0.0.1:9009 +``` + +Notes: +- The web frontend (`index.html` + `data.js`) now uses Rust backend APIs. +- Simulation logic runs server-side in Rust (shared with CLI/TUI engine). + ### Quick match (headless) ```bash @@ -143,6 +161,7 @@ Readable fullscreen data panels: ```text src/ โ”œโ”€โ”€ main.rs # CLI entrypoint and commands +โ”œโ”€โ”€ web.rs # Actix web server + JSON APIs โ”œโ”€โ”€ app.rs # App state and event loop โ”œโ”€โ”€ data.rs # Teams, flags, tactics, profiles โ”œโ”€โ”€ sim.rs # Match/league/knockout simulation engine diff --git a/data.js b/data.js index 9e2fd71..e7f6ec9 100644 --- a/data.js +++ b/data.js @@ -1,80 +1,376 @@ -// ======================================================= -// SoccerCloud Simulator Data File -// Contains all team, flag, and profile information. -// ======================================================= - -const teams = [ - // J-League - "Kashima Antlers","Urawa Red Diamonds","Gamba Osaka","Cerezo Osaka","Kawasaki Frontale", - "Yokohama F. Marinos","Nagoya Grampus","Shimizu S-Pulse","Sanfrecce Hiroshima","Consadole Sapporo", - "Ventforet Kofu","Tokyo Verdy","JEF United Chiba", - // Euro clubs - "Arsenal","FC Barcelona","Real Madrid","Manchester City","Manchester United","Liverpool", - "Bayern Munich","Borussia Dortmund","Paris Saint-Germain","Juventus","Inter","AC Milan", - "Ajax","Benfica","Porto","Celtic" -]; - -const TEAM_FLAGS = { - // Japan - "Kashima Antlers":"๐Ÿ‡ฏ๐Ÿ‡ต","Urawa Red Diamonds":"๐Ÿ‡ฏ๐Ÿ‡ต","Gamba Osaka":"๐Ÿ‡ฏ๐Ÿ‡ต","Cerezo Osaka":"๐Ÿ‡ฏ๐Ÿ‡ต","Kawasaki Frontale":"๐Ÿ‡ฏ๐Ÿ‡ต", - "Yokohama F. Marinos":"๐Ÿ‡ฏ๐Ÿ‡ต","Nagoya Grampus":"๐Ÿ‡ฏ๐Ÿ‡ต","Shimizu S-Pulse":"๐Ÿ‡ฏ๐Ÿ‡ต","Sanfrecce Hiroshima":"๐Ÿ‡ฏ๐Ÿ‡ต","Consadole Sapporo":"๐Ÿ‡ฏ๐Ÿ‡ต", - "Ventforet Kofu":"๐Ÿ‡ฏ๐Ÿ‡ต","Tokyo Verdy":"๐Ÿ‡ฏ๐Ÿ‡ต", "JEF United Chiba":"๐Ÿ‡ฏ๐Ÿ‡ต", - // UK - "Arsenal":"๐Ÿ‡ฌ๐Ÿ‡ง","Manchester City":"๐Ÿ‡ฌ๐Ÿ‡ง","Manchester United":"๐Ÿ‡ฌ๐Ÿ‡ง","Liverpool":"๐Ÿ‡ฌ๐Ÿ‡ง","Celtic":"๐Ÿ‡ฌ๐Ÿ‡ง", - // Spain - "FC Barcelona":"๐Ÿ‡ช๐Ÿ‡ธ","Real Madrid":"๐Ÿ‡ช๐Ÿ‡ธ", - // Germany - "Bayern Munich":"๐Ÿ‡ฉ๐Ÿ‡ช","Borussia Dortmund":"๐Ÿ‡ฉ๐Ÿ‡ช", - // France - "Paris Saint-Germain":"๐Ÿ‡ซ๐Ÿ‡ท", - // Italy - "Juventus":"๐Ÿ‡ฎ๐Ÿ‡น","Inter":"๐Ÿ‡ฎ๐Ÿ‡น","AC Milan":"๐Ÿ‡ฎ๐Ÿ‡น", - // Netherlands - "Ajax":"๐Ÿ‡ณ๐Ÿ‡ฑ", - // Portugal - "Benfica":"๐Ÿ‡ต๐Ÿ‡น","Porto":"๐Ÿ‡ต๐Ÿ‡น" +const state = { + teams: [], + simulations: [], + selectedDetailId: null, + pollHandle: null, }; -const FORMATIONS = ["4-4-2","4-3-3","4-2-3-1","3-5-2","5-4-1"]; +const $ = (id) => document.getElementById(id); -const TACTICS = { - counter: { label:"Counter", attackBias:1.10, goalMult:1.08, fastBreak:0.25, foulMult:1.00, blockMult:1.00, pressMult:0.95 }, - possession: { label:"Possession", attackBias:1.00, goalMult:0.95, fastBreak:0.10, foulMult:0.90, blockMult:1.00, pressMult:0.90 }, - high_press: { label:"High Press", attackBias:1.15, goalMult:1.00, fastBreak:0.20, foulMult:1.20, blockMult:0.95, pressMult:1.20 }, - low_block: { label:"Low Block", attackBias:0.92, goalMult:0.92, fastBreak:0.12, foulMult:0.95, blockMult:1.15, pressMult:0.85 }, -}; +function setStatus(message) { + $("statusText").textContent = message; +} -const TEAM_PROFILES = { - // Europe - "Arsenal": { formation:"4-3-3", tactic:"possession" }, - "FC Barcelona": { formation:"4-3-3", tactic:"possession" }, - "Real Madrid": { formation:"4-3-3", tactic:"counter" }, - "Manchester City": { formation:"4-3-3", tactic:"possession" }, - "Manchester United": { formation:"4-2-3-1", tactic:"high_press" }, - "Liverpool": { formation:"4-3-3", tactic:"high_press" }, - "Bayern Munich": { formation:"4-2-3-1", tactic:"high_press" }, - "Borussia Dortmund": { formation:"4-2-3-1", tactic:"high_press" }, - "Paris Saint-Germain": { formation:"4-3-3", tactic:"possession" }, - "Juventus": { formation:"3-5-2", tactic:"low_block" }, - "Inter": { formation:"3-5-2", tactic:"low_block" }, - "AC Milan": { formation:"4-2-3-1", tactic:"possession" }, - "Ajax": { formation:"4-3-3", tactic:"possession" }, - "Benfica": { formation:"4-2-3-1", tactic:"possession" }, - "Porto": { formation:"4-4-2", tactic:"counter" }, - "Celtic": { formation:"4-3-3", tactic:"possession" }, - // J-League (generic lean) - "Kawasaki Frontale": { formation:"4-3-3", tactic:"possession" }, - "Yokohama F. Marinos": { formation:"4-3-3", tactic:"high_press" }, - "Kashima Antlers": { formation:"4-4-2", tactic:"counter" }, - "Urawa Red Diamonds": { formation:"4-2-3-1", tactic:"possession" }, - "Gamba Osaka": { formation:"4-4-2", tactic:"counter" }, - "Cerezo Osaka": { formation:"4-4-2", tactic:"counter" }, - "Nagoya Grampus": { formation:"4-2-3-1", tactic:"low_block" }, - "Sanfrecce Hiroshima": { formation:"3-5-2", tactic:"possession" }, - "Consadole Sapporo": { formation:"3-5-2", tactic:"high_press" }, - "Shimizu S-Pulse": { formation:"4-4-2", tactic:"counter" }, - "Ventforet Kofu": { formation:"4-4-2", tactic:"counter" }, - "Tokyo Verdy": { formation:"4-3-3", tactic:"possession" }, - "JEF United Chiba": { formation:"4-3-3", tactic:"counter" } -}; +async function request(path, options = {}) { + const response = await fetch(path, options); + if (!response.ok) { + let msg = `${response.status} ${response.statusText}`; + try { + const body = await response.json(); + if (body && body.error) { + msg = body.error; + } + } catch (_) {} + throw new Error(msg); + } + if (response.status === 204) { + return null; + } + + const contentType = response.headers.get("content-type") || ""; + if (contentType.includes("application/json")) { + return response.json(); + } + return response.text(); +} + +function openModal(modalId) { + const modal = $(modalId); + if (!modal) return; + modal.classList.add("open"); + modal.setAttribute("aria-hidden", "false"); +} + +function closeModal(modalId) { + const modal = $(modalId); + if (!modal) return; + modal.classList.remove("open"); + modal.setAttribute("aria-hidden", "true"); +} + +function getModeTeamCount(mode) { + return mode === "single" ? 2 : 4; +} + +function renderTeamSelectors() { + const wrap = $("teamSelectWrap"); + const mode = $("modeSelect").value; + const required = getModeTeamCount(mode); + const autoFill = $("autoFill").checked; + + $("teamCount").innerHTML = ``; + + if (state.teams.length === 0) { + wrap.innerHTML = "

Loading teams...

"; + return; + } + + const options = state.teams + .map((team) => ``) + .join(""); + + const fields = []; + for (let i = 0; i < required; i++) { + fields.push(` + + `); + } + + wrap.innerHTML = fields.join(""); +} + +function getCreatePayload() { + const mode = $("modeSelect").value; + const autoFill = $("autoFill").checked; + const required = getModeTeamCount(mode); + + if (autoFill) { + return { mode, auto_fill: true }; + } + + const picks = [...$("teamSelectWrap").querySelectorAll("select")].map((s) => s.value); + const uniqueCount = new Set(picks).size; + if (picks.length !== required || uniqueCount !== picks.length) { + throw new Error("Please select unique teams for this mode"); + } + + return { mode, auto_fill: false, teams: picks }; +} + +function cardActions(sim) { + const common = ` + + + + `; + + if (sim.status === "pending") { + return ` + + ${common} + `; + } + + if (sim.status === "completed") { + return ` + + ${common} + `; + } + + return common; +} + +function createCardElement(sim) { + const article = document.createElement("article"); + article.className = "card"; + article.dataset.id = String(sim.id); + article.innerHTML = ` +
+
+

+

+
+ +
+

Progress:

+

+

Outcome:

+
+ `; + return article; +} + +function updateCardElement(card, sim) { + card.querySelector('[data-role="title"]').textContent = sim.title; + card.querySelector('[data-role="idline"]').textContent = `sim-${sim.id} | ${sim.mode} | seed=${sim.seed}`; + card.querySelector('[data-role="progress"]').textContent = sim.progress; + card.querySelector('[data-role="scoreboard"]').textContent = sim.scoreboard; + card.querySelector('[data-role="outcome"]').textContent = sim.outcome; + + const pill = card.querySelector('[data-role="pill"]'); + pill.className = `pill ${sim.status}`; + pill.textContent = sim.status; + + if (card.dataset.status !== sim.status) { + card.dataset.status = sim.status; + card.querySelector('[data-role="actions"]').innerHTML = cardActions(sim); + } +} + +function renderDashboard() { + const root = $("dashboard"); + if (!root) return; + + if (state.simulations.length === 0) { + root.innerHTML = `
No simulation instances yet. Create one to start the web simulation flow.
`; + return; + } + + const existingCards = new Map( + [...root.querySelectorAll("article.card")].map((card) => [Number(card.dataset.id), card]), + ); + + const activeIds = new Set(state.simulations.map((sim) => sim.id)); + const fragment = document.createDocumentFragment(); + + for (const sim of state.simulations) { + let card = existingCards.get(sim.id); + if (!card) { + card = createCardElement(sim); + } + updateCardElement(card, sim); + fragment.appendChild(card); + } + + for (const [id, card] of existingCards.entries()) { + if (!activeIds.has(id) && card.parentElement === root) { + card.remove(); + } + } + + root.replaceChildren(fragment); +} + +function renderStats() { + const total = state.simulations.length; + const running = state.simulations.filter((s) => s.status === "running").length; + const completed = state.simulations.filter((s) => s.status === "completed").length; + $("statInstances").textContent = String(total); + $("statRunning").textContent = String(running); + $("statCompleted").textContent = String(completed); + $("statTeams").textContent = String(state.teams.length); +} + +async function refreshSimulations() { + state.simulations = await request("/api/simulations"); + renderStats(); + renderDashboard(); + + if (state.selectedDetailId !== null) { + const exists = state.simulations.some((s) => s.id === state.selectedDetailId); + if (!exists) { + state.selectedDetailId = null; + closeModal("detailModal"); + return; + } + await loadDetail(state.selectedDetailId); + } +} + +function textOrPlaceholder(lines, fallback) { + if (!Array.isArray(lines) || lines.length === 0) { + return fallback; + } + return lines.join("\n"); +} + +async function loadDetail(id) { + const detail = await request(`/api/simulations/${id}`); + state.selectedDetailId = id; + + $("detailTitle").textContent = `sim-${detail.id} - ${detail.title}`; + $("detailScoreboard").textContent = `${detail.scoreboard} | ${detail.outcome}`; + $("detailLogs").textContent = textOrPlaceholder(detail.logs, "No events yet."); + $("detailStats").textContent = textOrPlaceholder(detail.stats_lines, "No stats available yet."); + $("detailCompetition").textContent = textOrPlaceholder(detail.competition_lines, "No standings/bracket available yet."); + $("detailHistory").textContent = textOrPlaceholder(detail.history_lines, "No history recorded yet."); +} + +async function createSimulation() { + try { + const payload = getCreatePayload(); + const created = await request("/api/simulations", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify(payload), + }); + closeModal("createModal"); + setStatus(`Created sim-${created.id}`); + await refreshSimulations(); + } catch (error) { + setStatus(`Create failed: ${error.message}`); + } +} + +async function startSimulation(id) { + try { + await request(`/api/simulations/${id}/start`, { method: "POST" }); + setStatus(`Started sim-${id}`); + await refreshSimulations(); + } catch (error) { + setStatus(`Start failed: ${error.message}`); + } +} + +async function cloneSimulation(id) { + try { + const created = await request(`/api/simulations/${id}/clone`, { method: "POST" }); + setStatus(`Cloned sim-${id} to sim-${created.id}`); + await refreshSimulations(); + } catch (error) { + setStatus(`Clone failed: ${error.message}`); + } +} + +async function deleteSimulation(id) { + try { + await request(`/api/simulations/${id}`, { method: "DELETE" }); + if (state.selectedDetailId === id) { + state.selectedDetailId = null; + closeModal("detailModal"); + } + setStatus(`Deleted sim-${id}`); + await refreshSimulations(); + } catch (error) { + setStatus(`Delete failed: ${error.message}`); + } +} + +function exportSimulation(id) { + window.location.href = `/api/simulations/${id}/export.csv`; + setStatus(`Exporting sim-${id} CSV...`); +} + +function bindEvents() { + $("openCreateBtn").addEventListener("click", () => openModal("createModal")); + $("createBtn").addEventListener("click", createSimulation); + $("modeSelect").addEventListener("change", renderTeamSelectors); + $("autoFill").addEventListener("change", renderTeamSelectors); + + document.querySelectorAll("[data-close]").forEach((button) => { + button.addEventListener("click", () => { + closeModal(button.dataset.close); + if (button.dataset.close === "detailModal") { + state.selectedDetailId = null; + } + }); + }); + + ["createModal", "detailModal"].forEach((id) => { + const modal = $(id); + modal.addEventListener("click", (event) => { + if (event.target === modal) { + closeModal(id); + if (id === "detailModal") { + state.selectedDetailId = null; + } + } + }); + }); + + $("dashboard").addEventListener("click", async (event) => { + const button = event.target.closest("button[data-action]"); + if (!button) return; + + const id = Number(button.dataset.id); + const action = button.dataset.action; + + if (action === "start") return startSimulation(id); + if (action === "clone") return cloneSimulation(id); + if (action === "delete") return deleteSimulation(id); + if (action === "export") return exportSimulation(id); + if (action === "view") { + try { + await loadDetail(id); + openModal("detailModal"); + } catch (error) { + setStatus(`Failed to load detail: ${error.message}`); + } + } + }); +} + +async function boot() { + bindEvents(); + try { + setStatus("Loading teams and simulations..."); + state.teams = await request("/api/teams"); + renderTeamSelectors(); + await refreshSimulations(); + setStatus("Connected to SoccerCloud backend on port 9009."); + } catch (error) { + setStatus(`Startup failed: ${error.message}`); + return; + } + + if (state.pollHandle) { + clearInterval(state.pollHandle); + } + + state.pollHandle = setInterval(async () => { + try { + await refreshSimulations(); + } catch (error) { + setStatus(`Polling issue: ${error.message}`); + } + }, 500); +} + +boot(); diff --git a/index.html b/index.html index 17a6ede..b12683e 100644 --- a/index.html +++ b/index.html @@ -3,648 +3,504 @@ - MentalNet.xyz | SoccerCloud Dashboard - - - + SoccerCloud Web + + + + .hero-card { + background: linear-gradient(145deg, #ffffff, #f7fbff); + border: 1px solid rgba(15, 23, 42, 0.08); + border-radius: 18px; + padding: 20px; + box-shadow: var(--shadow); + animation: rise 420ms ease both; + } + + .eyebrow { + font-size: 12px; + text-transform: uppercase; + letter-spacing: 0.14em; + color: var(--brand); + margin: 0 0 8px; + font-weight: 700; + } + + h1 { + font-family: "Sora", "Space Grotesk", sans-serif; + margin: 0; + font-size: clamp(28px, 5vw, 42px); + line-height: 1.05; + letter-spacing: -0.02em; + } + + .lead { + margin: 12px 0 0; + color: var(--muted); + font-size: 15px; + max-width: 56ch; + } + + .stat-grid { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 10px; + } + + .stat { + border: 1px solid var(--line); + background: var(--surface-soft); + border-radius: 12px; + padding: 10px 12px; + } + + .stat .label { + color: var(--muted); + font-size: 12px; + margin-bottom: 6px; + } + + .stat .value { + font-family: "Sora", "Space Grotesk", sans-serif; + font-size: 24px; + line-height: 1; + } + + .toolbar { + display: flex; + justify-content: space-between; + align-items: center; + gap: 12px; + margin: 18px 0; + } + + .status-text { + margin: 0; + color: var(--muted); + font-size: 13px; + } + + .btn { + border: 1px solid transparent; + background: var(--brand); + color: white; + border-radius: 10px; + padding: 10px 14px; + font-family: inherit; + font-size: 14px; + font-weight: 600; + cursor: pointer; + transition: background-color 180ms ease, border-color 180ms ease, box-shadow 180ms ease, opacity 180ms ease; + box-shadow: 0 12px 24px rgba(15, 118, 110, 0.25); + } + + .btn:hover { + background: rgba(15, 118, 110, 0.72); + border-color: rgba(255, 255, 255, 0.45); + box-shadow: 0 10px 26px rgba(15, 118, 110, 0.28), inset 0 1px 0 rgba(255, 255, 255, 0.28); + backdrop-filter: blur(8px) saturate(135%); + -webkit-backdrop-filter: blur(8px) saturate(135%); + } + .btn:active { + opacity: 0.96; + box-shadow: 0 6px 16px rgba(15, 118, 110, 0.2); + } + .btn.secondary { + background: white; + color: var(--text); + border-color: var(--line); + box-shadow: none; + } + .btn.secondary:hover { + background: rgba(255, 255, 255, 0.66); + border-color: rgba(148, 163, 184, 0.7); + box-shadow: 0 8px 18px rgba(15, 23, 42, 0.12), inset 0 1px 0 rgba(255, 255, 255, 0.34); + } + .btn.warn { + background: white; + color: var(--warn); + border-color: rgba(180, 35, 24, 0.34); + box-shadow: none; + } + .btn.warn:hover { + background: rgba(255, 255, 255, 0.66); + border-color: rgba(180, 35, 24, 0.45); + box-shadow: 0 8px 18px rgba(180, 35, 24, 0.16), inset 0 1px 0 rgba(255, 255, 255, 0.34); + } + + .dashboard { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); + gap: 14px; + } + + .card { + background: var(--surface); + border: 1px solid rgba(15, 23, 42, 0.08); + border-radius: 14px; + padding: 14px; + box-shadow: 0 10px 28px rgba(15, 23, 42, 0.08); + } + + .card-top { + display: flex; + justify-content: space-between; + align-items: flex-start; + gap: 8px; + margin-bottom: 10px; + } + + .card-title { + margin: 0; + font-size: 16px; + line-height: 1.2; + font-family: "Sora", "Space Grotesk", sans-serif; + } + + .card-id { + margin: 6px 0 0; + color: var(--muted); + font-size: 12px; + } + + .pill { + border-radius: 999px; + padding: 4px 10px; + font-size: 12px; + font-weight: 700; + text-transform: uppercase; + letter-spacing: 0.04em; + color: #fff; + } + .pill.pending { background: var(--pending); } + .pill.running { background: var(--running); } + .pill.completed { background: var(--completed); } + + .card-line { + margin: 8px 0; + color: var(--muted); + font-size: 13px; + min-height: 1.3em; + } + + .card-score { + margin: 8px 0; + font-size: 14px; + color: var(--text); + } + + .card-actions { + display: flex; + flex-wrap: wrap; + gap: 8px; + margin-top: 12px; + } + + .empty { + background: rgba(255, 255, 255, 0.72); + border: 1px dashed var(--line); + border-radius: 14px; + padding: 26px; + text-align: center; + color: var(--muted); + } + + .modal { + position: fixed; + inset: 0; + display: none; + align-items: center; + justify-content: center; + background: rgba(4, 10, 22, 0.62); + backdrop-filter: blur(4px); + z-index: 40; + padding: 16px; + } + + .modal.open { display: flex; } + + .modal-panel { + width: min(980px, 100%); + max-height: 92vh; + overflow: auto; + background: var(--surface); + border-radius: 16px; + border: 1px solid rgba(15, 23, 42, 0.08); + box-shadow: 0 24px 64px rgba(2, 6, 23, 0.28); + animation: pop 220ms ease both; + } + + .modal-header { + display: flex; + justify-content: space-between; + align-items: center; + gap: 10px; + border-bottom: 1px solid var(--line); + padding: 14px 16px; + position: sticky; + top: 0; + background: rgba(255, 255, 255, 0.9); + backdrop-filter: blur(2px); + z-index: 3; + } + + .modal-title { + margin: 0; + font-family: "Sora", "Space Grotesk", sans-serif; + font-size: 18px; + } + + .modal-body { + padding: 14px 16px 18px; + } + + .field-grid { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 12px; + margin-bottom: 12px; + } + + label { + display: flex; + flex-direction: column; + gap: 6px; + font-size: 13px; + color: var(--muted); + } + + select, + input[type="checkbox"] { + font: inherit; + } + + select { + padding: 10px; + border-radius: 10px; + border: 1px solid var(--line); + background: white; + color: var(--text); + } + + .inline { + display: flex; + align-items: center; + gap: 8px; + flex-wrap: wrap; + margin-bottom: 12px; + font-size: 14px; + color: var(--muted); + } + + .detail-grid { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 12px; + } + + .panel { + border: 1px solid var(--line); + border-radius: 12px; + padding: 10px; + background: var(--surface-soft); + } + + .panel h3 { + margin: 0 0 8px; + font-size: 13px; + text-transform: uppercase; + letter-spacing: 0.06em; + color: var(--brand); + } + + .mono { + margin: 0; + font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; + font-size: 12px; + line-height: 1.42; + white-space: pre-wrap; + color: #122033; + max-height: 260px; + overflow: auto; + } + + .scoreboard { + margin: 0 0 12px; + font-family: "Sora", "Space Grotesk", sans-serif; + font-size: 17px; + padding: 10px 12px; + border: 1px solid var(--line); + border-radius: 12px; + background: white; + } + + @keyframes rise { + from { opacity: 0; transform: translateY(8px); } + to { opacity: 1; transform: translateY(0); } + } + + @keyframes pop { + from { opacity: 0; transform: translateY(10px) scale(0.98); } + to { opacity: 1; transform: translateY(0) scale(1); } + } + + @media (max-width: 920px) { + .hero, + .detail-grid, + .field-grid { + grid-template-columns: 1fr; + } + + .app { padding: 18px 12px 32px; } + } + -
-
-

MentalNet SoccerCloud

-

Cloudified Soccer Simulation Environment

-
- -
-

Simulation Instances

-
- -
-
+
+
+
+

Rust Backend

+

SoccerCloud Web Control Room

+

A modern web frontend backed by the same Rust simulation engine used in CLI and TUI modes.

+
+
+
+
+
Instances
+
0
+
+
+
Running
+
0
+
+
+
Completed
+
0
+
+
+
Available Teams
+
0
+
+
-
-