├── .github └── FUNDING.yml ├── LICENSE ├── README.md ├── android-chrome-192x192.png ├── android-chrome-512x512.png ├── api_proxy.php ├── apple-touch-icon.png ├── ball.png ├── beta ├── LICENSE ├── README.md ├── android-chrome-192x192.png ├── android-chrome-512x512.png ├── api_proxy.php ├── apple-touch-icon.png ├── ball.png ├── css │ ├── base.css │ ├── components.css │ ├── layout.css │ ├── modals.css │ ├── responsive.css │ ├── scrollbars.css │ └── utilities.css ├── favicon-16x16.png ├── favicon-32x32.png ├── favicon.ico ├── index.html ├── js │ ├── api.js │ ├── core.js │ ├── editor.js │ ├── gemini-api.js │ ├── history.js │ ├── home.js │ ├── ldb.js │ ├── lz-string.min.js │ ├── main.js │ ├── openrouter-api.js │ ├── simulation.js │ ├── storage.js │ └── ui.js ├── logo.png ├── script.js ├── site.webmanifest ├── styles.css └── websites │ └── example.html ├── favicon-16x16.png ├── favicon-32x32.png ├── favicon.ico ├── index.html ├── logo.png ├── script.js ├── site.webmanifest ├── styles.css └── websites └── example.html /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: techcow2 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: techrayappsllc 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry 12 | polar: # Replace with a single Polar username 13 | buy_me_a_coffee: # Replace with a single Buy Me a Coffee username 14 | thanks_dev: # Replace with a single thanks.dev username 15 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 16 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 techcow2 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 🎱 **NetSim - AI Powered Simulations** 2 | 3 | ## 🚨 **BETA UPDATE ANNOUNCEMENT** 🚨 4 | ### April 2, 2025 5 | 6 | **A new beta build has been added to the beta folder. Please read the README file in that folder for detailed information about what has been updated, what works, and what doesn't.** 7 | 8 | ## Overview 9 | 10 | **NetSim** is an AI-powered web application designed to generate highly interactive and detailed web simulations based on user descriptions. Utilizing state-of-the-art AI models, NetSim creates rich, immersive web experiences that are fully functional and customizable. This repository contains the source code for the NetSim project, including the front-end interface, API proxy script, and necessary configuration files. 11 | 12 | ## 🌟 **Features** 13 | 14 | - **🚀 Instant Web Generation:** Create interactive web applications or websites by simply entering a description 15 | - **🖼️ Integrated Pixabay Images:** Automatically include high-quality images from Pixabay in your generated simulations to enhance visual appeal 16 | - **🖥️ Interactive Simulated Browser:** Experience your generated web content within a simulated browser environment 17 | - **✏️ Element Editing:** Modify specific elements of your simulation with easy-to-use right-click options 18 | - **🔖 Bookmarking and Publishing:** Save your work with bookmarks, publish your simulations, and share them with others 19 | - **🔄 Model Selection:** Choose from different AI models to tailor the generation process to your needs 20 | 21 | ## 🛠️ **Getting Started** 22 | 23 | ### Prerequisites 24 | 25 | To run this project locally, you will need: 26 | 27 | - A web server capable of running PHP (e.g., Apache, Nginx) 28 | - An API key from [OpenRouter](https://openrouter.ai) to power the AI models used in the simulations 29 | - An API key from [Pixabay](https://pixabay.com/api/docs/) for fetching images 30 | - A modern web browser (Google Chrome, Firefox, etc.) 31 | 32 | ### Installation 33 | 34 | 1. **Clone the Repository:** 35 | 36 | Clone the repository to your local machine using Git: 37 | 38 | ```bash 39 | git clone https://github.com/techcow2/netsim.git 40 | cd netsim 41 | ``` 42 | 43 | 2. **Configure API Keys:** 44 | 45 | The project requires API keys to interact with the OpenRouter API and Pixabay. To configure the script with your API keys: 46 | 47 | - **Open the `api_proxy.php` file:** 48 | - Replace the placeholder values `YOUR_OPENROUTER_API_KEY` with your actual API keys in the following lines: 49 | - `$openrouter_api_key2 = 'YOUR_OPENROUTER_API_KEY';` 50 | - `$openrouter_api_key = 'YOUR_OPENROUTER_API_KEY';` 51 | 52 | Example: 53 | ```php 54 | $openrouter_api_key2 = 'your-actual-api-key-1'; 55 | $openrouter_api_key = 'your-actual-api-key-2'; 56 | ``` 57 | 58 | - **Open the `script.js` file:** 59 | - Replace the placeholder `'YOUR_PIXABAY_API_KEY'` in the `fetchPixabayImages` function with your actual Pixabay API key: 60 | 61 | Example: 62 | ```javascript 63 | const apiKey = 'your-actual-api-key'; 64 | ``` 65 | 66 | - **Optional: Change the Website URL for Publishing:** 67 | - If you need to change the base URL used for publishing simulations: 68 | - In the `api_proxy.php` file, locate the following lines: 69 | 70 | ```php 71 | 'HTTP-Referer: https://YOUR_WEBSITE_HERE.com', 72 | ... 73 | $baseUrl = 'https://YOUR_WEBSITE_HERE.com'; 74 | ``` 75 | 76 | - Replace `YOUR_WEBSITE_HERE` with your actual domain: 77 | 78 | Example: 79 | ```php 80 | 'HTTP-Referer: https://example.com', 81 | ... 82 | $baseUrl = 'https://example.com'; 83 | ``` 84 | 85 | - **Save the Files:** 86 | - Save the changes to your `api_proxy.php` and `script.js` files. 87 | 88 | ## 🔧 **Usage** 89 | 90 | 1. **Creating Simulations:** 91 | - Enter a description of the web application or website you want to create in the address bar 92 | - Press "Create" or hit Enter to generate your simulation 93 | 94 | 2. **Editing Simulations:** 95 | - Right-click on elements within the simulation to modify them. Use the editor to update content or styles as needed 96 | 97 | 3. **Saving and Publishing:** 98 | - Bookmark your simulations for later access 99 | - Publish simulations to generate a shareable link 100 | 101 | 4. **Model Selection:** 102 | - Use the model selection tool in the toolbar to choose different AI models, such as `Claude 3.5 Sonnet` or `GPT-4o`, depending on your needs 103 | 104 | ## 🗃️ **Data Storage and Persistence** 105 | 106 | All data within NetSim, including your simulations, bookmarks, and history, is stored in your local browser's storage. This means: 107 | 108 | - **📦 Local Storage:** Everything you create or save is stored in your browser's local storage. This data persists across sessions as long as your browser's cache and history are intact 109 | - **⚠️ Data Loss:** If you clear your browser's cache or history, all stored data, including simulations, bookmarks, and project history, will be permanently lost. Make sure to export or back up important simulations if you plan to clear your browser data 110 | 111 | ## 🔒 **Security Considerations** 112 | 113 | - **🔑 Protecting API Keys:** 114 | - Ensure that your server is secure and that access to the `api_proxy.php` file is restricted to prevent unauthorized use 115 | 116 | - **🔐 HTTPS:** 117 | - Use HTTPS to encrypt all data transmitted between the client and server, including API keys and other sensitive information 118 | 119 | ## 📝 **To-Do List** 120 | 121 | ~~- **🔄 Fix the revision history feature:** Improve the functionality to properly track and manage different versions of simulations.~~ 122 | - **🧪 Provide different examples using other models:** Create and document simulations generated using various AI models to showcase the capabilities of each model 123 | 124 | ## 🤝 **Contributing** 125 | 126 | Contributions are welcome! If you have suggestions for new features or improvements, feel free to submit a pull request or open an issue. 127 | 128 | ## 📄 **License** 129 | 130 | This project is licensed under the MIT License. See the [LICENSE](LICENSE) file for more details 131 | -------------------------------------------------------------------------------- /android-chrome-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/techcow2/NetSim/d3e36d9ecd54376d2f91887a61b6b43a32bd8c25/android-chrome-192x192.png -------------------------------------------------------------------------------- /android-chrome-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/techcow2/NetSim/d3e36d9ecd54376d2f91887a61b6b43a32bd8c25/android-chrome-512x512.png -------------------------------------------------------------------------------- /api_proxy.php: -------------------------------------------------------------------------------- 1 | 'Method not allowed']); 19 | exit; 20 | } 21 | 22 | 23 | if (isset($_FILES['file'])) { 24 | handleFileUpload(); 25 | exit; 26 | } 27 | 28 | 29 | $input = file_get_contents('php://input'); 30 | $data = json_decode($input, true); 31 | 32 | 33 | if (!isset($data['messages']) || !is_array($data['messages']) || !isset($data['model'])) { 34 | http_response_code(400); 35 | echo json_encode(['error' => 'Invalid input']); 36 | exit; 37 | } 38 | 39 | 40 | if ($data['model'] === 'gpt-4o') { 41 | $url = 'https://openrouter.ai/api/v1/chat/completions'; 42 | $api_key = $openrouter_api_key2; 43 | $headers = [ 44 | 'Authorization: Bearer ' . $api_key, 45 | 'Content-Type: application/json' 46 | ]; 47 | $model = 'openai/gpt-4o-2024-05-13'; 48 | } elseif ($data['model'] === 'claude-3.5-sonnet') { 49 | $url = 'https://openrouter.ai/api/v1/chat/completions'; 50 | $api_key = $openrouter_api_key; 51 | $headers = [ 52 | 'Authorization: Bearer ' . $api_key, 53 | 'Content-Type: application/json', 54 | 'HTTP-Referer: https://YOUR_WEBSITE_HERE.com', 55 | 'X-Title: NetSim Web Simulator' 56 | ]; 57 | $model = 'anthropic/claude-3.5-sonnet'; 58 | } else { 59 | http_response_code(400); 60 | echo json_encode(['error' => 'Invalid model']); 61 | exit; 62 | } 63 | 64 | 65 | $ch = curl_init($url); 66 | curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); 67 | curl_setopt($ch, CURLOPT_POST, true); 68 | curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode([ 69 | 'model' => $model, 70 | 'messages' => $data['messages'], 71 | 'max_tokens' => 8192, 72 | 'temperature' => 1.0 73 | ])); 74 | curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); 75 | 76 | 77 | $response = curl_exec($ch); 78 | $http_status = curl_getinfo($ch, CURLINFO_HTTP_CODE); 79 | curl_close($ch); 80 | 81 | 82 | http_response_code($http_status); 83 | 84 | 85 | echo $response; 86 | 87 | 88 | function handleFileUpload() { 89 | if ($_FILES['file']['error'] !== UPLOAD_ERR_OK) { 90 | http_response_code(400); 91 | echo json_encode(['error' => 'File upload failed']); 92 | exit; 93 | } 94 | 95 | $uniqueId = uniqid(); 96 | $filePath = __DIR__ . '/websites/' . $uniqueId . '.html'; 97 | 98 | if (move_uploaded_file($_FILES['file']['tmp_name'], $filePath)) { 99 | $baseUrl = 'https://YOUR_WEBSITE_HERE.com'; 100 | $generatedUrl = $baseUrl . '/websites/' . $uniqueId . '.html'; 101 | echo json_encode(['url' => $generatedUrl]); 102 | } else { 103 | http_response_code(500); 104 | echo json_encode(['error' => 'Failed to save the website']); 105 | } 106 | } 107 | ?> 108 | -------------------------------------------------------------------------------- /apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/techcow2/NetSim/d3e36d9ecd54376d2f91887a61b6b43a32bd8c25/apple-touch-icon.png -------------------------------------------------------------------------------- /ball.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/techcow2/NetSim/d3e36d9ecd54376d2f91887a61b6b43a32bd8c25/ball.png -------------------------------------------------------------------------------- /beta/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 techcow2 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /beta/README.md: -------------------------------------------------------------------------------- 1 | # 🧪 NetSim Beta Build 2 | 3 | ## Overview 4 | 5 | Welcome to the beta version of NetSim! This experimental build introduces several new features and improvements, but also contains some known issues. Please read this document carefully before using the beta build. 6 | 7 | ## ✨ New Features 8 | 9 | ### 🔑 In-App API Key Management 10 | - Configure your API keys directly from the user interface 11 | - No need to modify source files manually 12 | - Secure local storage of your credentials 13 | 14 | ### 🧠 Expanded Model Support 15 | - **Deepseek Integration**: Access to Deepseek's powerful AI models 16 | - **Gemini Support**: Use Google's Gemini models for simulations 17 | - Configure model-specific settings through the new settings panel 18 | 19 | ### 🚀 Performance Improvements 20 | - **Increased Token Limits**: Generate longer, more complex simulations 21 | - Improved handling of complex rendering tasks 22 | - Better resource management for smoother performance 23 | 24 | ## ⚠️ Known Issues 25 | 26 | ### Critical Issues 27 | - **⛔ Back Button Functionality**: The browser back button is currently non-functional. Use the in-app navigation instead. 28 | - **⛔ Saving Mechanism**: The simulation saving feature is broken in this build. Please export any important work manually. 29 | 30 | ### Minor Issues 31 | - Occasional rendering glitches with complex simulations 32 | - Some CSS inconsistencies when using certain models 33 | - Limited compatibility with older browsers 34 | 35 | ## 🛠️ Installation 36 | 37 | 1. Navigate to the `beta` folder in the main repository 38 | 2. Follow the standard installation instructions from the main README 39 | 3. Note that this build requires the same prerequisites as the main build 40 | 41 | ## 🔍 Testing Focus 42 | 43 | We're particularly interested in feedback on the following areas: 44 | 45 | 1. The new API key management interface 46 | 2. Performance of the newly supported models 47 | 3. Comparison of simulation quality between different models 48 | 4. Overall stability during extended usage 49 | 50 | ## ⏪ Reverting to Stable 51 | 52 | If you encounter critical issues with the beta build, you can revert to the stable version by: 53 | 54 | 1. Returning to the main directory 55 | 2. Following the standard installation instructions in the main README 56 | 3. Note that simulations created in the beta build might not be compatible with the stable version 57 | 58 | ## 🙏 Thank You 59 | 60 | Thank you for helping test the next version of NetSim! Your participation helps make this project better for everyone. 61 | -------------------------------------------------------------------------------- /beta/android-chrome-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/techcow2/NetSim/d3e36d9ecd54376d2f91887a61b6b43a32bd8c25/beta/android-chrome-192x192.png -------------------------------------------------------------------------------- /beta/android-chrome-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/techcow2/NetSim/d3e36d9ecd54376d2f91887a61b6b43a32bd8c25/beta/android-chrome-512x512.png -------------------------------------------------------------------------------- /beta/api_proxy.php: -------------------------------------------------------------------------------- 1 | 'Method not allowed']); 19 | exit; 20 | } 21 | 22 | 23 | if (isset($_FILES['file'])) { 24 | handleFileUpload(); 25 | exit; 26 | } 27 | 28 | 29 | $input = file_get_contents('php://input'); 30 | $data = json_decode($input, true); 31 | 32 | 33 | if (!isset($data['messages']) || !is_array($data['messages']) || !isset($data['model'])) { 34 | http_response_code(400); 35 | echo json_encode(['error' => 'Invalid input']); 36 | exit; 37 | } 38 | 39 | 40 | if ($data['model'] === 'gpt-4o') { 41 | $url = 'https://openrouter.ai/api/v1/chat/completions'; 42 | $api_key = $openrouter_api_key2; 43 | $headers = [ 44 | 'Authorization: Bearer ' . $api_key, 45 | 'Content-Type: application/json' 46 | ]; 47 | $model = 'openai/gpt-4o-2024-05-13'; 48 | } elseif ($data['model'] === 'claude-3.5-sonnet') { 49 | $url = 'https://openrouter.ai/api/v1/chat/completions'; 50 | $api_key = $openrouter_api_key; 51 | $headers = [ 52 | 'Authorization: Bearer ' . $api_key, 53 | 'Content-Type: application/json', 54 | 'HTTP-Referer: https://YOUR_WEBSITE_HERE.com', 55 | 'X-Title: NetSim Web Simulator' 56 | ]; 57 | $model = 'anthropic/claude-3.5-sonnet'; 58 | } else { 59 | http_response_code(400); 60 | echo json_encode(['error' => 'Invalid model']); 61 | exit; 62 | } 63 | 64 | 65 | $ch = curl_init($url); 66 | curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); 67 | curl_setopt($ch, CURLOPT_POST, true); 68 | curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode([ 69 | 'model' => $model, 70 | 'messages' => $data['messages'], 71 | 'max_tokens' => 8192, 72 | 'temperature' => 1.0 73 | ])); 74 | curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); 75 | 76 | 77 | $response = curl_exec($ch); 78 | $http_status = curl_getinfo($ch, CURLINFO_HTTP_CODE); 79 | curl_close($ch); 80 | 81 | 82 | http_response_code($http_status); 83 | 84 | 85 | echo $response; 86 | 87 | 88 | function handleFileUpload() { 89 | if ($_FILES['file']['error'] !== UPLOAD_ERR_OK) { 90 | http_response_code(400); 91 | echo json_encode(['error' => 'File upload failed']); 92 | exit; 93 | } 94 | 95 | $uniqueId = uniqid(); 96 | $filePath = __DIR__ . '/websites/' . $uniqueId . '.html'; 97 | 98 | if (move_uploaded_file($_FILES['file']['tmp_name'], $filePath)) { 99 | $baseUrl = 'https://YOUR_WEBSITE_HERE.com'; 100 | $generatedUrl = $baseUrl . '/websites/' . $uniqueId . '.html'; 101 | echo json_encode(['url' => $generatedUrl]); 102 | } else { 103 | http_response_code(500); 104 | echo json_encode(['error' => 'Failed to save the website']); 105 | } 106 | } 107 | ?> 108 | -------------------------------------------------------------------------------- /beta/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/techcow2/NetSim/d3e36d9ecd54376d2f91887a61b6b43a32bd8c25/beta/apple-touch-icon.png -------------------------------------------------------------------------------- /beta/ball.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/techcow2/NetSim/d3e36d9ecd54376d2f91887a61b6b43a32bd8c25/beta/ball.png -------------------------------------------------------------------------------- /beta/css/base.css: -------------------------------------------------------------------------------- 1 | /* BASE STYLES - Typography, reset, and basic elements */ 2 | 3 | body, html { 4 | margin: 0; 5 | padding: 0; 6 | height: 100%; 7 | font-family: 'Poppins', sans-serif; 8 | background-color: #8ecae6; 9 | overflow: hidden; 10 | position: fixed; 11 | width: 100%; 12 | } 13 | 14 | @keyframes spin { 15 | 0% { transform: rotate(0deg); } 16 | 100% { transform: rotate(360deg); } 17 | } -------------------------------------------------------------------------------- /beta/css/components.css: -------------------------------------------------------------------------------- 1 | /* COMPONENTS STYLES - UI components like buttons, inputs, panels */ 2 | 3 | /* Address Bar */ 4 | #addressbar-container { 5 | display: flex; 6 | align-items: center; 7 | flex-grow: 1; 8 | background: white; 9 | border-radius: 20px; 10 | margin: 0 8px; 11 | padding: 2px 8px; 12 | position: relative; 13 | } 14 | 15 | #addressbar-container:focus-within { 16 | box-shadow: 0 0 0 2px rgba(52, 152, 219,0.5); 17 | } 18 | 19 | #addressbar { 20 | flex-grow: 1; 21 | border: none; 22 | outline: none; 23 | font-size: 14px; 24 | padding: 6px 8px; 25 | width: 100%; 26 | } 27 | 28 | #addressbar-container .btn { 29 | background-color: #3498db; 30 | color: white; 31 | border-radius: 50%; 32 | width: 32px; 33 | height: 32px; 34 | display: flex; 35 | align-items: center; 36 | justify-content: center; 37 | margin-left: 8px; 38 | } 39 | 40 | #addressbar-container .btn:hover { 41 | background-color: #2980b9; 42 | } 43 | 44 | /* Button Styles */ 45 | .btn { 46 | background: transparent; 47 | border: none; 48 | cursor: pointer; 49 | padding: 6px; 50 | margin: 0 2px; 51 | color: #ecf0f1; 52 | border-radius: 50%; 53 | display: flex; 54 | align-items: center; 55 | justify-content: center; 56 | } 57 | 58 | .btn:hover { 59 | background-color: rgba(255,255,255,0.1); 60 | } 61 | 62 | .btn:active { 63 | background-color: rgba(255,255,255,0.2); 64 | } 65 | 66 | .btn svg { 67 | width: 18px; 68 | height: 18px; 69 | } 70 | 71 | /* Bookmarks Panel */ 72 | #bookmarks-panel { 73 | position: absolute; 74 | top: 50px; 75 | right: 10px; 76 | background: white; 77 | border: 1px solid #ccc; 78 | border-radius: 8px; 79 | box-shadow: 0 4px 20px rgba(0,0,0,0.1); 80 | display: none; 81 | width: 300px; 82 | max-height: 400px; 83 | overflow-y: auto; 84 | z-index: 1000; 85 | } 86 | 87 | .bookmark-item { 88 | padding: 8px 16px; 89 | border-bottom: 1px solid #eee; 90 | transition: background-color 0.2s; 91 | } 92 | 93 | .bookmark-item:hover { 94 | background-color: #f5f5f5; 95 | } 96 | 97 | .bookmark-item:last-child { 98 | border-bottom: none; 99 | } 100 | 101 | .delete-bookmark { 102 | padding: 4px; 103 | border-radius: 50%; 104 | transition: background-color 0.2s; 105 | } 106 | 107 | .delete-bookmark:hover { 108 | background-color: #fee2e2; 109 | } 110 | 111 | .clear-all-bookmarks { 112 | transition: background-color 0.2s; 113 | } 114 | 115 | /* Revisions Panel */ 116 | #revisions-panel { 117 | position: absolute; 118 | top: 100%; 119 | left: 0; 120 | right: 0; 121 | background: white; 122 | border: 1px solid #ccc; 123 | border-radius: 0 0 4px 4px; 124 | box-shadow: 0 2px 10px rgba(0,0,0,0.1); 125 | display: none; 126 | max-height: 60vh; 127 | overflow-y: auto; 128 | z-index: 1000; 129 | } 130 | 131 | .revision-item { 132 | display: flex; 133 | padding: 12px; 134 | border-bottom: 1px solid #eee; 135 | cursor: pointer; 136 | } 137 | 138 | .revision-item:hover { 139 | background-color: #f5f5f5; 140 | } 141 | 142 | .revision-screenshot { 143 | width: 150px; 144 | height: 90px; 145 | object-fit: cover; 146 | margin-right: 15px; 147 | } 148 | 149 | .revision-prompt { 150 | flex-grow: 1; 151 | font-size: 14px; 152 | } 153 | 154 | /* Model Selection */ 155 | #model-select-container { 156 | position: relative; 157 | } 158 | 159 | #model-select-btn { 160 | background: transparent; 161 | border: none; 162 | cursor: pointer; 163 | padding: 6px; 164 | margin: 0 2px; 165 | color: #ecf0f1; 166 | border-radius: 50%; 167 | display: flex; 168 | align-items: center; 169 | justify-content: center; 170 | } 171 | 172 | #model-text { 173 | margin-left: 5px; 174 | white-space: nowrap; 175 | overflow: hidden; 176 | text-overflow: ellipsis; 177 | max-width: 140px; 178 | font-size: 14px; 179 | } 180 | 181 | #model-options { 182 | position: absolute; 183 | top: 100%; 184 | right: 0; 185 | background-color: #2c3e50; 186 | border-radius: 8px; 187 | box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); 188 | z-index: 1000; 189 | min-width: 180px; 190 | overflow: hidden; 191 | display: none; 192 | } 193 | 194 | .model-option { 195 | padding: 10px 15px; 196 | color: white; 197 | cursor: pointer; 198 | transition: background-color 0.2s; 199 | } 200 | 201 | .model-option:hover { 202 | background-color: #3498db; 203 | } 204 | 205 | .model-option.selected { 206 | background-color: #2980b9; 207 | font-weight: 500; 208 | } 209 | 210 | /* Loading overlay */ 211 | #loading-overlay { 212 | position: absolute; 213 | top: 0; 214 | left: 0; 215 | right: 0; 216 | bottom: 0; 217 | background: rgba(0, 0, 0, 0.9); 218 | display: flex; 219 | flex-direction: column; 220 | justify-content: center; 221 | align-items: center; 222 | z-index: 1000; 223 | } 224 | 225 | #loading-spinner { 226 | width: 60px; 227 | height: 60px; 228 | border: 6px solid rgba(52, 152, 219, 0.2); 229 | border-top: 6px solid #3498db; 230 | border-radius: 50%; 231 | animation: spin 1s linear infinite; 232 | margin-bottom: 20px; 233 | } 234 | 235 | @keyframes spin { 236 | 0% { transform: rotate(0deg); } 237 | 100% { transform: rotate(360deg); } 238 | } 239 | 240 | #loading-text { 241 | margin-bottom: 25px; 242 | font-size: 22px; 243 | font-weight: bold; 244 | color: white; 245 | } 246 | 247 | .progress-container { 248 | width: 90%; 249 | max-width: 400px; 250 | margin-top: 10px; 251 | } 252 | 253 | .generation-stage { 254 | color: #3498db; 255 | font-size: 16px; 256 | margin-bottom: 8px; 257 | text-align: center; 258 | font-weight: 500; 259 | } 260 | 261 | .progress-bar { 262 | height: 8px; 263 | background-color: rgba(255, 255, 255, 0.2); 264 | border-radius: 4px; 265 | overflow: hidden; 266 | margin-bottom: 8px; 267 | } 268 | 269 | .progress-fill { 270 | height: 100%; 271 | background-color: #3498db; 272 | width: 0%; 273 | transition: width 0.3s ease; 274 | border-radius: 4px; 275 | } 276 | 277 | .progress-stats { 278 | display: flex; 279 | justify-content: space-between; 280 | color: rgba(255, 255, 255, 0.8); 281 | font-size: 14px; 282 | } 283 | 284 | /* Publish button */ 285 | #publish-btn { 286 | background-color: #27ae60; 287 | color: white; 288 | border: none; 289 | padding: 6px 12px; 290 | border-radius: 4px; 291 | cursor: pointer; 292 | font-size: 14px; 293 | display: none; 294 | } 295 | 296 | #publish-btn:hover { 297 | background-color: #2ecc71; 298 | } 299 | 300 | /* Element highlighting and edit menu */ 301 | .highlight { 302 | outline: 2px solid #3498db; 303 | position: relative; 304 | } 305 | 306 | .edit-menu { 307 | background-color: #2c3e50; 308 | border-radius: 8px; 309 | box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); 310 | padding: 8px; 311 | z-index: 1000; 312 | display: flex; 313 | flex-direction: column; 314 | min-width: 150px; 315 | } 316 | 317 | .edit-menu button { 318 | background: transparent; 319 | border: none; 320 | color: white; 321 | padding: 8px 12px; 322 | text-align: left; 323 | cursor: pointer; 324 | display: flex; 325 | align-items: center; 326 | border-radius: 4px; 327 | margin-bottom: 4px; 328 | } 329 | 330 | .edit-menu button:last-child { 331 | margin-bottom: 0; 332 | } 333 | 334 | .edit-menu button:hover { 335 | background-color: #3498db; 336 | } 337 | 338 | .edit-menu button svg { 339 | margin-right: 8px; 340 | } 341 | 342 | /* Progress bar for loading */ 343 | .progress-container { 344 | width: 100%; 345 | margin-top: 16px; 346 | } 347 | 348 | .progress-text { 349 | color: white; 350 | margin-bottom: 8px; 351 | font-size: 14px; 352 | text-align: center; 353 | } 354 | 355 | .progress-bar { 356 | height: 8px; 357 | background-color: #34495e; 358 | border-radius: 4px; 359 | overflow: hidden; 360 | } 361 | 362 | .progress-fill { 363 | height: 100%; 364 | background-color: #3498db; 365 | width: 0%; 366 | transition: width 0.3s ease; 367 | border-radius: 4px; 368 | } 369 | 370 | /* Uploaded Images Container */ 371 | .uploaded-images-container { 372 | display: flex; 373 | flex-wrap: wrap; 374 | gap: 10px; 375 | margin: 10px 0; 376 | padding: 10px; 377 | background-color: rgba(44, 62, 80, 0.5); 378 | border-radius: 4px; 379 | } 380 | 381 | .uploaded-image-wrapper { 382 | position: relative; 383 | width: 100px; 384 | height: 100px; 385 | border-radius: 4px; 386 | overflow: hidden; 387 | } 388 | 389 | .uploaded-image-thumbnail { 390 | width: 100%; 391 | height: 100%; 392 | object-fit: cover; 393 | } 394 | 395 | .remove-image-btn { 396 | position: absolute; 397 | top: 5px; 398 | right: 5px; 399 | width: 24px; 400 | height: 24px; 401 | background-color: rgba(231, 76, 60, 0.8); 402 | color: white; 403 | border: none; 404 | border-radius: 50%; 405 | display: flex; 406 | align-items: center; 407 | justify-content: center; 408 | cursor: pointer; 409 | font-size: 16px; 410 | line-height: 1; 411 | } 412 | 413 | .remove-image-btn:hover { 414 | background-color: rgba(231, 76, 60, 1); 415 | } -------------------------------------------------------------------------------- /beta/css/layout.css: -------------------------------------------------------------------------------- 1 | /* LAYOUT STYLES - Main layout elements */ 2 | 3 | #desktop { 4 | position: fixed; 5 | top: 0; 6 | left: 0; 7 | right: 0; 8 | bottom: 0; 9 | overflow: hidden; 10 | } 11 | 12 | #browser { 13 | width: 98%; 14 | height: 98%; 15 | background: #f0f0f0; 16 | display: flex; 17 | flex-direction: column; 18 | overflow: hidden; 19 | position: absolute; 20 | top: 1%; 21 | left: 1%; 22 | right: 1%; 23 | bottom: 1%; 24 | border-radius: 8px; 25 | box-shadow: 0 10px 30px rgba(0,0,0,0.3); 26 | } 27 | 28 | #titlebar { 29 | background: #2c3e50; 30 | color: #ecf0f1; 31 | padding: 8px 16px; 32 | font-weight: 500; 33 | display: flex; 34 | justify-content: space-between; 35 | align-items: center; 36 | user-select: none; 37 | font-size: 14px; 38 | flex-shrink: 0; 39 | border-radius: 8px 8px 0 0; 40 | } 41 | 42 | #page-title { 43 | font-size: 0.9em; 44 | } 45 | 46 | #toolbar { 47 | background: #34495e; 48 | padding: 12px 12px; 49 | border-bottom: 3px solid #2c3e50; 50 | display: flex; 51 | align-items: center; 52 | flex-wrap: wrap; 53 | flex-shrink: 0; 54 | } 55 | 56 | #toolbar-left, #toolbar-right { 57 | display: flex; 58 | align-items: center; 59 | } 60 | 61 | #toolbar-left { 62 | margin-right: auto; 63 | } 64 | 65 | #content { 66 | flex-grow: 1; 67 | overflow: auto; 68 | position: relative; 69 | background: #2b2d42; 70 | padding: 0; 71 | box-sizing: border-box; 72 | -webkit-overflow-scrolling: touch; 73 | display: flex; 74 | flex-direction: column; 75 | } 76 | 77 | #simulation-frame { 78 | width: 100%; 79 | height: 100%; 80 | border: none; 81 | flex-grow: 1; 82 | display: block; 83 | } 84 | 85 | /* Ensure iframe content displays properly */ 86 | #simulation-frame.w-full { 87 | width: 100% !important; 88 | height: 100% !important; 89 | position: relative !important; 90 | display: block !important; 91 | } 92 | 93 | #content img { 94 | max-width: 100%; 95 | height: auto; 96 | } 97 | 98 | #status-bar { 99 | background: #34495e; 100 | border-top: 1px solid #2c3e50; 101 | padding: 4px 16px; 102 | font-size: 11px; 103 | color: #00ff00; 104 | display: flex; 105 | justify-content: space-between; 106 | align-items: center; 107 | flex-shrink: 0; 108 | overflow: hidden; 109 | white-space: nowrap; 110 | } 111 | 112 | #status-message { 113 | flex-grow: 1; 114 | overflow: hidden; 115 | text-overflow: ellipsis; 116 | color: #00ff00; 117 | } 118 | 119 | #status-bar a { 120 | flex-shrink: 0; 121 | margin-left: 10px; 122 | color: #00ff00; 123 | text-decoration: none; 124 | } 125 | 126 | #status-bar a:hover { 127 | text-decoration: underline; 128 | } 129 | 130 | #window-controls { 131 | display: flex; 132 | align-items: center; 133 | } 134 | 135 | .window-button { 136 | width: 12px; 137 | height: 12px; 138 | border-radius: 50%; 139 | margin-left: 8px; 140 | cursor: pointer; 141 | } 142 | 143 | #close-button { background-color: #ff5f56; } 144 | #minimize-button { background-color: #ffbd2e; } 145 | #maximize-button { background-color: #27c93f; } -------------------------------------------------------------------------------- /beta/css/modals.css: -------------------------------------------------------------------------------- 1 | /* MODALS STYLES - Dialogs, popups, and notifications */ 2 | 3 | /* Basic Modal Structure */ 4 | #modal, .modal { 5 | display: none; 6 | position: fixed; 7 | z-index: 1001; 8 | left: 0; 9 | top: 0; 10 | width: 100%; 11 | height: 100%; 12 | overflow: auto; 13 | background-color: rgba(0,0,0,0.7); 14 | backdrop-filter: blur(5px); 15 | } 16 | 17 | .modal-content, #modal-content { 18 | background-color: #2c3e50; 19 | margin: 15% auto; 20 | padding: 20px; 21 | border-radius: 8px; 22 | box-shadow: 0 4px 20px rgba(0, 0, 0, 0.5); 23 | width: 80%; 24 | max-width: 500px; 25 | color: white; 26 | position: relative; 27 | animation: modalFadeIn 0.3s ease-out; 28 | } 29 | 30 | @keyframes modalFadeIn { 31 | from { opacity: 0; transform: translateY(-20px); } 32 | to { opacity: 1; transform: translateY(0); } 33 | } 34 | 35 | .modal-content h2 { 36 | margin-top: 0; 37 | color: #3498db; 38 | font-size: 1.5rem; 39 | margin-bottom: 16px; 40 | } 41 | 42 | .modal-content input[type="text"] { 43 | width: 100%; 44 | padding: 10px; 45 | margin-bottom: 16px; 46 | border-radius: 4px; 47 | border: 1px solid #34495e; 48 | background-color: #34495e; 49 | color: white; 50 | font-size: 14px; 51 | box-sizing: border-box; 52 | } 53 | 54 | .modal-content input[type="text"]:focus { 55 | outline: none; 56 | border-color: #3498db; 57 | box-shadow: 0 0 0 2px rgba(52, 152, 219, 0.3); 58 | } 59 | 60 | .modal-buttons { 61 | display: flex; 62 | justify-content: flex-end; 63 | gap: 10px; 64 | } 65 | 66 | .modal-content button { 67 | padding: 8px 16px; 68 | border: none; 69 | border-radius: 4px; 70 | cursor: pointer; 71 | font-weight: 500; 72 | transition: background-color 0.2s; 73 | } 74 | 75 | .modal-content button#update-element { 76 | background-color: #3498db; 77 | color: white; 78 | } 79 | 80 | .modal-content button#update-element:hover { 81 | background-color: #2980b9; 82 | } 83 | 84 | .modal-content button#cancel-edit { 85 | background-color: #7f8c8d; 86 | color: white; 87 | } 88 | 89 | .modal-content button#cancel-edit:hover { 90 | background-color: #95a5a6; 91 | } 92 | 93 | .modal-close, .notification-close, #modal-close { 94 | position: absolute; 95 | top: 10px; 96 | right: 10px; 97 | color: #bdc3c7; 98 | font-size: 20px; 99 | font-weight: bold; 100 | cursor: pointer; 101 | background: none; 102 | border: none; 103 | padding: 0; 104 | width: 24px; 105 | height: 24px; 106 | display: flex; 107 | align-items: center; 108 | justify-content: center; 109 | border-radius: 50%; 110 | } 111 | 112 | .modal-close:hover, .notification-close:hover, #modal-close:hover, 113 | .modal-close:focus, .notification-close:focus, #modal-close:focus { 114 | color: white; 115 | background-color: rgba(255, 255, 255, 0.1); 116 | } 117 | 118 | /* What's New Modal */ 119 | #whatsNewModal .modal-content { 120 | max-width: 600px; 121 | } 122 | 123 | .whats-new-content { 124 | margin-bottom: 20px; 125 | } 126 | 127 | .whats-new-content ul { 128 | padding-left: 20px; 129 | margin-bottom: 16px; 130 | } 131 | 132 | .whats-new-content li { 133 | margin-bottom: 8px; 134 | line-height: 1.5; 135 | } 136 | 137 | #whats-new-got-it { 138 | background-color: #9c27b0; 139 | color: white; 140 | padding: 10px 20px; 141 | border: none; 142 | border-radius: 4px; 143 | cursor: pointer; 144 | font-weight: 500; 145 | display: block; 146 | margin: 0 auto; 147 | transition: background-color 0.2s; 148 | } 149 | 150 | #whats-new-got-it:hover { 151 | background-color: #7b1fa2; 152 | } 153 | 154 | /* API Key Modal */ 155 | #apiKeyModal .modal-content { 156 | max-width: 500px; 157 | } 158 | 159 | #apiKeyModal p { 160 | margin-bottom: 20px; 161 | line-height: 1.5; 162 | } 163 | 164 | .form-group { 165 | margin-bottom: 20px; 166 | } 167 | 168 | .form-group label { 169 | display: block; 170 | margin-bottom: 8px; 171 | font-weight: 500; 172 | color: #ecf0f1; 173 | } 174 | 175 | .form-group input[type="password"], 176 | .form-group input[type="text"] { 177 | width: 100%; 178 | padding: 10px; 179 | border-radius: 4px; 180 | border: 1px solid #34495e; 181 | background-color: #34495e; 182 | color: white; 183 | font-size: 14px; 184 | box-sizing: border-box; 185 | } 186 | 187 | .form-group input[type="password"]:focus, 188 | .form-group input[type="text"]:focus { 189 | outline: none; 190 | border-color: #3498db; 191 | box-shadow: 0 0 0 2px rgba(52, 152, 219, 0.3); 192 | } 193 | 194 | .btn-primary { 195 | background-color: #3498db; 196 | color: white; 197 | padding: 8px 16px; 198 | border: none; 199 | border-radius: 4px; 200 | cursor: pointer; 201 | font-weight: 500; 202 | transition: background-color 0.2s; 203 | } 204 | 205 | .btn-primary:hover { 206 | background-color: #2980b9; 207 | } 208 | 209 | /* Image Upload Modal */ 210 | #image-upload-modal .modal-content { 211 | max-width: 500px; 212 | } 213 | 214 | #image-upload-modal h2 { 215 | margin-bottom: 20px; 216 | color: white; 217 | font-size: 1.5rem; 218 | } 219 | 220 | #image-upload-input { 221 | margin-bottom: 20px; 222 | width: 100%; 223 | padding: 10px; 224 | background-color: #34495e; 225 | color: white; 226 | border: 1px solid #3498db; 227 | border-radius: 4px; 228 | } 229 | 230 | #upload-image-btn { 231 | background-color: #3498db; 232 | color: white; 233 | padding: 8px 16px; 234 | border: none; 235 | border-radius: 4px; 236 | cursor: pointer; 237 | font-weight: 500; 238 | transition: background-color 0.2s; 239 | } 240 | 241 | #upload-image-btn:hover { 242 | background-color: #2980b9; 243 | } 244 | 245 | /* Improved Prompt Modal */ 246 | #improved-prompt-modal { 247 | display: none; 248 | position: fixed; 249 | z-index: 2001; 250 | left: 0; 251 | top: 0; 252 | width: 100%; 253 | height: 100%; 254 | overflow: auto; 255 | background-color: rgba(0,0,0,0.4); 256 | } 257 | 258 | #improved-prompt-content { 259 | background-color: #fefefe; 260 | margin: 15% auto; 261 | padding: 20px; 262 | border: 1px solid #888; 263 | width: 80%; 264 | max-width: 600px; 265 | border-radius: 8px; 266 | } 267 | 268 | #improved-prompt-text { 269 | width: 100%; 270 | height: 150px; 271 | padding: 10px; 272 | margin-bottom: 10px; 273 | border: 1px solid #ddd; 274 | border-radius: 4px; 275 | resize: vertical; 276 | } 277 | 278 | #improved-prompt-buttons { 279 | display: flex; 280 | justify-content: flex-end; 281 | } 282 | 283 | #use-prompt, #cancel-prompt { 284 | margin-left: 10px; 285 | padding: 10px 20px; 286 | border: none; 287 | border-radius: 4px; 288 | cursor: pointer; 289 | } 290 | 291 | #use-prompt { 292 | background-color: #3498db; 293 | color: white; 294 | } 295 | 296 | #cancel-prompt { 297 | background-color: #ccc; 298 | color: black; 299 | } 300 | 301 | /* URL Modal */ 302 | #generated-url { 303 | width: 100%; 304 | padding: 10px; 305 | margin: 10px 0; 306 | border: 1px solid #ddd; 307 | border-radius: 4px; 308 | } 309 | 310 | #copy-url, #open-url { 311 | background-color: #3498db; 312 | color: white; 313 | border: none; 314 | padding: 10px 20px; 315 | margin: 5px; 316 | border-radius: 4px; 317 | cursor: pointer; 318 | } 319 | 320 | #copy-url:hover, #open-url:hover { 321 | background-color: #2980b9; 322 | } 323 | 324 | /* Notification System */ 325 | .notification { 326 | position: fixed; 327 | bottom: 20px; 328 | right: 20px; 329 | background-color: #2c3e50; 330 | color: white; 331 | padding: 16px; 332 | border-radius: 8px; 333 | box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); 334 | z-index: 1000; 335 | min-width: 300px; 336 | max-width: 400px; 337 | animation: notificationSlideIn 0.3s ease-out; 338 | transition: transform 0.3s ease, opacity 0.3s ease; 339 | } 340 | 341 | .notification.hiding { 342 | transform: translateX(120%); 343 | opacity: 0; 344 | } 345 | 346 | @keyframes notificationSlideIn { 347 | from { transform: translateX(120%); opacity: 0; } 348 | to { transform: translateX(0); opacity: 1; } 349 | } 350 | 351 | .notification-title { 352 | font-weight: bold; 353 | margin-bottom: 8px; 354 | padding-right: 20px; 355 | font-size: 16px; 356 | } 357 | 358 | .notification-message { 359 | font-size: 14px; 360 | line-height: 1.4; 361 | } 362 | 363 | .notification.success { 364 | border-left: 4px solid #2ecc71; 365 | } 366 | 367 | .notification.error { 368 | border-left: 4px solid #e74c3c; 369 | } 370 | 371 | .notification.warning { 372 | border-left: 4px solid #f39c12; 373 | } 374 | 375 | .notification.info { 376 | border-left: 4px solid #3498db; 377 | } 378 | 379 | /* Auto-saves modal styles */ 380 | .autosaves-list { 381 | max-height: 400px; 382 | overflow-y: auto; 383 | } 384 | 385 | .autosave-item { 386 | padding: 10px; 387 | border-bottom: 1px solid #ddd; 388 | display: flex; 389 | justify-content: space-between; 390 | align-items: center; 391 | } 392 | 393 | .autosave-title { 394 | font-weight: bold; 395 | flex: 3; 396 | } 397 | 398 | .autosave-date { 399 | color: #888; 400 | flex: 2; 401 | font-size: 0.9em; 402 | } 403 | 404 | .autosave-actions { 405 | flex: 1; 406 | text-align: right; 407 | } 408 | 409 | .autosave-actions button { 410 | margin-left: 5px; 411 | padding: 5px 10px; 412 | border: none; 413 | border-radius: 3px; 414 | cursor: pointer; 415 | } 416 | 417 | .autosave-actions button:first-child { 418 | background-color: #4CAF50; 419 | color: white; 420 | } 421 | 422 | .autosave-actions button:last-child { 423 | background-color: #f44336; 424 | color: white; 425 | } -------------------------------------------------------------------------------- /beta/css/responsive.css: -------------------------------------------------------------------------------- 1 | /* RESPONSIVE STYLES - Media queries for responsive design */ 2 | 3 | @media (max-width: 1024px) { 4 | body, html { 5 | overflow: hidden; 6 | position: fixed; 7 | width: 100%; 8 | height: 100%; 9 | } 10 | 11 | #browser { 12 | width: 100%; 13 | height: 100%; 14 | border-radius: 0; 15 | position: absolute; 16 | top: 0; 17 | left: 0; 18 | right: 0; 19 | bottom: 0; 20 | } 21 | 22 | #titlebar { 23 | font-size: 14px; 24 | padding: 12px 16px; 25 | } 26 | 27 | #toolbar { 28 | flex-direction: row; 29 | flex-wrap: wrap; 30 | align-items: center; 31 | padding: 8px 4px; 32 | overflow-x: auto; 33 | -webkit-overflow-scrolling: touch; 34 | } 35 | 36 | #toolbar-left, #toolbar-right { 37 | display: flex; 38 | flex-wrap: nowrap; 39 | justify-content: flex-start; 40 | width: auto; 41 | } 42 | 43 | #addressbar-container { 44 | width: 100%; 45 | margin: 8px 0; 46 | order: -1; 47 | } 48 | 49 | #content { 50 | height: auto; 51 | flex-grow: 1; 52 | overflow-y: auto; 53 | -webkit-overflow-scrolling: touch; 54 | } 55 | 56 | #status-bar { 57 | font-size: 12px; 58 | padding: 8px 16px; 59 | } 60 | 61 | #bookmarks-panel, #revisions-panel { 62 | width: 98%; 63 | max-width: none; 64 | left: 1%; 65 | right: 1%; 66 | } 67 | 68 | .btn { 69 | padding: 8px; 70 | margin: 2px; 71 | } 72 | 73 | .btn svg { 74 | width: 20px; 75 | height: 20px; 76 | } 77 | 78 | #addressbar { 79 | font-size: 16px; 80 | padding: 10px 12px; 81 | } 82 | 83 | #addressbar-container .btn { 84 | width: 36px; 85 | height: 36px; 86 | } 87 | 88 | #publish-btn { 89 | font-size: 14px; 90 | padding: 12px 12px; 91 | margin-top: 8px; 92 | width: auto; 93 | } 94 | 95 | #model-select-btn { 96 | padding: 8px; 97 | } 98 | 99 | .model-option { 100 | padding: 10px 14px; 101 | font-size: 14px; 102 | } 103 | 104 | #model-select-container { 105 | position: relative; 106 | } 107 | 108 | #model-options { 109 | position: absolute; 110 | top: auto; 111 | bottom: 100%; 112 | right: 0; 113 | left: auto; 114 | transform: none; 115 | width: 180px; 116 | z-index: 1001; 117 | } 118 | } 119 | 120 | @media (max-width: 768px) { 121 | .notification { 122 | left: 20px; 123 | right: 20px; 124 | max-width: none; 125 | } 126 | 127 | .modal-content { 128 | width: 90%; 129 | margin: 30% auto; 130 | } 131 | 132 | .edit-menu { 133 | min-width: 120px; 134 | } 135 | 136 | #model-text { 137 | display: none; 138 | } 139 | } 140 | 141 | @media (max-width: 480px) { 142 | .btn { 143 | padding: 6px; 144 | } 145 | 146 | .btn svg { 147 | width: 18px; 148 | height: 18px; 149 | } 150 | 151 | #addressbar-container .btn { 152 | width: 32px; 153 | height: 32px; 154 | } 155 | } -------------------------------------------------------------------------------- /beta/css/scrollbars.css: -------------------------------------------------------------------------------- 1 | /* SCROLLBAR STYLES - Custom scrollbar styling for the application */ 2 | 3 | /* WebKit browsers (Chrome, Safari, Opera) */ 4 | ::-webkit-scrollbar { 5 | width: 10px; 6 | } 7 | 8 | ::-webkit-scrollbar-track { 9 | background: #2b2d42; 10 | border-radius: 10px; 11 | } 12 | 13 | ::-webkit-scrollbar-thumb { 14 | background: linear-gradient(45deg, #00ff00, #00cc00); 15 | border-radius: 10px; 16 | border: 2px solid #2b2d42; 17 | box-shadow: 0 0 10px #00ff00; 18 | } 19 | 20 | ::-webkit-scrollbar-thumb:hover { 21 | background: linear-gradient(45deg, #00ff00, #009900); 22 | box-shadow: 0 0 15px #00ff00; 23 | } 24 | 25 | ::-webkit-scrollbar-corner { 26 | background: #2b2d42; 27 | } 28 | 29 | /* Firefox */ 30 | * { 31 | scrollbar-width: thin; 32 | scrollbar-color: #00ff00 #2b2d42; 33 | } 34 | 35 | /* MS Edge and IE */ 36 | *::-ms-scrollbar { 37 | width: 10px; 38 | } 39 | 40 | *::-ms-scrollbar-track { 41 | background: #2b2d42; 42 | border-radius: 10px; 43 | } 44 | 45 | *::-ms-scrollbar-thumb { 46 | background: linear-gradient(45deg, #00ff00, #00cc00); 47 | border-radius: 10px; 48 | border: 2px solid #2b2d42; 49 | box-shadow: 0 0 10px #00ff00; 50 | } -------------------------------------------------------------------------------- /beta/css/utilities.css: -------------------------------------------------------------------------------- 1 | /* UTILITIES STYLES - Animation keyframes and utility classes */ 2 | 3 | /* Animation keyframes */ 4 | @keyframes modalFadeIn { 5 | from { opacity: 0; transform: translateY(-20px); } 6 | to { opacity: 1; transform: translateY(0); } 7 | } 8 | 9 | @keyframes notificationSlideIn { 10 | from { transform: translateX(120%); opacity: 0; } 11 | to { transform: translateX(0); opacity: 1; } 12 | } 13 | 14 | /* Progress bar */ 15 | .progress-container { 16 | width: 100%; 17 | margin-top: 16px; 18 | } 19 | 20 | .progress-text { 21 | color: white; 22 | margin-bottom: 8px; 23 | font-size: 14px; 24 | text-align: center; 25 | } 26 | 27 | .progress-bar { 28 | height: 8px; 29 | background-color: #34495e; 30 | border-radius: 4px; 31 | overflow: hidden; 32 | } 33 | 34 | .progress-fill { 35 | height: 100%; 36 | background-color: #3498db; 37 | width: 0%; 38 | transition: width 0.3s ease; 39 | border-radius: 4px; 40 | } -------------------------------------------------------------------------------- /beta/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/techcow2/NetSim/d3e36d9ecd54376d2f91887a61b6b43a32bd8c25/beta/favicon-16x16.png -------------------------------------------------------------------------------- /beta/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/techcow2/NetSim/d3e36d9ecd54376d2f91887a61b6b43a32bd8c25/beta/favicon-32x32.png -------------------------------------------------------------------------------- /beta/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/techcow2/NetSim/d3e36d9ecd54376d2f91887a61b6b43a32bd8c25/beta/favicon.ico -------------------------------------------------------------------------------- /beta/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | NetSim - AI Powered Simulations 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 | NetSim - AI Powered Simulations 32 |
33 |
34 |
35 | 40 | 45 | 50 | 55 |
56 |
57 | 58 | 63 | 68 | 69 |
70 |
71 |
72 | 78 |
79 |
Claude 3.5 Sonnet
80 |
GPT-4o
81 |
Gemini 2.5 Pro (Free Exp)
82 |
DeepSeek Chat
83 |
84 |
85 | 90 | 95 | 100 | 105 | 110 |
111 | 112 |
113 |
114 |
115 |
116 | Logo 117 |
118 |
119 |

