Files
2026-07-27 16:14:41 +02:00

870 lines
28 KiB
Go

package main
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
"os"
"path/filepath"
"sort"
"strings"
"sync"
)
// Mutex to serialize calculation evaluations and capture output safely
var evalMu sync.Mutex
type CalculateResponse struct {
Output string `json:"output"`
Exit bool `json:"exit"`
}
type VariableResponse struct {
Name string `json:"name"`
Value string `json:"value"`
Dim string `json:"dim"`
}
type FunctionResponse struct {
Name string `json:"name"`
Args []string `json:"args"`
Expr string `json:"expr"`
}
// evaluateExpressionSafe redirects stdout for the duration of the evaluation to capture output safely
func evaluateExpressionSafe(line string, variablesFile string, functionsFile string) (string, bool) {
evalMu.Lock()
defer evalMu.Unlock()
// Keep backup of original stdout
oldStdout := os.Stdout
defer func() { os.Stdout = oldStdout }()
// Create pipe to intercept stdout
r, w, err := os.Pipe()
if err != nil {
return "Error capturing stdout", false
}
os.Stdout = w
// Run evaluation (prints to stdout)
exit := handleLine(line, false, variablesFile, functionsFile)
// Close write end of pipe and read captured output
w.Close()
var buf bytes.Buffer
_, _ = io.Copy(&buf, r)
_ = r.Close()
return buf.String(), exit
}
// startWebServer boots the HTTP server serving the Web GUI and APIs
func startWebServer(port string) {
home, _ := os.UserHomeDir()
variablesFile := filepath.Join(home, ".goca_variables.json")
functionsFile := filepath.Join(home, ".goca_functions.json")
// Load persisted variables and custom functions
_ = loadUserFuncs(functionsFile)
_ = loadVariables(variablesFile)
// Fetch current currency rates in the background to ensure units work
_ = fetchRates()
// Serve the interactive web dashboard
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/" && r.URL.Path != "/index.html" {
http.NotFound(w, r)
return
}
w.Header().Set("Content-Type", "text/html; charset=utf-8")
html := strings.ReplaceAll(indexHTML, "{{VERSION}}", Version)
_, _ = w.Write([]byte(html))
})
// Evaluation endpoint
http.HandleFunc("/api/calculate", func(w http.ResponseWriter, r *http.Request) {
query := r.URL.Query().Get("q")
if query == "" {
w.Header().Set("Content-Type", "application/json")
_ = json.NewEncoder(w).Encode(CalculateResponse{Output: "Empty expression", Exit: false})
return
}
output, exit := evaluateExpressionSafe(query, variablesFile, functionsFile)
w.Header().Set("Content-Type", "application/json")
_ = json.NewEncoder(w).Encode(CalculateResponse{
Output: output,
Exit: exit,
})
})
// List variables endpoint
http.HandleFunc("/api/variables", func(w http.ResponseWriter, r *http.Request) {
evalMu.Lock()
defer evalMu.Unlock()
list := []VariableResponse{}
for k, v := range variables {
// Skip the internal answer variables from bloating list
if k == "ans" || k == "_" {
continue
}
list = append(list, VariableResponse{
Name: k,
Value: fmt.Sprintf("%v", v.Value),
Dim: fmt.Sprintf("%v", v.Dim),
})
}
sort.Slice(list, func(i, j int) bool {
return list[i].Name < list[j].Name
})
w.Header().Set("Content-Type", "application/json")
_ = json.NewEncoder(w).Encode(list)
})
// List custom functions endpoint
http.HandleFunc("/api/functions", func(w http.ResponseWriter, r *http.Request) {
evalMu.Lock()
defer evalMu.Unlock()
list := []FunctionResponse{}
for k, v := range userFuncs {
list = append(list, FunctionResponse{
Name: k,
Args: v.Args,
Expr: v.Expr,
})
}
sort.Slice(list, func(i, j int) bool {
return list[i].Name < list[j].Name
})
w.Header().Set("Content-Type", "application/json")
_ = json.NewEncoder(w).Encode(list)
})
// Clear state endpoint
http.HandleFunc("/api/reset", func(w http.ResponseWriter, r *http.Request) {
evalMu.Lock()
defer evalMu.Unlock()
variables = make(map[string]Result)
_ = saveVariables(variablesFile)
userFuncs = make(map[string]UserFunc)
_ = saveUserFuncs(functionsFile)
w.Header().Set("Content-Type", "application/json")
_, _ = w.Write([]byte(`{"status":"success"}`))
})
fmt.Printf("Go-Ca web server started on http://localhost:%s\n", port)
if err := http.ListenAndServe(":"+port, nil); err != nil {
fmt.Printf("❌ Error starting web server: %v\n", err)
}
}
// Beautiful cyber-retro dashboard HTML layout string
const indexHTML = `<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Go-Ca | Scientific Cyber Calculator</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Outfit:wght@400;600;800&family=Share+Tech+Mono&display=swap" rel="stylesheet">
<style>
:root {
--color-cyan: #06b6d4;
--color-pink: #f43f5e;
--color-yellow: #fbbf24;
--color-green: #10b981;
--color-blue: #3b82f6;
--color-purple: #8b5cf6;
--color-red: #ef4444;
--color-orange: #ea580c;
--bg-dark: #080c14;
--bg-card: #0f172a;
--bg-input: #020617;
--border-neon: rgba(6, 182, 212, 0.18);
--border-warm: rgba(234, 88, 12, 0.22);
--font-display: 'Outfit', -apple-system, sans-serif;
--font-mono: 'Share Tech Mono', monospace;
}
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
background-color: var(--bg-dark);
background-image: radial-gradient(circle at top, #0f1c30 0%, #05080e 100%);
color: #f8fafc;
font-family: var(--font-display);
height: 100vh;
overflow: hidden;
display: flex;
flex-direction: column;
}
/* Top Navigation Header */
header {
background-color: rgba(15, 23, 42, 0.5);
border-bottom: 1px solid var(--border-neon);
backdrop-filter: blur(10px);
padding: 0.85rem 2rem;
display: flex;
justify-content: space-between;
align-items: center;
z-index: 10;
}
.header-logo {
font-size: 1.5rem;
font-weight: 800;
letter-spacing: 0.05em;
text-shadow: 0 0 10px rgba(6, 182, 212, 0.35);
}
.header-logo span {
background: linear-gradient(135deg, var(--color-cyan) 0%, #a5f3fc 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
}
.status-panel {
display: flex;
align-items: center;
gap: 1.5rem;
font-family: var(--font-mono);
font-size: 0.8rem;
}
.status-indicator {
display: flex;
align-items: center;
gap: 0.45rem;
color: var(--color-green);
}
.status-dot {
width: 8px;
height: 8px;
background-color: var(--color-green);
border-radius: 50%;
box-shadow: 0 0 10px var(--color-green);
animation: pulse 1.8s infinite;
}
.reset-btn {
background: rgba(244, 63, 94, 0.08);
border: 1px solid var(--color-pink);
border-radius: 4px;
color: var(--color-pink);
cursor: pointer;
font-family: var(--font-display);
font-size: 0.75rem;
font-weight: bold;
padding: 0.35rem 0.75rem;
transition: all 0.2s ease;
}
.reset-btn:hover {
background: rgba(244, 63, 94, 0.2);
box-shadow: 0 0 10px rgba(244, 63, 94, 0.25);
}
.dashboard-grid {
display: flex;
flex-direction: column;
flex: 1;
min-height: 0;
padding: 1.5rem 2rem;
}
/* Column 1: Terminal Console Window */
.console-card {
display: flex;
flex-direction: column;
background-color: rgba(2, 6, 23, 0.65);
border: 1px solid var(--border-neon);
border-radius: 12px;
box-shadow: 0 8px 32px 0 rgba(0, 0, 0, 0.4), inset 0 0 15px rgba(255, 255, 255, 0.01);
overflow: hidden;
flex: 1;
min-height: 0;
}
.console-header {
background-color: rgba(15, 23, 42, 0.4);
border-bottom: 1px solid rgba(6, 182, 212, 0.1);
padding: 0.6rem 1.25rem;
display: flex;
justify-content: space-between;
align-items: center;
font-family: var(--font-mono);
font-size: 0.75rem;
color: var(--color-cyan);
}
.console-terminal {
flex: 1;
overflow-y: auto;
padding: 1.5rem;
font-family: var(--font-mono);
font-size: 0.95rem;
line-height: 1.6;
scrollbar-width: thin;
scrollbar-color: rgba(255, 255, 255, 0.05) transparent;
}
.terminal-welcome {
color: var(--color-blue);
margin-bottom: 1rem;
border-left: 2px solid var(--color-blue);
padding-left: 0.75rem;
}
.history-item {
margin-bottom: 0.85rem;
}
.history-query {
color: #94a3b8;
}
.history-output {
padding-left: 0.85rem;
}
/* Interactive Command Prompt Line */
.prompt-container {
display: flex;
align-items: center;
background-color: var(--bg-input);
border-top: 1px solid rgba(6, 182, 212, 0.1);
padding: 1rem 1.25rem;
gap: 0.75rem;
}
.prompt-symbol {
color: var(--color-green);
font-family: var(--font-mono);
font-weight: bold;
font-size: 1.1rem;
}
.prompt-input {
flex: 1;
background: transparent;
border: none;
outline: none;
color: #f1f5f9;
font-family: var(--font-mono);
font-size: 1.05rem;
caret-color: var(--color-green);
}
.submit-btn {
background-color: var(--color-cyan);
border: none;
border-radius: 6px;
color: #020617;
cursor: pointer;
font-family: var(--font-display);
font-size: 0.8rem;
font-weight: bold;
padding: 0.5rem 1rem;
transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
}
.submit-btn:hover {
box-shadow: 0 0 15px var(--color-cyan);
transform: translateY(-1px);
}
/* Keyframe animations */
@keyframes pulse {
0% {
box-shadow: 0 0 5px var(--color-green);
opacity: 0.8;
}
50% {
box-shadow: 0 0 15px var(--color-green);
opacity: 1;
}
100% {
box-shadow: 0 0 5px var(--color-green);
opacity: 0.8;
}
}
/* Help manual formatting */
.help-card {
background-color: rgba(15, 23, 42, 0.4);
border: 1px solid rgba(6, 182, 212, 0.15);
border-radius: 8px;
padding: 1.25rem;
margin-top: 0.5rem;
width: 100%;
max-width: 900px;
}
.help-title {
font-size: 1.1rem;
font-weight: bold;
color: var(--color-cyan);
margin-bottom: 1rem;
text-transform: uppercase;
letter-spacing: 0.05em;
border-bottom: 1px solid rgba(6, 182, 212, 0.2);
padding-bottom: 0.35rem;
}
.help-grid {
display: flex;
flex-direction: column;
gap: 1.5rem;
}
.help-group {
display: flex;
flex-direction: column;
gap: 0.5rem;
}
.help-group-title {
font-size: 0.85rem;
font-weight: bold;
color: var(--color-yellow);
text-transform: uppercase;
letter-spacing: 0.05em;
margin-bottom: 0.25rem;
}
.help-item {
display: flex;
flex-direction: column;
background-color: rgba(2, 6, 23, 0.4);
border: 1px solid rgba(255, 255, 255, 0.03);
border-radius: 6px;
padding: 0.5rem 0.75rem;
font-size: 0.75rem;
line-height: 1.4;
cursor: pointer;
transition: all 0.15s ease;
}
.help-item:hover {
background-color: rgba(6, 182, 212, 0.05);
border-color: rgba(6, 182, 212, 0.15);
transform: translateY(-1px);
}
.help-syntax {
font-family: var(--font-mono);
color: var(--color-cyan);
font-weight: bold;
margin-bottom: 0.10rem;
}
.help-desc {
color: #94a3b8;
}
/* Responsive styling for tablet and mobile devices */
@media (max-width: 768px) {
.dashboard-grid {
padding: 0.75rem;
}
header {
padding: 0.75rem 1rem;
}
.header-logo {
font-size: 1.25rem;
}
.status-indicator span {
display: none; /* Hide status text on small screens */
}
.status-panel {
gap: 0.75rem;
}
.reset-btn {
padding: 0.35rem 0.6rem;
font-size: 0.7rem;
}
.console-terminal {
padding: 1rem;
font-size: 0.85rem;
}
.prompt-container {
padding: 0.75rem 0.85rem;
}
.prompt-input {
font-size: 0.95rem;
}
.submit-btn {
padding: 0.45rem 0.85rem;
font-size: 0.75rem;
}
.help-card {
padding: 0.85rem;
}
.help-title {
font-size: 0.95rem;
margin-bottom: 0.75rem;
}
.help-grid {
gap: 0.75rem;
}
.help-item {
padding: 0.45rem 0.6rem;
}
}
</style>
</head>
<body>
<header>
<div class="header-logo">
<span>Go-Ca</span> SYSTEM
</div>
<div class="status-panel">
<div class="status-indicator">
<div class="status-dot"></div>
<span>SERVER CONSOLE ACTIVE</span>
</div>
<button class="reset-btn" id="btn-reset" title="Deletes all user variables & functions">RESET STATE</button>
</div>
</header>
<div class="dashboard-grid">
<!-- Calculator shell terminal -->
<div class="console-card">
<div class="console-header">
<span></span>
<span id="session-time">12:00:00</span>
</div>
<div class="console-terminal" id="terminal-scroller">
<div class="terminal-welcome">
goca {{VERSION}}, type 'help' for examples.
</div>
<div id="terminal-history"></div>
</div>
<div class="prompt-container">
<span class="prompt-symbol">goca&gt;</span>
<input type="text" id="cmd-input" class="prompt-input" autocomplete="off" autofocus placeholder="Type expression (e.g. 10 mi to km)">
<button class="submit-btn" id="btn-submit">RUN</button>
</div>
</div>
</div>
<script>
// Local input history list tracker
const cmdInput = document.getElementById('cmd-input');
const btnSubmit = document.getElementById('btn-submit');
const btnReset = document.getElementById('btn-reset');
const historyContainer = document.getElementById('terminal-history');
const terminalScroller = document.getElementById('terminal-scroller');
const inputHistory = [];
let historyIdx = -1;
// Tick clock
setInterval(function() {
const d = new Date();
document.getElementById('session-time').textContent = d.toTimeString().split(' ')[0];
}, 1000);
// Trigger evaluate
btnSubmit.addEventListener('click', runCommand);
cmdInput.addEventListener('keydown', function(e) {
if (e.key === 'Enter') {
runCommand();
} else if (e.key === 'ArrowUp') {
e.preventDefault();
if (inputHistory.length > 0) {
historyIdx = Math.min(historyIdx + 1, inputHistory.length - 1);
cmdInput.value = inputHistory[inputHistory.length - 1 - historyIdx];
}
} else if (e.key === 'ArrowDown') {
e.preventDefault();
if (historyIdx > 0) {
historyIdx--;
cmdInput.value = inputHistory[inputHistory.length - 1 - historyIdx];
} else if (historyIdx === 0) {
historyIdx = -1;
cmdInput.value = '';
}
}
});
btnReset.addEventListener('click', function() {
if (confirm("Clear all user-defined variables and custom functions?")) {
fetch('/api/reset', { method: 'POST' })
.then(function(res) { return res.json(); })
.then(function() {
appendHistoryLine('unset *', '<span style="color: var(--color-pink);">All variables and functions deleted.</span>');
});
}
});
function runCommand() {
const line = cmdInput.value.trim();
if (!line) return;
inputHistory.push(line);
historyIdx = -1;
cmdInput.value = '';
if (line.toLowerCase() === 'clear') {
historyContainer.innerHTML = '';
return;
}
if (line.toLowerCase() === 'help') {
appendHistoryLine(line, getInteractiveHelpHTML());
return;
}
fetch('/api/calculate?q=' + encodeURIComponent(line))
.then(function(res) { return res.json(); })
.then(function(data) {
const formattedOutput = ansiToHtml(data.output);
appendHistoryLine(line, formattedOutput);
})
.catch(function(err) {
appendHistoryLine(line, '<span style="color: var(--color-red);">Error: Failed to connect to server.</span>');
});
}
function appendHistoryLine(query, output) {
const div = document.createElement('div');
div.className = 'history-item';
div.innerHTML = '<div class="history-query">goca&gt; ' + escapeHtml(query) + '</div>' +
'<div class="history-output">' + output + '</div>';
historyContainer.appendChild(div);
terminalScroller.scrollTop = terminalScroller.scrollHeight;
}
// Convert Go CLI ANSI color tags to styled HTML spans
function ansiToHtml(text) {
return text
.replace(/\u001b\[1;33m/g, '<span style="color: var(--color-yellow); font-weight: bold;">')
.replace(/\u001b\[1;31m/g, '<span style="color: var(--color-red); font-weight: bold;">')
.replace(/\u001b\[1;32m/g, '<span style="color: var(--color-green); font-weight: bold;">')
.replace(/\u001b\[1;34m/g, '<span style="color: var(--color-cyan); font-weight: bold;">') // Cyan style logo
.replace(/\u001b\[1;30m/g, '<span style="color: #475569;">')
.replace(/\u001b\[1m/g, '<span style="font-weight: bold;">')
.replace(/\u001b\[0m/g, '</span>')
.replace(/\n/g, '<br>');
}
function escapeHtml(str) {
return str.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
}
function insertSample(expr) {
cmdInput.value = expr;
cmdInput.focus();
}
function getInteractiveHelpHTML() {
var html = '';
html += '<div class="help-card">';
html += ' <div class="help-title">Go-Ca Interactive Manual</div>';
html += ' <div class="help-grid">';
html += ' <div class="help-group">';
html += ' <div class="help-group-title">Arithmetic &amp; Operators</div>';
html += ' <div class="help-item" onclick="insertSample(\'5 + 3 * 2\')">';
html += ' <div class="help-syntax">5 + 3 * 2</div>';
html += ' <div class="help-desc">Basic: +, -, *, /, % (modulo)</div>';
html += ' </div>';
html += ' <div class="help-item" onclick="insertSample(\'10 ** 3\')">';
html += ' <div class="help-syntax">10 ** 3</div>';
html += ' <div class="help-desc">Power: a ** b or pow(a, b)</div>';
html += ' </div>';
html += ' <div class="help-item" onclick="insertSample(\'0xFF &amp; 0b1010\')">';
html += ' <div class="help-syntax">0xFF &amp; 0b1010</div>';
html += ' <div class="help-desc">Bitwise: &amp;, |, ^, ~, &lt;&lt; (Left Shift), &gt;&gt; (Right Shift)</div>';
html += ' </div>';
html += ' <div class="help-item" onclick="insertSample(\'10 > 5\')">';
html += ' <div class="help-syntax">10 &gt; 5</div>';
html += ' <div class="help-desc">Logic: ==, !=, &lt;, &gt;, &lt;=, &gt;=, ! (NOT)</div>';
html += ' </div>';
html += ' <div class="help-item" onclick="insertSample(\'2(3 + 4)\')">';
html += ' <div class="help-syntax">2(3 + 4)</div>';
html += ' <div class="help-desc">Implicit: 2(3 + 4) or 2m (implicit multiplication)</div>';
html += ' </div>';
html += ' <div class="help-item" onclick="insertSample(\'0xFF\')">';
html += ' <div class="help-syntax">0xFF</div>';
html += ' <div class="help-desc">Numbers: Decimal (10), Hex (0xFF), Binary (0b1010), Octal (0o77)</div>';
html += ' </div>';
html += ' <div class="help-item" onclick="insertSample(\'x = 5.5\')">';
html += ' <div class="help-syntax">x = 5.5</div>';
html += ' <div class="help-desc">Variables: Assign (e.g. x = 5), Use \'ans\' or \'_\' for last result</div>';
html += ' </div>';
html += ' <div class="help-item" onclick="insertSample(\'f(x, y) = x * y + 2\')">';
html += ' <div class="help-syntax">f(x, y) = x * y + 2</div>';
html += ' <div class="help-desc">Custom Fn: f(x, y) = x * y + 2</div>';
html += ' </div>';
html += ' </div>';
html += ' <div class="help-group">';
html += ' <div class="help-group-title">Scientific Functions</div>';
html += ' <div class="help-item" onclick="insertSample(\'sin(90 deg)\')">';
html += ' <div class="help-syntax">sin(90 deg)</div>';
html += ' <div class="help-desc">Trig: sin(x), cos(x), tan(x) (accepts angles like 90 deg or pi rad)</div>';
html += ' </div>';
html += ' <div class="help-item" onclick="insertSample(\'asin(1) to deg\')">';
html += ' <div class="help-syntax">asin(1) to deg</div>';
html += ' <div class="help-desc">Inv Trig: asin(x), acos(x), atan(x) (returns angles, e.g. asin(1) to deg)</div>';
html += ' </div>';
html += ' <div class="help-item" onclick="insertSample(\'sinh(1)\')">';
html += ' <div class="help-syntax">sinh(1)</div>';
html += ' <div class="help-desc">Hyper: sinh(x), cosh(x), tanh(x)</div>';
html += ' </div>';
html += ' <div class="help-item" onclick="insertSample(\'sqrt(16)\')">';
html += ' <div class="help-syntax">sqrt(16)</div>';
html += ' <div class="help-desc">General: sqrt(x), abs(x), exp(x), ln(x) (natural), log(x) (base 10), log2(x)</div>';
html += ' </div>';
html += ' <div class="help-item" onclick="insertSample(\'round(5.5)\')">';
html += ' <div class="help-syntax">round(5.5)</div>';
html += ' <div class="help-desc">Rounding: ceil(x), floor(x), round(x)</div>';
html += ' </div>';
html += ' <div class="help-item" onclick="insertSample(\'min(10, 20, 5)\')">';
html += ' <div class="help-syntax">min(10, 20, 5)</div>';
html += ' <div class="help-desc">Stats: min(a, b, ...), max(a, b, ...), mod(a, b), fact(x) (factorial)</div>';
html += ' </div>';
html += ' <div class="help-item" onclick="insertSample(\'if(5 > 3, PI, E)\')">';
html += ' <div class="help-syntax">if(5 &gt; 3, PI, E)</div>';
html += ' <div class="help-desc">Logic: if(cond, true_val, false_val)</div>';
html += ' </div>';
html += ' <div class="help-item" onclick="insertSample(\'PI\')">';
html += ' <div class="help-syntax">PI</div>';
html += ' <div class="help-desc">Constants: PI, E</div>';
html += ' </div>';
html += ' </div>';
html += ' <div class="help-group">';
html += ' <div class="help-group-title">IP &amp; Subnet</div>';
html += ' <div class="help-item" onclick="insertSample(\'ip(\\"192.168.1.1\\")\')">';
html += ' <div class="help-syntax">ip("192.168.1.1")</div>';
html += ' <div class="help-desc">Parse: ip("192.168.1.1"), cidr("10.0.0.0/24")</div>';
html += ' </div>';
html += ' <div class="help-item" onclick="insertSample(\'network(cidr(\\"10.0.0.0/24\\"))\')">';
html += ' <div class="help-syntax">network(cidr("10.0.0.0/24"))</div>';
html += ' <div class="help-desc">Subnet: network(c), broadcast(c), mask(c), hosts(c), range(c)</div>';
html += ' </div>';
html += ' </div>';
html += ' <div class="help-group">';
html += ' <div class="help-group-title">Units &amp; Conversions</div>';
html += ' <div class="help-item" onclick="insertSample(\'10 mi to km\')">';
html += ' <div class="help-syntax">10 mi to km</div>';
html += ' <div class="help-desc">Syntax: &lt;value&gt; &lt;unit&gt; to/in &lt;unit&gt; (e.g. 10 mi to km, 1 GB in MB)</div>';
html += ' </div>';
html += ' <div class="help-item" onclick="insertSample(\'units\')">';
html += ' <div class="help-syntax">units</div>';
html += ' <div class="help-desc">Types: Length, Mass, Time, Digital, Area, Temperature, Angle</div>';
html += ' </div>';
html += ' <div class="help-item" onclick="insertSample(\'0 C to K\')">';
html += ' <div class="help-syntax">0 C to K</div>';
html += ' <div class="help-desc">Example: 0 C to K, 90 deg to rad, 32 F to C</div>';
html += ' </div>';
html += ' </div>';
html += ' <div class="help-group">';
html += ' <div class="help-group-title">Currencies (Live Rates)</div>';
html += ' <div class="help-item" onclick="insertSample(\'100 USD to EUR\')">';
html += ' <div class="help-syntax">100 USD to EUR</div>';
html += ' <div class="help-desc">Usage: Exchange currency codes (e.g. 100 USD to EUR)</div>';
html += ' </div>';
html += ' <div class="help-item" onclick="insertSample(\'10$ to €\')">';
html += ' <div class="help-syntax">10$ to €</div>';
html += ' <div class="help-desc">Symbols: $, €, £, ¥ (e.g. 10$ to €)</div>';
html += ' </div>';
html += ' </div>';
html += ' <div class="help-group">';
html += ' <div class="help-group-title">Commands</div>';
html += ' <div class="help-item" onclick="insertSample(\'help\')">';
html += ' <div class="help-syntax">help</div>';
html += ' <div class="help-desc">help: Show this help information</div>';
html += ' </div>';
html += ' <div class="help-item" onclick="insertSample(\'units\')">';
html += ' <div class="help-syntax">units</div>';
html += ' <div class="help-desc">units: List all supported measurement units</div>';
html += ' </div>';
html += ' <div class="help-item" onclick="insertSample(\'rates\')">';
html += ' <div class="help-syntax">rates</div>';
html += ' <div class="help-desc">rates: Show currency exchange rates relative to base</div>';
html += ' </div>';
html += ' <div class="help-item" onclick="insertSample(\'cur\')">';
html += ' <div class="help-syntax">cur</div>';
html += ' <div class="help-desc">cur: List all supported live currency codes</div>';
html += ' </div>';
html += ' <div class="help-item" onclick="insertSample(\'var\')">';
html += ' <div class="help-syntax">var</div>';
html += ' <div class="help-desc">var: List all user-defined variables</div>';
html += ' </div>';
html += ' <div class="help-item" onclick="insertSample(\'funcs\')">';
html += ' <div class="help-syntax">funcs</div>';
html += ' <div class="help-desc">funcs: List all user-defined custom functions</div>';
html += ' </div>';
html += ' <div class="help-item" onclick="insertSample(\'unset x\')">';
html += ' <div class="help-syntax">unset x</div>';
html += ' <div class="help-desc">unset &lt;v&gt;: Delete variable &lt;v&gt; (or \'unset *\' to delete all)</div>';
html += ' </div>';
html += ' <div class="help-item" onclick="insertSample(\'base USD\')">';
html += ' <div class="help-syntax">base USD</div>';
html += ' <div class="help-desc">base &lt;C&gt;: Change base currency (e.g. base USD)</div>';
html += ' </div>';
html += ' <div class="help-item" onclick="insertSample(\'clear\')">';
html += ' <div class="help-syntax">clear</div>';
html += ' <div class="help-desc">clear: Clear the terminal screen</div>';
html += ' </div>';
html += ' <div class="help-item" onclick="insertSample(\'exit\')">';
html += ' <div class="help-syntax">exit</div>';
html += ' <div class="help-desc">exit/quit: Exit goca</div>';
html += ' </div>';
html += ' </div>';
html += ' </div>';
html += '</div>';
return html;
}
</script>
</body>
</html>`