├── .gitignore ├── .gitmodules ├── vite.config.js ├── client ├── example-app │ ├── README.md │ ├── example-app.css │ ├── index.html │ ├── help-content.html │ └── example-app.js ├── index.html ├── app.js ├── help-content-template.html ├── help-modal.js └── bespoke-template.css ├── package.json ├── .github └── workflows │ └── build-release.yml ├── LICENSE ├── AGENTS.md ├── server.js ├── README.md └── BESPOKE-TEMPLATE.md /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist/ 3 | .DS_Store 4 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "client/design-system"] 2 | path = client/design-system 3 | url = https://github.com/CodeSignal/learn_bespoke-design-system.git 4 | -------------------------------------------------------------------------------- /vite.config.js: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite'; 2 | 3 | export default defineConfig({ 4 | root: './client', 5 | server: { 6 | host: '0.0.0.0', 7 | hmr: true, 8 | allowedHosts: true, 9 | port: 3000, 10 | proxy: { 11 | '/message': { 12 | target: 'http://localhost:3001', 13 | changeOrigin: true 14 | }, 15 | '/ws': { 16 | target: 'ws://localhost:3001', 17 | ws: true, 18 | changeOrigin: true 19 | } 20 | } 21 | }, 22 | build: { 23 | outDir: '../dist', 24 | emptyOutDir: true 25 | } 26 | }); 27 | 28 | -------------------------------------------------------------------------------- /client/example-app/README.md: -------------------------------------------------------------------------------- 1 | # Example App 2 | 3 | This directory contains an example application that demonstrates how to use the Bespoke Simulation template and its design system components. The example app showcases a simple interactive counter application that uses buttons, inputs, dropdowns, tags, and other design system components to illustrate the template's features and usage patterns. Accessible via the development server at `http://localhost:3000/example-app/index.html`. 4 | 5 | **Important:** This example app is included for reference and testing purposes only. When customizing this template for your own application, you should remove this entire `example-app` directory and replace it with your own application code: 6 | 7 | ```bash 8 | rm -rf client/example-app 9 | ``` 10 | 11 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bespoke-template", 3 | "version": "0.0.2", 4 | "description": "Bespoke template with local development server and WebSocket messaging", 5 | "main": "server.js", 6 | "scripts": { 7 | "start": "npm run start:prod", 8 | "start:prod": "IS_PRODUCTION=true node server.js", 9 | "start:dev": "concurrently \"npm run dev:vite\" \"npm run dev:api\"", 10 | "dev:vite": "vite", 11 | "dev:api": "PORT=3001 node server.js", 12 | "build": "vite build" 13 | }, 14 | "keywords": [ 15 | "bespoke", 16 | "template", 17 | "development", 18 | "server", 19 | "websocket" 20 | ], 21 | "author": "", 22 | "license": "MIT", 23 | "dependencies": { 24 | "ws": "^8.14.2" 25 | }, 26 | "devDependencies": { 27 | "concurrently": "^8.2.2", 28 | "vite": "^7.2.4" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /.github/workflows/build-release.yml: -------------------------------------------------------------------------------- 1 | name: Build and Release 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | 7 | permissions: 8 | contents: write 9 | 10 | jobs: 11 | build-and-release: 12 | runs-on: ubuntu-latest 13 | 14 | steps: 15 | - name: Checkout repository 16 | uses: actions/checkout@v4 17 | 18 | - name: Populate design system submodule 19 | run: git submodule update --init 20 | 21 | - name: Setup Node.js 22 | uses: actions/setup-node@v4 23 | with: 24 | node-version: '22.13.1' 25 | cache: 'npm' 26 | 27 | - name: Install all dependencies 28 | run: npm ci 29 | 30 | - name: Build project 31 | run: npm run build 32 | 33 | - name: Install production dependencies only 34 | run: | 35 | npm ci --production 36 | 37 | - name: Create release tarball 38 | run: | 39 | tar -czf release.tar.gz dist/ package.json server.js node_modules/ 40 | 41 | - name: Upload build artifact (for workflow logs) 42 | uses: actions/upload-artifact@v4 43 | with: 44 | name: dist 45 | path: dist 46 | 47 | - name: Create GitHub Release and upload asset 48 | uses: ncipollo/release-action@v1 49 | with: 50 | token: ${{ secrets.GITHUB_TOKEN }} 51 | tag: v${{ github.run_number }} 52 | name: Release ${{ github.run_number }} 53 | body: | 54 | Latest build from main branch. 55 | artifacts: release.tar.gz 56 | allowUpdates: false 57 | draft: false 58 | prerelease: false 59 | makeLatest: true 60 | -------------------------------------------------------------------------------- /client/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | 5 |Help content could not be loaded. Please check that help-content-template.html exists.
', 77 | theme: 'auto' 78 | }); 79 | setStatus('Ready (help content unavailable)'); 80 | } 81 | } 82 | 83 | // Initialize both help modal and WebSocket when DOM is ready 84 | function initialize() { 85 | initializeHelpModal(); 86 | initializeWebSocket(); 87 | } 88 | 89 | if (document.readyState === 'loading') { 90 | document.addEventListener('DOMContentLoaded', initialize); 91 | } else { 92 | initialize(); 93 | } 94 | })(); 95 | -------------------------------------------------------------------------------- /client/example-app/example-app.css: -------------------------------------------------------------------------------- 1 | /* Example App Styles - Interactive Component Showcase */ 2 | 3 | .bespoke .sidebar { 4 | padding: var(--UI-Spacing-spacing-xl); 5 | overflow-y: auto; 6 | } 7 | 8 | .bespoke .sidebar-section { 9 | display: flex; 10 | flex-direction: column; 11 | gap: var(--UI-Spacing-spacing-xl); 12 | } 13 | 14 | .bespoke .sidebar-section h2 { 15 | font-size: var(--Fonts-Headlines-sm); 16 | font-weight: 600; 17 | color: var(--Colors-Text-Body-Strongest); 18 | margin: 0 0 var(--UI-Spacing-spacing-ml) 0; 19 | } 20 | 21 | .bespoke .control-group { 22 | display: flex; 23 | flex-direction: column; 24 | gap: var(--UI-Spacing-spacing-s); 25 | } 26 | 27 | .bespoke .control-group label { 28 | font-size: var(--Fonts-Body-Default-sm); 29 | font-weight: 500; 30 | color: var(--Colors-Text-Body-Strong); 31 | } 32 | 33 | .bespoke .control-group .button { 34 | width: 100%; 35 | } 36 | 37 | .bespoke .control-group-buttons { 38 | flex-direction: row; 39 | gap: var(--UI-Spacing-spacing-s); 40 | } 41 | 42 | .bespoke .control-group-buttons .button { 43 | flex: 1; 44 | } 45 | 46 | .bespoke .content-area { 47 | overflow-y: auto; 48 | padding: var(--UI-Spacing-spacing-xl); 49 | } 50 | 51 | .bespoke .display-container { 52 | max-width: 800px; 53 | margin: 0 auto; 54 | display: flex; 55 | flex-direction: column; 56 | gap: var(--UI-Spacing-spacing-xl); 57 | } 58 | 59 | .bespoke .counter-display { 60 | flex-direction: column; 61 | align-items: center; 62 | justify-content: center; 63 | padding: var(--UI-Spacing-spacing-2xl); 64 | text-align: center; 65 | } 66 | 67 | .bespoke .counter-display h2 { 68 | font-size: var(--Fonts-Headlines-sm); 69 | font-weight: 600; 70 | color: var(--Colors-Text-Body-Strong); 71 | margin: 0 0 var(--UI-Spacing-spacing-ml) 0; 72 | text-align: center; 73 | } 74 | 75 | .bespoke .counter-value { 76 | font-size: 4rem; 77 | font-weight: 700; 78 | font-family: var(--heading-family); 79 | color: var(--Colors-Text-Body-Strongest); 80 | line-height: 1; 81 | text-align: center; 82 | } 83 | 84 | .bespoke .tags-container { 85 | display: flex; 86 | gap: var(--UI-Spacing-spacing-ml); 87 | justify-content: center; 88 | flex-wrap: wrap; 89 | } 90 | 91 | .bespoke .settings-display { 92 | flex-direction: column; 93 | align-items: flex-start; 94 | justify-content: flex-start; 95 | padding: var(--UI-Spacing-spacing-xl); 96 | } 97 | 98 | .bespoke .settings-display h3 { 99 | font-size: var(--Fonts-Headlines-xs); 100 | font-weight: 600; 101 | color: var(--Colors-Text-Body-Strong); 102 | margin: 0 0 var(--UI-Spacing-spacing-ml) 0; 103 | width: 100%; 104 | } 105 | 106 | .bespoke .setting-item { 107 | font-size: var(--Fonts-Body-Default-sm); 108 | color: var(--Colors-Text-Body-Default); 109 | margin-bottom: var(--UI-Spacing-spacing-s); 110 | width: 100%; 111 | } 112 | 113 | .bespoke .setting-item:last-child { 114 | margin-bottom: 0; 115 | } 116 | 117 | .bespoke .setting-item strong { 118 | color: var(--Colors-Text-Body-Strong); 119 | margin-right: var(--UI-Spacing-spacing-xs); 120 | } 121 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Elastic License 2.0 2 | 3 | URL: https://www.elastic.co/licensing/elastic-license 4 | 5 | ## Acceptance 6 | 7 | By using the software, you agree to all of the terms and conditions below. 8 | 9 | ## Copyright License 10 | 11 | The licensor grants you a non-exclusive, royalty-free, worldwide, 12 | non-sublicensable, non-transferable license to use, copy, distribute, make 13 | available, and prepare derivative works of the software, in each case subject to 14 | the limitations and conditions below. 15 | 16 | ## Limitations 17 | 18 | You may not provide the software to third parties as a hosted or managed 19 | service, where the service provides users with access to any substantial set of 20 | the features or functionality of the software. 21 | 22 | You may not move, change, disable, or circumvent the license key functionality 23 | in the software, and you may not remove or obscure any functionality in the 24 | software that is protected by the license key. 25 | 26 | You may not alter, remove, or obscure any licensing, copyright, or other notices 27 | of the licensor in the software. Any use of the licensor’s trademarks is subject 28 | to applicable law. 29 | 30 | ## Patents 31 | 32 | The licensor grants you a license, under any patent claims the licensor can 33 | license, or becomes able to license, to make, have made, use, sell, offer for 34 | sale, import and have imported the software, in each case subject to the 35 | limitations and conditions in this license. This license does not cover any 36 | patent claims that you cause to be infringed by modifications or additions to 37 | the software. If you or your company make any written claim that the software 38 | infringes or contributes to infringement of any patent, your patent license for 39 | the software granted under these terms ends immediately. If your company makes 40 | such a claim, your patent license ends immediately for work on behalf of your 41 | company. 42 | 43 | ## Notices 44 | 45 | You must ensure that anyone who gets a copy of any part of the software from you 46 | also gets a copy of these terms. 47 | 48 | If you modify the software, you must include in any modified copies of the 49 | software prominent notices stating that you have modified the software. 50 | 51 | ## No Other Rights 52 | 53 | These terms do not imply any licenses other than those expressly granted in 54 | these terms. 55 | 56 | ## Termination 57 | 58 | If you use the software in violation of these terms, such use is not licensed, 59 | and your licenses will automatically terminate. If the licensor provides you 60 | with a notice of your violation, and you cease all violation of this license no 61 | later than 30 days after you receive that notice, your licenses will be 62 | reinstated retroactively. However, if you violate these terms after such 63 | reinstatement, any additional violation of these terms will cause your licenses 64 | to terminate automatically and permanently. 65 | 66 | ## No Liability 67 | 68 | *As far as the law allows, the software comes as is, without any warranty or 69 | condition, and the licensor will not be liable to you for any damages arising 70 | out of these terms or the use or nature of the software, under any kind of 71 | legal claim.* 72 | 73 | ## Definitions 74 | 75 | The **licensor** is the entity offering these terms, and the **software** is the 76 | software the licensor makes available under these terms, including any portion 77 | of it. 78 | 79 | **you** refers to the individual or entity agreeing to these terms. 80 | 81 | **your company** is any legal entity, sole proprietorship, or other kind of 82 | organization that you work for, plus all organizations that have control over, 83 | are under the control of, or are under common control with that 84 | organization. **control** means ownership of substantially all the assets of an 85 | entity, or the power to direct its management and policies by vote, contract, or 86 | otherwise. Control can be direct or indirect. 87 | 88 | **your licenses** are all the licenses granted to you for the software under 89 | these terms. 90 | 91 | **use** means anything you do with the software requiring one of your licenses. 92 | 93 | **trademark** means trademarks, service marks, and similar rights. 94 | -------------------------------------------------------------------------------- /AGENTS.md: -------------------------------------------------------------------------------- 1 | # Repository Guidelines 2 | 3 | This repository contains a template for building embedded applications using 4 | the Bespoke Simulation framework. For complete template documentation, see 5 | [BESPOKE-TEMPLATE.md](./BESPOKE-TEMPLATE.md). 6 | 7 | ## Overview 8 | 9 | This template provides: 10 | - CodeSignal Design System integration 11 | - Consistent layout components (header, sidebar, main content area) 12 | - Help modal system 13 | - Local development server with WebSocket support 14 | - Standardized file structure and naming conventions 15 | 16 | ## Quick Start 17 | 18 | 1. **Customize the HTML template** (`client/index.html`): 19 | - Replace `` with your page title 20 | - Replace `` with your app name 21 | - Add your main content at `` 22 | - Add app-specific CSS links at `` 23 | - Add app-specific JavaScript at `` 24 | 25 | 2. **Create your application files**: 26 | - App-specific CSS (e.g., `my-app.css`) 27 | - App-specific JavaScript (e.g., `my-app.js`) 28 | - Help content (based on `help-content-template.html`) 29 | 30 | 3. **Start the development server**: 31 | ```bash 32 | npm start 33 | ``` 34 | Server runs on `http://localhost:3000` 35 | 36 | ## Key Conventions 37 | 38 | ### Status Messages 39 | 40 | Use these exact status messages for consistency: 41 | 42 | - "Ready" - Application loaded successfully 43 | - "Loading..." - Data is being loaded 44 | - "Saving..." - Data is being saved 45 | - "Changes saved" - Auto-save completed successfully 46 | - "Save failed (will retry)" - Server save failed, will retry 47 | - "Failed to load data" - Data loading failed 48 | - "Auto-save initialized" - Auto-save system started 49 | 50 | ### File Naming 51 | 52 | - CSS files: kebab-case (e.g., `my-app.css`) 53 | - JavaScript files: kebab-case (e.g., `my-app.js`) 54 | - Data files: kebab-case (e.g., `solution.json`) 55 | - Image files: kebab-case (e.g., `overview.png`) 56 | 57 | ### Error Handling 58 | 59 | - Wrap all async operations in try-catch blocks 60 | - Provide meaningful error messages to users 61 | - Log errors to console for debugging 62 | - Implement retry logic for network operations 63 | - Handle localStorage quota exceeded errors 64 | - Validate data before saving operations 65 | 66 | ## Development Workflow 67 | 68 | ### Build and Test 69 | 70 | ```bash 71 | # Start development server 72 | npm start 73 | 74 | # Development mode (same as start) 75 | npm run dev 76 | ``` 77 | 78 | ### WebSocket Messaging 79 | 80 | The server provides a `POST /message` endpoint for real-time messaging: 81 | 82 | ```bash 83 | curl -X POST http://localhost:3000/message \ 84 | -H "Content-Type: application/json" \ 85 | -d '{"message": "Your message here"}' 86 | ``` 87 | 88 | This sends alerts to connected clients. Requires `ws` package: 89 | ```bash 90 | npm install 91 | ``` 92 | 93 | ## Template Documentation 94 | 95 | For detailed information about: 96 | - Design System usage and components 97 | - CSS implementation guidelines 98 | - JavaScript API (HelpModal, status management) 99 | - Component reference and examples 100 | - Customization options 101 | 102 | See [BESPOKE-TEMPLATE.md](./BESPOKE-TEMPLATE.md). 103 | 104 | ## Project Structure 105 | 106 | ``` 107 | client/ 108 | ├── index.html # Main HTML template 109 | ├── app.js # Application logic 110 | ├── bespoke-template.css # Template-specific styles 111 | ├── help-modal.js # Help modal system 112 | ├── help-content-template.html # Help content template 113 | └── design-system/ # CodeSignal Design System 114 | ├── colors/ 115 | ├── spacing/ 116 | ├── typography/ 117 | └── components/ 118 | server.js # Development server 119 | ``` 120 | 121 | ## Notes for AI Agents 122 | 123 | When working on applications built with this template: 124 | 125 | 1. **Always reference BESPOKE-TEMPLATE.md** for template-specific 126 | implementation details 127 | 2. **Follow the conventions** listed above for status messages and file naming 128 | 3. **Use Design System components** directly - see BESPOKE-TEMPLATE.md for 129 | component classes and usage 130 | 4. **Maintain consistency** with the template's structure and patterns 131 | 5. **Keep guidelines up to date** by editing this AGENTS.md file as the codebase evolves -------------------------------------------------------------------------------- /client/example-app/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |This page explains how to use the : .
24 | 25 | 26 |To begin using the :
32 |Description of feature 1 and how to use it.
47 | 48 |Description of feature 2 and how to use it.
50 | 51 |Description of feature 3 and how to use it.
53 | 54 | 55 | 56 |Here's the typical workflow for using this application:
62 |Answer to common question 1 with helpful details.
96 |Answer to common question 2 with helpful details.
101 |Answer to common question 3 with helpful details.
106 |Place image files in the help/img/ directory and reference them with relative paths like <img src="./img/example.png" alt="Description">
Yes, the app automatically saves your work. You'll see status messages indicating when saves occur.
116 |This is an interactive showcase application that demonstrates Design System components through a clicker counter app. The app features input controls in the sidebar and visual components in the main content area that update in real-time based on your interactions.
18 |This showcase demonstrates:
19 |The sidebar contains input controls that affect the counter:
31 | 32 |Use the text input field to change the label displayed above the counter value. Type any text you want, and it will update immediately in the main display area.
34 | 35 |Select the increment amount from the dropdown menu. You can choose:
37 |The selected increment amount is displayed in the settings panel below the counter.
44 | 45 |The main content area shows visual components that update based on your interactions:
57 | 58 |The counter value is displayed prominently in a card box component. The label above the counter updates when you change it in the sidebar.
60 | 61 |Status tags automatically update based on the counter value:
63 |Action buttons in the display area mirror the sidebar controls. You can use either set of buttons to control the counter.
71 | 72 |The settings panel shows the current label and increment amount in a read-only card component, providing a clear view of the current configuration.
74 |This app demonstrates the following Design System components:
80 | 81 |Help content could not be loaded. Please check that help-content.html exists.
', 172 | theme: 'auto' 173 | }); 174 | } 175 | } 176 | } 177 | 178 | // Initialize everything when DOM is ready 179 | function initialize() { 180 | setStatus('Loading...'); 181 | 182 | // Initialize event listeners 183 | initializeEventListeners(); 184 | 185 | // Initialize help modal 186 | initializeHelpModal(); 187 | 188 | // Initialize dropdown after a short delay to ensure Dropdown class is loaded 189 | setTimeout(() => { 190 | initializeDropdown(); 191 | updateCounterDisplay(); 192 | setStatus('Ready'); 193 | }, 100); 194 | } 195 | 196 | if (document.readyState === 'loading') { 197 | document.addEventListener('DOMContentLoaded', initialize); 198 | } else { 199 | initialize(); 200 | } 201 | })(); 202 | -------------------------------------------------------------------------------- /server.js: -------------------------------------------------------------------------------- 1 | const http = require('http'); 2 | const fs = require('fs'); 3 | const path = require('path'); 4 | const url = require('url'); 5 | 6 | // Try to load WebSocket module, fallback if not available 7 | let WebSocket = null; 8 | let isWebSocketAvailable = false; 9 | try { 10 | WebSocket = require('ws'); 11 | isWebSocketAvailable = true; 12 | console.log('WebSocket support enabled'); 13 | } catch (error) { 14 | console.log('WebSocket support disabled (ws package not installed)'); 15 | console.log('Install with: npm install ws'); 16 | } 17 | 18 | const DIST_DIR = path.join(__dirname, 'dist'); 19 | // Check if IS_PRODUCTION is set to true 20 | const isProduction = process.env.IS_PRODUCTION === 'true'; 21 | // In production mode, dist directory must exist 22 | if (isProduction && !fs.existsSync(DIST_DIR)) { 23 | throw new Error(`Production mode enabled but dist directory does not exist: ${DIST_DIR}`); 24 | } 25 | // Force port 3000 in production, otherwise use PORT environment variable or default to 3000 26 | const PORT = isProduction ? 3000 : (process.env.PORT || 3000); 27 | 28 | // Track connected WebSocket clients 29 | const wsClients = new Set(); 30 | 31 | // MIME types for different file extensions 32 | const mimeTypes = { 33 | '.html': 'text/html', 34 | '.js': 'text/javascript', 35 | '.css': 'text/css', 36 | '.json': 'application/json', 37 | '.png': 'image/png', 38 | '.jpg': 'image/jpeg', 39 | '.jpeg': 'image/jpeg', 40 | '.gif': 'image/gif', 41 | '.svg': 'image/svg+xml', 42 | '.ico': 'image/x-icon', 43 | '.woff': 'font/woff', 44 | '.woff2': 'font/woff2', 45 | '.ttf': 'font/ttf', 46 | '.eot': 'application/vnd.ms-fontobject' 47 | }; 48 | 49 | // Get MIME type based on file extension 50 | function getMimeType(filePath) { 51 | const ext = path.extname(filePath).toLowerCase(); 52 | return mimeTypes[ext] || 'text/plain'; 53 | } 54 | 55 | // Serve static files 56 | function serveFile(filePath, res) { 57 | fs.readFile(filePath, (err, data) => { 58 | if (err) { 59 | res.writeHead(404, { 'Content-Type': 'text/plain' }); 60 | res.end('File not found'); 61 | return; 62 | } 63 | 64 | const mimeType = getMimeType(filePath); 65 | res.writeHead(200, { 'Content-Type': mimeType }); 66 | res.end(data); 67 | }); 68 | } 69 | 70 | // Handle POST requests 71 | function handlePostRequest(req, res, parsedUrl) { 72 | if (parsedUrl.pathname === '/message') { 73 | let body = ''; 74 | 75 | req.on('data', chunk => { 76 | body += chunk.toString(); 77 | }); 78 | 79 | req.on('end', () => { 80 | try { 81 | const data = JSON.parse(body); 82 | const message = data.message; 83 | 84 | if (!message) { 85 | res.writeHead(400, { 'Content-Type': 'application/json' }); 86 | res.end(JSON.stringify({ error: 'Message is required' })); 87 | return; 88 | } 89 | 90 | // Check if WebSocket is available 91 | if (!isWebSocketAvailable) { 92 | res.writeHead(503, { 'Content-Type': 'application/json' }); 93 | res.end(JSON.stringify({ 94 | error: 'WebSocket functionality not available', 95 | details: 'Install the ws package with: npm install ws' 96 | })); 97 | return; 98 | } 99 | 100 | // Broadcast message to all connected WebSocket clients 101 | wsClients.forEach(client => { 102 | if (client.readyState === WebSocket.OPEN) { 103 | client.send(JSON.stringify({ type: 'message', message: message })); 104 | } 105 | }); 106 | 107 | res.writeHead(200, { 'Content-Type': 'application/json' }); 108 | res.end(JSON.stringify({ success: true, clientCount: wsClients.size })); 109 | 110 | } catch (error) { 111 | res.writeHead(400, { 'Content-Type': 'application/json' }); 112 | res.end(JSON.stringify({ error: 'Invalid JSON' })); 113 | } 114 | }); 115 | } else { 116 | res.writeHead(404, { 'Content-Type': 'text/plain' }); 117 | res.end('Not found'); 118 | } 119 | } 120 | 121 | // Create HTTP server 122 | const server = http.createServer((req, res) => { 123 | const parsedUrl = url.parse(req.url, true); 124 | let pathName = parsedUrl.pathname === '/' ? '/index.html' : parsedUrl.pathname; 125 | 126 | // Handle POST requests 127 | if (req.method === 'POST') { 128 | handlePostRequest(req, res, parsedUrl); 129 | return; 130 | } 131 | 132 | // In production mode, serve static files from dist directory 133 | if (isProduction) { 134 | // Strip leading slashes so path.join/resolve can't ignore DIST_DIR 135 | let filePath = path.join(DIST_DIR, pathName.replace(/^\/+/, '')); 136 | 137 | // Security check - prevent directory traversal 138 | const resolvedDistDir = path.resolve(DIST_DIR); 139 | const resolvedFilePath = path.resolve(filePath); 140 | const relativePath = path.relative(resolvedDistDir, resolvedFilePath); 141 | 142 | // Reject if path tries to traverse outside the base directory 143 | if (relativePath.startsWith('..')) { 144 | res.writeHead(403, { 'Content-Type': 'text/plain' }); 145 | res.end('Forbidden'); 146 | return; 147 | } 148 | 149 | serveFile(filePath, res); 150 | } else { 151 | // Development mode - static files are served by Vite 152 | res.writeHead(404, { 'Content-Type': 'text/plain' }); 153 | res.end('Not found (development mode - use Vite dev server `npm run start:dev`)'); 154 | } 155 | }); 156 | 157 | // Create WebSocket server only if WebSocket is available 158 | // Note: WebSocket upgrade handling is performed automatically by the ws library 159 | // when attached to the HTTP server. The HTTP request handler should NOT send 160 | // a response for upgrade requests - the ws library handles the upgrade internally. 161 | if (isWebSocketAvailable) { 162 | const wss = new WebSocket.Server({ 163 | server, 164 | path: '/ws' 165 | }); 166 | 167 | wss.on('connection', (ws, req) => { 168 | console.log('New WebSocket client connected'); 169 | wsClients.add(ws); 170 | 171 | ws.on('close', () => { 172 | console.log('WebSocket client disconnected'); 173 | wsClients.delete(ws); 174 | }); 175 | 176 | ws.on('error', (error) => { 177 | console.error('WebSocket error:', error); 178 | wsClients.delete(ws); 179 | }); 180 | }); 181 | } 182 | 183 | // Start server 184 | server.listen(PORT, () => { 185 | console.log(`Server running at http://localhost:${PORT}`); 186 | if (isProduction) { 187 | console.log(`Serving static files from: ${DIST_DIR}`); 188 | } else { 189 | console.log(`Development mode - static files served by Vite`); 190 | } 191 | if (isWebSocketAvailable) { 192 | console.log(`WebSocket server running on /ws`); 193 | } else { 194 | console.log(`WebSocket functionality disabled - install 'ws' package to enable`); 195 | } 196 | console.log('Press Ctrl+C to stop the server'); 197 | }); 198 | 199 | // Handle server errors 200 | server.on('error', (err) => { 201 | if (err.code === 'EADDRINUSE') { 202 | console.error(`Port ${PORT} is already in use. Please try a different port.`); 203 | } else { 204 | console.error('Server error:', err); 205 | } 206 | process.exit(1); 207 | }); 208 | 209 | // Graceful shutdown 210 | process.on('SIGINT', () => { 211 | console.log('\nShutting down server...'); 212 | server.close(() => { 213 | console.log('Server closed'); 214 | process.exit(0); 215 | }); 216 | }); 217 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Bespoke Simulation Template 2 | 3 | This directory contains a template for creating embedded applications that share a consistent design system and user experience. 4 | 5 | ## Components 6 | 7 | ### 1. Design System Integration 8 | This template uses the CodeSignal Design System located in `client/design-system/`: 9 | - **Foundations**: Colors, spacing, typography tokens 10 | - **Components**: Buttons, boxes, inputs, dropdowns, tags 11 | - Light and dark theme support (automatic) 12 | - See the [design system repository](https://github.com/CodeSignal/learn_bespoke-design-system) for full documentation 13 | 14 | ### 2. `client/bespoke-template.css` 15 | Template-specific CSS providing: 16 | - Layout components (header, sidebar, main-layout) 17 | - Utility classes (row, spacer, status) 18 | - Temporary components (modals, form elements) - will be replaced when design system adds them 19 | 20 | ### 3. `client/index.html` 21 | A base HTML template that includes: 22 | - Navigation header with app name and help button 23 | - Main layout structure (sidebar + content area) 24 | - Help modal integration 25 | - Proper CSS and JavaScript loading 26 | 27 | ### 4. `client/help-modal.js` 28 | A dependency-free JavaScript module for the help modal system: 29 | - Consistent modal behavior across all apps 30 | - Keyboard navigation (ESC to close) 31 | - Focus management 32 | - Custom event system 33 | 34 | ### 5. `client/help-content-template.html` 35 | A template for creating consistent help content: 36 | - Table of contents navigation 37 | - Standardized section structure 38 | - FAQ with collapsible details 39 | - Image integration guidelines 40 | 41 | ## Usage Instructions 42 | 43 | ### Setting Up a New Application 44 | 45 | 1. **Clone the repository** 46 | 2. **Ensure the design-system submodule is initialized**: 47 | ```bash 48 | git submodule update --init --recursive 49 | ``` 50 | 51 | 3. **Customize the HTML template** by replacing placeholders: 52 | - `` - Your application title 53 | - `` - Your application name (appears in header) 54 | - `` - Any additional header elements 55 | - `` - Your main content area 56 | - `` - Links to your app-specific CSS files 57 | - `` - Links to your app-specific JavaScript files 58 | 59 | 3. **Use Design System Components** 60 | The template uses design system components directly. Use these classes: 61 | - Buttons: `button button-primary`, `button button-secondary`, `button button-danger`, `button button-text` 62 | - Boxes/Cards: `box card` for card containers 63 | - Inputs: Add `input` class to input elements: `` 64 | 65 | 4. **Implement your application logic**. You can use Cursor or other agents for it. There is a file called `AGENTS.md` that contains context LLM can use. 66 | 5. **Customise your help content** using the help content template 67 | 3. **Use Design System Components** 68 | The template uses design system components directly. Use these classes: 69 | - Buttons: `button button-primary`, `button button-secondary`, `button button-danger`, `button button-text` 70 | - Boxes/Cards: `box card` for card containers 71 | - Inputs: Add `input` class to input elements: `` 72 | 73 | 4. **Implement your application logic**. You can use Cursor or other agents for it. There is a file called `AGENTS.md` that contains context LLM can use. 74 | 5. **Customise your help content** using the help content template 75 | 76 | ### Customizing Help Content 77 | 78 | Use the `help-content-template.html` as a starting point: 79 | 80 | 1. **Replace placeholders** like `` with your actual content 81 | 2. **Add sections** as needed for your application 82 | 3. **Include images** by placing them in a `help/img/` directory 83 | 4. **Use the provided structure** for consistency across applications 84 | 85 | 86 | ### Help Modal API 87 | 88 | The `HelpModal` class provides several methods: 89 | 90 | ```javascript 91 | // Initialize 92 | const modal = HelpModal.init({ 93 | triggerSelector: '#btn-help', 94 | content: helpContent, 95 | theme: 'auto' 96 | }); 97 | 98 | // Update content dynamically 99 | modal.updateContent(newHelpContent); 100 | 101 | // Destroy the modal 102 | modal.destroy(); 103 | ``` 104 | 105 | ## Server 106 | 107 | This template includes a local development server (`server.js`) that provides: 108 | - Static file serving for your application 109 | - WebSocket support for real-time messaging 110 | - A REST API for triggering client-side alerts 111 | 112 | ### Starting the Server 113 | 114 | ```bash 115 | # Local development 116 | npm run start:dev # Vite + API for local development 117 | # Production 118 | npm run build # Create production build in dist/ 119 | npm run start:prod # Serve built assets from dist/ 120 | ``` 121 | 122 | 123 | ### Environment Variables 124 | 125 | The server supports the following environment variables: 126 | 127 | - **`PORT`** - Server port number 128 | - Development: Can be set to any port (e.g., `PORT=3001`), defaulting to `3000` 129 | - Production: Ignored (always `3000` when `IS_PRODUCTION=true`) 130 | 131 | - **`IS_PRODUCTION`** - Enables production mode 132 | - Set to `'true'` to enable production mode 133 | - When enabled: 134 | - Server serves static files from `dist/` directory 135 | - Port is forced to `3000` 136 | - Requires `dist/` directory to exist (throws error if missing) 137 | 138 | 139 | ### Vite Build System 140 | 141 | This project uses [Vite](https://vitejs.dev/) as the build tool for fast development and optimized production builds. 142 | 143 | #### Build Process 144 | 145 | Running `npm run build` executes `vite build`, which: 146 | - Reads source files from the `client/` directory (configured in `vite.config.js`) 147 | - Processes and bundles JavaScript, CSS, and other assets 148 | - Outputs optimized production files to the `dist/` directory 149 | - Generates hashed filenames for cache busting 150 | 151 | ### WebSocket Messaging API 152 | 153 | The server provides a `POST /message` endpoint that allows you to send real-time messages to connected clients. This can be used to signal changes in the client during events like "Run" or "Submit". When a message is sent, the preview window with the application open will display an alert with the message. 154 | 155 | It uses the `ws` package, so if you want to use it, install the packages (but this is optional). 156 | 157 | ``` 158 | npm install 159 | ``` 160 | 161 | #### Endpoint: `POST /message` 162 | 163 | **Request Format:** 164 | ```json 165 | { 166 | "message": "Your message here" 167 | } 168 | ``` 169 | 170 | **Example using curl:** 171 | ```bash 172 | curl -X POST http://localhost:3000/message \ 173 | -H "Content-Type: application/json" \ 174 | -d '{"message": "Hello from the server!"}' 175 | ``` 176 | 177 | ## CI/CD and Automated Releases 178 | 179 | This template includes a GitHub Actions workflow (`.github/workflows/build-release.yml`) that automatically builds and releases your application when you push to the `main` branch. 180 | 181 | ### How It Works 182 | 183 | When you push to `main`, the workflow will: 184 | 185 | 1. **Build the project** - Runs `npm run build` to create production assets in `dist/` 186 | 2. **Create a release tarball** - Packages `dist/`, `package.json`, `server.js`, and production `node_modules/` into `release.tar.gz` 187 | 3. **Create a GitHub Release** - Automatically creates a new release tagged as `v{run_number}` with the tarball attached 188 | 189 | ### Release Contents 190 | 191 | The release tarball (`release.tar.gz`) contains everything needed to deploy the application: 192 | - `dist/` - Built production assets 193 | - `package.json` - Project dependencies and scripts 194 | - `server.js` - Production server 195 | - `node_modules/` - Production dependencies only 196 | 197 | ### Using Releases 198 | 199 | To deploy a release: 200 | 201 | 1. Download `release.tar.gz` from the latest GitHub Release (e.g. with `wget`) 202 | 2. Extract (and remove) the tarball: `tar -xzf release.tar.gz && rm release.tar.gz` 203 | 3. Start the production server: `npm run start:prod` 204 | -------------------------------------------------------------------------------- /client/bespoke-template.css: -------------------------------------------------------------------------------- 1 | /* ===== BESPOKE TEMPLATE CSS ===== */ 2 | /* Template-specific components using CodeSignal Design System tokens */ 3 | /* This file provides layout, utilities, and temporary components not yet in the design system */ 4 | 5 | /* ===== LAYOUT COMPONENTS ===== */ 6 | 7 | /* Bespoke wrapper for scoping */ 8 | .bespoke { 9 | font-family: var(--body-family); 10 | color: var(--Colors-Text-Body-Default); 11 | background: var(--Colors-Backgrounds-Main-Default); 12 | line-height: 1.6; 13 | margin: 0; 14 | padding: 0; 15 | } 16 | 17 | .bespoke * { 18 | box-sizing: border-box; 19 | } 20 | 21 | /* Header */ 22 | .bespoke .header { 23 | display: flex; 24 | align-items: center; 25 | gap: var(--UI-Spacing-spacing-ml); 26 | padding: var(--UI-Spacing-spacing-s) var(--UI-Spacing-spacing-mxl); 27 | border-bottom: 1px solid var(--Colors-Stroke-Default); 28 | background: var(--Colors-Backgrounds-Main-Top); 29 | width: 100%; 30 | } 31 | 32 | .bespoke .header h1 { 33 | font-size: var(--Fonts-Body-Default-lg); 34 | margin: 0; 35 | font-weight: 600; 36 | font-family: var(--heading-family); 37 | color: var(--Colors-Text-Body-Strongest); 38 | } 39 | 40 | .bespoke .header .status { 41 | font-size: var(--Fonts-Body-Default-xs); 42 | color: var(--Colors-Text-Body-Medium); 43 | } 44 | 45 | /* Main Layout */ 46 | .bespoke .main-layout { 47 | display: grid; 48 | grid-template-columns: 300px 1fr; 49 | height: calc(100% - 42px); 50 | } 51 | 52 | .bespoke .sidebar { 53 | padding: var(--UI-Spacing-spacing-s); 54 | overflow: auto; 55 | border-right: 1px solid var(--Colors-Stroke-Default); 56 | background: var(--Colors-Backgrounds-Main-Default); 57 | } 58 | 59 | .bespoke .content-area { 60 | width: 100%; 61 | height: 100%; 62 | } 63 | 64 | /* ===== UTILITY CLASSES ===== */ 65 | 66 | /* Flexbox Utilities */ 67 | .bespoke .row { 68 | display: flex; 69 | align-items: center; 70 | gap: var(--UI-Spacing-spacing-s); 71 | } 72 | 73 | .bespoke .row-between { 74 | display: flex; 75 | align-items: center; 76 | justify-content: space-between; 77 | gap: var(--UI-Spacing-spacing-ml); 78 | } 79 | 80 | .bespoke .spacer { 81 | flex: 1; 82 | } 83 | 84 | /* Dividers */ 85 | .bespoke hr { 86 | border: none; 87 | border-top: 0.5px solid var(--Colors-Stroke-Default); 88 | margin: var(--UI-Spacing-spacing-ml) 0; 89 | } 90 | 91 | /* ===== TEMPORARY COMPONENTS (TODO: Replace when design system adds these) ===== */ 92 | 93 | /* Modal Components - TODO: Remove when design system adds modal component */ 94 | .bespoke .modal { 95 | position: fixed; 96 | top: 0; 97 | left: 0; 98 | width: 100%; 99 | height: 100%; 100 | z-index: 500; 101 | display: flex; 102 | align-items: center; 103 | justify-content: center; 104 | padding: var(--UI-Spacing-spacing-xl); 105 | box-sizing: border-box; 106 | margin: 0; 107 | } 108 | 109 | .bespoke .modal-backdrop { 110 | position: absolute; 111 | top: 0; 112 | left: 0; 113 | width: 100%; 114 | height: 100%; 115 | background: rgba(0, 0, 0, 0.5); 116 | backdrop-filter: blur(2px); 117 | } 118 | 119 | .bespoke .modal-content { 120 | position: relative; 121 | background: var(--Colors-Backgrounds-Main-Top); 122 | border: 1px solid var(--Colors-Stroke-Default); 123 | border-radius: var(--UI-Radius-radius-m); 124 | max-width: 800px; 125 | width: calc(100% - 40px); 126 | max-height: 90vh; 127 | display: flex; 128 | flex-direction: column; 129 | box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04); 130 | margin: 0; 131 | } 132 | 133 | .bespoke .modal-header { 134 | display: flex; 135 | align-items: center; 136 | justify-content: space-between; 137 | padding: var(--UI-Spacing-spacing-xl); 138 | border-bottom: 1px solid var(--Colors-Stroke-Default); 139 | background: var(--Colors-Backgrounds-Main-Top); 140 | border-radius: var(--UI-Radius-radius-m) var(--UI-Radius-radius-m) 0 0; 141 | } 142 | 143 | .bespoke .modal-header h2 { 144 | margin: 0; 145 | font-size: var(--Fonts-Body-Default-xl); 146 | color: var(--Colors-Text-Body-Strongest); 147 | font-family: var(--heading-family); 148 | font-weight: 500; 149 | } 150 | 151 | .bespoke .modal-close { 152 | background: none; 153 | border: none; 154 | font-size: var(--Fonts-Body-Default-xxxl); 155 | color: var(--Colors-Text-Body-Medium); 156 | cursor: pointer; 157 | padding: var(--UI-Spacing-spacing-xxs) var(--UI-Spacing-spacing-s); 158 | border-radius: var(--UI-Radius-radius-xxs); 159 | line-height: 1; 160 | transition: all 0.2s ease; 161 | } 162 | 163 | .bespoke .modal-close:hover { 164 | background: var(--Colors-Backgrounds-Main-Medium); 165 | color: var(--Colors-Text-Body-Default); 166 | } 167 | 168 | .bespoke .modal-body { 169 | padding: var(--UI-Spacing-spacing-xl); 170 | overflow-y: auto; 171 | flex: 1; 172 | line-height: 1.6; 173 | } 174 | 175 | .bespoke .modal-body h2 { 176 | margin-top: var(--UI-Spacing-spacing-xxxl); 177 | margin-bottom: var(--UI-Spacing-spacing-ml); 178 | font-size: var(--Fonts-Body-Default-xl); 179 | color: var(--Colors-Text-Body-Strongest); 180 | font-family: var(--heading-family); 181 | font-weight: 500; 182 | } 183 | 184 | .bespoke .modal-body h2:first-child { 185 | margin-top: 0; 186 | } 187 | 188 | .bespoke .modal-body h3 { 189 | margin-top: var(--UI-Spacing-spacing-xl); 190 | margin-bottom: var(--UI-Spacing-spacing-s); 191 | font-size: var(--Fonts-Body-Default-lg); 192 | color: var(--Colors-Text-Body-Strongest); 193 | font-family: var(--heading-family); 194 | font-weight: 500; 195 | } 196 | 197 | .bespoke .modal-body p, 198 | .bespoke .modal-body li { 199 | color: var(--Colors-Text-Body-Default); 200 | margin-bottom: var(--UI-Spacing-spacing-s); 201 | } 202 | 203 | .bespoke .modal-body ul, 204 | .bespoke .modal-body ol { 205 | margin: var(--UI-Spacing-spacing-s) 0 var(--UI-Spacing-spacing-ml) 0; 206 | padding-left: var(--UI-Spacing-spacing-xl); 207 | } 208 | 209 | .bespoke .modal-body code { 210 | font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; 211 | background: rgba(148, 163, 184, 0.12); 212 | border-radius: var(--UI-Radius-radius-xxs); 213 | padding: 0.15em 0.35em; 214 | font-size: var(--Fonts-Body-Default-xs); 215 | } 216 | 217 | .bespoke .modal-body pre { 218 | font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; 219 | background: rgba(148, 163, 184, 0.12); 220 | border-radius: var(--UI-Radius-radius-xs); 221 | padding: var(--UI-Spacing-spacing-ms); 222 | overflow: auto; 223 | margin: var(--UI-Spacing-spacing-ml) 0; 224 | } 225 | 226 | .bespoke .modal-body img, 227 | .bespoke .modal-body video { 228 | max-width: 100%; 229 | height: auto; 230 | border-radius: var(--UI-Radius-radius-xs); 231 | border: 1px solid var(--Colors-Stroke-Default); 232 | margin: var(--UI-Spacing-spacing-ml) 0; 233 | } 234 | 235 | /* Form Elements - TODO: Remove when design system adds form components */ 236 | .bespoke label { 237 | display: flex; 238 | flex-direction: column; 239 | gap: var(--UI-Spacing-spacing-xxs); 240 | margin: var(--UI-Spacing-spacing-ms) 0 var(--UI-Spacing-spacing-s) 0; 241 | } 242 | 243 | .bespoke label.row { 244 | flex-direction: row; 245 | align-items: center; 246 | gap: var(--UI-Spacing-spacing-s); 247 | margin: var(--UI-Spacing-spacing-s) 0; 248 | } 249 | 250 | /* Textarea */ 251 | .bespoke textarea { 252 | padding: var(--UI-Spacing-spacing-ms); 253 | border: 1px solid var(--Colors-Input-Border-Default); 254 | border-radius: var(--UI-Radius-radius-s); 255 | background: var(--Colors-Input-Background-Default); 256 | color: var(--Colors-Input-Text-Default); 257 | font-family: var(--body-family); 258 | font-size: var(--Fonts-Body-Default-md); 259 | min-height: 6rem; 260 | resize: vertical; 261 | transition: border-color 0.2s ease; 262 | } 263 | 264 | .bespoke textarea:hover { 265 | border-color: var(--Colors-Input-Border-Hover); 266 | } 267 | 268 | .bespoke textarea:focus-visible { 269 | outline: none; 270 | border-color: var(--Colors-Input-Border-Focus); 271 | box-shadow: 0 0 0 4px var(--Colors-Input-Shadow-Focus); 272 | } 273 | 274 | .bespoke textarea::placeholder { 275 | color: var(--Colors-Input-Text-Placeholder); 276 | opacity: 1; 277 | } 278 | 279 | /* Select styling - TODO: Remove when design system adds select component */ 280 | .bespoke select { 281 | -webkit-appearance: none; 282 | -moz-appearance: none; 283 | appearance: none; 284 | background-image: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23666' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3e%3cpolyline points='6,9 12,15 18,9'%3e%3c/polyline%3e%3c/svg%3e"); 285 | background-repeat: no-repeat; 286 | background-position: right 0.75rem center; 287 | background-size: 1rem; 288 | padding-right: 3rem; 289 | } 290 | 291 | .bespoke select::-ms-expand { 292 | display: none; 293 | } 294 | 295 | /* Radio Buttons - TODO: Remove when design system adds radio component */ 296 | .bespoke input[type="radio"] { 297 | appearance: none; 298 | width: 1rem; 299 | height: 1rem; 300 | border: 2px solid var(--Colors-Input-Border-Default); 301 | border-radius: 50%; 302 | background: var(--Colors-Input-Background-Default); 303 | cursor: pointer; 304 | position: relative; 305 | transition: all 0.2s ease; 306 | flex-shrink: 0; 307 | padding: 0; 308 | } 309 | 310 | .bespoke input[type="radio"]:checked { 311 | border-color: var(--Colors-Stroke-Primary); 312 | background: var(--Colors-Stroke-Primary); 313 | } 314 | 315 | .bespoke input[type="radio"]:checked::after { 316 | content: ''; 317 | position: absolute; 318 | top: 50%; 319 | left: 50%; 320 | transform: translate(-50%, -50%); 321 | width: 0.375rem; 322 | height: 0.375rem; 323 | border-radius: 50%; 324 | background: white; 325 | } 326 | 327 | .bespoke input[type="radio"]:hover { 328 | border-color: var(--Colors-Input-Border-Hover); 329 | } 330 | 331 | .bespoke input[type="radio"]:focus-visible { 332 | outline: none; 333 | border-color: var(--Colors-Input-Border-Focus); 334 | box-shadow: 0 0 0 3px var(--Colors-Input-Shadow-Focus); 335 | } 336 | 337 | .bespoke .radio-group { 338 | display: flex; 339 | flex-direction: column; 340 | gap: var(--UI-Spacing-spacing-s); 341 | } 342 | 343 | .bespoke .radio-group.horizontal { 344 | flex-direction: row; 345 | align-items: center; 346 | gap: var(--UI-Spacing-spacing-ml); 347 | } 348 | 349 | /* Checkbox - TODO: Remove when design system adds checkbox component */ 350 | .bespoke input[type="checkbox"] { 351 | padding: 0; 352 | margin: 0; 353 | } 354 | 355 | /* Toggle Switch - TODO: Remove when design system adds toggle component */ 356 | .bespoke .toggle { 357 | position: relative; 358 | display: inline-block; 359 | width: 3rem; 360 | height: 1.5rem; 361 | } 362 | 363 | .bespoke .toggle-input { 364 | opacity: 0; 365 | width: 0; 366 | height: 0; 367 | } 368 | 369 | .bespoke .toggle-slider { 370 | position: absolute; 371 | cursor: pointer; 372 | top: 0; 373 | left: 0; 374 | right: 0; 375 | bottom: 0; 376 | background-color: var(--Colors-Stroke-Medium); 377 | transition: 0.3s; 378 | border-radius: 1.5rem; 379 | } 380 | 381 | .bespoke .toggle-slider:before { 382 | position: absolute; 383 | content: ""; 384 | height: 1.125rem; 385 | width: 1.125rem; 386 | left: 0.1875rem; 387 | bottom: 0.1875rem; 388 | background-color: white; 389 | transition: 0.3s; 390 | border-radius: 50%; 391 | box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2); 392 | } 393 | 394 | .bespoke .toggle-input:checked + .toggle-slider { 395 | background-color: var(--Colors-Primary-Default); 396 | } 397 | 398 | .bespoke .toggle-input:checked + .toggle-slider:before { 399 | transform: translateX(1.5rem); 400 | } 401 | 402 | .bespoke .toggle-input:focus + .toggle-slider { 403 | box-shadow: 0 0 0 3px var(--Colors-Input-Shadow-Focus); 404 | } 405 | 406 | .bespoke .toggle-input:disabled + .toggle-slider { 407 | opacity: 0.5; 408 | cursor: not-allowed; 409 | } 410 | 411 | .bespoke .toggle-label { 412 | margin-left: var(--UI-Spacing-spacing-s); 413 | font-size: var(--Fonts-Body-Default-xs); 414 | color: var(--Colors-Text-Body-Default); 415 | cursor: pointer; 416 | } 417 | 418 | /* Dark mode adjustments */ 419 | @media (prefers-color-scheme: dark) { 420 | .bespoke .modal-backdrop { 421 | background: rgba(0, 0, 0, 0.7); 422 | } 423 | 424 | .bespoke .modal-body code, 425 | .bespoke .modal-body pre { 426 | background: rgba(148, 163, 184, 0.2); 427 | } 428 | 429 | .bespoke select { 430 | background-image: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23c1c7d7' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3e%3cpolyline points='6,9 12,15 18,9'%3e%3c/polyline%3e%3c/svg%3e"); 431 | } 432 | } 433 | 434 | /* Responsive Design */ 435 | @media (max-width: 768px) { 436 | .bespoke .main-layout { 437 | grid-template-columns: 1fr; 438 | grid-template-rows: auto 1fr; 439 | } 440 | 441 | .bespoke .sidebar { 442 | border-right: none; 443 | border-bottom: 1px solid var(--Colors-Stroke-Default); 444 | } 445 | 446 | .bespoke .modal { 447 | padding: var(--UI-Spacing-spacing-s); 448 | } 449 | 450 | .bespoke .modal-content { 451 | max-height: 95vh; 452 | } 453 | 454 | .bespoke .modal-header { 455 | padding: var(--UI-Spacing-spacing-mxl); 456 | } 457 | 458 | .bespoke .modal-body { 459 | padding: var(--UI-Spacing-spacing-mxl); 460 | } 461 | 462 | .bespoke .modal-header h2 { 463 | font-size: var(--Fonts-Body-Default-lg); 464 | } 465 | } 466 | 467 | -------------------------------------------------------------------------------- /BESPOKE-TEMPLATE.md: -------------------------------------------------------------------------------- 1 | # Bespoke Simulation Template 2 | 3 | This document provides precise implementation instructions for creating 4 | embedded applications using the Bespoke Simulation template. Follow these 5 | instructions exactly to ensure consistency across all applications. 6 | NOTE: Never edit this `BESPOKE-TEMPLATE.md` file. Codebase changes should be reflected in the `AGENTS.md` file. 7 | 8 | ## Required Files Structure 9 | 10 | Every application should include these files in the following order: 11 | 12 | 1. CodeSignal Design System foundations: 13 | - colors/colors.css 14 | - spacing/spacing.css 15 | - typography/typography.css 16 | - components/button/button.css (used in header) 17 | 2. CodeSignal Design System components (optional): 18 | - components/boxes/boxes.css 19 | - components/dropdown/dropdown.css 20 | - components/input/input.css 21 | - components/tags/tags.css 22 | 3. bespoke-template.css (template-specific layout, utilities, temporary 23 | components) 24 | 4. help-modal.js (help system) 25 | 5. app.js (application logic) 26 | 6. server.js (server) 27 | 28 | ## HTML Template Implementation 29 | 30 | 1. REPLACE the following placeholders in index.html EXACTLY as specified: 31 | 32 | a) `` 33 | Replace with your application's page title 34 | Example: "Database Designer" or "Task Manager" 35 | 36 | b) `` 37 | Replace with your application's display name (appears in header) 38 | Example: "Database Designer" or "Task Manager" 39 | 40 | c) `` 41 | Add your application's main content area 42 | Example: `` or `` 43 | 44 | d) `` 45 | Add links to your application-specific CSS files 46 | Example: `` 47 | 48 | e) `` 49 | Add links to your application-specific JavaScript files 50 | Example: `` 51 | 52 | 2. DO NOT modify the core structure (header, script loading order, etc.) 53 | 54 | ## CSS Implementation 55 | 56 | 1. ALWAYS use the `.bespoke` class on the body element for scoping 57 | 2. USE design system components directly with proper classes: 58 | - Buttons: `button button-primary`, `button button-secondary`, 59 | `button button-danger`, `button button-text` 60 | - Boxes/Cards: `box card` for card containers 61 | - Inputs: Add `input` class to input elements: 62 | `` 63 | 3. USE design system CSS custom properties for styling: 64 | - Colors: `--Colors-*` (e.g., `--Colors-Primary-Default`, 65 | `--Colors-Text-Body-Default`) 66 | - Spacing: `--UI-Spacing-*` (e.g., `--UI-Spacing-spacing-ml`, 67 | `--UI-Spacing-spacing-xl`) 68 | - Typography: `--Fonts-*` (e.g., `--Fonts-Body-Default-md`, 69 | `--Fonts-Headlines-sm`) 70 | - Borders: `--UI-Radius-*` (e.g., `--UI-Radius-radius-s`, 71 | `--UI-Radius-radius-m`) 72 | - Font families: `--body-family`, `--heading-family` 73 | 4. FOR custom styling, create app-specific CSS files 74 | 5. OVERRIDE design system variables in your app-specific CSS, not in 75 | bespoke-template.css 76 | 6. FOLLOW design system naming conventions for consistency 77 | 78 | ## JavaScript Implementation 79 | 80 | 1. HELP MODAL SETUP: 81 | a) Create help content using help-content-template.html as reference 82 | b) Initialize HelpModal with: 83 | - triggerSelector: `'#btn-help'` 84 | - content: your help content (string or loaded from file) 85 | - theme: `'auto'` 86 | 87 | 2. STATUS MANAGEMENT: 88 | a) Use the provided setStatus() function for status updates 89 | b) Update status for: loading, saving, errors, user actions 90 | c) Keep status messages concise and informative 91 | 92 | ## Error Handling Requirements 93 | 94 | 1. WRAP all async operations in try-catch blocks 95 | 2. PROVIDE meaningful error messages to users 96 | 3. LOG errors to console for debugging 97 | 4. IMPLEMENT retry logic for network operations 98 | 5. HANDLE localStorage quota exceeded errors 99 | 6. VALIDATE data before saving operations 100 | 101 | ## Status Message Conventions 102 | 103 | Use these EXACT status messages for consistency: 104 | 105 | - "Ready" - Application loaded successfully 106 | - "Loading..." - Data is being loaded 107 | - "Saving..." - Data is being saved 108 | - "Changes saved" - Auto-save completed successfully 109 | - "Save failed (will retry)" - Server save failed, will retry 110 | - "Failed to load data" - Data loading failed 111 | - "Auto-save initialized" - Auto-save system started 112 | 113 | ## File Naming Conventions 114 | 115 | 1. CSS files: kebab-case (e.g., my-app.css, task-manager.css) 116 | 2. JavaScript files: kebab-case (e.g., my-app.js, task-manager.js) 117 | 3. Data files: kebab-case (e.g., solution.json, initial-data.json) 118 | 4. Image files: kebab-case (e.g., overview.png, help-icon.svg) 119 | 120 | --- 121 | 122 | # Bespoke Template Design System Guidelines 123 | 124 | This section explains how to use the CodeSignal Design System with the 125 | Bespoke template for embedded applications. 126 | 127 | ## Overview 128 | 129 | The Bespoke template uses the CodeSignal Design System for components and 130 | tokens, with template-specific layout and utilities. All styles are scoped 131 | under the `.bespoke` class to prevent interference with parent site styles. 132 | The template uses design system components directly where available, and 133 | provides temporary components (modals, form elements) that will be replaced 134 | when the design system adds them. 135 | 136 | ## Basic Usage 137 | 138 | ### 1. Include the CSS 139 | 140 | ```html 141 | 142 | ``` 143 | 144 | ### 2. Wrap Your Application 145 | 146 | ```html 147 |Card content goes here
216 |