when it reaches the end of the bottom of the screen moves everything written up one line to be able to write commands - Initial Deployment
427ba09
verified
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Acorn BBC BASIC Console</title> | |
| <script src="https://cdn.tailwindcss.com"></script> | |
| <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> | |
| <style> | |
| @font-face { | |
| font-family: 'BBC Micro'; | |
| src: url('https://style.bbc.com/fonts/bbc-micro/bbc-micro-regular.woff2') format('woff2'); | |
| } | |
| .terminal { | |
| font-family: 'Courier New', monospace; | |
| line-height: 1.2; | |
| } | |
| .cursor-blink { | |
| animation: blink 1s step-end infinite; | |
| } | |
| @keyframes blink { | |
| from, to { opacity: 1; } | |
| 50% { opacity: 0; } | |
| } | |
| .scanlines { | |
| position: relative; | |
| } | |
| .scanlines::before { | |
| content: ""; | |
| position: absolute; | |
| top: 0; | |
| left: 0; | |
| right: 0; | |
| bottom: 0; | |
| background: linear-gradient( | |
| rgba(18, 16, 16, 0) 50%, | |
| rgba(0, 0, 0, 0.25) 50% | |
| ); | |
| background-size: 100% 4px; | |
| pointer-events: none; | |
| z-index: 10; | |
| } | |
| </style> | |
| </head> | |
| <body class="bg-gray-900 min-h-screen flex items-center justify-center p-4"> | |
| <div class="w-full max-w-4xl"> | |
| <div class="bg-gray-800 rounded-lg overflow-hidden shadow-2xl"> | |
| <!-- Console Header --> | |
| <div class="bg-gray-700 px-4 py-3 flex items-center justify-between"> | |
| <div class="flex items-center space-x-2"> | |
| <div class="w-3 h-3 rounded-full bg-red-500"></div> | |
| <div class="w-3 h-3 rounded-full bg-yellow-500"></div> | |
| <div class="w-3 h-3 rounded-full bg-green-500"></div> | |
| </div> | |
| <div class="text-gray-300 font-bold text-sm">Acorn BBC BASIC</div> | |
| <div class="text-gray-400 text-xs">Model B</div> | |
| </div> | |
| <!-- Terminal Screen --> | |
| <div class="scanlines bg-black text-green-400 terminal p-4 h-[32rem] overflow-y-auto" id="terminal"> | |
| <div class="mb-2">BBC Computer 32K</div> | |
| <div class="mb-2">Acorn MOS</div> | |
| <div class="mb-4">BASIC</div> | |
| <div>> <span id="command-line"></span><span class="cursor-blink bg-green-400 w-2 h-5 inline-block align-middle" id="cursor"></span></div> | |
| <div id="output" class="space-y-1"></div> | |
| </div> | |
| </div> | |
| <!-- Status Bar --> | |
| <div class="text-gray-400 text-xs mt-2 flex justify-between"> | |
| <div>READY</div> | |
| <div>MODE 7</div> | |
| <div>32K</div> | |
| </div> | |
| </div> | |
| <script> | |
| document.addEventListener('DOMContentLoaded', function() { | |
| const commandLine = document.getElementById('command-line'); | |
| const output = document.getElementById('output'); | |
| const cursor = document.getElementById('cursor'); | |
| const keys = document.querySelectorAll('.key'); | |
| let currentCommand = ''; | |
| let commandHistory = []; | |
| let historyIndex = -1; | |
| // Focus the terminal for keyboard input | |
| document.getElementById('terminal').focus(); | |
| // Handle keyboard input | |
| document.addEventListener('keydown', function(e) { | |
| e.preventDefault(); | |
| if (e.key === 'Enter') { | |
| handleKeyPress('RETURN'); | |
| } else if (e.key === 'Backspace') { | |
| handleKeyPress('DELETE'); | |
| } else if (e.key === 'ArrowUp') { | |
| handleKeyPress('UP'); | |
| } else if (e.key === 'ArrowDown') { | |
| handleKeyPress('DOWN'); | |
| } else if (e.key === 'Escape') { | |
| handleKeyPress('ESC'); | |
| } else if (e.key === ' ') { | |
| handleKeyPress('SPACE'); | |
| } else if (e.key.length === 1 && /[a-zA-ZÀ-ÖØ-öø-ÿ0-9\;\,\.\/\'\?\!\@\#\$\%\&\*\(\)\-\_\+\{\}\[\]\:\\\|\<\>\=]/.test(e.key)) { | |
| // Block specific characters | |
| if (!['~', '^', '`'].includes(e.key)) { | |
| handleKeyPress(e.key.toUpperCase()); | |
| } | |
| } | |
| }); | |
| function handleKeyPress(key) { | |
| switch(key) { | |
| case 'RETURN': | |
| executeCommand(); | |
| break; | |
| case 'DELETE': | |
| if (currentCommand.length > 0) { | |
| currentCommand = currentCommand.slice(0, -1); | |
| updateCommandLine(); | |
| } | |
| break; | |
| case 'UP': | |
| if (commandHistory.length > 0 && historyIndex < commandHistory.length - 1) { | |
| historyIndex++; | |
| currentCommand = commandHistory[commandHistory.length - 1 - historyIndex]; | |
| updateCommandLine(); | |
| } | |
| break; | |
| case 'DOWN': | |
| if (historyIndex > 0) { | |
| historyIndex--; | |
| currentCommand = commandHistory[commandHistory.length - 1 - historyIndex]; | |
| updateCommandLine(); | |
| } else if (historyIndex === 0) { | |
| historyIndex--; | |
| currentCommand = ''; | |
| updateCommandLine(); | |
| } | |
| break; | |
| case 'ESC': | |
| currentCommand = ''; | |
| updateCommandLine(); | |
| break; | |
| case 'SPACE': | |
| currentCommand += ' '; | |
| updateCommandLine(); | |
| break; | |
| default: | |
| if (key.length === 1) { | |
| currentCommand += key; | |
| updateCommandLine(); | |
| } | |
| } | |
| } | |
| function updateCommandLine() { | |
| commandLine.textContent = currentCommand; | |
| } | |
| function executeCommand() { | |
| // Add to history | |
| if (currentCommand.trim() !== '') { | |
| commandHistory.push(currentCommand); | |
| historyIndex = -1; | |
| } | |
| // Check if we need to scroll up (remove oldest line) | |
| const terminal = document.getElementById('terminal'); | |
| if (terminal.scrollHeight > terminal.clientHeight) { | |
| // Remove the first child that's not the initial text blocks | |
| const initialBlocks = 4; // Number of initial text blocks (BBC Computer 32K, Acorn MOS, BASIC, prompt) | |
| if (output.children.length > 0) { | |
| output.removeChild(output.children[0]); | |
| } | |
| } | |
| // Display command | |
| const commandDiv = document.createElement('div');10 | |
| commandDiv.textContent = '> ' + currentCommand; | |
| output.appendChild(commandDiv); | |
| // Process command | |
| const resultDiv = document.createElement('div'); | |
| try { | |
| const result = processBasicCommand(currentCommand); | |
| resultDiv.textContent = result; | |
| } catch (e) { | |
| resultDiv.textContent = e.message; | |
| resultDiv.className = 'text-red-400'; | |
| } | |
| output.appendChild(resultDiv); | |
| // Reset command line | |
| currentCommand = ''; | |
| updateCommandLine(); | |
| // Scroll to bottom | |
| terminal.scrollTop = terminal.scrollHeight; | |
| } | |
| let programLines = {}; | |
| let variables = { A: 0, B: 0, C: 0 }; | |
| function processBasicCommand(command) { | |
| const cmd = command.trim(); | |
| if (cmd === '') { | |
| return ''; | |
| } else if (cmd.toUpperCase() === 'HELP') { | |
| return 'BBC BASIC Commands: PRINT, LIST, RUN, NEW, CLS, HELP\nDirect commands: GOTO, REM, INPUT, LET, IF...THEN\n= ASCII decimal: 61 Unicode (hexadecimal): U+003D HTML entity: = ou ='; | |
| } else if (cmd.toUpperCase() === 'CLS') { | |
| output.innerHTML = ''; | |
| return ''; | |
| } else if (cmd.toUpperCase() === 'LIST') { | |
| if (Object.keys(programLines).length === 0) return 'No program'; | |
| return Object.keys(programLines).sort((a,b) => a-b).map(n => n + ' ' + programLines[n]).join('\n'); | |
| } else if (cmd.toUpperCase() === 'RUN') { | |
| return runProgram(); | |
| } else if (cmd.toUpperCase() === 'NEW') { | |
| programLines = {}; | |
| output.innerHTML = ''; | |
| return 'Program cleared'; | |
| } else if (/^\d+ /.test(cmd)) { | |
| // Program line entry | |
| const spacePos = cmd.indexOf(' '); | |
| const lineNum = cmd.substring(0, spacePos); | |
| const lineContent = cmd.substring(spacePos + 1); | |
| if (lineContent.trim().toUpperCase() === '') { | |
| // Delete line if empty | |
| delete programLines[lineNum]; | |
| return ''; | |
| } else { | |
| programLines[lineNum] = lineContent; | |
| return ''; | |
| } | |
| } else if (cmd.toUpperCase().startsWith('LET ') || cmd.includes('=')) { | |
| // Handle LET command or direct assignment (e.g. LET C=A+B or C=A+B) | |
| let assignment = cmd; | |
| if (cmd.toUpperCase().startsWith('LET ')) { | |
| assignment = cmd.substring(4).trim(); | |
| } | |
| const parts = assignment.split('='); | |
| if (parts.length === 2) { | |
| const varName = parts[0].trim(); | |
| const expr = parts[1].trim(); | |
| if (['A', 'B', 'C'].includes(varName)) { | |
| variables[varName] = evaluateExpression(expr); | |
| return ''; | |
| } | |
| } | |
| throw new Error('Bad assignment'); | |
| } else if (cmd.toUpperCase().startsWith('PRINT ')) { | |
| // Immediate PRINT | |
| const printExpr = cmd.substring(6); | |
| // Handle printing variables or expressions directly | |
| if (['A', 'B', 'C'].includes(printExpr.trim())) { | |
| return variables[printExpr.trim()].toString(); | |
| } | |
| return evaluateExpression(printExpr); | |
| } else { | |
| throw new Error('Syntax error'); | |
| } | |
| } | |
| function runProgram() { | |
| if (Object.keys(programLines).length === 0) return 'No program'; | |
| const lines = Object.keys(programLines).sort((a,b) => a-b); | |
| let outputText = ''; | |
| for (const lineNum of lines) { | |
| const line = programLines[lineNum]; | |
| if (line.toUpperCase().startsWith('PRINT ')) { | |
| outputText += evaluateExpression(line.substring(6)) + '\n'; | |
| } else if (line.toUpperCase().startsWith('LET ') || line.includes('=')) { | |
| // Process assignments in program lines | |
| let assignment = line; | |
| if (line.toUpperCase().startsWith('LET ')) { | |
| assignment = line.substring(4).trim(); | |
| } | |
| const parts = assignment.split('='); | |
| if (parts.length === 2) { | |
| const varName = parts[0].trim(); | |
| const expr = parts[1].trim(); | |
| if (['A', 'B', 'C'].includes(varName)) { | |
| variables[varName] = evaluateExpression(expr); | |
| } | |
| } | |
| } | |
| } | |
| return outputText || '(Program run with no output)'; | |
| } | |
| function evaluateExpression(expr) { | |
| // Simple expression evaluator - would need expansion for full BASIC | |
| try { | |
| // Handle strings | |
| if (expr.startsWith('"') && expr.endsWith('"')) { | |
| return expr.substring(1, expr.length - 1); | |
| } | |
| // Handle variables in expressions | |
| const varNames = ['A', 'B', 'C']; | |
| let processedExpr = expr; | |
| varNames.forEach(v => { | |
| processedExpr = processedExpr.replace(new RegExp(v, 'g'), variables[v]); | |
| }); | |
| // Handle simple arithmetic | |
| return eval(processedExpr.replace(/MOD/gi, '%').replace(/DIV/gi, '/')); | |
| } catch (e) { | |
| return expr; // Return as-is if we can't evaluate | |
| } | |
| } | |
| // Simulate cursor blink | |
| setInterval(() => { | |
| cursor.style.opacity = cursor.style.opacity === '0' ? '1' : '0'; | |
| }, 500); | |
| }); | |
| </script> | |
| <p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=Luis-Filipe/bbc-computer-frontend" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body> | |
| </html> |