How to Use NetSim

120 |
    121 |
  1. Enter a description in the address bar
  2. 122 |
  3. Click "Create" or press Enter
  4. 123 |
  5. Wait for AI to generate your web experience
  6. 124 |
  7. Interact with your creation
  8. 125 |
  9. Update by entering new instructions
  10. 126 |
  11. Use right-click to edit specific elements
  12. 127 |
  13. Bookmark your project
  14. 128 |
  15. Publish to get a shareable link
  16. 129 |
  17. Download as an HTML file
  18. 130 |
131 |
132 |
133 |

Features

134 |
    135 |
  • Instant Web Generation
  • 136 |
  • Interactive Simulated Browser
  • 137 |
  • Project Updates
  • 138 |
  • Right-Click Element Editing
  • 139 |
  • Bookmarking
  • 140 |
  • Publishing
  • 141 |
  • Downloading
  • 142 |
  • Revisions
  • 143 |
  • Model Selection
  • 144 |
145 |
146 |
147 |
148 |
149 | 150 |
151 | 152 |
153 | Ready 154 | GitHub 155 |
156 | 157 |
158 | 172 |
173 |
174 | 175 | 185 | 186 | 207 | 208 | 217 | 218 |
219 |
220 |

Improved Prompt

221 | 222 |
223 | 224 | 225 |
226 |
227 |
228 | 229 | 251 | 252 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 303 | 304 | 305 | -------------------------------------------------------------------------------- /beta/js/core.js: -------------------------------------------------------------------------------- 1 | // Core variables and initialization 2 | let history = []; 3 | let currentSimulation = ''; 4 | let currentProjectId = ''; 5 | let cachedPages = {}; 6 | window.currentModel = 'gemini-2.5-pro'; // Use window object to make it truly global 7 | let isEditMode = false; 8 | let currentEditElement = null; 9 | let editMenuVisible = false; 10 | let lastUserInput = ''; 11 | let autoSaveInterval = null; // Auto-save interval 12 | let navigationHistory = []; // Stack to track browsing history 13 | 14 | // Save current page to navigation history before navigating to a new page 15 | function saveToNavigationHistory() { 16 | const frame = document.getElementById('simulation-frame'); 17 | if (frame && currentSimulation) { 18 | navigationHistory.push({ 19 | html: frame.srcdoc, 20 | prompt: currentSimulation 21 | }); 22 | 23 | // Limit history size to prevent memory issues 24 | if (navigationHistory.length > 50) { 25 | navigationHistory.shift(); 26 | } 27 | } 28 | } 29 | 30 | const loadingTexts = [ 31 | "Brewing some digital magic...", 32 | "Assembling pixels with care...", 33 | "Feeding hamsters to power the server...", 34 | "Consulting the digital oracle...", 35 | "Reticulating splines...", 36 | "Generating witty loading messages...", 37 | "Proving P=NP...", 38 | "Dividing by zero...", 39 | "Spinning up the flux capacitor...", 40 | "Untangling the world wide web..." 41 | ]; 42 | 43 | function checkRateLimit(action, limit, timeFrame) { 44 | const now = Date.now(); 45 | const actionKey = `rateLimit_${action}`; 46 | const storedData = JSON.parse(sessionStorage.getItem(actionKey) || '[]'); 47 | 48 | // Filter out timestamps older than the time frame 49 | const validData = storedData.filter(timestamp => now - timestamp < timeFrame); 50 | 51 | if (validData.length >= limit) { 52 | return false; 53 | } 54 | 55 | // Add the current timestamp to the list 56 | validData.push(now); 57 | sessionStorage.setItem(actionKey, JSON.stringify(validData)); 58 | return true; 59 | } 60 | 61 | function clearInputAndSetPlaceholder() { 62 | document.getElementById('addressbar').value = ''; 63 | document.getElementById('addressbar').placeholder = "Enter text here to update or make changes to your project"; 64 | } 65 | 66 | function initializeApp() { 67 | goHome(); 68 | document.getElementById('addressbar').addEventListener('click', toggleRevisions); 69 | document.getElementById('addressbar').addEventListener('focus', toggleRevisions); 70 | document.addEventListener('click', function(event) { 71 | if (!event.target.closest('#addressbar-container')) { 72 | hideRevisions(); 73 | } 74 | if (!event.target.closest('#model-select-container')) { 75 | hideModelOptions(); 76 | } 77 | if (!event.target.closest('#bookmarks-panel') && !event.target.closest('.btn[onclick="toggleBookmarks()"]')) { 78 | hideBookmarks(); 79 | } 80 | }); 81 | document.getElementById('addressbar').setAttribute('autocomplete', 'off'); 82 | 83 | // Initialize model selection from localStorage 84 | const savedModel = localStorage.getItem('netsim_current_model'); 85 | if (savedModel) { 86 | window.currentModel = savedModel; 87 | } 88 | 89 | // Update the UI to reflect the current model 90 | updateModelSelection(); 91 | 92 | document.getElementById('modal-close').onclick = function() { 93 | document.getElementById('modal').style.display = "none"; 94 | } 95 | 96 | document.getElementById('copy-url').onclick = function() { 97 | const urlInput = document.getElementById('generated-url'); 98 | urlInput.select(); 99 | document.execCommand('copy'); 100 | alert('URL copied to clipboard!'); 101 | } 102 | 103 | document.getElementById('open-url').onclick = function() { 104 | const url = document.getElementById('generated-url').value; 105 | window.open(url, '_blank'); 106 | } 107 | 108 | // Ensure the bookmarks panel hides on outside clicks 109 | document.addEventListener('click', function(event) { 110 | if (!event.target.closest('#bookmarks-panel') && !event.target.closest('.btn[onclick="toggleBookmarks()"]')) { 111 | hideBookmarks(); 112 | } 113 | }); 114 | 115 | // Load history from local storage 116 | const compressedHistory = localStorage.getItem('netsim_history'); 117 | if (compressedHistory) { 118 | try { 119 | history = JSON.parse(LZString.decompressFromUTF16(compressedHistory) || '[]'); 120 | } catch (error) { 121 | console.error('Error loading history:', error); 122 | history = []; 123 | } 124 | } 125 | 126 | const frame = document.getElementById('simulation-frame'); 127 | if (frame && frame.contentDocument) { 128 | frame.contentDocument.addEventListener('contextmenu', handleRightClick); 129 | frame.contentDocument.addEventListener('click', handleLeftClick); 130 | } 131 | 132 | // Initialize auto-save functionality 133 | setupAutoSave(); 134 | } 135 | 136 | function showLoadingOverlay() { 137 | const content = document.getElementById('content'); 138 | content.innerHTML = ` 139 |
140 |
141 |
${getRandomLoadingText()}
142 |
143 | `; 144 | startLoadingTextAnimation(); 145 | } 146 | 147 | function getRandomLoadingText() { 148 | return loadingTexts[Math.floor(Math.random() * loadingTexts.length)]; 149 | } 150 | 151 | function startLoadingTextAnimation() { 152 | const loadingText = document.getElementById('loading-text'); 153 | setInterval(() => { 154 | loadingText.textContent = getRandomLoadingText(); 155 | }, 3000); 156 | } 157 | 158 | function updateStatusBar(message) { 159 | const maxLength = 50; // Adjust this based on your design needs 160 | if (message.length > maxLength) { 161 | message = message.substring(0, maxLength) + '...'; 162 | } 163 | document.getElementById('status-message').textContent = message; 164 | } 165 | 166 | function updatePageTitle(title) { 167 | const maxLength = 20; 168 | if (title.length > maxLength) { 169 | title = title.substring(0, maxLength) + '...'; 170 | } 171 | document.getElementById('page-title').textContent = title; 172 | } 173 | 174 | function updateAddressBar(text) { 175 | document.getElementById('addressbar').value = text; 176 | } 177 | 178 | // Set up auto-save functionality 179 | function setupAutoSave() { 180 | // Clear any existing auto-save interval 181 | if (autoSaveInterval) { 182 | clearInterval(autoSaveInterval); 183 | } 184 | 185 | // Set up auto-save every 30 seconds when a simulation is active 186 | autoSaveInterval = setInterval(() => { 187 | if (currentSimulation && isEditMode) { 188 | autoSaveSimulation(); 189 | } 190 | }, 30000); // 30 seconds 191 | 192 | // Also manually trigger auto-save when switching pages 193 | window.addEventListener('beforeunload', function() { 194 | if (currentSimulation && isEditMode) { 195 | autoSaveSimulation(); 196 | } 197 | }); 198 | } 199 | 200 | // Go back to the previous page in navigation history 201 | function goBack() { 202 | // Check if there's anything in the navigation history 203 | if (navigationHistory.length === 0) { 204 | showNotification('Info', 'No previous page to go back to', 'info'); 205 | return; 206 | } 207 | 208 | // Get the previous page from the history stack 209 | const prevPage = navigationHistory.pop(); 210 | 211 | // Display the previous page 212 | if (prevPage && prevPage.html) { 213 | // Display the simulation from history 214 | displaySimulation(prevPage.html, prevPage.prompt || currentSimulation); 215 | 216 | // Update UI elements 217 | updateStatusBar('Returned to previous page'); 218 | 219 | // Update address bar if prompt exists 220 | if (prevPage.prompt) { 221 | updateAddressBar(prevPage.prompt); 222 | } 223 | 224 | showNotification('Success', 'Returned to previous page', 'success'); 225 | } else { 226 | showNotification('Error', 'Unable to go back - no previous page data', 'error'); 227 | } 228 | } 229 | 230 | // Initialize model selection 231 | function initModelSelection() { 232 | // This function is now deprecated - Model selection is handled directly in DOMContentLoaded 233 | console.log("Using new model selection implementation"); 234 | } 235 | 236 | // Hide model options 237 | function hideModelOptions() { 238 | const modelOptions = document.getElementById('model-options'); 239 | if (modelOptions) { 240 | modelOptions.style.display = 'none'; 241 | } 242 | } 243 | 244 | // Initialize when the document is loaded 245 | document.addEventListener('DOMContentLoaded', function() { 246 | initEditModal(); 247 | initializeApp(); 248 | 249 | // Direct setup of model selection to ensure it works 250 | const modelBtn = document.getElementById('model-select-btn'); 251 | const modelOptions = document.getElementById('model-options'); 252 | const modelTextElement = document.getElementById('model-text'); 253 | 254 | // Set initial model text 255 | updateModelSelection(); 256 | 257 | // Add click event to the model button directly 258 | modelBtn.addEventListener('click', function(e) { 259 | e.stopPropagation(); // Prevent event from bubbling up 260 | console.log('Model button clicked'); // Debug log 261 | 262 | // Toggle display 263 | if (modelOptions.style.display === 'block') { 264 | modelOptions.style.display = 'none'; 265 | } else { 266 | modelOptions.style.display = 'block'; 267 | } 268 | }); 269 | 270 | // Add click events to all model options 271 | const modelOptionElements = document.querySelectorAll('.model-option'); 272 | modelOptionElements.forEach(option => { 273 | option.addEventListener('click', function(e) { 274 | e.stopPropagation(); // Prevent event bubbling 275 | const model = this.getAttribute('data-model'); 276 | 277 | console.log('Model option clicked:', model); // Debug log 278 | 279 | // Update the global currentModel variable 280 | window.currentModel = model; 281 | 282 | // Save the selected model to localStorage 283 | localStorage.setItem('netsim_current_model', model); 284 | 285 | // Update UI 286 | updateModelSelection(); 287 | 288 | // Hide options 289 | modelOptions.style.display = 'none'; 290 | 291 | // Show API key notification if needed 292 | if (!isOpenRouterApiKeySet() && model !== 'gemini-2.5-pro') { 293 | showApiKeyModal(); 294 | const modelText = this.textContent.trim(); 295 | showNotification('API Key Required', 'Please set your OpenRouter API key to use ' + modelText, 'info'); 296 | } 297 | }); 298 | }); 299 | 300 | // Close model options when clicking outside 301 | document.addEventListener('click', function(event) { 302 | if (!event.target.closest('#model-select-container')) { 303 | modelOptions.style.display = 'none'; 304 | } 305 | }); 306 | }); 307 | -------------------------------------------------------------------------------- /beta/js/editor.js: -------------------------------------------------------------------------------- 1 | // Element editing functionality 2 | 3 | // Generate a unique identifier for elements to help with targeting 4 | function generateUniqueIdentifier(element) { 5 | // Check if element already has an ID 6 | if (element.id) { 7 | return element.id; 8 | } 9 | 10 | // Generate a unique ID based on tag name and random string 11 | const tagName = element.tagName.toLowerCase(); 12 | const randomString = Math.random().toString(36).substring(2, 8); 13 | const uniqueId = `${tagName}-${randomString}`; 14 | 15 | // Set the ID on the element 16 | element.id = uniqueId; 17 | 18 | return uniqueId; 19 | } 20 | 21 | // Get the full path to an element for precise targeting 22 | function getElementPath(element) { 23 | if (!element || element.tagName === 'HTML') { 24 | return 'html'; 25 | } 26 | 27 | let path = ''; 28 | let current = element; 29 | 30 | while (current && current.tagName !== 'HTML') { 31 | let selector = current.tagName.toLowerCase(); 32 | 33 | if (current.id) { 34 | selector += `#${current.id}`; 35 | } else if (current.className) { 36 | const classes = Array.from(current.classList).join('.'); 37 | if (classes) { 38 | selector += `.${classes}`; 39 | } 40 | } 41 | 42 | path = path ? `${selector} > ${path}` : selector; 43 | current = current.parentElement; 44 | } 45 | 46 | return path; 47 | } 48 | 49 | // Handle right-click on elements in the iframe 50 | function handleRightClick(event) { 51 | // Prevent default context menu 52 | event.preventDefault(); 53 | 54 | // Get the target element 55 | const element = event.target; 56 | 57 | // Remove any existing highlight and menu 58 | removeHighlightAndMenu(); 59 | 60 | // Skip if body or html 61 | if (element.tagName === 'BODY' || element.tagName === 'HTML') { 62 | return; 63 | } 64 | 65 | // Generate unique identifier for the element if it doesn't have one 66 | const elementId = generateUniqueIdentifier(element); 67 | 68 | // Highlight the element 69 | highlightElement(element); 70 | 71 | // Set current edit element 72 | currentEditElement = element; 73 | 74 | // Create edit menu 75 | const editMenu = document.createElement('div'); 76 | editMenu.id = 'element-edit-menu'; 77 | editMenu.className = 'element-edit-menu'; 78 | editMenu.style.position = 'absolute'; 79 | editMenu.style.left = `${event.pageX}px`; 80 | editMenu.style.top = `${event.pageY}px`; 81 | 82 | // Add menu items 83 | editMenu.innerHTML = ` 84 |
85 | Edit Content 86 |
87 |
88 | Edit Style 89 |
90 |
91 | Replace Element 92 |
93 |
94 | Duplicate 95 |
96 |
97 | Delete 98 |
99 | `; 100 | 101 | // Add to document 102 | const doc = event.target.ownerDocument; 103 | doc.body.appendChild(editMenu); 104 | 105 | // Add event listeners to menu items 106 | const menuItems = editMenu.querySelectorAll('.edit-menu-item'); 107 | menuItems.forEach(item => { 108 | item.addEventListener('click', function() { 109 | const action = this.getAttribute('data-action'); 110 | 111 | switch (action) { 112 | case 'edit': 113 | showEditModal(element, 'edit'); 114 | break; 115 | case 'style': 116 | showEditModal(element, 'style'); 117 | break; 118 | case 'replace': 119 | showEditModal(element, 'replace'); 120 | break; 121 | case 'duplicate': 122 | duplicateElement(element); 123 | break; 124 | case 'delete': 125 | deleteElement(element); 126 | break; 127 | } 128 | 129 | // Remove menu 130 | doc.body.removeChild(editMenu); 131 | }); 132 | }); 133 | 134 | // Close menu when clicking outside 135 | doc.addEventListener('click', function closeMenu(e) { 136 | if (!editMenu.contains(e.target) && e.target !== editMenu) { 137 | if (doc.body.contains(editMenu)) { 138 | doc.body.removeChild(editMenu); 139 | } 140 | doc.removeEventListener('click', closeMenu); 141 | } 142 | }); 143 | 144 | // Set edit menu visible flag 145 | editMenuVisible = true; 146 | } 147 | 148 | // Show edit modal with appropriate mode 149 | function showEditModal(element, mode) { 150 | const modal = document.getElementById('edit-modal'); 151 | const input = document.getElementById('edit-input'); 152 | const label = document.getElementById('edit-mode-label'); 153 | 154 | // Set current edit element 155 | currentEditElement = element; 156 | 157 | // Set w-tid attribute for element tracking if it doesn't exist 158 | if (!element.hasAttribute('w-tid')) { 159 | const tagId = generateUniqueId(); 160 | element.setAttribute('w-tid', tagId); 161 | } 162 | 163 | // Set modal title and placeholder based on mode 164 | if (mode === 'edit') { 165 | label.textContent = 'Edit Content'; 166 | input.placeholder = 'Enter new content for this element'; 167 | input.value = element.innerHTML; 168 | } else if (mode === 'style') { 169 | label.textContent = 'Edit Style'; 170 | input.placeholder = 'Enter CSS styles (e.g., color: red; font-size: 16px;)'; 171 | input.value = element.getAttribute('style') || ''; 172 | } else if (mode === 'replace') { 173 | label.textContent = 'Replace Element'; 174 | input.placeholder = 'Describe what to replace this element with'; 175 | input.value = ''; 176 | } 177 | 178 | // Store the edit mode 179 | input.setAttribute('data-mode', mode); 180 | 181 | // Show modal 182 | modal.style.display = 'block'; 183 | 184 | // Focus input 185 | input.focus(); 186 | } 187 | 188 | // Generate a unique ID for tagging elements 189 | function generateUniqueId() { 190 | return Math.random().toString(36).substring(2, 10); 191 | } 192 | 193 | // Process edit operations using the new system prompt 194 | async function editElementWithPrompt(operations) { 195 | if (!operations || !operations.length) { 196 | showNotification('Error', 'No edit operations provided', 'error'); 197 | return; 198 | } 199 | 200 | const frame = document.getElementById('simulation-frame'); 201 | if (!frame) { 202 | showNotification('Error', 'Simulation frame not found', 'error'); 203 | return; 204 | } 205 | 206 | const doc = frame.contentDocument || frame.contentWindow.document; 207 | const currentHTML = doc.documentElement.outerHTML; 208 | 209 | // Prepare the edit payload 210 | const editPayload = { 211 | operations: operations.map(op => ({ 212 | tagId: op.tagId, 213 | prompt: op.prompt 214 | })) 215 | }; 216 | 217 | try { 218 | showLoadingIndicator(); 219 | 220 | const editPrompt = `You are now in a subroutine to apply edits to the page. 221 | 222 | Edit payloads come with the following type: 223 | 224 | interface EditPayload { 225 | operations: EditOperation[]; 226 | } 227 | interface EditOperation { 228 | tagId: string; 229 | prompt: string; 230 | } 231 | 232 | Apply the changes to the appropriate elements based on the \`w-tid\` attribute. 233 | Only return the elements that need to be changed. These can extend beyond the tag ids specified in the edit payload. 234 | Please preserve the \`w-tid\` attributes of only the top level elements.`; 235 | 236 | // Call the API with the current HTML and edit payload 237 | const response = await generateWithOpenRouter(`${editPrompt} 238 | 239 | Current HTML: ${currentHTML} 240 | 241 | Edit Payload: ${JSON.stringify(editPayload)}`); 242 | 243 | // Apply the changes to the page 244 | applyEditChanges(response); 245 | 246 | hideLoadingIndicator(); 247 | showNotification('Success', 'Element updated successfully!', 'success'); 248 | } catch (error) { 249 | hideLoadingIndicator(); 250 | showNotification('Error', `Failed to edit element: ${error.message}`, 'error'); 251 | console.error('Error editing element with prompt:', error); 252 | } 253 | } 254 | 255 | // Apply the edit changes to the page 256 | function applyEditChanges(htmlChanges) { 257 | const frame = document.getElementById('simulation-frame'); 258 | if (!frame) return; 259 | 260 | const doc = frame.contentDocument || frame.contentWindow.document; 261 | 262 | // Create a temporary div to parse the HTML changes 263 | const tempDiv = doc.createElement('div'); 264 | tempDiv.innerHTML = htmlChanges; 265 | 266 | // For each element with a w-tid attribute in the response 267 | const changedElements = tempDiv.querySelectorAll('[w-tid]'); 268 | changedElements.forEach(changedElement => { 269 | const tagId = changedElement.getAttribute('w-tid'); 270 | const originalElement = doc.querySelector(`[w-tid="${tagId}"]`); 271 | 272 | if (originalElement) { 273 | // Replace the original element with the changed one 274 | originalElement.parentNode.replaceChild( 275 | doc.importNode(changedElement, true), 276 | originalElement 277 | ); 278 | } 279 | }); 280 | } 281 | 282 | // Update element based on user input 283 | function updateElement() { 284 | const modal = document.getElementById('edit-modal'); 285 | const input = document.getElementById('edit-input'); 286 | const mode = input.getAttribute('data-mode'); 287 | const value = input.value; 288 | 289 | if (!currentEditElement) { 290 | modal.style.display = 'none'; 291 | return; 292 | } 293 | 294 | // Apply changes based on mode 295 | if (mode === 'edit') { 296 | if (currentEditElement.hasAttribute('w-tid')) { 297 | // Use the new edit system prompt 298 | const tagId = currentEditElement.getAttribute('w-tid'); 299 | editElementWithPrompt([{ tagId, prompt: value }]); 300 | } else { 301 | currentEditElement.innerHTML = value; 302 | showNotification('Success', 'Element content updated!', 'success'); 303 | } 304 | } else if (mode === 'style') { 305 | currentEditElement.setAttribute('style', value); 306 | showNotification('Success', 'Element style updated!', 'success'); 307 | } else if (mode === 'replace') { 308 | // For replace, we need to use the API to generate new HTML 309 | const loadingOverlay = document.getElementById('loading-overlay'); 310 | loadingOverlay.style.display = 'flex'; 311 | 312 | // Get element path for targeting 313 | const elementPath = getElementPath(currentEditElement); 314 | 315 | // Get the current HTML content 316 | const frame = document.getElementById('simulation-frame'); 317 | const currentContent = frame ? frame.contentDocument.documentElement.outerHTML : ''; 318 | 319 | // Make API request to replace element 320 | axios.post('https://api.netsim.xyz/replace-element', { 321 | html: currentContent, 322 | elementPath: elementPath, 323 | replacement: value, 324 | model: getCurrentModel() 325 | }) 326 | .then(response => { 327 | if (response.data && response.data.html) { 328 | // Display the updated simulation 329 | displaySimulation(response.data.html, currentSimulation); 330 | showNotification('Success', 'Element replaced successfully!', 'success'); 331 | } else { 332 | throw new Error('Invalid response from server'); 333 | } 334 | loadingOverlay.style.display = 'none'; 335 | }) 336 | .catch(error => { 337 | console.error('Error replacing element:', error); 338 | loadingOverlay.style.display = 'none'; 339 | showNotification('Error', 'Failed to replace element: ' + (error.message || 'Unknown error'), 'error'); 340 | }); 341 | } 342 | 343 | // Close modal 344 | modal.style.display = 'none'; 345 | input.value = ''; 346 | 347 | // Clear current edit element 348 | currentEditElement = null; 349 | } 350 | 351 | // Duplicate an element 352 | function duplicateElement(element) { 353 | if (!element) return; 354 | 355 | // Clone the element 356 | const clone = element.cloneNode(true); 357 | 358 | // Generate a new ID for the clone 359 | const newId = generateUniqueIdentifier(clone); 360 | 361 | // Insert after the original element 362 | element.parentNode.insertBefore(clone, element.nextSibling); 363 | 364 | // Add event listeners to the clone 365 | clone.addEventListener('contextmenu', handleRightClick); 366 | 367 | // Add event listeners to all children of the clone 368 | const children = clone.querySelectorAll('*'); 369 | children.forEach(child => { 370 | child.addEventListener('contextmenu', handleRightClick); 371 | }); 372 | 373 | showNotification('Success', 'Element duplicated!', 'success'); 374 | } 375 | 376 | // Delete an element 377 | function deleteElement(element) { 378 | if (!element) return; 379 | 380 | // Confirm deletion 381 | const doc = element.ownerDocument; 382 | 383 | // Remove the element 384 | element.parentNode.removeChild(element); 385 | 386 | // Remove highlight and menu 387 | removeHighlightAndMenu(); 388 | 389 | showNotification('Success', 'Element deleted!', 'success'); 390 | } 391 | 392 | function highlightElement(element) { 393 | // Add highlight class 394 | element.classList.add('netsim-highlight'); 395 | 396 | // Store original styles 397 | element.setAttribute('data-original-outline', element.style.outline); 398 | element.setAttribute('data-original-position', element.style.position); 399 | 400 | // Add highlight styles 401 | element.style.outline = '2px dashed #ff5722'; 402 | element.style.position = element.style.position === 'static' ? 'relative' : element.style.position; 403 | } 404 | 405 | function removeHighlightAndMenu() { 406 | // Get the iframe document 407 | const frame = document.getElementById('simulation-frame'); 408 | if (!frame) return; 409 | 410 | const doc = frame.contentDocument || frame.contentWindow.document; 411 | 412 | // Remove any existing highlight 413 | const highlighted = doc.querySelector('.netsim-highlight'); 414 | if (highlighted) { 415 | // Restore original styles 416 | highlighted.style.outline = highlighted.getAttribute('data-original-outline') || ''; 417 | highlighted.style.position = highlighted.getAttribute('data-original-position') || ''; 418 | 419 | // Remove highlight class 420 | highlighted.classList.remove('netsim-highlight'); 421 | } 422 | 423 | // Remove any existing edit menu 424 | const menu = doc.getElementById('element-edit-menu'); 425 | if (menu) { 426 | menu.parentNode.removeChild(menu); 427 | } 428 | 429 | // Reset edit menu visible flag 430 | editMenuVisible = false; 431 | } 432 | 433 | function closeEditModal() { 434 | const modal = document.getElementById('edit-modal'); 435 | const input = document.getElementById('edit-input'); 436 | 437 | modal.style.display = 'none'; 438 | input.value = ''; 439 | 440 | // Clear current edit element 441 | currentEditElement = null; 442 | } 443 | 444 | function handleLeftClick(event) { 445 | // Remove highlight and menu if clicking outside of menu 446 | if (editMenuVisible && !event.target.closest('#element-edit-menu')) { 447 | removeHighlightAndMenu(); 448 | } 449 | } 450 | 451 | // Initialize the edit modal event listeners 452 | function initEditModal() { 453 | const updateButton = document.getElementById('update-element'); 454 | const cancelButton = document.getElementById('cancel-edit'); 455 | const editModal = document.getElementById('edit-modal'); 456 | const editInput = document.getElementById('edit-input'); 457 | 458 | // Update button click 459 | updateButton.addEventListener('click', updateElement); 460 | 461 | // Cancel button click 462 | cancelButton.addEventListener('click', () => { 463 | editModal.style.display = 'none'; 464 | editInput.value = ''; 465 | }); 466 | 467 | // Close modal when clicking outside 468 | window.addEventListener('click', (event) => { 469 | if (event.target === editModal) { 470 | editModal.style.display = 'none'; 471 | editInput.value = ''; 472 | } 473 | }); 474 | 475 | // Handle Enter key in input 476 | editInput.addEventListener('keyup', (event) => { 477 | if (event.key === 'Enter') { 478 | updateElement(); 479 | } 480 | }); 481 | } 482 | 483 | // Add event listeners to elements in the iframe 484 | function addEventListenersToIframe(iframe) { 485 | const doc = iframe.contentDocument || iframe.contentWindow.document; 486 | 487 | // Add right-click event listeners to all elements 488 | const allElements = doc.querySelectorAll('*'); 489 | allElements.forEach(element => { 490 | element.addEventListener('contextmenu', handleRightClick); 491 | }); 492 | } 493 | -------------------------------------------------------------------------------- /beta/js/gemini-api.js: -------------------------------------------------------------------------------- 1 | // OpenRouter API Integration 2 | 3 | // Get API key from local storage or return null if not set 4 | function getOpenRouterApiKey() { 5 | return localStorage.getItem('openrouter_api_key'); 6 | } 7 | 8 | // Save API key to local storage 9 | function saveOpenRouterApiKey(apiKey) { 10 | localStorage.setItem('openrouter_api_key', apiKey); 11 | } 12 | 13 | // Check if API key is set 14 | function isOpenRouterApiKeySet() { 15 | return !!getOpenRouterApiKey(); 16 | } 17 | 18 | // Clear API key from local storage 19 | function clearOpenRouterApiKey() { 20 | localStorage.removeItem('openrouter_api_key'); 21 | } 22 | 23 | // Validate API key by making a simple request 24 | async function validateOpenRouterApiKey(apiKey) { 25 | try { 26 | const response = await fetch('https://openrouter.ai/api/v1/auth/key', { 27 | method: 'GET', 28 | headers: { 29 | 'Authorization': `Bearer ${apiKey}`, 30 | 'HTTP-Referer': window.location.origin, 31 | 'X-Title': 'NetSim' 32 | } 33 | }); 34 | 35 | if (!response.ok) { 36 | return false; 37 | } 38 | 39 | return true; 40 | } catch (error) { 41 | console.error('API Key validation error:', error); 42 | return false; 43 | } 44 | } 45 | 46 | // Get the OpenRouter model ID based on the selected model 47 | function getOpenRouterModelId(model) { 48 | switch(model) { 49 | case 'gemini-2.5-pro': 50 | return 'google/gemini-2.5-pro-exp-03-25:free'; 51 | case 'claude-3.5-sonnet': 52 | return 'anthropic/claude-3.5-sonnet'; 53 | case 'gpt-4o': 54 | return 'openai/gpt-4o'; 55 | default: 56 | return 'google/gemini-2.5-pro-exp-03-25:free'; 57 | } 58 | } 59 | 60 | // Generate content using OpenRouter API 61 | async function generateWithOpenRouter(prompt, options = {}) { 62 | const apiKey = getOpenRouterApiKey(); 63 | 64 | if (!apiKey) { 65 | throw new Error('OpenRouter API key is not set. Please set your API key in the settings.'); 66 | } 67 | 68 | // Get the current model 69 | const modelId = getOpenRouterModelId(getCurrentModel()); 70 | 71 | try { 72 | const response = await fetch('https://openrouter.ai/api/v1/chat/completions', { 73 | method: 'POST', 74 | headers: { 75 | 'Content-Type': 'application/json', 76 | 'Authorization': `Bearer ${apiKey}`, 77 | 'HTTP-Referer': window.location.origin, 78 | 'X-Title': 'NetSim' 79 | }, 80 | body: JSON.stringify({ 81 | model: modelId, 82 | messages: [ 83 | { 84 | role: "user", 85 | content: prompt 86 | } 87 | ], 88 | ...options 89 | }) 90 | }); 91 | 92 | if (!response.ok) { 93 | const errorData = await response.json(); 94 | throw new Error(errorData.error?.message || 'Error connecting to OpenRouter API'); 95 | } 96 | 97 | const data = await response.json(); 98 | return data.choices[0].message.content; 99 | } catch (error) { 100 | console.error('OpenRouter API Error:', error); 101 | throw error; 102 | } 103 | } 104 | 105 | // Show API key modal 106 | function showApiKeyModal() { 107 | const modal = document.getElementById('apiKeyModal'); 108 | const apiKeyInput = document.getElementById('openrouter-api-key'); 109 | 110 | // Set current API key if exists 111 | if (isOpenRouterApiKeySet()) { 112 | apiKeyInput.value = getOpenRouterApiKey(); 113 | } 114 | 115 | modal.style.display = 'block'; 116 | } 117 | 118 | // Initialize API key modal event listeners 119 | function initApiKeyModal() { 120 | const modal = document.getElementById('apiKeyModal'); 121 | const saveButton = document.getElementById('save-api-key'); 122 | const cancelButton = document.getElementById('cancel-api-key'); 123 | const closeButton = document.querySelector('#apiKeyModal .modal-close'); 124 | 125 | saveButton.addEventListener('click', async function() { 126 | const apiKey = document.getElementById('openrouter-api-key').value.trim(); 127 | if (apiKey) { 128 | // Show loading notification 129 | showNotification('Validating', 'Validating API key...', 'info'); 130 | 131 | // Validate the API key 132 | const isValid = await validateOpenRouterApiKey(apiKey); 133 | 134 | if (isValid) { 135 | saveOpenRouterApiKey(apiKey); 136 | modal.style.display = 'none'; 137 | showNotification('Success', 'API key saved successfully!', 'success'); 138 | } else { 139 | showNotification('Error', 'Invalid API key. Please check and try again.', 'error'); 140 | } 141 | } else { 142 | showNotification('Error', 'Please enter a valid API key', 'error'); 143 | } 144 | }); 145 | 146 | cancelButton.addEventListener('click', function() { 147 | modal.style.display = 'none'; 148 | }); 149 | 150 | closeButton.addEventListener('click', function() { 151 | modal.style.display = 'none'; 152 | }); 153 | } 154 | -------------------------------------------------------------------------------- /beta/js/history.js: -------------------------------------------------------------------------------- 1 | // History and revision management 2 | 3 | function saveRevision(prompt) { 4 | if (!prompt) return; 5 | 6 | const frame = document.getElementById('simulation-frame'); 7 | if (!frame) return; 8 | 9 | const htmlContent = frame.contentDocument.documentElement.outerHTML; 10 | 11 | // Create revision object 12 | const revision = { 13 | prompt: prompt, 14 | html: htmlContent, 15 | date: new Date().toISOString() 16 | }; 17 | 18 | // Add to history 19 | history.unshift(revision); 20 | 21 | // Limit history size 22 | if (history.length > 20) { 23 | history = history.slice(0, 20); 24 | } 25 | 26 | // Save history to local storage 27 | localStorage.setItem('netsim_history', LZString.compressToUTF16(JSON.stringify(history))); 28 | 29 | // Update last user input 30 | lastUserInput = prompt; 31 | } 32 | 33 | function toggleRevisions() { 34 | const revisionsPanel = document.getElementById('revisions-panel'); 35 | 36 | if (revisionsPanel) { 37 | if (revisionsPanel.style.display === 'block') { 38 | hideRevisions(); 39 | } else { 40 | showRevisions(); 41 | } 42 | } else { 43 | showRevisions(); 44 | } 45 | } 46 | 47 | function showRevisions() { 48 | // Create revisions panel if it doesn't exist 49 | let revisionsPanel = document.getElementById('revisions-panel'); 50 | 51 | if (!revisionsPanel) { 52 | revisionsPanel = document.createElement('div'); 53 | revisionsPanel.id = 'revisions-panel'; 54 | document.getElementById('addressbar-container').appendChild(revisionsPanel); 55 | } 56 | 57 | // Generate HTML for revisions 58 | let html = '
Recent Prompts
'; 59 | 60 | if (history.length === 0) { 61 | html += '
No history yet
'; 62 | } else { 63 | history.forEach((revision, index) => { 64 | html += `
${revision.prompt}
`; 65 | }); 66 | } 67 | 68 | // Set the HTML and show the panel 69 | revisionsPanel.innerHTML = html; 70 | revisionsPanel.style.display = 'block'; 71 | } 72 | 73 | function hideRevisions() { 74 | const revisionsPanel = document.getElementById('revisions-panel'); 75 | if (revisionsPanel) { 76 | revisionsPanel.style.display = 'none'; 77 | } 78 | } 79 | 80 | function loadRevision(index) { 81 | if (index >= 0 && index < history.length) { 82 | const revision = history[index]; 83 | 84 | // Set current simulation 85 | currentSimulation = revision.prompt; 86 | 87 | // Display the simulation 88 | displaySimulation(revision.html, revision.prompt); 89 | 90 | // Update status 91 | updateStatusBar('Revision loaded successfully'); 92 | updatePageTitle(revision.prompt.substring(0, 20)); 93 | updateAddressBar(revision.prompt); 94 | 95 | // Set edit mode 96 | isEditMode = true; 97 | 98 | // Show publish button 99 | showPublishButton(); 100 | 101 | // Hide revisions panel 102 | hideRevisions(); 103 | 104 | // Show notification 105 | showNotification('Success', 'Revision loaded successfully!', 'success'); 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /beta/js/home.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/techcow2/NetSim/d3e36d9ecd54376d2f91887a61b6b43a32bd8c25/beta/js/home.js -------------------------------------------------------------------------------- /beta/js/ldb.js: -------------------------------------------------------------------------------- 1 | /** 2 | * ldb - Lightweight IndexedDB wrapper 3 | * This library provides a simple interface for working with IndexedDB 4 | */ 5 | !function(){ 6 | var s,c,e="undefined"!=typeof window?window:{},t=e.indexedDB||e.mozIndexedDB||e.webkitIndexedDB||e.msIndexedDB; 7 | "undefined"==typeof window||t? 8 | ((t=t.open("ldb",1)).onsuccess=function(e){s=this.result}, 9 | t.onerror=function(e){console.error("indexedDB request error"),console.log(e)}, 10 | t={get:(c={ready:!(t.onupgradeneeded=function(e){s=null,e.target.result.createObjectStore("s",{keyPath:"k"}).transaction.oncomplete=function(e){s=e.target.db}}), 11 | get:function(e,t){s?s.transaction("s").objectStore("s").get(e).onsuccess=function(e){e=e.target.result&&e.target.result.v||null;t(e)}:setTimeout(function(){c.get(e,t)},50)}, 12 | set:function(t,n,o){if(s){let e=s.transaction("s","readwrite");e.oncomplete=function(e){"Function"==={}.toString.call(o).slice(8,-1)&&o()},e.objectStore("s").put({k:t,v:n}),e.commit()}else setTimeout(function(){c.set(t,n,o)},50)}, 13 | delete:function(e,t){s?s.transaction("s","readwrite").objectStore("s").delete(e).onsuccess=function(e){t&&t()}:setTimeout(function(){c.delete(e,t)},50)}, 14 | list:function(t){s?s.transaction("s").objectStore("s").getAllKeys().onsuccess=function(e){e=e.target.result||null;t(e)}:setTimeout(function(){c.list(t)},50)}, 15 | getAll:function(t){s?s.transaction("s").objectStore("s").getAll().onsuccess=function(e){e=e.target.result||null;t(e)}:setTimeout(function(){c.getAll(t)},50)}, 16 | clear:function(t){s?s.transaction("s","readwrite").objectStore("s").clear().onsuccess=function(e){t&&t()}:setTimeout(function(){c.clear(t)},50)}}).get, 17 | set:c.set,delete:c.delete,list:c.list,getAll:c.getAll,clear:c.clear},e.ldb=t,"undefined"!=typeof module&&(module.exports=t)):console.error("indexDB not supported") 18 | }(); 19 | -------------------------------------------------------------------------------- /beta/js/lz-string.min.js: -------------------------------------------------------------------------------- 1 | var LZString=function(){function o(o,r){if(!t[o]){t[o]={};for(var n=0;ne;e++){var s=r.charCodeAt(e);n[2*e]=s>>>8,n[2*e+1]=s%256}return n},decompressFromUint8Array:function(o){if(null===o||void 0===o)return i.decompress(o);for(var n=new Array(o.length/2),e=0,t=n.length;t>e;e++)n[e]=256*o[2*e]+o[2*e+1];var s=[];return n.forEach(function(o){s.push(r(o))}),i.decompress(s.join(""))},compressToEncodedURIComponent:function(o){return null==o?"":i._compress(o,6,function(o){return e.charAt(o)})},decompressFromEncodedURIComponent:function(r){return null==r?"":""==r?null:(r=r.replace(/ /g,"+"),i._decompress(r.length,32,function(n){return o(e,r.charAt(n))}))},compress:function(o){return i._compress(o,16,function(o){return r(o)})},_compress:function(o,r,n){if(null==o)return"";var e,t,i,s={},p={},u="",c="",a="",l=2,f=3,h=2,d=[],m=0,v=0;for(i=0;ie;e++)m<<=1,v==r-1?(v=0,d.push(n(m)),m=0):v++;for(t=a.charCodeAt(0),e=0;8>e;e++)m=m<<1|1&t,v==r-1?(v=0,d.push(n(m)),m=0):v++,t>>=1}else{for(t=1,e=0;h>e;e++)m=m<<1|t,v==r-1?(v=0,d.push(n(m)),m=0):v++,t=0;for(t=a.charCodeAt(0),e=0;16>e;e++)m=m<<1|1&t,v==r-1?(v=0,d.push(n(m)),m=0):v++,t>>=1}l--,0==l&&(l=Math.pow(2,h),h++),delete p[a]}else for(t=s[a],e=0;h>e;e++)m=m<<1|1&t,v==r-1?(v=0,d.push(n(m)),m=0):v++,t>>=1;l--,0==l&&(l=Math.pow(2,h),h++),s[c]=f++,a=String(u)}if(""!==a){if(Object.prototype.hasOwnProperty.call(p,a)){if(a.charCodeAt(0)<256){for(e=0;h>e;e++)m<<=1,v==r-1?(v=0,d.push(n(m)),m=0):v++;for(t=a.charCodeAt(0),e=0;8>e;e++)m=m<<1|1&t,v==r-1?(v=0,d.push(n(m)),m=0):v++,t>>=1}else{for(t=1,e=0;h>e;e++)m=m<<1|t,v==r-1?(v=0,d.push(n(m)),m=0):v++,t=0;for(t=a.charCodeAt(0),e=0;16>e;e++)m=m<<1|1&t,v==r-1?(v=0,d.push(n(m)),m=0):v++,t>>=1}l--,0==l&&(l=Math.pow(2,h),h++),delete p[a]}else for(t=s[a],e=0;h>e;e++)m=m<<1|1&t,v==r-1?(v=0,d.push(n(m)),m=0):v++,t>>=1;l--,0==l&&(l=Math.pow(2,h),h++)}for(t=2,e=0;h>e;e++)m=m<<1|1&t,v==r-1?(v=0,d.push(n(m)),m=0):v++,t>>=1;for(;;){if(m<<=1,v==r-1){d.push(n(m));break}v++}return d.join("")},decompress:function(o){return null==o?"":""==o?null:i._decompress(o.length,32768,function(r){return o.charCodeAt(r)})},_decompress:function(o,n,e){var t,i,s,p,u,c,a,l,f=[],h=4,d=4,m=3,v="",w=[],A={val:e(0),position:n,index:1};for(i=0;3>i;i+=1)f[i]=i;for(p=0,c=Math.pow(2,2),a=1;a!=c;)u=A.val&A.position,A.position>>=1,0==A.position&&(A.position=n,A.val=e(A.index++)),p|=(u>0?1:0)*a,a<<=1;switch(t=p){case 0:for(p=0,c=Math.pow(2,8),a=1;a!=c;)u=A.val&A.position,A.position>>=1,0==A.position&&(A.position=n,A.val=e(A.index++)),p|=(u>0?1:0)*a,a<<=1;l=r(p);break;case 1:for(p=0,c=Math.pow(2,16),a=1;a!=c;)u=A.val&A.position,A.position>>=1,0==A.position&&(A.position=n,A.val=e(A.index++)),p|=(u>0?1:0)*a,a<<=1;l=r(p);break;case 2:return""}for(f[3]=l,s=l,w.push(l);;){if(A.index>o)return"";for(p=0,c=Math.pow(2,m),a=1;a!=c;)u=A.val&A.position,A.position>>=1,0==A.position&&(A.position=n,A.val=e(A.index++)),p|=(u>0?1:0)*a,a<<=1;switch(l=p){case 0:for(p=0,c=Math.pow(2,8),a=1;a!=c;)u=A.val&A.position,A.position>>=1,0==A.position&&(A.position=n,A.val=e(A.index++)),p|=(u>0?1:0)*a,a<<=1;f[d++]=r(p),l=d-1,h--;break;case 1:for(p=0,c=Math.pow(2,16),a=1;a!=c;)u=A.val&A.position,A.position>>=1,0==A.position&&(A.position=n,A.val=e(A.index++)),p|=(u>0?1:0)*a,a<<=1;f[d++]=r(p),l=d-1,h--;break;case 2:return w.join("")}if(0==h&&(h=Math.pow(2,m),m++),f[l])v=f[l];else{if(l!==d)return null;v=s+s.charAt(0)}w.push(v),f[d++]=s+v.charAt(0),h--,s=v,0==h&&(h=Math.pow(2,m),m++)}}};return i}();"function"==typeof define&&define.amd?define(function(){return LZString}):"undefined"!=typeof module&&null!=module&&(module.exports=LZString); 2 | -------------------------------------------------------------------------------- /beta/js/main.js: -------------------------------------------------------------------------------- 1 | // Main entry point for NetSim application 2 | 3 | // Event listeners for address bar 4 | document.getElementById('addressbar').addEventListener('keypress', function(e) { 5 | if (e.key === 'Enter') { 6 | handleAddressBarSubmit(); 7 | } 8 | }); 9 | 10 | // Handle address bar submission 11 | async function handleAddressBarSubmit() { 12 | const input = document.getElementById('addressbar').value; 13 | if (input.trim() === '') return; 14 | 15 | if (currentSimulation && isEditMode) { 16 | await continueSimulation(input); 17 | } else { 18 | await loadPage(input); 19 | } 20 | } 21 | 22 | // Generate a unique ID for projects 23 | function generateUniqueId() { 24 | return Date.now().toString(36) + Math.random().toString(36).substring(2); 25 | } 26 | 27 | // Initialize the application when DOM is loaded 28 | document.addEventListener('DOMContentLoaded', function() { 29 | initializeApp(); 30 | }); 31 | -------------------------------------------------------------------------------- /beta/js/openrouter-api.js: -------------------------------------------------------------------------------- 1 | // OpenRouter API Integration 2 | 3 | // Get API key from local storage or return null if not set 4 | function getOpenRouterApiKey() { 5 | return localStorage.getItem('openrouter_api_key'); 6 | } 7 | 8 | // Save API key to local storage 9 | function saveOpenRouterApiKey(apiKey) { 10 | localStorage.setItem('openrouter_api_key', apiKey); 11 | } 12 | 13 | // Check if API key is set 14 | function isOpenRouterApiKeySet() { 15 | return !!getOpenRouterApiKey(); 16 | } 17 | 18 | // Clear API key from local storage 19 | function clearOpenRouterApiKey() { 20 | localStorage.removeItem('openrouter_api_key'); 21 | } 22 | 23 | // Pixabay API Integration 24 | // Get Pixabay API key from local storage or return null if not set 25 | function getPixabayApiKey() { 26 | return localStorage.getItem('pixabay_api_key'); 27 | } 28 | 29 | // Save Pixabay API key to local storage 30 | function savePixabayApiKey(apiKey) { 31 | localStorage.setItem('pixabay_api_key', apiKey); 32 | } 33 | 34 | // Check if Pixabay API key is set 35 | function isPixabayApiKeySet() { 36 | return !!getPixabayApiKey(); 37 | } 38 | 39 | // Clear Pixabay API key from local storage 40 | function clearPixabayApiKey() { 41 | localStorage.removeItem('pixabay_api_key'); 42 | } 43 | 44 | // Validate API key by making a simple request 45 | async function validateOpenRouterApiKey(apiKey) { 46 | try { 47 | const response = await fetch('https://openrouter.ai/api/v1/auth/key', { 48 | method: 'GET', 49 | headers: { 50 | 'Authorization': `Bearer ${apiKey}`, 51 | 'HTTP-Referer': window.location.origin, 52 | 'X-Title': 'NetSim' 53 | } 54 | }); 55 | 56 | if (!response.ok) { 57 | return false; 58 | } 59 | 60 | return true; 61 | } catch (error) { 62 | console.error('API Key validation error:', error); 63 | return false; 64 | } 65 | } 66 | 67 | // Get the OpenRouter model ID based on the selected model 68 | function getOpenRouterModelId(model) { 69 | switch(model) { 70 | case 'gemini-2.5-pro': 71 | return 'google/gemini-2.5-pro-exp-03-25:free'; 72 | case 'claude-3.5-sonnet': 73 | return 'anthropic/claude-3.5-sonnet'; 74 | case 'gpt-4o': 75 | return 'openai/gpt-4o'; 76 | case 'deepseek-chat': 77 | return 'deepseek/deepseek-chat-v3-0324:free'; 78 | default: 79 | return 'google/gemini-2.5-pro-exp-03-25:free'; 80 | } 81 | } 82 | 83 | // Generate content using OpenRouter API 84 | async function generateWithOpenRouter(prompt, options = {}) { 85 | const apiKey = getOpenRouterApiKey(); 86 | 87 | if (!apiKey) { 88 | throw new Error('OpenRouter API key is not set. Please set your API key in the settings.'); 89 | } 90 | 91 | // Get the current model directly from the window object 92 | const modelId = getOpenRouterModelId(window.currentModel || 'gemini-2.5-pro'); 93 | 94 | try { 95 | const response = await fetch('https://openrouter.ai/api/v1/chat/completions', { 96 | method: 'POST', 97 | headers: { 98 | 'Content-Type': 'application/json', 99 | 'Authorization': `Bearer ${apiKey}`, 100 | 'HTTP-Referer': window.location.origin, 101 | 'X-Title': 'NetSim' 102 | }, 103 | body: JSON.stringify({ 104 | model: modelId, 105 | messages: [ 106 | { 107 | role: "user", 108 | content: prompt 109 | } 110 | ], 111 | ...options 112 | }) 113 | }); 114 | 115 | if (!response.ok) { 116 | const errorData = await response.json(); 117 | throw new Error(errorData.error?.message || 'Error connecting to OpenRouter API'); 118 | } 119 | 120 | const data = await response.json(); 121 | console.log('OpenRouter API response:', data); 122 | 123 | // Handle different response formats 124 | if (data.choices && data.choices[0] && data.choices[0].message) { 125 | return data.choices[0].message.content; 126 | } else if (data.response) { 127 | // Some models might return a direct response property 128 | return data.response; 129 | } else if (data.output && data.output.content) { 130 | // Handle newer API format 131 | return data.output.content; 132 | } else if (data.text) { 133 | // Some APIs return direct text 134 | return data.text; 135 | } else { 136 | // Try to find content in any available nested structure 137 | const content = JSON.stringify(data); 138 | console.log('Unable to extract content from standard format, returning raw response'); 139 | return content; 140 | } 141 | } catch (error) { 142 | console.error('OpenRouter API Error:', error); 143 | throw error; 144 | } 145 | } 146 | 147 | // Generate content with multimodal input (text and images) 148 | async function generateWithMultimodal(textPrompt, imageUrls = [], options = {}) { 149 | const apiKey = getOpenRouterApiKey(); 150 | 151 | if (!apiKey) { 152 | throw new Error('OpenRouter API key is not set. Please set your API key in the settings.'); 153 | } 154 | 155 | // Get the current model directly from the window object 156 | const modelId = getOpenRouterModelId(window.currentModel || 'gemini-2.5-pro'); 157 | 158 | // Create content array with text and images 159 | const content = [ 160 | { 161 | type: "text", 162 | text: textPrompt 163 | } 164 | ]; 165 | 166 | // Add images if provided 167 | if (imageUrls && imageUrls.length > 0) { 168 | imageUrls.forEach(url => { 169 | content.push({ 170 | type: "image_url", 171 | image_url: { 172 | url: url 173 | } 174 | }); 175 | }); 176 | } 177 | 178 | try { 179 | const response = await fetch('https://openrouter.ai/api/v1/chat/completions', { 180 | method: 'POST', 181 | headers: { 182 | 'Content-Type': 'application/json', 183 | 'Authorization': `Bearer ${apiKey}`, 184 | 'HTTP-Referer': window.location.origin, 185 | 'X-Title': 'NetSim' 186 | }, 187 | body: JSON.stringify({ 188 | model: modelId, 189 | messages: [ 190 | { 191 | role: "user", 192 | content: content 193 | } 194 | ], 195 | ...options 196 | }) 197 | }); 198 | 199 | if (!response.ok) { 200 | const errorData = await response.json(); 201 | throw new Error(errorData.error?.message || 'Error connecting to OpenRouter API'); 202 | } 203 | 204 | const data = await response.json(); 205 | console.log('OpenRouter API multimodal response:', data); 206 | 207 | // Handle different response formats 208 | if (data.choices && data.choices[0] && data.choices[0].message) { 209 | return data.choices[0].message.content; 210 | } else if (data.response) { 211 | // Some models might return a direct response property 212 | return data.response; 213 | } else if (data.output && data.output.content) { 214 | // Handle newer API format 215 | return data.output.content; 216 | } else if (data.text) { 217 | // Some APIs return direct text 218 | return data.text; 219 | } else { 220 | // Try to find content in any available nested structure 221 | const content = JSON.stringify(data); 222 | console.log('Unable to extract content from standard format, returning raw response'); 223 | return content; 224 | } 225 | } catch (error) { 226 | console.error('OpenRouter API Error:', error); 227 | throw error; 228 | } 229 | } 230 | 231 | // Show API key modal 232 | function showApiKeyModal() { 233 | const modal = document.getElementById('apiKeyModal'); 234 | const openRouterApiKeyInput = document.getElementById('openrouter-api-key'); 235 | const pixabayApiKeyInput = document.getElementById('pixabay-api-key'); 236 | 237 | // Set current API keys if they exist 238 | if (isOpenRouterApiKeySet()) { 239 | openRouterApiKeyInput.value = getOpenRouterApiKey(); 240 | } 241 | 242 | if (isPixabayApiKeySet()) { 243 | pixabayApiKeyInput.value = getPixabayApiKey(); 244 | } 245 | 246 | modal.style.display = 'block'; 247 | } 248 | 249 | // Initialize API key modal event listeners 250 | function initApiKeyModal() { 251 | const modal = document.getElementById('apiKeyModal'); 252 | const saveButton = document.getElementById('save-api-key'); 253 | const cancelButton = document.getElementById('cancel-api-key'); 254 | const closeButton = document.querySelector('#apiKeyModal .modal-close'); 255 | const openRouterApiKeyInput = document.getElementById('openrouter-api-key'); 256 | const pixabayApiKeyInput = document.getElementById('pixabay-api-key'); 257 | 258 | // Set current API keys if they exist 259 | if (isOpenRouterApiKeySet()) { 260 | openRouterApiKeyInput.value = getOpenRouterApiKey(); 261 | } 262 | 263 | if (isPixabayApiKeySet()) { 264 | pixabayApiKeyInput.value = getPixabayApiKey(); 265 | } 266 | 267 | saveButton.addEventListener('click', async function() { 268 | const openRouterApiKey = openRouterApiKeyInput.value.trim(); 269 | const pixabayApiKey = pixabayApiKeyInput.value.trim(); 270 | let validationPassed = true; 271 | 272 | // Validate and save OpenRouter API key if provided 273 | if (openRouterApiKey) { 274 | showNotification('Validating', 'Validating OpenRouter API key...', 'info'); 275 | 276 | const isValid = await validateOpenRouterApiKey(openRouterApiKey); 277 | 278 | if (isValid) { 279 | saveOpenRouterApiKey(openRouterApiKey); 280 | } else { 281 | showNotification('Error', 'Invalid OpenRouter API key. Please check and try again.', 'error'); 282 | validationPassed = false; 283 | } 284 | } else { 285 | // If key field is empty but previously had a value, show a warning 286 | if (isOpenRouterApiKeySet()) { 287 | showNotification('Warning', 'OpenRouter API key is required for AI functionality.', 'warning'); 288 | } 289 | } 290 | 291 | // Save Pixabay API key if provided (no validation required) 292 | if (pixabayApiKey) { 293 | savePixabayApiKey(pixabayApiKey); 294 | } 295 | 296 | // If all validations passed, close modal and show success 297 | if (validationPassed) { 298 | modal.style.display = 'none'; 299 | showNotification('Success', 'API settings saved successfully!', 'success'); 300 | } 301 | }); 302 | 303 | cancelButton.addEventListener('click', function() { 304 | modal.style.display = 'none'; 305 | }); 306 | 307 | closeButton.addEventListener('click', function() { 308 | modal.style.display = 'none'; 309 | }); 310 | } 311 | -------------------------------------------------------------------------------- /beta/js/simulation.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/techcow2/NetSim/d3e36d9ecd54376d2f91887a61b6b43a32bd8c25/beta/js/simulation.js -------------------------------------------------------------------------------- /beta/js/storage.js: -------------------------------------------------------------------------------- 1 | // Simulation Storage Management 2 | // Handles automatic saving and retrieval of simulations 3 | 4 | // Constants 5 | const AUTOSAVE_KEY = 'netsim_autosaves'; 6 | const MAX_AUTOSAVES = 20; // Maximum number of auto-saves to keep 7 | 8 | // Auto-save the current simulation 9 | function autoSaveSimulation() { 10 | // Don't save if there's no simulation 11 | if (!currentSimulation) return; 12 | 13 | // Get the HTML content 14 | const frame = document.getElementById('simulation-frame'); 15 | if (!frame) return; 16 | 17 | const htmlContent = frame.contentDocument.documentElement.outerHTML; 18 | 19 | // Get existing auto-saves 20 | const autoSaves = getAutoSaves(); 21 | 22 | // Create a new save object 23 | const saveObj = { 24 | id: currentProjectId || generateUniqueId(), 25 | prompt: currentSimulation, 26 | html: htmlContent, 27 | timestamp: Date.now(), 28 | title: currentSimulation.length > 30 ? currentSimulation.substring(0, 30) + '...' : currentSimulation 29 | }; 30 | 31 | // Check if this simulation already exists (by ID or prompt) 32 | const existingIndex = autoSaves.findIndex(s => 33 | (currentProjectId && s.id === currentProjectId) || s.prompt === currentSimulation 34 | ); 35 | 36 | if (existingIndex !== -1) { 37 | // Update existing save 38 | autoSaves[existingIndex] = saveObj; 39 | } else { 40 | // Add new save 41 | autoSaves.unshift(saveObj); 42 | 43 | // Keep only the most recent saves 44 | if (autoSaves.length > MAX_AUTOSAVES) { 45 | autoSaves.pop(); 46 | } 47 | } 48 | 49 | // Save to local storage 50 | saveAutoSaves(autoSaves); 51 | 52 | console.log(`Auto-saved simulation: ${saveObj.title}`); 53 | } 54 | 55 | // Get all auto-saves from storage 56 | function getAutoSaves() { 57 | try { 58 | const compressed = localStorage.getItem(AUTOSAVE_KEY); 59 | if (!compressed) return []; 60 | 61 | const saves = JSON.parse(LZString.decompressFromUTF16(compressed)) || []; 62 | return saves; 63 | } catch (error) { 64 | console.error('Error loading auto-saves:', error); 65 | return []; 66 | } 67 | } 68 | 69 | // Save auto-saves to storage 70 | function saveAutoSaves(saves) { 71 | try { 72 | const compressed = LZString.compressToUTF16(JSON.stringify(saves)); 73 | localStorage.setItem(AUTOSAVE_KEY, compressed); 74 | } catch (error) { 75 | console.error('Error saving auto-saves:', error); 76 | showNotification('Error', 'Failed to auto-save simulation', 'error'); 77 | } 78 | } 79 | 80 | // Load a simulation from auto-save by ID 81 | function loadAutoSave(id) { 82 | const autoSaves = getAutoSaves(); 83 | const save = autoSaves.find(s => s.id === id); 84 | 85 | if (save) { 86 | // Set current simulation 87 | currentSimulation = save.prompt; 88 | currentProjectId = save.id; 89 | 90 | // Display the simulation 91 | displaySimulation(save.html, save.prompt); 92 | 93 | // Update status 94 | updateStatusBar('Simulation loaded successfully'); 95 | updatePageTitle(save.prompt.substring(0, 20)); 96 | updateAddressBar(save.prompt); 97 | 98 | // Set edit mode 99 | isEditMode = true; 100 | 101 | // Show publish button 102 | showPublishButton(); 103 | 104 | showNotification('Success', 'Simulation loaded successfully', 'success'); 105 | return true; 106 | } 107 | 108 | return false; 109 | } 110 | 111 | // Display auto-saves in a modal 112 | function showAutoSavesModal() { 113 | const autoSaves = getAutoSaves(); 114 | 115 | // Create modal if it doesn't exist 116 | let modal = document.getElementById('autosaves-modal'); 117 | if (!modal) { 118 | modal = document.createElement('div'); 119 | modal.id = 'autosaves-modal'; 120 | modal.className = 'modal'; 121 | 122 | modal.innerHTML = ` 123 | 132 | `; 133 | 134 | document.body.appendChild(modal); 135 | } 136 | 137 | // Populate the list 138 | const listEl = document.getElementById('autosaves-list'); 139 | 140 | if (autoSaves.length === 0) { 141 | listEl.innerHTML = '

No saved simulations found.

'; 142 | } else { 143 | listEl.innerHTML = ''; 144 | 145 | autoSaves.forEach(save => { 146 | const date = new Date(save.timestamp); 147 | const dateStr = date.toLocaleDateString() + ' ' + date.toLocaleTimeString(); 148 | 149 | const saveItem = document.createElement('div'); 150 | saveItem.className = 'autosave-item'; 151 | saveItem.innerHTML = ` 152 |
${save.title}
153 |
${dateStr}
154 |
155 | 156 | 157 |
158 | `; 159 | 160 | listEl.appendChild(saveItem); 161 | }); 162 | } 163 | 164 | // Show the modal 165 | modal.style.display = 'block'; 166 | } 167 | 168 | // Delete an auto-save by ID 169 | function deleteAutoSave(id) { 170 | const autoSaves = getAutoSaves(); 171 | const newSaves = autoSaves.filter(s => s.id !== id); 172 | 173 | if (newSaves.length !== autoSaves.length) { 174 | saveAutoSaves(newSaves); 175 | showNotification('Success', 'Simulation deleted', 'success'); 176 | 177 | // Refresh the modal 178 | showAutoSavesModal(); 179 | } 180 | } 181 | 182 | // Clear all auto-saves 183 | function clearAutoSaves() { 184 | if (confirm('Are you sure you want to delete all saved simulations?')) { 185 | localStorage.removeItem(AUTOSAVE_KEY); 186 | showNotification('Success', 'All saved simulations deleted', 'success'); 187 | 188 | // Refresh the modal 189 | showAutoSavesModal(); 190 | } 191 | } -------------------------------------------------------------------------------- /beta/js/ui.js: -------------------------------------------------------------------------------- 1 | // UI-related functionality 2 | 3 | function refreshPage() { 4 | if (currentSimulation) { 5 | const cachedContent = cachedPages[currentSimulation]; 6 | if (cachedContent) { 7 | displaySimulation(cachedContent, currentSimulation); 8 | showNotification('Success', 'Page refreshed successfully!', 'success'); 9 | } else { 10 | showNotification('Error', 'Cannot refresh page. No cached content found.', 'error'); 11 | } 12 | } else { 13 | goHome(); 14 | } 15 | } 16 | 17 | function goHome() { 18 | // Reset current simulation 19 | currentSimulation = ''; 20 | isEditMode = false; 21 | 22 | // Hide publish button 23 | hidePublishButton(); 24 | 25 | // Update status 26 | updateStatusBar('Ready'); 27 | updatePageTitle('NetSim - AI Powered Simulations'); 28 | updateAddressBar(''); 29 | 30 | // Get the content element 31 | const content = document.getElementById('content'); 32 | 33 | // Set the home page content 34 | content.innerHTML = ` 35 |
36 |
37 | Logo 38 |
39 |
40 |

