├── .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 |
31 |
NetSim - AI Powered Simulations
32 |
33 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
How to Use NetSim
120 |
121 | Enter a description in the address bar
122 | Click "Create" or press Enter
123 | Wait for AI to generate your web experience
124 | Interact with your creation
125 | Update by entering new instructions
126 | Use right-click to edit specific elements
127 | Bookmark your project
128 | Publish to get a shareable link
129 | Download as an HTML file
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 |
159 |
160 |
Generating Simulation...
161 |
162 |
Initializing...
163 |
166 |
167 | 0%
168 | Est. time: calculating...
169 |
170 |
171 |
172 |
173 |
174 |
175 |
176 |
177 |
Edit Element
178 |
179 |
180 | Update
181 | Cancel
182 |
183 |
184 |
185 |
186 |
187 |
188 |
×
189 |
190 |
191 |
192 |
What's New
193 |
194 |
195 |
196 |
197 | Enhanced element editing with multiple options: Edit, Style, Replace, Duplicate, and Delete
198 | Improved element selection with more precise targeting
199 | Added visual feedback with notifications
200 | Better progress indicators during simulation generation
201 | More detailed element identification for better editing accuracy
202 |
203 |
204 |
Got it!
205 |
206 |
207 |
208 |
209 |
210 | ×
211 |
Published URL
212 |
213 | Copy URL
214 | Open URL
215 |
216 |
217 |
218 |
219 |
220 |
Improved Prompt
221 |
222 |
223 | Use this prompt
224 | Cancel
225 |
226 |
227 |
228 |
229 |
230 |
231 |
×
232 |
233 |
234 |
API Settings
235 |
236 |
Enter your API keys to use the required services. Your API keys are stored locally in your browser.
237 |
238 | OpenRouter API Key:
239 |
240 |
241 |
242 | Pixabay API Key:
243 |
244 |
245 |
246 | Save
247 | Cancel
248 |
249 |
250 |
251 |
252 |
253 |
254 | ×
255 |
Upload Image
256 |
257 | Upload
258 |
259 |
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 |
87 |
90 |
93 |
96 |
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 = '';
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 | Load
156 | Delete
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 |
38 |
39 |
40 |
How to Use NetSim
41 |
42 | Enter a description in the address bar
43 | Click "Create" or press Enter
44 | Wait for AI to generate your web experience
45 | Interact with your creation
46 | Update by entering new instructions
47 | Use right-click to edit specific elements
48 | Bookmark your project
49 | Publish to get a shareable link
50 | Download as an HTML file
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 | Clear All
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 | Load
224 | Remove
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 |
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 |
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 |
30 |
NetSim - AI Powered Simulations
31 |
32 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
How to Use NetSim
96 |
97 | Enter a description in the address bar
98 | Click "Create" or press Enter
99 | Wait for AI to generate your web experience
100 | Interact with your creation
101 | Update by entering new instructions
102 | Use right-click to edit specific elements
103 | Bookmark your project
104 | Publish to get a shareable link
105 | Download as an HTML file
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 |
135 |
136 |
137 |
138 |
139 |
140 |
141 |
Improving Prompt...
142 |
143 |
144 |
145 |
146 |
147 |
148 | ×
149 |
Published URL
150 |
151 | Copy URL
152 | Open URL
153 |
154 |
155 |
156 |
157 |
158 |
Improved Prompt
159 |
160 |
161 | Use this prompt
162 | Cancel
163 |
164 |
165 |
166 |
167 |
168 |
169 |
Edit Element
170 |
171 | Update
172 | Cancel
173 |
174 |
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 |
--------------------------------------------------------------------------------