bbc-computer-frontend / index.html
Luis-Filipe's picture
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
<!DOCTYPE html>
<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: &#61; 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>