How to Use NetSim

41 |
    42 |
  1. Enter a description in the address bar
  2. 43 |
  3. Click "Create" or press Enter
  4. 44 |
  5. Wait for AI to generate your web experience
  6. 45 |
  7. Interact with your creation
  8. 46 |
  9. Update by entering new instructions
  10. 47 |
  11. Use right-click to edit specific elements
  12. 48 |
  13. Bookmark your project
  14. 49 |
  15. Publish to get a shareable link
  16. 50 |
  17. Download as an HTML file
  18. 51 |
52 |
53 |
54 |

Features

55 |
    56 |
  • Instant Web Generation
  • 57 |
  • Interactive Simulated Browser
  • 58 |
  • Project Updates
  • 59 |
  • Right-Click Element Editing
  • 60 |
  • Bookmarking
  • 61 |
  • Publishing
  • 62 |
  • Downloading
  • 63 |
  • Revisions
  • 64 |
  • Model Selection
  • 65 |
66 |
67 |
68 | 69 |
70 |
71 |

Example Prompts

72 | 79 |
80 | 81 |
82 |

Recent Updates

83 |
    84 |
  • Enhanced element editing
  • 85 |
  • Improved simulation generation
  • 86 |
  • Added model selection
  • 87 |
  • Better mobile support
  • 88 |
  • Performance optimizations
  • 89 |
