diff --git a/README.md b/README.md index 8775ee8..61c6c6d 100644 --- a/README.md +++ b/README.md @@ -1,68 +1,141 @@ -![License](https://img.shields.io/badge/License-GPLv3-blue.svg) -![Build Status](https://img.shields.io/github/actions/workflow/status/pushkar-gr/ghostlink/ci.yml?label=Build) -![Made with Love](https://img.shields.io/badge/Made%20with-Rust-orange.svg) +
+ GhostLink Banner + + + Build Status + + + Latest Version + + + License + + + Language + +
+ +# πŸ‘» GhostLink + +**High-Performance Serverless P2P Messaging** + +GhostLink is a decentralized chat application engineered for direct peer-to-peer communication. By leveraging UDP hole punching and a custom reliability layer, it eliminates the need for central relay servers, ensuring messages travel the shortest path possible between peers. + +This project prioritizes low-latency networking, asynchronous runtime efficiency, and clean architecture. --- -# πŸ‘» GhostLink +## πŸ—ΊοΈ Roadmap & Status + +| Version | Status | Description | +|---------|--------------|---------------------------------------------------------------------------------------------------------------| +| v1.0 | **Stable** | Current release. Feature complete for text exchange and reliable UDP transport. | +| v1.1 | **In Progress** | Security Hardening (QUIC). Replacing the KCP stack with QUIC (via `quinn`) to implement TLS 1.3 End-to-End Encryption. | + +--- + +## βš™οΈ Architectural Overview + +GhostLink operates by decoupling the Web Interface from the P2P Networking Core, bridging them via shared thread-safe state. + +[![](https://mermaid.ink/img/pako:eNqFVFtv2jAU_iuWHyYqESBcSskQUkt3QYOWNUVIW_ZgklNikdiZ7YzSqv99xwnXdWrzdI59vu87t_iZhjIC6lHHcQIRSvHAl14gCDExpOARAdKJmFoFogh4SOQ6jJkyZHyHUTpfLBXLYmt8sUbjZ0CnCjQIwwyXgozZBhSpzEZnAf1lectvDguM7C8GaJCJjPIE-vXFoL9Qg8vHPK3PmcqID-oPqC0ORPQ_QRdphlIYJRMyTZgAUrmPgVwpxsWp5DYqAVUqTzDi6HAn72PiQCYsjDmSfWBp9pEMYyYEJPqEbwJasyUXyy3dziVjueThjg6hhnzl2ki12bJ9liplxmDo26U1kfheMaEzaftdNvIOEs4WPOFmc1qefz-7KTOxVtE7Hu6bes11KLGZmxPMt-G0hKBB5qiZHfpwJLTN-1ZFoN7NuoWMN2DWUq32ObM1GdVv_8lXhiswpfzserr1yR1omatD4n6GGRCfi1XdNwpY-rZ6Gwm_xFKbMSLIpcIpGghNruBEfL-ur4_c10fN10etQw7lNpO-4-z3BPddY-FDmaZMRPqMOM7gaNVK0MHHa6z7dw7akNGUVK6kUnK97UgJtjMtYcV0nRr5nuM0Sc3eFXHHt5YOaxaW7X3pkeCGs4Q_AW6N1vavtaD9QpeYw37bSq82BnQRhqtTBtgdsld22lNmUyoDjtM7FZ5lEbM0Fd__VJ_7ZaXYS1qlS8Uj6hmVQ5WmgP-LdemzZQlo8TQF1EOzeJpoIF4QkzHxQ8p0B1MyX8bUe2CJRi8vtK45w305hOAEQQ1lLgz1Wm5BQb1n-kg9t9eqNc4vXLfTbLVb3U6VbqjnNBu13oXbcbvdntttXzTa3ZcqfSpE3VoDMW6v0eueu63OebtKIeL420_K97V8WunLX189u5E?type=png)](https://mermaid.live/edit#pako:eNqFVFtv2jAU_iuWHyYqESBcSskQUkt3QYOWNUVIW_ZgklNikdiZ7YzSqv99xwnXdWrzdI59vu87t_iZhjIC6lHHcQIRSvHAl14gCDExpOARAdKJmFoFogh4SOQ6jJkyZHyHUTpfLBXLYmt8sUbjZ0CnCjQIwwyXgozZBhSpzEZnAf1lectvDguM7C8GaJCJjPIE-vXFoL9Qg8vHPK3PmcqID-oPqC0ORPQ_QRdphlIYJRMyTZgAUrmPgVwpxsWp5DYqAVUqTzDi6HAn72PiQCYsjDmSfWBp9pEMYyYEJPqEbwJasyUXyy3dziVjueThjg6hhnzl2ki12bJ9liplxmDo26U1kfheMaEzaftdNvIOEs4WPOFmc1qefz-7KTOxVtE7Hu6bes11KLGZmxPMt-G0hKBB5qiZHfpwJLTN-1ZFoN7NuoWMN2DWUq32ObM1GdVv_8lXhiswpfzserr1yR1omatD4n6GGRCfi1XdNwpY-rZ6Gwm_xFKbMSLIpcIpGghNruBEfL-ur4_c10fN10etQw7lNpO-4-z3BPddY-FDmaZMRPqMOM7gaNVK0MHHa6z7dw7akNGUVK6kUnK97UgJtjMtYcV0nRr5nuM0Sc3eFXHHt5YOaxaW7X3pkeCGs4Q_AW6N1vavtaD9QpeYw37bSq82BnQRhqtTBtgdsld22lNmUyoDjtM7FZ5lEbM0Fd__VJ_7ZaXYS1qlS8Uj6hmVQ5WmgP-LdemzZQlo8TQF1EOzeJpoIF4QkzHxQ8p0B1MyX8bUe2CJRi8vtK45w305hOAEQQ1lLgz1Wm5BQb1n-kg9t9eqNc4vXLfTbLVb3U6VbqjnNBu13oXbcbvdntttXzTa3ZcqfSpE3VoDMW6v0eueu63OebtKIeL420_K97V8WunLX189u5E) + +### Communication Flow -**Direct. Simple. Yours.** +[![](https://mermaid.ink/img/pako:eNqVVsFu20YQ_ZXpnmxUZEnKsiweXJB2ERupBcK0EaRVUWzIkUSY5LK7yzSq4d5yyye0P5cv6SxJhZIluYkOAld8M_Pe25ldPbJEpMh8pvCPGssELzO-kLyYlUAfXmtR1sU7lO26_a641FmSVbzUEABXcK9Q0tPRq6VQ-uesfDjeBU6Du98bMD0Y7K2oNco9wNiAXgmxyBHiu_vp_lThOlX4QqrwC7lwm1wLnQqNIN4b7oPQh2jJFYLrw3WKpc70Ci4zlZj3KzgyTI43TQis8_PvYx_eLAXwAq5_hKMwK9OsXMCtsVLpDh5bhLQCH96KGrhE8Jyh7diuO7Rdf-Q4DhxF9bs8S-A62qoQfluFsK_gTs7skWu7jmN7_ulOib3qjU0KElGtrIorenUdKSh4WfM8X8H7jLduyPSH-CZ-0UHPhzgr6lzzEkWt4ErQTkZ1mSyJ-nMLg7YyJMTuQcGMXYiyxETPWG9C-AKmReVCVPBTs1PkaKGM3rYexJpr7Izq-gMibPo10BqLSqv-5ZpV06wkA8uUysVvpzMGWkC4jWxQHdpwvIwg4skDamoXmfiGijOAS6V9MJtw_CzaeCezxVKDmENXsR2ORCKRViDrHH0I8lz82WQA67xJ-n-JiMznfz9145FKUSmS3RKbiiYrzIVxYIV6gxUntY014QFrwl7sc2uCXWvC3sgda057a0YHrMlxvimoFbPPmVHnzOl-ZzbzBL0xwTNjbnmC1P40XzoT5QYhEtoumoe9fW_bNgRzOoPgb9fyQKHJo8zP6wDTdXGdJKjUvM7hTnIKVjzvy3xd2x1suW1INzWf__m4tr2xCtPvSKjZfvyQKa02VIZWO4x9bSu4eL2eQr724Ou74GAH7IoJvolpYLXi9jM9vEnt4TT0jfmlqoTUcF_RVZfinjPpihQ3obFoSJG21xfR1pH0EqTPRcBf6eff4Ia2ni-oaWfsCkkivBEyT_tjrq3bYkkPG7CFzFLma1njgBUoC26W7NEEzJheYoEzZtKlXD6YPE8UQ9feL0IU6zAp6sWS-XOeK1rVVUrD013wXyDkGMoLUZea-cNhk4L5j-wD890JXVSnZ6478oYnw_FowFbMtzzHnpy5I3c8nrjjkzPnZPw0YH81RV3boRh34njDoeedjJzxgCENlJA37Z8Mmot5tmBP_wHtB3Ud?type=png)](https://mermaid.live/edit#pako:eNqVVsFu20YQ_ZXpnmxUZEnKsiweXJB2ERupBcK0EaRVUWzIkUSY5LK7yzSq4d5yyye0P5cv6SxJhZIluYkOAld8M_Pe25ldPbJEpMh8pvCPGssELzO-kLyYlUAfXmtR1sU7lO26_a641FmSVbzUEABXcK9Q0tPRq6VQ-uesfDjeBU6Du98bMD0Y7K2oNco9wNiAXgmxyBHiu_vp_lThOlX4QqrwC7lwm1wLnQqNIN4b7oPQh2jJFYLrw3WKpc70Ci4zlZj3KzgyTI43TQis8_PvYx_eLAXwAq5_hKMwK9OsXMCtsVLpDh5bhLQCH96KGrhE8Jyh7diuO7Rdf-Q4DhxF9bs8S-A62qoQfluFsK_gTs7skWu7jmN7_ulOib3qjU0KElGtrIorenUdKSh4WfM8X8H7jLduyPSH-CZ-0UHPhzgr6lzzEkWt4ErQTkZ1mSyJ-nMLg7YyJMTuQcGMXYiyxETPWG9C-AKmReVCVPBTs1PkaKGM3rYexJpr7Izq-gMibPo10BqLSqv-5ZpV06wkA8uUysVvpzMGWkC4jWxQHdpwvIwg4skDamoXmfiGijOAS6V9MJtw_CzaeCezxVKDmENXsR2ORCKRViDrHH0I8lz82WQA67xJ-n-JiMznfz9145FKUSmS3RKbiiYrzIVxYIV6gxUntY014QFrwl7sc2uCXWvC3sgda057a0YHrMlxvimoFbPPmVHnzOl-ZzbzBL0xwTNjbnmC1P40XzoT5QYhEtoumoe9fW_bNgRzOoPgb9fyQKHJo8zP6wDTdXGdJKjUvM7hTnIKVjzvy3xd2x1suW1INzWf__m4tr2xCtPvSKjZfvyQKa02VIZWO4x9bSu4eL2eQr724Ou74GAH7IoJvolpYLXi9jM9vEnt4TT0jfmlqoTUcF_RVZfinjPpihQ3obFoSJG21xfR1pH0EqTPRcBf6eff4Ia2ni-oaWfsCkkivBEyT_tjrq3bYkkPG7CFzFLma1njgBUoC26W7NEEzJheYoEzZtKlXD6YPE8UQ9feL0IU6zAp6sWS-XOeK1rVVUrD013wXyDkGMoLUZea-cNhk4L5j-wD890JXVSnZ6478oYnw_FowFbMtzzHnpy5I3c8nrjjkzPnZPw0YH81RV3boRh34njDoeedjJzxgCENlJA37Z8Mmot5tmBP_wHtB3Ud) -Welcome to **GhostLink**! This is a serverless P2P chat application built with love (and a lot of Rust). It helps you connect directly with a friend without needing big central servers, complicated setups, or port forwarding headaches. It just works! +1. **Initialization**: The application starts the Axum web server and the Tokio UDP listener simultaneously. +2. **Discovery**: The UDP layer queries a STUN server to resolve the machine's Public IP and punch a NAT hole. +3. **Signaling**: The user manually exchanges Public IPs with a peer via the Web UI. +4. **Transport**: Messages are routed from the UI β†’ Shared State β†’ KCP Stream β†’ Peer. --- -## ✨ What is it? +## πŸš€ Key Technical Features -Imagine two tin cans connected by a string, but digital and engineered for speed. GhostLink lets you establish a direct link with another computer anywhere in the world. +- **True Peer-to-Peer**: Direct client-to-client connections minimize latency and remove dependency on third-party infrastructure. +- **Reliable UDP (ARQ)**: Utilizes KCP (via `tokio_kcp`) to provide TCP-like reliability with the speed advantages of UDP. +- **Automated NAT Traversal**: Integrated STUN client allows for seamless connectivity across different network configurations without manual port forwarding. +- **Asynchronous Core**: Built entirely on the Tokio runtime for non-blocking I/O and high concurrency. +- **Real-Time Updates**: State changes are pushed to the browser immediately via Server-Sent Events (SSE). -* **No Intermediaries:** Your messages go straight to your friend. -* **Serverless:** We use a little magic (called STUN) to find each other, but the chat is all you. -* **Web Interface:** Comes with a clean, dark-mode UI that runs right in your browser. +--- + +## πŸ› οΈ Technology Stack + +| Component | Technology | Role | +|---------------|---------------|-----------------------------------------------------| +| Runtime | Tokio | Asynchronous I/O scheduler and task management. | +| Transport | Tokio KCP | Reliable UDP protocol implementation. | +| Web Framework | Axum | HTTP/REST interface and SSE stream handling. | +| State | Arc/RwLock | Thread-safe state synchronization between tasks. | +| Discovery | STUN | Public IP resolution and NAT hole punching. | -## ⚠️ A Little Heads-Up (Important!) +--- + +## πŸ”’ Security Notice -While GhostLink is super cool for direct chats, please keep this in mind: +> **WARNING**: *Protocol Status: Cleartext* -**There is NO Encryption.** πŸ”“ +GhostLink v1.0 transmits data in plain text. While the transport layer provides reliability, it does not currently implement end-to-end encryption. -Think of this like passing a note in class or shouting across a playground. It's fast and direct, but it sends data as "plain text." Please **do not** send passwords, credit card numbers, or your deepest secrets over this link just yet. It's strictly for fun, casual chats! +Do not transmit sensitive data (PII, credentials, financial information) over this version. Encryption is slated for the v1.1 release cycle (via QUIC). -## πŸ› οΈ Under the Hood (For the Geeks) +--- -We didn't just throw this together; we built it on a robust asynchronous stack. Here is the tech that makes the ghost fly: +## πŸ“¦ Installation & Usage -* **Runtime:** Built on **Tokio** for asynchronous, non-blocking I/O, ensuring the app stays responsive even when juggling network packets. -* **Web Framework:** The UI is served by **Axum**, which also handles the REST API and pushes real-time updates to your browser via **Server-Sent Events (SSE)**. -* **Reliability:** We use **KCP** (via `tokio_kcp`) over UDP. This gives us the speed of UDP with the reliability of TCP (ARQ), meaning your messages won't get lost even if the network is a bit shaky. -* **NAT Traversal:** We implement **STUN** (Session Traversal Utilities for NAT) to punch holes through routers, allowing direct P2P connections without manual port forwarding. -* **State Management:** Thread-safe state sharing using `Arc>`, bridging the gap between the web server and the UDP network controller. +### Prerequisites -## πŸš€ How to Run It +Ensure you have the latest stable version of Rust and Cargo installed. -Getting started is easy if you have Rust installed. +### Quick Start -1. **Clone the repo:** +**Step 1**: Clone the repository. ```bash git clone https://github.com/pushkar-gr/ghostlink.git cd ghostlink +``` +**Step 2**: Build and run. +```bash +cargo run --release ``` +The first build may take a moment to compile dependencies. -2. **Fire it up:** -```bash -cargo run +**Step 3**: Initiate Connection. +- Navigate to `http://localhost:8080` in your web browser. +- Copy your Public IP displayed on the dashboard. +- Share your IP with a friend and input their IP into the Target Address field. +- Click **Establish Link**. -``` +--- +## 🀝 Contributing + +We welcome contributions! πŸš€ + +1. Fork the repository. +2. Create a feature branch. + ```bash + git checkout -b feature/amazing-feature + ``` +3. Commit your changes. + ```bash + git commit -m "Add some amazing feature" + ``` +4. Push to the branch. + ```bash + git push origin feature/amazing-feature + ``` +5. Open a Pull Request. -3. **Start Chatting:** -Open your browser and go to `http://localhost:8080`. Send your public IP to a friend, put their IP in the box, and hit **Establish Link**! +--- ## πŸ“„ License -This project is open-source and available under the **GNU General Public License v3.0**. Feel free to tinker, fork, and play around with it! +This project is open-source and available under the **GNU General Public License v3.0**. See the [LICENSE](./LICENSE) file for details. --- diff --git a/static/banner.png b/static/banner.png new file mode 100644 index 0000000..e520f73 Binary files /dev/null and b/static/banner.png differ diff --git a/static/index.html b/static/index.html index 9f6aa15..de6e2a3 100644 --- a/static/index.html +++ b/static/index.html @@ -3,169 +3,163 @@ - GhostLink | P2P Connection + GhostLink // TERMINAL + + + -
-
-

GhostLink

-
- - Disconnected -
-
+ +
+
+
+
- -
-
-
- - Detecting NAT... -
- -
- -
- Initializing... - -
- -
+
+
+
+
+
+ +
+
+
+
+

GHOSTLINK v1.0

+
+
+ + Initializing...
+
-
- -
- Initializing... - -
- + +
+
+
+
+
+ NETWORK_CONFIG + SCANNING +
+ +
+
+
PUBLIC_ENDPOINT
+
+ --.--.--.-- + +
+
+ +
+
LOCAL_ENDPOINT
+
+ --.--.--.-- + +
+
+
+
-
-
-
-
- -
-
-
- -
Invalid IPv4 format
+
+
+
+ TARGET_ACQUISITION +
+ +
+
+ + + INVALID IP FORMAT +
+
+ + + INVALID PORT +
+
+ +
+ +
+
-
- -
Invalid Port
-
-
-
-
- +
-
- -
-
-
-
-
- + +
+
+
+
+
+
+
+
ESTABLISHING TUNNEL...
+ +
--s
+
-
You
-
...
-
+ + + + -
-
-
-
-
+
+
-
- -
--s
-
-
- +
+
+ SYSTEM_LOGS +
+
+
-
Peer
-
Target
-
-
- - - - Handshake Logs -
-
-
> Initializing Hole Punching Sequence...
-
-
- -
- -
-
- - -
-
-
-
-
- -
-
-
Connected Peer
-
...
+ +
+
+
+
+
+
+
SECURE UPLINK ESTABLISHED
+
...
+
+
- -
- -
-
-
- -
-

Connection Established

-

You can now send and receive messages securely via P2P

+ +
+
CHANNEL ENCRYPTION VERIFIED
-
- -
-
- - + + + > + +
-
+
- Notification + ALERT
diff --git a/static/script.js b/static/script.js index a66b752..4203093 100644 --- a/static/script.js +++ b/static/script.js @@ -181,7 +181,7 @@ function handleStatusChange(statusStr, data = {}) { // DISCONNECTED els.viewHome.classList.add('active'); els.submitBtn.disabled = !(state.isIpValid && state.isPortValid); - els.submitBtn.innerText = "Establish Link"; + els.submitBtn.innerText = "INITIATE LINK SEQUENCE"; } } @@ -189,7 +189,7 @@ function resetDisconnectButtons() { if(els.disconnectBtn) els.disconnectBtn.disabled = false; if(els.cancelPunchBtn) { els.cancelPunchBtn.disabled = false; - els.cancelPunchBtn.innerText = "Cancel Connection"; + els.cancelPunchBtn.innerText = "ABORT SEQUENCE"; } } @@ -287,7 +287,7 @@ function renderNatType() { function renderStatusBadge() { const s = state.connectionStatus; - els.statusText.innerText = s.charAt(0).toUpperCase() + s.slice(1); + els.statusText.innerText = s.toUpperCase(); let color, bg, border; if (s === 'connected') { @@ -312,9 +312,9 @@ function renderMyInfo(success) { els.apiErrorMsg.style.display = 'none'; els.copyBtn.style.display = 'flex'; } else { - els.myIpDisplay.innerText = "Connection Failed"; + els.myIpDisplay.innerText = "CONN_FAIL"; els.myIpDisplay.classList.add('error'); - els.apiErrorMsg.innerText = "Could not reach node."; + els.apiErrorMsg.innerText = "COULD NOT REACH NODE"; els.apiErrorMsg.style.display = 'block'; els.copyBtn.style.display = 'none'; } @@ -325,7 +325,7 @@ function renderMyInfo(success) { els.myLocalIpDisplay.classList.remove('error'); els.copyLocalBtn.style.display = 'flex'; } else { - els.myLocalIpDisplay.innerText = "Not Available"; + els.myLocalIpDisplay.innerText = "N/A"; els.myLocalIpDisplay.classList.add('error'); els.copyLocalBtn.style.display = 'none'; } @@ -335,7 +335,7 @@ function addLog(message) { const row = document.createElement('div'); row.className = `log-line system`; const timeStr = new Date().toLocaleTimeString('en-US', {hour12: false, hour:"2-digit", minute:"2-digit", second:"2-digit"}); - row.innerHTML = `[${timeStr}] ${message}`; + row.innerHTML = `[${timeStr}] ${message.toUpperCase()}`; els.punchLogs.appendChild(row); els.punchLogs.scrollTop = els.punchLogs.scrollHeight; } @@ -349,8 +349,8 @@ function clearChatUI() {
-

Connection Established

-

You can now send and receive messages securely via P2P

+

CONNECTION ESTABLISHED

+

SECURE CHANNEL OPEN. BEGIN TRANSMISSION.

`; } @@ -380,7 +380,7 @@ function addChatMessage(content, fromMe) { const timeDiv = document.createElement('span'); timeDiv.className = 'message-time'; const now = new Date(); - timeDiv.textContent = now.toLocaleTimeString(undefined, {hour: '2-digit', minute: '2-digit', hour12: true}); + timeDiv.textContent = now.toLocaleTimeString(undefined, {hour: '2-digit', minute: '2-digit', hour12: false}); bubbleDiv.appendChild(contentDiv); bubbleDiv.appendChild(timeDiv); @@ -421,7 +421,7 @@ async function handleChatSubmit(e) { } catch (err) { console.error('Failed to send message:', err); - showToast('Failed to send message'); + showToast('TRANSMISSION FAILED'); } finally { els.sendBtn.disabled = false; } @@ -438,7 +438,7 @@ async function handleConnect(e) { state.peerAddress = `${ip}:${port}`; const btn = els.submitBtn; - btn.innerText = "Initiating..."; + btn.innerText = "INITIATING..."; btn.disabled = true; try { @@ -453,8 +453,8 @@ async function handleConnect(e) { // No longer using lastSavedMessage } catch (err) { - showToast("Connection failed to start"); - btn.innerText = "Establish Link"; + showToast("CONNECTION FAILED TO START"); + btn.innerText = "INITIATE LINK SEQUENCE"; btn.disabled = false; } } @@ -468,7 +468,7 @@ async function handleDisconnect(e) { if(els.disconnectBtn) els.disconnectBtn.disabled = true; if(els.cancelPunchBtn) { els.cancelPunchBtn.disabled = true; - els.cancelPunchBtn.innerText = "Canceling..."; + els.cancelPunchBtn.innerText = "ABORTING..."; } try { @@ -481,13 +481,13 @@ async function handleDisconnect(e) { } catch (err) { console.error('Disconnect failed:', err); - showToast("Failed to disconnect"); + showToast("FAILED TO DISCONNECT"); // Re-enable buttons on failure so user can try again if(els.disconnectBtn) els.disconnectBtn.disabled = false; if(els.cancelPunchBtn) { els.cancelPunchBtn.disabled = false; - els.cancelPunchBtn.innerText = "Cancel Connection"; + els.cancelPunchBtn.innerText = "ABORT SEQUENCE"; } } } @@ -498,7 +498,7 @@ function toggleSubmitButton() { } function showToast(message) { - els.toastMsg.textContent = message; + els.toastMsg.textContent = message.toUpperCase(); els.toast.classList.add('show'); setTimeout(() => els.toast.classList.remove('show'), 3000); } @@ -511,7 +511,7 @@ function copyToClipboard() { textarea.select(); try { document.execCommand('copy'); - showToast("Public IP copied to clipboard"); + showToast("PUBLIC IP COPIED"); } catch (err) { console.error('Copy failed', err); } @@ -527,7 +527,7 @@ function copyLocalToClipboard() { textarea.select(); try { document.execCommand('copy'); - showToast("Local IP copied to clipboard"); + showToast("LOCAL IP COPIED"); } catch (err) { console.error('Copy failed', err); } diff --git a/static/style.css b/static/style.css index d19203c..698ff6a 100644 --- a/static/style.css +++ b/static/style.css @@ -1,566 +1,401 @@ -/* ... [Variable] ... */ :root { - --bg-color: #0f172a; - --card-bg: rgba(30, 41, 59, 0.75); /* More transparent for glass effect */ - --text-primary: #f1f5f9; - --text-secondary: #94a3b8; - --accent: #22d3ee; - --accent-hover: #06b6d4; - --danger: #ef4444; - --success: #10b981; - --warning: #f59e0b; - --border-radius: 16px; /* Modern, softer corners */ - - /* Improved Font Stacks */ - --font-family: 'Inter', system-ui, -apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif; - --font-mono: 'JetBrains Mono', 'Fira Code', 'Menlo', 'Monaco', 'Consolas', 'Courier New', monospace; + --bg-color: #060010; + --text-main: #e2e8f0; + --text-dim: #64748b; + --accent: #00f3ff; + --accent-dim: rgba(0, 243, 255, 0.1); + --danger: #ff003c; + --success: #00ff9d; + --warning: #f59e0b; + --font-ui: 'Oxanium', sans-serif; + --font-mono: 'JetBrains Mono', monospace; + --scan-strength: 0.15; } * { box-sizing: border-box; margin: 0; padding: 0; } body { background-color: var(--bg-color); - /* Subtle gradient to add depth */ - background-image: radial-gradient(circle at 50% 0%, #1e293b 0%, #0f172a 70%); - color: var(--text-primary); - font-family: var(--font-family); - -webkit-font-smoothing: antialiased; + color: var(--text-main); + font-family: var(--font-ui); height: 100vh; - margin: 0; + width: 100vw; overflow: hidden; + margin: 0; } -.container { - width: 100%; - max-width: 480px; /* Default narrow width for Disconnected/Home */ - padding: 2rem; - position: relative; - margin: 0 auto; - /* Smoothly animate width changes */ - transition: max-width 0.6s cubic-bezier(0.25, 1, 0.5, 1), padding 0.6s cubic-bezier(0.25, 1, 0.5, 1); - min-height: 100vh; - display: flex; - flex-direction: column; - justify-content: center; +/* --- CRT Effects --- */ +.crt-overlay { + position: fixed; top: 0; left: 0; width: 100%; height: 100%; + background: radial-gradient(circle, rgba(16, 25, 30, 0) 60%, rgba(0,0,0,0.4) 100%); + z-index: 900; pointer-events: none; + transition: opacity 0.5s ease; } -/* Dynamically widen the container ONLY when the Punching view is active */ -.container:has(#view-punching.active) { - max-width: 800px; +.scanlines { + position: fixed; top: 0; left: 0; width: 100%; height: 100%; + background: linear-gradient(to bottom, rgba(255,255,255,0), rgba(255,255,255,0) 50%, rgba(0,0,0,0.1) 50%, rgba(0,0,0,0.1)); + background-size: 100% 4px; + z-index: 901; pointer-events: none; + animation: scrollLines 10s linear infinite; + opacity: var(--scan-strength); + transition: opacity 0.5s ease; } -/* Make container fullscreen when chat is active */ -.container:has(#view-connected.active) { - max-width: 100%; - padding: 0; - justify-content: flex-start; +.vignette { + position: fixed; top: 0; left: 0; width: 100%; height: 100%; + box-shadow: inset 0 0 50px rgba(0,0,0,0.7); + z-index: 902; pointer-events: none; + transition: opacity 0.5s ease; } -/* --- View Transitions --- */ -.view-section { - display: none; +/* --- DYNAMIC STATE: Connected (Remove Effects) --- */ +body:has(#view-connected.active) .crt-overlay, +body:has(#view-connected.active) .scanlines, +body:has(#view-connected.active) .vignette { opacity: 0; - transform: translateY(15px); - transition: opacity 0.4s ease, transform 0.4s ease; } -.view-section.active { - display: block; - opacity: 1; - transform: translateY(0); +.grid-bg { + position: fixed; top: 0; left: 0; width: 200%; height: 200%; + background-image: + linear-gradient(rgba(0, 243, 255, 0.03) 1px, transparent 1px), + linear-gradient(90deg, rgba(0, 243, 255, 0.03) 1px, transparent 1px); + background-size: 40px 40px; + z-index: 0; + transform: perspective(500px) rotateX(20deg); + animation: gridDrift 60s linear infinite; + pointer-events: none; } -/* --- Header & Status --- */ -.header { - text-align: center; - margin-bottom: 2.5rem; - transition: all 0.6s cubic-bezier(0.25, 1, 0.5, 1); +@keyframes scrollLines { + 0% { background-position: 0 0; } + 100% { background-position: 0 100%; } } -/* Compact header when chat is active */ -.container:has(#view-connected.active) .header { - margin-bottom: 0; - padding: 0.75rem 1.5rem; - display: flex; - justify-content: space-between; - align-items: center; - text-align: left; - background: rgba(15, 23, 42, 0.95); - border-bottom: 1px solid rgba(255, 255, 255, 0.08); +@keyframes gridDrift { + 0% { transform: perspective(500px) rotateX(20deg) translateY(0); } + 100% { transform: perspective(500px) rotateX(20deg) translateY(-40px); } } -.header h1 { - font-size: 2.5rem; - font-weight: 800; - letter-spacing: -0.025em; - margin-bottom: 0.75rem; - background: linear-gradient(135deg, #fff 0%, var(--accent) 100%); - -webkit-background-clip: text; - -webkit-text-fill-color: transparent; - text-shadow: 0 10px 30px rgba(34, 211, 238, 0.2); - transition: all 0.6s cubic-bezier(0.25, 1, 0.5, 1); -} - -/* Smaller header text when chat is active */ -.container:has(#view-connected.active) .header h1 { - font-size: 1.5rem; - margin-bottom: 0; -} -.status-badge { - display: inline-flex; align-items: center; padding: 0.35rem 0.85rem; - background: rgba(15, 23, 42, 0.5); - border: 1px solid rgba(255, 255, 255, 0.1); - border-radius: 999px; font-size: 0.75rem; - font-family: var(--font-mono); - text-transform: uppercase; - letter-spacing: 0.05em; - font-weight: 600; - transition: all 0.3s ease; +/* --- Layout Frame --- */ +.ui-frame { + position: relative; + width: 100vw; + height: 100vh; + background: rgba(6, 0, 16, 0.92); + display: flex; + flex-direction: column; + z-index: 10; } -.status-dot { - width: 8px; height: 8px; background-color: var(--danger); border-radius: 50%; - margin-right: 10px; box-shadow: 0 0 8px var(--danger); transition: all 0.3s ease; + +.corner-decor { + position: absolute; width: 40px; height: 40px; + border: 3px solid #fff; z-index: 20; + animation: breathe 4s ease-in-out infinite; + box-shadow: 0 0 10px rgba(255,255,255,0.2); } +.tl { top: 20px; left: 20px; border-right: none; border-bottom: none; } +.tr { top: 20px; right: 20px; border-left: none; border-bottom: none; } +.bl { bottom: 20px; left: 20px; border-right: none; border-top: none; } +.br { bottom: 20px; right: 20px; border-left: none; border-top: none; } -/* --- Cards & Inputs --- */ -.card { - background-color: var(--card-bg); - padding: 2rem; - border-radius: var(--border-radius); - box-shadow: 0 20px 40px -10px rgba(0, 0, 0, 0.3); - margin-bottom: 1.5rem; - border: 1px solid rgba(255, 255, 255, 0.06); - backdrop-filter: blur(12px); - -webkit-backdrop-filter: blur(12px); +@keyframes breathe { + 0%, 100% { opacity: 0.5; box-shadow: 0 0 5px rgba(255,255,255,0.1); border-color: rgba(255,255,255,0.5); } + 50% { opacity: 1; box-shadow: 0 0 20px rgba(255,255,255,0.4); border-color: #fff; } } -/* NAT Badge Styles */ -.nat-badge { - padding: 2px 8px; - border-radius: 6px; - font-size: 0.7rem; - font-family: var(--font-mono); - font-weight: 600; - text-transform: uppercase; - background: rgba(255, 255, 255, 0.05); - border: 1px solid rgba(255, 255, 255, 0.1); - color: var(--text-secondary); - transition: all 0.3s ease; +.container { + width: 100%; height: 100%; + padding: 3rem 4rem; + display: flex; flex-direction: column; + position: relative; + overflow: hidden; } -.nat-badge.cone { color: var(--success); background: rgba(16, 185, 129, 0.1); border-color: rgba(16, 185, 129, 0.2); } -.nat-badge.symmetric { color: var(--danger); background: rgba(239, 68, 68, 0.1); border-color: rgba(239, 68, 68, 0.2); } -.label { - display: block; font-size: 0.7rem; text-transform: uppercase; letter-spacing: 0.1em; - color: var(--text-secondary); margin-bottom: 0.75rem; font-weight: 700; +.container:has(#view-connected.active) { padding: 0; } + +.view-section { + display: none; flex: 1; + flex-direction: column; + overflow: hidden; height: 100%; } -.ip-display-group { - display: flex; align-items: center; justify-content: space-between; - background: rgba(0, 0, 0, 0.3); padding: 0.875rem 1.25rem; - border-radius: 10px; border: 1px solid rgba(255, 255, 255, 0.08); +.view-section.active { display: flex; animation: fadeUp 0.5s ease-out; } + +@keyframes fadeUp { + from { opacity: 0; transform: translateY(15px); } + to { opacity: 1; transform: translateY(0); } } -.ip-text { - font-family: var(--font-mono); font-size: 1.2rem; color: var(--accent); - letter-spacing: -0.5px; transition: color 0.3s; font-weight: 500; + +/* --- Header Layout --- */ +.header { + margin-bottom: 2rem; + border-bottom: 1px solid rgba(255,255,255,0.1); + padding-bottom: 1rem; + flex-shrink: 0; + transition: all 0.3s ease; } -.ip-text.error { color: var(--danger); font-size: 1rem; } -.action-buttons { display: flex; gap: 0.5rem; align-items: center; } -.icon-btn { - background: none; border: none; color: var(--text-secondary); cursor: pointer; - padding: 8px; border-radius: 8px; transition: all 0.2s; display: flex; -} -.icon-btn:hover:not(:disabled) { background: rgba(255, 255, 255, 0.1); color: var(--text-primary); } -.icon-btn.danger { color: var(--danger); } -.icon-btn.danger:hover:not(:disabled) { background: rgba(239, 68, 68, 0.1); color: #fff; } -.icon-btn:disabled { opacity: 0.5; cursor: not-allowed; } -.spin svg { animation: spin 1s linear infinite; } -@keyframes spin { 100% { transform: rotate(360deg); } } -.input-row { display: flex; gap: 1rem; } -input { - width: 100%; padding: 1rem 1.25rem; background-color: rgba(15, 23, 42, 0.6); - border: 1px solid rgba(255, 255, 255, 0.1); border-radius: 10px; - color: var(--text-primary); font-size: 1rem; font-family: var(--font-mono); - transition: all 0.2s ease; +.header-content { + display: flex; + justify-content: space-between; + align-items: center; + width: 100%; } -input:focus { - outline: none; - border-color: var(--accent); - box-shadow: 0 0 0 3px rgba(34, 211, 238, 0.15); - background-color: rgba(15, 23, 42, 0.9); + +.header h1 { + font-size: 3rem; letter-spacing: 6px; color: #fff; + text-shadow: 0 0 20px rgba(255,255,255,0.5); + margin: 0; + transition: font-size 0.3s ease; } -input.error { border-color: var(--danger); } -input.valid { border-color: var(--success); } -.error-msg { color: var(--danger); font-size: 0.75rem; margin-top: 0.5rem; display: none; margin-left: 0.25rem; } -.btn-primary { - width: 100%; padding: 1rem; background-color: var(--accent); color: #0f172a; - border: none; border-radius: 10px; font-size: 1rem; font-weight: 700; cursor: pointer; - transition: all 0.2s ease; - box-shadow: 0 4px 15px rgba(34, 211, 238, 0.3); +.version { font-size: 1rem; color: var(--accent); opacity: 0.7; } + +.status-badge { + display: inline-flex; align-items: center; gap: 12px; + font-family: var(--font-mono); font-size: 1rem; color: var(--text-dim); } -.btn-primary:hover:not(:disabled) { - background-color: var(--accent-hover); - transform: translateY(-1px); - box-shadow: 0 6px 20px rgba(34, 211, 238, 0.4); +.status-dot { width: 10px; height: 10px; background: var(--danger); box-shadow: 0 0 10px var(--danger); transition: background-color 0.3s; } + +/* --- DYNAMIC STATE: Connected Header --- */ +/* Shrink the main header when connected */ +body:has(#view-connected.active) .header { + margin-bottom: 0; + padding: 0.5rem 2rem; + background: rgba(0,0,0,0.8); + border-bottom: none; } -.btn-primary:active:not(:disabled) { transform: translateY(1px); } -.btn-primary:disabled { - opacity: 0.5; cursor: not-allowed; - background-color: rgba(255,255,255,0.05); - color: var(--text-secondary); - box-shadow: none; + +body:has(#view-connected.active) .header h1 { + font-size: 1.2rem; /* Much smaller */ + letter-spacing: 2px; } -.btn-secondary { - padding: 0.75rem 1.5rem; background-color: transparent; color: var(--text-secondary); - border: 1px solid rgba(255, 255, 255, 0.1); border-radius: 10px; - font-size: 0.9rem; font-weight: 600; cursor: pointer; - transition: all 0.2s ease; -} -.btn-secondary:hover:not(:disabled) { - background-color: rgba(255, 255, 255, 0.05); - color: var(--text-primary); - border-color: rgba(255, 255, 255, 0.2); -} -.btn-secondary:disabled { - opacity: 0.5; - cursor: not-allowed; -} - - -/* --- PUNCHING VISUALIZATION --- */ -.network-viz { display: flex; align-items: center; justify-content: space-between; padding: 1rem 0; position: relative; } -.node { display: flex; flex-direction: column; align-items: center; gap: 0.75rem; width: 100px; z-index: 2; } -.icon-circle { - width: 72px; height: 72px; border-radius: 50%; background: #0f172a; - border: 2px solid var(--text-secondary); display: flex; align-items: center; - justify-content: center; color: var(--text-secondary); transition: all 0.3s; -} -.icon-circle.client { border-color: var(--accent); color: var(--accent); box-shadow: 0 0 20px rgba(34, 211, 238, 0.15); } -.icon-circle.peer { border-color: var(--warning); color: var(--warning); box-shadow: 0 0 20px rgba(245, 158, 11, 0.15); } -.node-label { font-size: 0.7rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.05em; margin-top: 0.25rem; } -.node-ip { font-family: var(--font-mono); font-size: 0.75rem; color: var(--text-secondary); background: rgba(0,0,0,0.3); padding: 2px 6px; border-radius: 4px; } - -.connection-path { - flex: 1; height: 2px; background: rgba(255, 255, 255, 0.05); position: relative; margin: 0 -10px; -} -.packet-stream { - position: absolute; top: 50%; left: 0; width: 100%; height: 100%; transform: translateY(-50%); overflow: hidden; -} -.packet { - position: absolute; width: 8px; height: 8px; background: var(--accent); - border-radius: 50%; box-shadow: 0 0 10px var(--accent); opacity: 0; - animation: travel 1.5s infinite linear; -} -.packet:nth-child(2) { animation-delay: 0.5s; } -.packet:nth-child(3) { animation-delay: 1.0s; } -@keyframes travel { - 0% { left: 0%; opacity: 0; } - 10% { opacity: 1; } - 90% { opacity: 1; } - 100% { left: 100%; opacity: 0; } -} - -.timeout-badge { - position: absolute; - top: -30px; - left: 50%; - transform: translateX(-50%); - background: rgba(15, 23, 42, 0.9); - border: 1px solid var(--warning); - color: var(--warning); - padding: 4px 10px; - border-radius: 6px; +body:has(#view-connected.active) .status-badge { font-size: 0.8rem; - font-family: var(--font-mono); - font-weight: bold; - z-index: 5; - box-shadow: 0 4px 15px rgba(245, 158, 11, 0.2); - backdrop-filter: blur(4px); } -/* --- TERMINAL --- */ -.terminal-card { - padding: 0; overflow: hidden; - background: #09090b; /* Even darker for terminal */ - border: 1px solid rgba(255,255,255,0.08); - display: flex; flex-direction: column; - border-radius: 12px; -} -.terminal-header { - background: #18181b; padding: 0.6rem 1rem; display: flex; align-items: center; - gap: 8px; border-bottom: 1px solid rgba(255,255,255,0.05); -} -.terminal-header .dot { width: 10px; height: 10px; border-radius: 50%; } -.terminal-header .red { background: #ef4444; } -.terminal-header .yellow { background: #f59e0b; } -.terminal-header .green { background: #10b981; } -.terminal-header .title { margin-left: 0.5rem; font-size: 0.75rem; font-weight: 600; color: #71717a; font-family: var(--font-family); letter-spacing: 0.02em; } -.terminal-body { - padding: 1.25rem; height: 220px; - overflow-y: auto; - overflow-x: auto; - font-family: var(--font-mono); - font-size: 0.85rem; color: #e4e4e7; display: flex; flex-direction: column; gap: 6px; -} -.terminal-body::-webkit-scrollbar { width: 8px; height: 8px; } -.terminal-body::-webkit-scrollbar-track { background: #18181b; } -.terminal-body::-webkit-scrollbar-thumb { background: #3f3f46; border-radius: 4px; } -.terminal-body::-webkit-scrollbar-corner { background: #18181b; } -.log-line { - line-height: 1.5; - white-space: nowrap; +/* --- Dashboard / Panels --- */ +.dashboard-grid { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 2rem; + height: 100%; + align-content: stretch; } -.log-line.system { color: #71717a; font-style: italic; } -.log-line.out { color: var(--accent); } -.log-line.in { color: var(--success); } -.log-timestamp { color: #52525b; margin-right: 12px; user-select: none; font-size: 0.75rem; } -/* --- CHAT INTERFACE --- */ -.chat-container { - display: flex; - flex-direction: column; - height: calc(100vh - 60px); /* Full height minus compact header */ - background: var(--card-bg); - border-radius: 0; /* No border radius for fullscreen */ - border: none; - backdrop-filter: blur(12px); - -webkit-backdrop-filter: blur(12px); - overflow: hidden; +.dashboard-col { display: flex; flex-direction: column; } + +.panel { + background: rgba(255,255,255,0.02); + border: 1px solid rgba(255,255,255,0.1); + padding: 2rem; position: relative; + display: flex; flex-direction: column; + transition: box-shadow 0.3s ease; } +.panel:hover { box-shadow: 0 0 20px rgba(0, 243, 255, 0.05); border-color: rgba(0, 243, 255, 0.2); } +.panel.full-height { height: 100%; } -.chat-header { - background: rgba(15, 23, 42, 0.8); - padding: 0.875rem 1.5rem; /* Reduced padding for compact look */ - border-bottom: 1px solid rgba(255, 255, 255, 0.08); - display: flex; - align-items: center; - justify-content: space-between; - min-height: 60px; /* Fixed height like WhatsApp */ +.panel-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 2rem; } +.panel-header.compact { margin-bottom: 1rem; } +.panel-title { font-size: 1rem; letter-spacing: 3px; color: var(--accent); text-shadow: 0 0 8px var(--accent); } +.nat-badge { font-family: var(--font-mono); font-size: 0.9rem; background: #fff; color: #000; padding: 4px 8px; } +.nat-badge.cone { color: #000; background: var(--success); } +.nat-badge.symmetric { color: #fff; background: var(--danger); } + +.config-display { display: flex; flex-direction: column; gap: 3rem; justify-content: center; flex: 1; } +.config-item { border-bottom: 1px solid rgba(255,255,255,0.05); padding-bottom: 1rem; } +.info-label { font-size: 0.8rem; color: var(--text-dim); margin-bottom: 0.5rem; letter-spacing: 1px; } +.info-val-group.large { display: flex; justify-content: space-between; align-items: center; } +.info-val-group.large .ip-text { font-size: 1.8rem; color: #fff; } + +.icon-btn { + background: transparent; border: 1px solid var(--text-dim); + color: var(--text-dim); padding: 4px 10px; cursor: pointer; + font-size: 0.8rem; font-family: var(--font-mono); transition: 0.2s; } +.icon-btn:hover { border-color: var(--accent); color: var(--accent); box-shadow: 0 0 15px var(--accent); } -.chat-peer-info { - display: flex; - align-items: center; - gap: 1rem; +.connect-form-layout { display: flex; flex-direction: column; justify-content: center; flex: 1; } +.input-grid { display: flex; gap: 1.5rem; margin-bottom: 2rem; } +.input-group { flex: 1; display: flex; flex-direction: column; gap: 0.8rem; } +.input-group.small { flex: 0.4; } +label { font-size: 0.8rem; color: var(--text-dim); letter-spacing: 1px; } + +input { + background: rgba(0,0,0,0.5); border: 1px solid rgba(255,255,255,0.2); + color: var(--accent); padding: 1.2rem; font-family: var(--font-mono); + font-size: 1.4rem; width: 100%; outline: none; transition: 0.3s; } +input:focus { border-color: var(--accent); box-shadow: 0 0 20px var(--accent-dim); } +input.valid { border-color: var(--success); box-shadow: 0 0 10px rgba(0, 255, 157, 0.2); } +input.error { border-color: var(--danger); box-shadow: 0 0 10px rgba(255, 0, 60, 0.2); } -.peer-avatar { - width: 38px; /* Slightly smaller for compact header */ - height: 38px; - border-radius: 50%; - background: rgba(34, 211, 238, 0.15); - display: flex; - align-items: center; - justify-content: center; - color: var(--accent); - border: 2px solid rgba(34, 211, 238, 0.3); +.validation-error { color: var(--danger); font-size: 0.8rem; display: none; margin-top: 5px; } + +.btn-primary { + width: 100%; padding: 1.5rem; + background: var(--accent); color: #000; + border: none; font-family: var(--font-ui); + font-weight: bold; font-size: 1.2rem; letter-spacing: 3px; + cursor: pointer; transition: 0.3s; margin-top: auto; } +.btn-primary:hover:not(:disabled) { background: #fff; box-shadow: 0 0 30px rgba(255,255,255,0.5); } +.btn-primary:disabled { background: #333; color: #555; cursor: not-allowed; } -.peer-details { - display: flex; - flex-direction: column; - gap: 0.2rem; /* Reduced gap */ +.punch-grid { + display: grid; grid-template-columns: 40% 1fr; + gap: 2rem; height: 100%; min-height: 0; padding-bottom: 1rem; } -.peer-name { - font-weight: 600; - color: var(--text-primary); - font-size: 0.9rem; /* Slightly smaller */ +.matchmaking-column { + display: flex; flex-direction: column; justify-content: center; + align-items: center; border-right: 1px dashed rgba(255,255,255,0.15); + padding-right: 2rem; } -.peer-address { - font-size: 0.7rem; /* Smaller for compact look */ - color: var(--text-secondary); - font-family: var(--font-mono); +.matchmaking-display { + position: relative; width: 100%; flex: 1; + display: flex; justify-content: center; align-items: center; } -.chat-messages { - flex: 1; - overflow-y: auto; - padding: 1.5rem; - display: flex; - flex-direction: column; - gap: 0.75rem; - background: rgba(0, 0, 0, 0.2); +.match-ring { + position: absolute; width: 350px; height: 350px; + border: 2px solid var(--accent); border-radius: 50%; + animation: pulse 2s infinite; opacity: 0.5; +} +.match-ring-inner { + position: absolute; width: 280px; height: 280px; + border: 4px dashed rgba(255,255,255,0.3); border-radius: 50%; + animation: spin 10s linear infinite; } -.chat-messages::-webkit-scrollbar { width: 8px; } -.chat-messages::-webkit-scrollbar-track { background: rgba(0, 0, 0, 0.2); } -.chat-messages::-webkit-scrollbar-thumb { background: rgba(255, 255, 255, 0.1); border-radius: 4px; } +@keyframes pulse { 0% { transform: scale(0.95); opacity: 0.2; } 50% { transform: scale(1.05); opacity: 0.6; } 100% { transform: scale(0.95); opacity: 0.2; } } +@keyframes spin { 100% { transform: rotate(360deg); } } -.chat-welcome { - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - text-align: center; - padding: 2rem 1rem; - color: var(--text-secondary); -} +.timer-container { z-index: 2; text-align: center; } +.timer-label { font-size: 1rem; letter-spacing: 3px; margin-bottom: 1rem; animation: blink 1s infinite; } +.timer-value { font-size: 5rem; font-family: var(--font-mono); color: #fff; text-shadow: 0 0 30px #fff; } +@keyframes blink { 50% { opacity: 0.5; } } -.welcome-icon { - width: 72px; - height: 72px; - border-radius: 50%; - background: rgba(16, 185, 129, 0.1); - display: flex; - align-items: center; - justify-content: center; - color: var(--success); - margin-bottom: 1rem; - border: 2px solid rgba(16, 185, 129, 0.2); +.abort-container { width: 80%; } +.btn-secondary { + background: transparent; border: 1px solid var(--danger); + color: var(--danger); padding: 1rem 0; width: 100%; cursor: pointer; + font-family: var(--font-ui); letter-spacing: 3px; transition: 0.3s; + font-weight: bold; font-size: 1rem; } +.btn-secondary:hover:not(:disabled) { background: var(--danger); color: #000; box-shadow: 0 0 25px var(--danger); } +.btn-secondary:disabled { opacity: 0.5; cursor: not-allowed; } -.chat-welcome h3 { - color: var(--text-primary); - margin-bottom: 0.5rem; - font-size: 1.1rem; - font-weight: 600; +.logs-column { display: flex; flex-direction: column; height: 100%; min-height: 0; } +.terminal-container { + flex: 1; background: #000; border: 1px solid rgba(255,255,255,0.15); + position: relative; overflow: hidden; display: flex; flex-direction: column; } +.terminal-logs { + flex: 1; overflow-y: auto; padding: 1.5rem; + font-family: var(--font-mono); font-size: 0.9rem; color: var(--text-dim); + display: flex; flex-direction: column; gap: 10px; +} +.terminal-logs::-webkit-scrollbar { width: 8px; } +.terminal-logs::-webkit-scrollbar-track { background: #000; } +.terminal-logs::-webkit-scrollbar-thumb { background: #333; } +.terminal-logs::-webkit-scrollbar-thumb:hover { background: var(--accent); } -.chat-welcome p { - font-size: 0.85rem; +.log-line { + border-left: 3px solid var(--accent); padding-left: 15px; + line-height: 1.5; word-break: break-all; } +.log-line:last-child { color: var(--accent); animation: flashLog 0.5s; } +@keyframes flashLog { from { background: rgba(255,255,255,0.2); } to { background: transparent; } } + +.chat-interface { display: flex; flex-direction: column; height: 100%; } +.chat-header { + display: flex; justify-content: space-between; align-items: center; + /* Reduced padding and height */ + padding: 0 2rem; + border-bottom: 1px solid rgba(255,255,255,0.1); + background: rgba(0,0,0,0.5); + height: 50px; +} +.peer-status { display: flex; gap: 15px; align-items: center; } +.peer-icon { width: 24px; height: 24px; background: var(--success); clip-path: polygon(20% 0, 100% 0, 100% 80%, 80% 100%, 0 100%, 0 20%); } +.peer-name { font-size: 0.9rem; letter-spacing: 2px; color: var(--success); font-weight: bold; } +.peer-ip { font-size: 0.8rem; font-family: var(--font-mono); color: var(--text-dim); } +.btn-danger { background: rgba(255, 0, 60, 0.2); border: 1px solid var(--danger); color: var(--danger); padding: 4px 12px; cursor: pointer; font-size: 0.8rem; letter-spacing: 2px; transition: 0.3s; } +.btn-danger:hover { background: var(--danger); color: #fff; box-shadow: 0 0 15px var(--danger); } + +.chat-viewport { + flex: 1; overflow-y: auto; padding: 2rem 4rem; + font-family: var(--font-mono); font-size: 1rem; + display: flex; flex-direction: column; gap: 0.8rem; + background: rgba(0,0,0,0.3); +} +.system-msg { text-align: center; color: var(--success); font-size: 0.9rem; margin: 2rem 0; opacity: 0.8; letter-spacing: 2px; } + +/* -- Logic Script Chat Styles -- */ +.chat-welcome { text-align: center; color: var(--text-dim); margin-top: 2rem; } +.chat-welcome h3 { color: var(--accent); margin: 10px 0; } -/* Message bubbles */ .message { display: flex; margin-bottom: 0.5rem; animation: messageSlide 0.2s ease-out; } - @keyframes messageSlide { - from { - opacity: 0; - transform: translateY(10px); - } - to { - opacity: 1; - transform: translateY(0); - } -} - -.message.from-me { - justify-content: flex-end; + from { opacity: 0; transform: translateY(10px); } + to { opacity: 1; transform: translateY(0); } } -.message.from-peer { - justify-content: flex-start; -} +.message.from-me { justify-content: flex-end; } +.message.from-peer { justify-content: flex-start; } .message-bubble { max-width: 70%; - padding: 0.75rem 1rem; - border-radius: 16px; - word-wrap: break-word; - position: relative; + padding: 1rem 1.5rem; + font-family: var(--font-mono); font-size: 0.9rem; - line-height: 1.4; + position: relative; + border: 1px solid rgba(255,255,255,0.1); } .message.from-me .message-bubble { - background: linear-gradient(135deg, var(--accent) 0%, #06b6d4 100%); - color: #0f172a; - border-bottom-right-radius: 4px; - box-shadow: 0 2px 8px rgba(34, 211, 238, 0.3); + background: rgba(0, 243, 255, 0.1); + border-color: var(--accent); + color: #fff; + clip-path: polygon(10px 0, 100% 0, 100% calc(100% - 10px), calc(100% - 10px) 100%, 0 100%, 0 10px); } .message.from-peer .message-bubble { - background: rgba(30, 41, 59, 0.8); - color: var(--text-primary); - border-bottom-left-radius: 4px; - border: 1px solid rgba(255, 255, 255, 0.08); + background: rgba(255, 255, 255, 0.05); + color: var(--text-main); + clip-path: polygon(0 0, calc(100% - 10px) 0, 100% 10px, 100% 100%, 10px 100%, 0 calc(100% - 10px)); } .message-time { - font-size: 0.7rem; - opacity: 0.6; - margin-top: 0.25rem; - display: block; -} - -.message.from-me .message-time { - text-align: right; + display: block; font-size: 0.7rem; opacity: 0.5; margin-top: 5px; text-align: right; } -.message.from-peer .message-time { - text-align: left; - color: var(--text-secondary); +.chat-input-area { + display: flex; padding: 2rem 4rem; border-top: 1px solid rgba(255,255,255,0.1); + background: rgba(0,0,0,0.8); align-items: center; gap: 20px; } +.prompt { color: var(--accent); font-family: var(--font-mono); font-size: 1.2rem; } +#chatInput { background: transparent; border: none; padding: 0; font-size: 1.2rem; flex: 1; } +.btn-send { background: var(--text-main); color: #000; border: none; padding: 10px 30px; cursor: pointer; font-weight: bold; font-size: 1rem; letter-spacing: 2px; } -/* Chat input */ -.chat-input-container { - padding: 1rem 1.5rem; - background: rgba(15, 23, 42, 0.8); - border-top: 1px solid rgba(255, 255, 255, 0.08); -} - -.chat-input-form { - display: flex; - gap: 0.75rem; - align-items: center; -} - -.chat-input-form input { - flex: 1; - padding: 0.875rem 1.25rem; - background: rgba(0, 0, 0, 0.3); - border: 1px solid rgba(255, 255, 255, 0.1); - border-radius: 24px; - color: var(--text-primary); - font-size: 0.95rem; - transition: all 0.2s ease; -} - -.chat-input-form input:focus { - outline: none; - border-color: var(--accent); - box-shadow: 0 0 0 3px rgba(34, 211, 238, 0.1); - background: rgba(0, 0, 0, 0.4); -} - -.send-btn { - width: 44px; - height: 44px; - border-radius: 50%; - background: var(--accent); - border: none; - color: #0f172a; - display: flex; - align-items: center; - justify-content: center; - cursor: pointer; - transition: all 0.2s ease; - box-shadow: 0 4px 12px rgba(34, 211, 238, 0.3); -} - -.send-btn:hover:not(:disabled) { - background: var(--accent-hover); - transform: scale(1.05); - box-shadow: 0 6px 16px rgba(34, 211, 238, 0.4); -} - -.send-btn:active:not(:disabled) { - transform: scale(0.95); -} - -.send-btn:disabled { - opacity: 0.5; - cursor: not-allowed; - background: rgba(255, 255, 255, 0.1); -} - -/* --- Toast --- */ .toast { - position: fixed; bottom: 2rem; left: 50%; transform: translateX(-50%) translateY(100px); - background-color: #1e293b; color: var(--text-primary); padding: 0.875rem 1.75rem; - border-radius: 99px; font-size: 0.9rem; font-weight: 500; opacity: 0; - transition: all 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275); - box-shadow: 0 10px 30px rgba(0, 0, 0, 0.5); - display: flex; align-items: center; gap: 10px; pointer-events: none; z-index: 100; - border: 1px solid rgba(255,255,255,0.1); + position: fixed; bottom: 50px; left: 50%; transform: translateX(-50%); + background: rgba(0,0,0,0.95); border: 1px solid var(--accent); + color: var(--accent); padding: 15px 30px; + font-family: var(--font-mono); font-size: 1rem; + font-weight: bold; z-index: 1000; + pointer-events: none; + opacity: 0; + transition: opacity 0.5s ease-in-out; + box-shadow: 0 0 30px var(--accent-dim); } -.toast.show { transform: translateX(-50%) translateY(0); opacity: 1; } +.toast.show { opacity: 1; }