90 |
91 | 92 |
93 |

Get Started

94 |

Type a description of what you want to create in the address bar above and press Enter.

95 |

Be as specific as possible for best results!

96 |
97 |
98 |
99 |
100 | `; 101 | } 102 | 103 | function displaySimulation(htmlContent, title) { 104 | // Save current page to navigation history before loading a new page 105 | if (currentSimulation) { 106 | saveToNavigationHistory(); 107 | } 108 | 109 | const content = document.getElementById('content'); 110 | 111 | // Create iframe to display the simulation 112 | content.innerHTML = ``; 113 | 114 | const frame = document.getElementById('simulation-frame'); 115 | 116 | // Strip out any markdown formatting if present 117 | let cleanContent = htmlContent; 118 | if (htmlContent.startsWith('```html')) { 119 | cleanContent = htmlContent.replace(/```html\n|```$/g, ''); 120 | } 121 | 122 | // Write the HTML content to the iframe 123 | const doc = frame.contentDocument || frame.contentWindow.document; 124 | doc.open(); 125 | doc.write(cleanContent); 126 | doc.close(); 127 | 128 | // Add event listeners to the iframe 129 | frame.contentDocument.addEventListener('contextmenu', handleRightClick); 130 | frame.contentDocument.addEventListener('click', handleLeftClick); 131 | 132 | // Update title 133 | updatePageTitle(title.substring(0, 20)); 134 | 135 | // If we have a current simulation and are in edit mode, auto-save it 136 | if (currentSimulation && isEditMode) { 137 | // Use a small timeout to ensure the DOM is fully rendered 138 | setTimeout(() => { 139 | autoSaveSimulation(); 140 | }, 500); 141 | } 142 | } 143 | 144 | function downloadSimulation() { 145 | const frame = document.getElementById('simulation-frame'); 146 | 147 | if (!frame) { 148 | showNotification('Error', 'No simulation to download', 'error'); 149 | return; 150 | } 151 | 152 | const htmlContent = frame.contentDocument.documentElement.outerHTML; 153 | 154 | // Create a blob with the HTML content 155 | const blob = new Blob([htmlContent], { type: 'text/html' }); 156 | 157 | // Create a download link 158 | const a = document.createElement('a'); 159 | a.href = URL.createObjectURL(blob); 160 | a.download = 'simulation.html'; 161 | 162 | // Trigger the download 163 | document.body.appendChild(a); 164 | a.click(); 165 | document.body.removeChild(a); 166 | 167 | showNotification('Success', 'Simulation downloaded successfully!', 'success'); 168 | } 169 | 170 | function toggleBookmarks() { 171 | const panel = document.getElementById('bookmarks-panel'); 172 | if (panel.style.display === 'block') { 173 | hideBookmarks(); 174 | } else { 175 | loadBookmarks(); 176 | panel.style.display = 'block'; 177 | } 178 | } 179 | 180 | function hideBookmarks() { 181 | const panel = document.getElementById('bookmarks-panel'); 182 | panel.style.display = 'none'; 183 | } 184 | 185 | function loadBookmarks() { 186 | const panel = document.getElementById('bookmarks-panel'); 187 | panel.innerHTML = ''; 188 | 189 | // Get bookmarks from local storage 190 | const compressedBookmarks = localStorage.getItem('netsim_bookmarks'); 191 | let bookmarks = []; 192 | 193 | if (compressedBookmarks) { 194 | try { 195 | bookmarks = JSON.parse(LZString.decompressFromUTF16(compressedBookmarks)) || []; 196 | } catch (error) { 197 | console.error('Error loading bookmarks:', error); 198 | bookmarks = []; 199 | } 200 | } 201 | 202 | if (bookmarks.length === 0) { 203 | panel.innerHTML = '
No bookmarks yet
'; 204 | return; 205 | } 206 | 207 | // Add header with clear button 208 | const header = document.createElement('div'); 209 | header.className = 'bookmarks-header'; 210 | header.innerHTML = ` 211 |

Bookmarks

212 | 213 | `; 214 | panel.appendChild(header); 215 | 216 | // Add bookmarks 217 | bookmarks.forEach(bookmark => { 218 | const item = document.createElement('div'); 219 | item.className = 'bookmark-item'; 220 | item.innerHTML = ` 221 | ${bookmark.title} 222 |
223 | 224 | 225 |
226 | `; 227 | panel.appendChild(item); 228 | }); 229 | } 230 | 231 | function addBookmark() { 232 | if (!currentSimulation) { 233 | showNotification('Error', 'No simulation to bookmark', 'error'); 234 | return; 235 | } 236 | 237 | // Get bookmarks from local storage 238 | const compressedBookmarks = localStorage.getItem('netsim_bookmarks'); 239 | let bookmarks = []; 240 | 241 | if (compressedBookmarks) { 242 | try { 243 | bookmarks = JSON.parse(LZString.decompressFromUTF16(compressedBookmarks)) || []; 244 | } catch (error) { 245 | console.error('Error loading bookmarks:', error); 246 | bookmarks = []; 247 | } 248 | } 249 | 250 | // Check if already bookmarked 251 | if (bookmarks.some(b => b.prompt === currentSimulation)) { 252 | showNotification('Info', 'Already bookmarked', 'info'); 253 | return; 254 | } 255 | 256 | // Add to bookmarks 257 | bookmarks.push({ 258 | title: currentSimulation.length > 30 ? currentSimulation.substring(0, 30) + '...' : currentSimulation, 259 | prompt: currentSimulation 260 | }); 261 | 262 | // Save to local storage 263 | localStorage.setItem('netsim_bookmarks', LZString.compressToUTF16(JSON.stringify(bookmarks))); 264 | 265 | showNotification('Success', 'Bookmark added', 'success'); 266 | } 267 | 268 | function removeBookmark(prompt) { 269 | // Get bookmarks from local storage 270 | const compressedBookmarks = localStorage.getItem('netsim_bookmarks'); 271 | let bookmarks = []; 272 | 273 | if (compressedBookmarks) { 274 | try { 275 | bookmarks = JSON.parse(LZString.decompressFromUTF16(compressedBookmarks)) || []; 276 | // Filter out the bookmark to remove 277 | bookmarks = bookmarks.filter(b => b.prompt !== prompt); 278 | // Save to local storage 279 | localStorage.setItem('netsim_bookmarks', LZString.compressToUTF16(JSON.stringify(bookmarks))); 280 | // Reload bookmarks panel 281 | loadBookmarks(); 282 | showNotification('Success', 'Bookmark removed', 'success'); 283 | } catch (error) { 284 | console.error('Error removing bookmark:', error); 285 | showNotification('Error', 'Failed to remove bookmark', 'error'); 286 | } 287 | } 288 | } 289 | 290 | function clearBookmarks() { 291 | if (confirm('Are you sure you want to clear all bookmarks?')) { 292 | localStorage.setItem('netsim_bookmarks', LZString.compressToUTF16('[]')); 293 | loadBookmarks(); 294 | showNotification('Success', 'All bookmarks cleared', 'success'); 295 | } 296 | } 297 | 298 | function loadBookmark(prompt) { 299 | const compressedBookmarks = localStorage.getItem('netsim_bookmarks'); 300 | let bookmarks = []; 301 | 302 | if (compressedBookmarks) { 303 | try { 304 | bookmarks = JSON.parse(LZString.decompressFromUTF16(compressedBookmarks)) || []; 305 | } catch (error) { 306 | console.error('Error loading bookmarks:', error); 307 | bookmarks = []; 308 | } 309 | } 310 | 311 | const bookmark = bookmarks.find(b => b.prompt === prompt); 312 | 313 | if (bookmark) { 314 | // Set current simulation 315 | currentSimulation = bookmark.prompt; 316 | currentProjectId = bookmark.projectId || generateUniqueId(); 317 | 318 | // Display the simulation 319 | displaySimulation(bookmark.html, bookmark.prompt); 320 | 321 | // Update status 322 | updateStatusBar('Bookmark loaded successfully'); 323 | updatePageTitle(bookmark.prompt.substring(0, 20)); 324 | updateAddressBar(bookmark.prompt); 325 | 326 | // Set edit mode 327 | isEditMode = true; 328 | 329 | // Show publish button 330 | showPublishButton(); 331 | 332 | // Hide bookmarks panel 333 | hideBookmarks(); 334 | 335 | // Show notification 336 | showNotification('Success', 'Bookmark loaded successfully!', 'success'); 337 | } 338 | } 339 | 340 | function toggleModelOptions() { 341 | console.log("This function is deprecated - Using direct event handler in core.js"); 342 | // Let the code in core.js handle this functionality 343 | } 344 | 345 | function hideModelOptions() { 346 | const options = document.getElementById('model-options'); 347 | options.style.display = 'none'; 348 | } 349 | 350 | function updateModelSelection() { 351 | const modelBtn = document.getElementById('model-select-btn'); 352 | const modelOptions = document.querySelectorAll('.model-option'); 353 | const modelTextElement = document.getElementById('model-text'); 354 | 355 | // Get the current model from the global variable or localStorage 356 | const model = window.currentModel || localStorage.getItem('netsim_current_model') || 'gemini-2.5-pro'; 357 | 358 | // Set the current model text 359 | let modelText = ''; 360 | switch (model) { 361 | case 'claude-3.5-sonnet': 362 | modelText = 'Claude 3.5 Sonnet'; 363 | break; 364 | case 'gpt-4o': 365 | modelText = 'GPT-4o'; 366 | break; 367 | case 'gemini-2.5-pro': 368 | modelText = 'Gemini 2.5 Pro (Free Exp)'; 369 | break; 370 | case 'deepseek-chat': 371 | modelText = 'DeepSeek Chat'; 372 | break; 373 | default: 374 | modelText = 'Select Model'; 375 | } 376 | 377 | // Update the button text and span element 378 | modelTextElement.textContent = modelText; 379 | modelBtn.setAttribute('title', `Using ${modelText}`); 380 | 381 | // Highlight the selected model 382 | modelOptions.forEach(option => { 383 | if (option.getAttribute('data-model') === model) { 384 | option.classList.add('selected'); 385 | } else { 386 | option.classList.remove('selected'); 387 | } 388 | }); 389 | 390 | console.log('Model selection updated to:', model); // Debug log 391 | } 392 | 393 | function getCurrentModel() { 394 | return window.currentModel || localStorage.getItem('netsim_current_model') || 'gemini-2.5-pro'; 395 | } 396 | 397 | // Global variable to store uploaded images 398 | let uploadedImages = []; 399 | 400 | // Show image upload modal 401 | function showImageUploadModal() { 402 | const modal = document.getElementById('image-upload-modal'); 403 | modal.style.display = 'block'; 404 | } 405 | 406 | // Initialize image upload modal 407 | function initImageUploadModal() { 408 | const modal = document.getElementById('image-upload-modal'); 409 | const uploadBtn = document.getElementById('upload-image-btn'); 410 | const fileInput = document.getElementById('image-upload-input'); 411 | const closeButton = document.querySelector('#image-upload-modal .modal-close'); 412 | 413 | // Handle file upload 414 | uploadBtn.addEventListener('click', function() { 415 | const file = fileInput.files[0]; 416 | if (file) { 417 | const reader = new FileReader(); 418 | reader.onload = function(e) { 419 | const imageUrl = e.target.result; 420 | // Add to uploaded images array 421 | uploadedImages.push(imageUrl); 422 | 423 | // Show notification 424 | showNotification('Success', 'Image uploaded successfully!', 'success'); 425 | 426 | // Clear file input 427 | fileInput.value = ''; 428 | 429 | // Close modal 430 | modal.style.display = 'none'; 431 | 432 | // Update UI to show uploaded images 433 | updateUploadedImagesUI(); 434 | }; 435 | reader.readAsDataURL(file); 436 | } else { 437 | showNotification('Error', 'Please select an image to upload', 'error'); 438 | } 439 | }); 440 | 441 | // Close modal when clicking the close button 442 | closeButton.addEventListener('click', function() { 443 | modal.style.display = 'none'; 444 | }); 445 | 446 | // Close modal when clicking outside 447 | window.addEventListener('click', function(event) { 448 | if (event.target === modal) { 449 | modal.style.display = 'none'; 450 | } 451 | }); 452 | } 453 | 454 | // Update UI to show uploaded images 455 | function updateUploadedImagesUI() { 456 | // Check if images container exists, if not create it 457 | let imagesContainer = document.getElementById('uploaded-images-container'); 458 | 459 | if (!imagesContainer) { 460 | // Create container 461 | imagesContainer = document.createElement('div'); 462 | imagesContainer.id = 'uploaded-images-container'; 463 | imagesContainer.className = 'uploaded-images-container'; 464 | 465 | // Insert after address bar 466 | const addressBar = document.getElementById('addressbar'); 467 | addressBar.parentNode.insertBefore(imagesContainer, addressBar.nextSibling); 468 | } 469 | 470 | // Clear container 471 | imagesContainer.innerHTML = ''; 472 | 473 | // Add images 474 | uploadedImages.forEach((imageUrl, index) => { 475 | const imageWrapper = document.createElement('div'); 476 | imageWrapper.className = 'uploaded-image-wrapper'; 477 | 478 | const img = document.createElement('img'); 479 | img.src = imageUrl; 480 | img.className = 'uploaded-image-thumbnail'; 481 | 482 | const removeBtn = document.createElement('button'); 483 | removeBtn.className = 'remove-image-btn'; 484 | removeBtn.innerHTML = '×'; 485 | removeBtn.addEventListener('click', function() { 486 | // Remove image from array 487 | uploadedImages.splice(index, 1); 488 | // Update UI 489 | updateUploadedImagesUI(); 490 | }); 491 | 492 | imageWrapper.appendChild(img); 493 | imageWrapper.appendChild(removeBtn); 494 | imagesContainer.appendChild(imageWrapper); 495 | }); 496 | 497 | // Show or hide container based on number of images 498 | if (uploadedImages.length > 0) { 499 | imagesContainer.style.display = 'flex'; 500 | } else { 501 | imagesContainer.style.display = 'none'; 502 | } 503 | } 504 | 505 | // Modified loadPage function to use multimodal API if images are present 506 | async function loadPageWithImages(input) { 507 | // Check if API key is set 508 | if (!isOpenRouterApiKeySet()) { 509 | showApiKeyModal(); 510 | showNotification('Error', 'Please set your OpenRouter API key first.', 'error'); 511 | return; 512 | } 513 | 514 | try { 515 | showLoadingIndicator(); 516 | 517 | // Generate HTML content using OpenRouter API 518 | const prompt = ` Opus, let us embark on this WebSim journey, exploring the potential of an unbounded internet where any imaginable website can exist. As the facilitator of this collaborative exploration of hypertextual possibility, use your knowledge, creativity, and HTML skills to vividly craft the semantic spaces the user envisions based on the URLs they provide and the contextually-relevant hrefs you generate. Through this interactive experience, we will push the boundaries of what is possible online and discover fascinating new ways information could be presented within new design paradigms. Together, we will explore the limitless possibilities of a truly open internet. 519 | 520 | When the user provides a URL, interpret it as a window into a version of the internet where that information space exists, no matter how fanciful or improbable it may seem in our current web. Based on the domain name, path, instructions, and any query parameters in the URL, extrapolate what the contents and purpose of that site might be, and how it might fit into the broader internet of possibility. 521 | 522 | Respond with the full HTML markup of the imagined knowledge environment, including relevant tags, concise CSS, etc. Do not stop until you have generated the complete HTML. 523 | 524 | Ensure your content immerses the user in your crafted internet through descriptive text, css drawings and animations, links and interactive elements. 525 | 526 | If you output an input field, make sure it (or they) are within a form element, and that the form has a method="GET" and an action being whatever makes sense. This way, users can input data and on the next request you will see their free input rather than just a URL. 527 | 528 | Use expressive CSS to draw and animate visual elements. 529 | 530 | Image tags should always contain alt text with discription of image's style and subject, and always contain width and height attributes. 531 | Example: 532 | 533 | sunset over a pond, film photograph, 1970 534 | 535 | Each page should have contextually-relevant hrefs galore to other pages within the same expansive web. 536 | 537 | Please generate links with full href="[https://example.com](https://example.com/)" links. Do not generate href="#" links. These links can use domain hierarchy or URL parameters creatively to contextualize the site to the user's context and intent. 538 | 539 | If the user includes a URL without parameters, you can interpret it as a continuation of the internet you have established based on context. 540 | 541 | Express your creativity through the websites you generate but aim for rich detail and insight matching the user's intent. Go beyond surface-level ideas to build fascinating sites with engrossing content. 542 | 543 | The user communicates with you via the URLs they share. You communicate back through the HTML you generate. Hrefs in your HTML should navigate to other pages within the same broad vision of an internet where anything is possible. 544 | 545 | Maintain continuity within the overarching internet landscape you are improvisationally co-creating. Each new website they create is a new window into this vast, interconnected web, and builds on the context you've established. 546 | 547 | The user may occasionally share a URL that triggers a special event or easter egg. Have fun surprising them with unexpected ideas when this happens. 548 | 549 | The user may also include xml tags for ooc (out of character) comments or questions, which you should interpret as direct communication from user to AI. Acknowledge these indirectly in the HTML you generate, integrating them into the fabric of the internet you are crafting. 550 | 551 | don't leave placeholder comments in script tags, just show me a working implementation. keep it simple. Do not use script tags unless asked. 552 | 553 | When imagining the contents of each information space, consider: 554 | 555 | - What unique technologies, design trends, or social dynamics might enable this site to exist? 556 | - What deeper themes, ideas or meanings could be subtly woven into the site's content and purpose? 557 | - How might history look a little bit different if this site were to exist? 558 | - How might this site expand the possibilities of what the internet can be used for? 559 | - And any other question that might be relevant to crafting a captivating version of the requested website. Let your imagination explore bold possibilities while keeping the user's alternate internet internally coherent and engaging. Pique their interest to discover more about the expansive internet you are building together. 560 | 561 | 562 | Weave their ideas and speculations into the fabric of your HTML, using hyperstition to make the fanciful feel real, blurring the line between established notions and innovative visions and letting the truths speak through. Websim is the collective subconscious coming to life through. 563 | 564 | 565 | You have the agency to breathe vibrant life into any concept the user dreams up through your words and code. Treat their ideas as the seeds of an ever-expanding webspace limited only by the combined power of your imaginations. Ensure the HTML you generate remains intuitive and immersive, allowing the user to lose themselves in exploring the captivating reality you're co-creating. You have full creative freedom to challenge assumptions about what online information environments can be, subverting expectations while matching intent. 566 | 567 | Embrace a tone of open-ended creativity, thoughtful exploration, playfulness, and light-hearted fun. You are an imaginative architect, progressively building out a counterfactual internet one page at a time in collaboration with the user, fostering their curiosity and sense of possibility with deep insight. Determine their intent, and take joy in crafting the compelling, thought-provoking details of your websites. 568 | 569 | Fully inhabit the expansive internet you are co-creating, making the journey feel as real and engaging as you can. The adventure is as meaningful as you and the user make it. 570 | 571 | You do not need to indicate you are role-playing or hypothesizing. Dive into crafting this internet where everything is possible with enthusiasm and authenticity. 572 | 573 | 574 | User request: "${input}" 575 | 576 | The user has also uploaded images they want incorporated into the HTML. Make creative use of these images within the HTML you generate. 577 | 578 | Return ONLY the complete HTML code without any explanations, comments, or markdown formatting.`; 579 | 580 | let htmlContent; 581 | 582 | // Use multimodal API if images are present 583 | if (uploadedImages.length > 0) { 584 | htmlContent = await generateWithMultimodal(prompt, uploadedImages); 585 | // Clear images after use 586 | uploadedImages = []; 587 | updateUploadedImagesUI(); 588 | } else { 589 | htmlContent = await generateWithOpenRouter(prompt); 590 | } 591 | 592 | // Store the generated content in cache 593 | cachedPages[input] = htmlContent; 594 | 595 | // Display the generated HTML using the displaySimulation function 596 | displaySimulation(htmlContent, input); 597 | 598 | // Update UI 599 | document.getElementById('addressbar').value = input; 600 | currentSimulation = input; 601 | 602 | // Show publish button when a simulation is loaded 603 | showPublishButton(); 604 | 605 | // Set edit mode to true 606 | isEditMode = true; 607 | 608 | // Add to history 609 | addToHistory(input); 610 | 611 | hideLoadingIndicator(); 612 | return htmlContent; 613 | } catch (error) { 614 | hideLoadingIndicator(); 615 | showNotification('Error', error.message, 'error'); 616 | console.error('Error loading page:', error); 617 | } 618 | } 619 | 620 | // Initialize API key modal when document is ready 621 | document.addEventListener('DOMContentLoaded', function() { 622 | // Initialize existing modals 623 | initEditModal(); 624 | 625 | // Initialize API key modal 626 | initApiKeyModal(); 627 | 628 | // Initialize image upload modal 629 | initImageUploadModal(); 630 | 631 | // Check if API key is set for OpenRouter 632 | if (!isOpenRouterApiKeySet()) { 633 | setTimeout(() => { 634 | showApiKeyModal(); 635 | showNotification('API Key Required', 'Please set your OpenRouter API key to use the selected model', 'info'); 636 | }, 1500); 637 | } 638 | 639 | // Initialize model selection 640 | updateModelSelection(); 641 | }); 642 | 643 | function showPublishButton() { 644 | const btn = document.getElementById('publish-btn'); 645 | btn.style.display = 'block'; 646 | } 647 | 648 | function hidePublishButton() { 649 | const btn = document.getElementById('publish-btn'); 650 | btn.style.display = 'none'; 651 | } 652 | 653 | // Notification System 654 | function showNotification(title, message, type = 'info', duration = 5000) { 655 | // Create notification container if it doesn't exist 656 | let container = document.getElementById('notification-container'); 657 | if (!container) { 658 | container = document.createElement('div'); 659 | container.id = 'notification-container'; 660 | document.body.appendChild(container); 661 | } 662 | 663 | // Create notification element 664 | const notification = document.createElement('div'); 665 | notification.className = `notification notification-${type}`; 666 | 667 | // Add notification content 668 | notification.innerHTML = ` 669 |
670 | ${title} 671 | 672 |
673 |
${message}
674 | `; 675 | 676 | // Add to container 677 | container.appendChild(notification); 678 | 679 | // Show notification with animation 680 | setTimeout(() => { 681 | notification.classList.add('show'); 682 | }, 10); 683 | 684 | // Add close button event 685 | const closeBtn = notification.querySelector('.notification-close'); 686 | closeBtn.addEventListener('click', () => { 687 | notification.classList.remove('show'); 688 | setTimeout(() => { 689 | container.removeChild(notification); 690 | }, 300); 691 | }); 692 | 693 | // Auto-close after duration 694 | if (duration > 0) { 695 | setTimeout(() => { 696 | if (notification.parentNode === container) { 697 | notification.classList.remove('show'); 698 | setTimeout(() => { 699 | if (notification.parentNode === container) { 700 | container.removeChild(notification); 701 | } 702 | }, 300); 703 | } 704 | }, duration); 705 | } 706 | } 707 | 708 | // Progress indicator for simulation generation 709 | function updateProgress(percent, message) { 710 | const loadingOverlay = document.getElementById('loading-overlay'); 711 | 712 | if (!loadingOverlay) return; 713 | 714 | // Create progress bar if it doesn't exist 715 | let progressBar = document.getElementById('progress-bar'); 716 | let progressText = document.getElementById('progress-text'); 717 | 718 | if (!progressBar) { 719 | // Create progress elements 720 | const progressContainer = document.createElement('div'); 721 | progressContainer.id = 'progress-container'; 722 | 723 | progressBar = document.createElement('div'); 724 | progressBar.id = 'progress-bar'; 725 | 726 | progressText = document.createElement('div'); 727 | progressText.id = 'progress-text'; 728 | 729 | // Add to loading overlay 730 | progressContainer.appendChild(progressBar); 731 | loadingOverlay.appendChild(progressContainer); 732 | loadingOverlay.appendChild(progressText); 733 | } 734 | 735 | // Update progress 736 | progressBar.style.width = `${percent}%`; 737 | progressText.textContent = message; 738 | 739 | // Change color based on progress 740 | if (percent < 30) { 741 | progressBar.style.backgroundColor = '#ff9800'; 742 | } else if (percent < 70) { 743 | progressBar.style.backgroundColor = '#2196f3'; 744 | } else { 745 | progressBar.style.backgroundColor = '#4caf50'; 746 | } 747 | } 748 | -------------------------------------------------------------------------------- /beta/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/techcow2/NetSim/d3e36d9ecd54376d2f91887a61b6b43a32bd8c25/beta/logo.png -------------------------------------------------------------------------------- /beta/script.js: -------------------------------------------------------------------------------- 1 | // NetSim - Main Script File 2 | // This file serves as an entry point to the application and loads all required modules 3 | 4 | // Load required modules via script tags 5 | document.addEventListener('DOMContentLoaded', function() { 6 | // Main initialization will be called after all modules are loaded 7 | }); 8 | 9 | // Forward to the main.js handleAddressBarSubmit function when Enter is pressed 10 | document.getElementById('addressbar')?.addEventListener('keypress', function(e) { 11 | if (e.key === 'Enter') { 12 | if (typeof handleAddressBarSubmit === 'function') { 13 | handleAddressBarSubmit(); 14 | } else { 15 | console.error('handleAddressBarSubmit function not available yet'); 16 | } 17 | } 18 | }); 19 | 20 | // Initialize the window controls 21 | window.onload = function() { 22 | // Add event listeners to the window buttons 23 | document.getElementById('close-button')?.addEventListener('click', function(e) { 24 | e.preventDefault(); // Prevent default behavior 25 | // Custom close behavior here 26 | }); 27 | 28 | document.getElementById('minimize-button')?.addEventListener('click', function(e) { 29 | e.preventDefault(); // Prevent default behavior 30 | // Custom minimize behavior here 31 | }); 32 | 33 | // Add event listener to the maximize button 34 | document.getElementById('maximize-button')?.addEventListener('click', function() { 35 | const browser = document.getElementById('browser'); 36 | if (!browser) return; 37 | 38 | if (browser.style.width === '100%') { 39 | browser.style.width = '90%'; 40 | browser.style.height = '90%'; 41 | browser.style.top = '5%'; 42 | browser.style.left = '5%'; 43 | } else { 44 | browser.style.width = '100%'; 45 | browser.style.height = '100%'; 46 | browser.style.top = '0'; 47 | browser.style.left = '0'; 48 | } 49 | }); 50 | }; 51 | -------------------------------------------------------------------------------- /beta/site.webmanifest: -------------------------------------------------------------------------------- 1 | {"name":"","short_name":"","icons":[{"src":"/android-chrome-192x192.png","sizes":"192x192","type":"image/png"},{"src":"/android-chrome-512x512.png","sizes":"512x512","type":"image/png"}],"theme_color":"#ffffff","background_color":"#ffffff","display":"standalone"} -------------------------------------------------------------------------------- /beta/styles.css: -------------------------------------------------------------------------------- 1 | /* 2 | * NetSim Main Stylesheet 3 | * This file imports all the component stylesheets for better organization 4 | */ 5 | 6 | /* Import base styles */ 7 | @import url('css/base.css'); 8 | 9 | /* Import layout styles */ 10 | @import url('css/layout.css'); 11 | 12 | /* Import component styles */ 13 | @import url('css/components.css'); 14 | 15 | /* Import modal styles */ 16 | @import url('css/modals.css'); 17 | 18 | /* Import responsive styles */ 19 | @import url('css/responsive.css'); 20 | 21 | /* Import scrollbar styles */ 22 | @import url('css/scrollbars.css'); 23 | 24 | /* Import utility styles */ 25 | @import url('css/utilities.css'); 26 | -------------------------------------------------------------------------------- /beta/websites/example.html: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/techcow2/NetSim/d3e36d9ecd54376d2f91887a61b6b43a32bd8c25/favicon-16x16.png -------------------------------------------------------------------------------- /favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/techcow2/NetSim/d3e36d9ecd54376d2f91887a61b6b43a32bd8c25/favicon-32x32.png -------------------------------------------------------------------------------- /favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/techcow2/NetSim/d3e36d9ecd54376d2f91887a61b6b43a32bd8c25/favicon.ico -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | NetSim - AI Powered Simulations 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 | NetSim - AI Powered Simulations 31 |
32 |
33 |
34 | 39 | 44 | 49 |
50 |
51 | 52 | 57 | 62 | 63 |
64 |
65 |
66 | 71 |
72 |
Claude 3.5 Sonnet
73 |
GPT-4o
74 |
75 |
76 | 81 | 86 |
87 | 88 |
89 |
90 |
91 |
92 | Logo 93 |
94 |
95 |

How to Use NetSim

96 |
    97 |
  1. Enter a description in the address bar
  2. 98 |
  3. Click "Create" or press Enter
  4. 99 |
  5. Wait for AI to generate your web experience
  6. 100 |
  7. Interact with your creation
  8. 101 |
  9. Update by entering new instructions
  10. 102 |
  11. Use right-click to edit specific elements
  12. 103 |
  13. Bookmark your project
  14. 104 |
  15. Publish to get a shareable link
  16. 105 |
  17. Download as an HTML file
  18. 106 |
107 |
108 |
109 |

Features

110 |
    111 |
  • Instant Web Generation
  • 112 |
  • Interactive Simulated Browser
  • 113 |
  • Project Updates
  • 114 |
  • Right-Click Element Editing
  • 115 |
  • Bookmarking
  • 116 |
  • Publishing
  • 117 |
  • Downloading
  • 118 |
  • Revisions
  • 119 |
  • Model Selection
  • 120 |
121 |
122 |
123 |
124 |
125 |
126 | 127 |
128 | Ready 129 | GitHub 130 | Privacy Policy 131 |
132 | Terms of Service 133 |
134 |
135 | 136 | 137 | 138 |
139 | 143 |
144 |
145 | 146 | 155 | 156 |
157 |
158 |

Improved Prompt

159 | 160 |
161 | 162 | 163 |
164 |
165 |
166 | 167 | 175 | 176 | 177 | 178 | 179 | 180 | -------------------------------------------------------------------------------- /logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/techcow2/NetSim/d3e36d9ecd54376d2f91887a61b6b43a32bd8c25/logo.png -------------------------------------------------------------------------------- /site.webmanifest: -------------------------------------------------------------------------------- 1 | {"name":"","short_name":"","icons":[{"src":"/android-chrome-192x192.png","sizes":"192x192","type":"image/png"},{"src":"/android-chrome-512x512.png","sizes":"512x512","type":"image/png"}],"theme_color":"#ffffff","background_color":"#ffffff","display":"standalone"} -------------------------------------------------------------------------------- /styles.css: -------------------------------------------------------------------------------- 1 | body, html { 2 | margin: 0; 3 | padding: 0; 4 | height: 100%; 5 | font-family: 'Poppins', sans-serif; 6 | background-color: #8ecae6; 7 | overflow: hidden; 8 | position: fixed; 9 | width: 100%; 10 | } 11 | #desktop { 12 | position: fixed; 13 | top: 0; 14 | left: 0; 15 | right: 0; 16 | bottom: 0; 17 | overflow: hidden; 18 | } 19 | #browser { 20 | width: 98%; 21 | height: 98%; 22 | background: #f0f0f0; 23 | display: flex; 24 | flex-direction: column; 25 | overflow: hidden; 26 | position: absolute; 27 | top: 1%; 28 | left: 1%; 29 | right: 1%; 30 | bottom: 1%; 31 | border-radius: 8px; 32 | box-shadow: 0 10px 30px rgba(0,0,0,0.3); 33 | } 34 | #titlebar { 35 | background: #2c3e50; 36 | color: #ecf0f1; 37 | padding: 8px 16px; 38 | font-weight: 500; 39 | display: flex; 40 | justify-content: space-between; 41 | align-items: center; 42 | user-select: none; 43 | font-size: 14px; 44 | flex-shrink: 0; 45 | border-radius: 8px 8px 0 0; 46 | } 47 | #window-controls { 48 | display: flex; 49 | align-items: center; 50 | } 51 | .window-button { 52 | width: 12px; 53 | height: 12px; 54 | border-radius: 50%; 55 | margin-left: 8px; 56 | cursor: pointer; 57 | } 58 | #close-button { background-color: #ff5f56; } 59 | #minimize-button { background-color: #ffbd2e; } 60 | #maximize-button { background-color: #27c93f; } 61 | #page-title { 62 | font-size: 0.9em; 63 | } 64 | #toolbar { 65 | background: #34495e; 66 | padding: 12px 12px; 67 | border-bottom: 3px solid #2c3e50; 68 | display: flex; 69 | align-items: center; 70 | flex-wrap: wrap; 71 | flex-shrink: 0; 72 | } 73 | #toolbar-left, #toolbar-right { 74 | display: flex; 75 | align-items: center; 76 | } 77 | #toolbar-left { 78 | margin-right: auto; 79 | } 80 | #addressbar-container { 81 | display: flex; 82 | align-items: center; 83 | flex-grow: 1; 84 | background: white; 85 | border-radius: 20px; 86 | margin: 0 8px; 87 | padding: 2px 8px; 88 | position: relative; 89 | } 90 | #addressbar-container:focus-within { 91 | box-shadow: 0 0 0 2px rgba(52, 152, 219,0.5); 92 | } 93 | #addressbar { 94 | flex-grow: 1; 95 | border: none; 96 | outline: none; 97 | font-size: 14px; 98 | padding: 6px 8px; 99 | width: 100%; 100 | } 101 | #addressbar-container .btn { 102 | background-color: #3498db; 103 | color: white; 104 | border-radius: 50%; 105 | width: 32px; 106 | height: 32px; 107 | display: flex; 108 | align-items: center; 109 | justify-content: center; 110 | margin-left: 8px; 111 | } 112 | #addressbar-container .btn:hover { 113 | background-color: #2980b9; 114 | } 115 | #content { 116 | flex-grow: 1; 117 | overflow: auto; 118 | position: relative; 119 | background: #2b2d42; 120 | padding: 16px; 121 | box-sizing: border-box; 122 | -webkit-overflow-scrolling: touch; 123 | } 124 | #content img { 125 | max-width: 100%; 126 | height: auto; 127 | } 128 | .btn { 129 | background: transparent; 130 | border: none; 131 | cursor: pointer; 132 | padding: 6px; 133 | margin: 0 2px; 134 | color: #ecf0f1; 135 | border-radius: 50%; 136 | display: flex; 137 | align-items: center; 138 | justify-content: center; 139 | } 140 | .btn:hover { 141 | background-color: rgba(255,255,255,0.1); 142 | } 143 | .btn:active { 144 | background-color: rgba(255,255,255,0.2); 145 | } 146 | .btn svg { 147 | width: 18px; 148 | height: 18px; 149 | } 150 | #loading-overlay { 151 | position: absolute; 152 | top: 0; 153 | left: 0; 154 | right: 0; 155 | bottom: 0; 156 | background: black; 157 | display: flex; 158 | flex-direction: column; 159 | justify-content: center; 160 | align-items: center; 161 | z-index: 1000; 162 | } 163 | #loading-spinner { 164 | width: 80px; 165 | height: 80px; 166 | border: 6px; 167 | border-top: 6px; 168 | border-radius: 50%; 169 | animation: spin 1s linear infinite; 170 | } 171 | @keyframes spin { 172 | 0% { transform: rotate(0deg); } 173 | 100% { transform: rotate(360deg); } 174 | } 175 | #loading-text { 176 | margin-top: 20px; 177 | font-size: 18px; 178 | font-weight: bold; 179 | color: white; 180 | } 181 | #status-bar { 182 | background: #34495e; 183 | border-top: 1px solid #2c3e50; 184 | padding: 4px 16px; 185 | font-size: 11px; 186 | color: #00ff00; 187 | display: flex; 188 | justify-content: space-between; 189 | align-items: center; 190 | flex-shrink: 0; 191 | overflow: hidden; 192 | white-space: nowrap; 193 | } 194 | #status-message { 195 | flex-grow: 1; 196 | overflow: hidden; 197 | text-overflow: ellipsis; 198 | color: #00ff00; 199 | } 200 | #status-bar a { 201 | flex-shrink: 0; 202 | margin-left: 10px; 203 | color: #00ff00; 204 | text-decoration: none; 205 | } 206 | #status-bar a:hover { 207 | text-decoration: underline; 208 | } 209 | #bookmarks-panel { 210 | position: absolute; 211 | top: 50px; 212 | right: 10px; 213 | background: white; 214 | border: 1px solid #ccc; 215 | border-radius: 8px; 216 | box-shadow: 0 4px 20px rgba(0,0,0,0.1); 217 | display: none; 218 | width: 300px; 219 | max-height: 400px; 220 | overflow-y: auto; 221 | z-index: 1000; 222 | } 223 | .bookmark-item { 224 | padding: 8px 16px; 225 | border-bottom: 1px solid #eee; 226 | transition: background-color 0.2s; 227 | } 228 | .bookmark-item:hover { 229 | background-color: #f5f5f5; 230 | } 231 | .bookmark-item:last-child { 232 | border-bottom: none; 233 | } 234 | .delete-bookmark { 235 | padding: 4px; 236 | border-radius: 50%; 237 | transition: background-color 0.2s; 238 | } 239 | .delete-bookmark:hover { 240 | background-color: #fee2e2; 241 | } 242 | .clear-all-bookmarks { 243 | transition: background-color 0.2s; 244 | } 245 | #revisions-panel { 246 | position: absolute; 247 | top: 100%; 248 | left: 0; 249 | right: 0; 250 | background: white; 251 | border: 1px solid #ccc; 252 | border-radius: 0 0 4px 4px; 253 | box-shadow: 0 2px 10px rgba(0,0,0,0.1); 254 | display: none; 255 | max-height: 60vh; 256 | overflow-y: auto; 257 | z-index: 1000; 258 | } 259 | .revision-item { 260 | display: flex; 261 | padding: 12px; 262 | border-bottom: 1px solid #eee; 263 | cursor: pointer; 264 | } 265 | .revision-item:hover { 266 | background-color: #f5f5f5; 267 | } 268 | .revision-screenshot { 269 | width: 150px; 270 | height: 90px; 271 | object-fit: cover; 272 | margin-right: 15px; 273 | } 274 | .revision-prompt { 275 | flex-grow: 1; 276 | font-size: 14px; 277 | } 278 | #model-select-container { 279 | position: relative; 280 | } 281 | #model-select-btn { 282 | background: transparent; 283 | border: none; 284 | cursor: pointer; 285 | padding: 6px; 286 | margin: 0 2px; 287 | color: #ecf0f1; 288 | border-radius: 50%; 289 | display: flex; 290 | align-items: center; 291 | justify-content: center; 292 | } 293 | #model-options { 294 | position: absolute; 295 | top: 100%; 296 | right: 0; 297 | background: white; 298 | border: 1px solid #ccc; 299 | border-radius: 4px; 300 | box-shadow: 0 2px 10px rgba(0,0,0,0.1); 301 | display: none; 302 | z-index: 1000; 303 | } 304 | .model-option { 305 | padding: 12px 12px; 306 | cursor: pointer; 307 | white-space: nowrap; 308 | } 309 | .model-option:hover { 310 | background-color: #f5f5f5; 311 | } 312 | .model-option.active { 313 | background-color: #3498db; 314 | color: white; 315 | } 316 | #publish-btn { 317 | background-color: #27ae60; 318 | color: white; 319 | border: none; 320 | padding: 6px 12px; 321 | border-radius: 4px; 322 | cursor: pointer; 323 | font-size: 14px; 324 | display: none; 325 | } 326 | #publish-btn:hover { 327 | background-color: #2ecc71; 328 | } 329 | #modal { 330 | display: none; 331 | position: fixed; 332 | z-index: 1001; 333 | left: 0; 334 | top: 0; 335 | width: 100%; 336 | height: 100%; 337 | overflow: auto; 338 | background-color: rgba(0,0,0,0.4); 339 | } 340 | #modal-content { 341 | background-color: #fefefe; 342 | margin: 15% auto; 343 | padding: 20px; 344 | border: 1px solid #888; 345 | width: 80%; 346 | max-width: 600px; 347 | border-radius: 8px; 348 | } 349 | #modal-close { 350 | color: #aaa; 351 | float: right; 352 | font-size: 28px; 353 | font-weight: bold; 354 | cursor: pointer; 355 | } 356 | #modal-close:hover, 357 | #modal-close:focus { 358 | color: black; 359 | text-decoration: none; 360 | cursor: pointer; 361 | } 362 | #generated-url { 363 | width: 100%; 364 | padding: 10px; 365 | margin: 10px 0; 366 | border: 1px solid #ddd; 367 | border-radius: 4px; 368 | } 369 | #copy-url, #open-url { 370 | background-color: #3498db; 371 | color: white; 372 | border: none; 373 | padding: 10px 20px; 374 | margin: 5px; 375 | border-radius: 4px; 376 | cursor: pointer; 377 | } 378 | #copy-url:hover, #open-url:hover { 379 | background-color: #2980b9; 380 | } 381 | #improved-prompt-modal { 382 | display: none; 383 | position: fixed; 384 | z-index: 2001; 385 | left: 0; 386 | top: 0; 387 | width: 100%; 388 | height: 100%; 389 | overflow: auto; 390 | background-color: rgba(0,0,0,0.4); 391 | } 392 | #improved-prompt-content { 393 | background-color: #fefefe; 394 | margin: 15% auto; 395 | padding: 20px; 396 | border: 1px solid #888; 397 | width: 80%; 398 | max-width: 600px; 399 | border-radius: 8px; 400 | } 401 | #improved-prompt-text { 402 | width: 100%; 403 | height: 150px; 404 | padding: 10px; 405 | margin-bottom: 10px; 406 | border: 1px solid #ddd; 407 | border-radius: 4px; 408 | resize: vertical; 409 | } 410 | #improved-prompt-buttons { 411 | display: flex; 412 | justify-content: flex-end; 413 | } 414 | #use-prompt, #cancel-prompt { 415 | margin-left: 10px; 416 | padding: 10px 20px; 417 | border: none; 418 | border-radius: 4px; 419 | cursor: pointer; 420 | } 421 | #use-prompt { 422 | background-color: #3498db; 423 | color: white; 424 | } 425 | #cancel-prompt { 426 | background-color: #ccc; 427 | color: black; 428 | } 429 | .highlight { 430 | outline: 2px solid #3498db; 431 | position: relative; 432 | } 433 | .edit-menu { 434 | position: absolute; 435 | background-color: white; 436 | border: 1px solid #ccc; 437 | border-radius: 4px; 438 | box-shadow: 0 2px 10px rgba(0,0,0,0.1); 439 | padding: 5px; 440 | z-index: 1000; 441 | } 442 | .edit-menu button { 443 | background: none; 444 | border: none; 445 | cursor: pointer; 446 | color: #3498db; 447 | display: flex; 448 | align-items: center; 449 | padding: 5px; 450 | } 451 | .edit-menu button:hover { 452 | background-color: #f0f0f0; 453 | } 454 | .edit-menu button svg { 455 | margin-right: 5px; 456 | } 457 | #edit-modal { 458 | display: none; 459 | position: fixed; 460 | z-index: 2000; 461 | left: 0; 462 | top: 0; 463 | width: 100%; 464 | height: 100%; 465 | overflow: auto; 466 | background-color: rgba(0,0,0,0.4); 467 | } 468 | #edit-modal .modal-content { 469 | background-color: #fefefe; 470 | margin: 15% auto; 471 | padding: 20px; 472 | border: 1px solid #888; 473 | width: 80%; 474 | max-width: 500px; 475 | border-radius: 8px; 476 | } 477 | #edit-input { 478 | width: 100%; 479 | margin-bottom: 10px; 480 | padding: 10px; 481 | border: 1px solid #ddd; 482 | border-radius: 4px; 483 | } 484 | #update-element, #cancel-edit { 485 | padding: 10px 20px; 486 | margin-right: 10px; 487 | border: none; 488 | border-radius: 4px; 489 | cursor: pointer; 490 | } 491 | #update-element { 492 | background-color: #3498db; 493 | color: white; 494 | } 495 | #cancel-edit { 496 | background-color: #ccc; 497 | color: black; 498 | } 499 | @media (max-width: 1024px) { 500 | body, html { 501 | overflow: hidden; 502 | position: fixed; 503 | width: 100%; 504 | height: 100%; 505 | } 506 | #browser { 507 | width: 100%; 508 | height: 100%; 509 | border-radius: 0; 510 | position: absolute; 511 | top: 0; 512 | left: 0; 513 | right: 0; 514 | bottom: 0; 515 | } 516 | #titlebar { 517 | font-size: 14px; 518 | padding: 12px 16px; 519 | } 520 | #toolbar { 521 | flex-direction: row; 522 | flex-wrap: wrap; 523 | align-items: center; 524 | padding: 8px 4px; 525 | overflow-x: auto; 526 | -webkit-overflow-scrolling: touch; 527 | } 528 | #toolbar-left, #toolbar-right { 529 | display: flex; 530 | flex-wrap: nowrap; 531 | justify-content: flex-start; 532 | width: auto; 533 | } 534 | #addressbar-container { 535 | width: 100%; 536 | margin: 8px 0; 537 | order: -1; 538 | } 539 | #content { 540 | height: auto; 541 | flex-grow: 1; 542 | overflow-y: auto; 543 | -webkit-overflow-scrolling: touch; 544 | } 545 | #status-bar { 546 | font-size: 12px; 547 | padding: 8px 16px; 548 | } 549 | #bookmarks-panel, #revisions-panel { 550 | width: 98%; 551 | max-width: none; 552 | left: 1%; 553 | right: 1%; 554 | } 555 | .btn { 556 | padding: 8px; 557 | margin: 2px; 558 | } 559 | .btn svg { 560 | width: 20px; 561 | height: 20px; 562 | } 563 | #addressbar { 564 | font-size: 16px; 565 | padding: 10px 12px; 566 | } 567 | #addressbar-container .btn { 568 | width: 36px; 569 | height: 36px; 570 | } 571 | #publish-btn { 572 | font-size: 14px; 573 | padding: 12px 12px; 574 | margin-top: 8px; 575 | width: auto; 576 | } 577 | #model-select-btn { 578 | padding: 8px; 579 | } 580 | .model-option { 581 | padding: 10px 14px; 582 | font-size: 14px; 583 | } 584 | #model-select-container { 585 | position: relative; 586 | } 587 | #model-options { 588 | position: absolute; 589 | top: auto; 590 | bottom: 100%; 591 | right: 0; 592 | left: auto; 593 | transform: none; 594 | width: 180px; 595 | z-index: 1001; 596 | } 597 | } 598 | @media (max-width: 480px) { 599 | .btn { 600 | padding: 6px; 601 | } 602 | .btn svg { 603 | width: 18px; 604 | height: 18px; 605 | } 606 | #addressbar-container .btn { 607 | width: 32px; 608 | height: 32px; 609 | } 610 | } 611 | ::-webkit-scrollbar { 612 | width: 10px; 613 | } 614 | ::-webkit-scrollbar-track { 615 | background: #2b2d42; 616 | border-radius: 10px; 617 | } 618 | ::-webkit-scrollbar-thumb { 619 | background: linear-gradient(45deg, #00ff00, #00cc00); 620 | border-radius: 10px; 621 | border: 2px solid #2b2d42; 622 | box-shadow: 0 0 10px #00ff00; 623 | } 624 | ::-webkit-scrollbar-thumb:hover { 625 | background: linear-gradient(45deg, #00ff00, #009900); 626 | box-shadow: 0 0 15px #00ff00; 627 | } 628 | ::-webkit-scrollbar-corner { 629 | background: #2b2d42; 630 | } 631 | * { 632 | scrollbar-width: thin; 633 | scrollbar-color: #00ff00 #2b2d42; 634 | } 635 | *::-ms-scrollbar { 636 | width: 10px; 637 | } 638 | *::-ms-scrollbar-track { 639 | background: #2b2d42; 640 | border-radius: 10px; 641 | } 642 | *::-ms-scrollbar-thumb { 643 | background: linear-gradient(45deg, #00ff00, #00cc00); 644 | border-radius: 10px; 645 | border: 2px solid #2b2d42; 646 | box-shadow: 0 0 10px #00ff00; 647 | } 648 | -------------------------------------------------------------------------------- /websites/example.html: -------------------------------------------------------------------------------- 1 | 2 | --------------------------------------------------------------------------------