diff --git a/apps/Uke/ChangeLog b/apps/Uke/ChangeLog index 5560f00bc..ef5ffa3fe 100644 --- a/apps/Uke/ChangeLog +++ b/apps/Uke/ChangeLog @@ -1 +1,3 @@ 0.01: New App! +0.02: Increased Legibility, GUI rework +0.03: 13 new chords diff --git a/apps/Uke/README.md b/apps/Uke/README.md index 49ceea1ed..b6236e307 100644 --- a/apps/Uke/README.md +++ b/apps/Uke/README.md @@ -4,7 +4,8 @@ An app that simply describes finger placements on a Ukulele to form common chord ## Usage -Use the button to scroll through the available chords. +Select a chord to view. +Use the button to return to the chord selection menu. ## Creator diff --git a/apps/Uke/app.js b/apps/Uke/app.js index c60c49a6b..095477f3f 100644 --- a/apps/Uke/app.js +++ b/apps/Uke/app.js @@ -16,52 +16,12 @@ const cc = [ const dd = [ "D", + "22", "23", - "22", "24", "x" ]; -const gg = [ - "G", - "x", - "21", - "33", - "22", -]; - -const am = [ - "Am", - "22", - "x", - "x", - "x" -]; - -const em = [ - "Em", - "x", - "43", - "32", - "21" -]; - -const aa = [ - "A", - "22", - "11", - "x", - "x" -]; - -const ff = [ - "F", - "22", - "x", - "11", - "x" -]; - var ee = [ "E", "33", @@ -70,14 +30,187 @@ var ee = [ "11" ]; +const ff = [ + "F", + "22", + "x", + "11", + "x" +]; + +const gg = [ + "G", + "x", + "21", + "33", + "22", +]; + +const aa = [ + "A", + "22", + "11", + "x", + "x" +]; + +const bb = [ + "B", + "42", + "43", + "44", + "21" +]; + +const cm = [ + "Cm", + "11", + "x", + "12", + "34" +]; + +const dm = [ + "Dm", + "x", + "22", + "33", + "11" +]; + +const em = [ + "Em", + "x", + "43", + "32", + "21" +]; + +const fm = [ + "Fm", + "33", + "11", + "11", + "11" +]; + +const gm = [ + "Gm", + "x", + "22", + "33", + "11" +]; + +const am = [ + "Am", + "22", + "23", + "11", + "x" +]; + +const bm = [ + "Bm", + "x", + "43", + "32", + "21" +]; + +const c7 = [ + "C7", + "22", + "33", + "11", + "x" +]; + +const d7 = [ + "D7", + "x", + "22", + "11", + "23" +]; + +const e7 = [ + "E7", + "x", + "11", + "x", + "x" +]; + +const f7 = [ + "F7", + "11", + "22", + "11", + "11" +]; + +const g7 = [ + "G7", + "x", + "x", + "x", + "11" +]; + +const a7 = [ + "A7", + "21", + "21", + "21", + "32" +]; + +const b7 = [ + "B7", + "11", + "22", + "x", + "23" +]; + + + var index = 0; var chords = []; +var menu = { + "" : { "title" : "Uke Chords" }, + "C" : function() { draw(cc); }, + "D" : function() { draw(dd); }, + "E" : function() { draw(ee); }, + "F" : function() { draw(ff); }, + "G" : function() { draw(gg); }, + "A" : function() { draw(aa); }, + "B" : function() { draw(bb); }, + "C7" : function() { draw(c7); }, + "D7" : function() { draw(d7); }, + "E7" : function() { draw(e7); }, + "F7" : function() { draw(f7); }, + "G7" : function() { draw(g7); }, + "A7" : function() { draw(a7); }, + "B7" : function() { draw(b7); }, + "Cm" : function() { draw(cm); }, + "Dm" : function() { draw(dm); }, + "Em" : function() { draw(em); }, + "Fm" : function() { draw(fm); }, + "Gm" : function() { draw(gm); }, + "Am" : function() { draw(am); }, + "Bm" : function() { draw(bm); }, + "About" : function() { + E.showMessage( + "Created By:\nNovaDawn999", { + title:"About" + } + ); + } +}; + -function init() { - g.setFontAlign(0,0); // center font - g.setFont("6x8",2); // bitmap font, 8x magnified - chords.push(cc, dd, gg, am, em, aa, ff, ee); -} function drawBase() { for (let i = 0; i < 4; i++) { @@ -87,18 +220,18 @@ function drawBase() { } function drawChord(chord) { - g.drawString(chord[0], g.getWidth() * 0.5 + 2, 18); + g.drawString(chord[0], g.getWidth() * 0.5 - (chord[0].length * 5), 16); for (let i = 0; i < chord.length; i++) { if (i === 0 || chord[i][0] === "x") { continue; } if (chord[i][0] === "0") { - g.drawString(chord[i][1], x + (i - 1) * stringInterval + 1, y + fretHeight * chord[i][0], true); - g.drawCircle(x + (i - 1) * stringInterval -1, y + fretHeight * chord[i][0], 8); + g.drawString(chord[i][1], x + (i - 1) * stringInterval - 5, y + fretHeight * chord[i][0] + 2, true); + g.drawCircle(x + (i - 1) * stringInterval -1, y + fretHeight * chord[i][0], 10); } else { - g.drawString(chord[i][1], x + (i - 1) * stringInterval + 1, y -fingerOffset + fretHeight * chord[i][0], true); - g.drawCircle(x + (i - 1) * stringInterval -1, y -fingerOffset + fretHeight * chord[i][0], 8); + g.drawString(chord[i][1], x + (i - 1) * stringInterval -5, y -fingerOffset + fretHeight * chord[i][0] + 2, true); + g.drawCircle(x + (i - 1) * stringInterval -1, y -fingerOffset + fretHeight * chord[i][0], 10); } } } @@ -107,22 +240,19 @@ function buttonPress() { setWatch(() => { buttonPress(); }, BTN); - index++; - if (index >= chords.length) { index = 0; } - draw(); + E.showMenu(menu); } -function draw() { +function draw(chord) { g.clear(); drawBase(); - drawChord(chords[index]); + drawChord(chord); } function main() { - init(); - draw(); + E.showMenu(menu); setWatch(() => { buttonPress(); }, BTN); diff --git a/apps/Uke/metadata.json b/apps/Uke/metadata.json index 10c3b3e79..ef31e3663 100644 --- a/apps/Uke/metadata.json +++ b/apps/Uke/metadata.json @@ -1,7 +1,7 @@ { "id": "Uke", "name": "Uke Chords", "shortName":"Uke", - "version":"0.01", + "version":"0.03", "description": "Wrist mounted ukulele chords", "icon": "app.png", "tags": "uke, chords", diff --git a/apps/boot/ChangeLog b/apps/boot/ChangeLog index 82e55fa91..d7405e763 100644 --- a/apps/boot/ChangeLog +++ b/apps/boot/ChangeLog @@ -66,3 +66,4 @@ If settings.bootDebug is set, output timing for each section of .boot0 0.56: Settings.log = 0,1,2,3 for off,display, log, both 0.57: Handle the whitelist being disabled +0.58: "Make Connectable" temporarily bypasses the whitelist diff --git a/apps/boot/bootupdate.js b/apps/boot/bootupdate.js index 84745b792..a12d41e1b 100644 --- a/apps/boot/bootupdate.js +++ b/apps/boot/bootupdate.js @@ -79,7 +79,7 @@ if (global.save) boot += `global.save = function() { throw new Error("You can't if (s.options) boot+=`Bangle.setOptions(${E.toJS(s.options)});\n`; if (s.brightness && s.brightness!=1) boot+=`Bangle.setLCDBrightness(${s.brightness});\n`; if (s.passkey!==undefined && s.passkey.length==6) boot+=`NRF.setSecurity({passkey:${E.toJS(s.passkey.toString())}, mitm:1, display:1});\n`; -if (s.whitelist && !s.whitelist_disabled) boot+=`NRF.on('connect', function(addr) { if (!(require('Storage').readJSON('setting.json',1)||{}).whitelist.includes(addr)) NRF.disconnect(); });\n`; +if (s.whitelist && !s.whitelist_disabled) boot+=`NRF.on('connect', function(addr) { if (!NRF.ignoreWhitelist && !(require('Storage').readJSON('setting.json',1)||{}).whitelist.includes(addr)) NRF.disconnect(); });\n`; if (s.rotate) boot+=`g.setRotation(${s.rotate&3},${s.rotate>>2});\n` // screen rotation // ================================================== FIXING OLDER FIRMWARES if (FWVERSION<215.068) // 2v15.68 and before had compass heading inverted. diff --git a/apps/boot/metadata.json b/apps/boot/metadata.json index c652f6136..0a4e7e9d1 100644 --- a/apps/boot/metadata.json +++ b/apps/boot/metadata.json @@ -1,7 +1,7 @@ { "id": "boot", "name": "Bootloader", - "version": "0.57", + "version": "0.58", "description": "This is needed by Bangle.js to automatically load the clock, menu, widgets and settings", "icon": "bootloader.png", "type": "bootloader", diff --git a/apps/guitar/ChangeLog b/apps/guitar/ChangeLog new file mode 100644 index 000000000..22c67383d --- /dev/null +++ b/apps/guitar/ChangeLog @@ -0,0 +1,2 @@ +0.01: New App! +0.02: More Chords, formatting, fret offset support. \ No newline at end of file diff --git a/apps/guitar/README.md b/apps/guitar/README.md new file mode 100644 index 000000000..ad4ecca4a --- /dev/null +++ b/apps/guitar/README.md @@ -0,0 +1,12 @@ +# Guitar Chords + +An app that simply describes finger placements on a Guitar to form common chords. + +## Usage + +Select a chord to view. +Use the button to return to the chord selection menu. + +## Creator + +NovaDawn999 diff --git a/apps/guitar/app-icon.js b/apps/guitar/app-icon.js new file mode 100644 index 000000000..490541b44 --- /dev/null +++ b/apps/guitar/app-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEwwkCkQA/AGMkoQXVptEFytEogwUCoIYBLqlUGIIXTopHVknUoXULylNpouUIoKmUUi0hoMUailEiMSCR/d7pdECx8tC4IYBolULqAWC7qLSFwfdiKMRC4dEoK6RFwYWBppdW7vSLiPd6gXPConVgIWCYYYtM9vdosUqgXOFwndilBqoGDLh/eqtEioXR9xHCoIXDO5SKEpvU6kVppeQ73kqgwB7wuNOwosEXqSlB9xFNR49RpwXV6pICIxhIF73ePAIXTAAgXOJApePGBQXPGA4XPGAxeOGBAWQDAouRDAgWUAH4AZ")) diff --git a/apps/guitar/app.js b/apps/guitar/app.js new file mode 100644 index 000000000..6c172f920 --- /dev/null +++ b/apps/guitar/app.js @@ -0,0 +1,340 @@ +const stringInterval = 24; +const stringLength = 138; +const fretHeight = 35; +const fingerOffset = 17; +const xOffset = 26; +const yOffset = 34; + +const cc = [ + "C", + "0X", + "33", + "22", + "x", + "11", + "x", + "0" +]; + +const dd = [ + "D", + "0X", + "0X", + "x", + "21", + "33", + "22", + "0" +]; + +const gg = [ + "G", + "32", + "21", + "x", + "x", + "x", + "33", + "0" +]; + +const am = [ + "Am", + "0x", + "x", + "23", + "22", + "11", + "x", + "0" +]; + +const em = [ + "Em", + "x", + "22", + "23", + "x", + "x", + "x", + "0" +]; + +const aa = [ + "A", + "0X", + "x", + "21", + "22", + "23", + "x", + "0" +]; + +var ee = [ + "E", + "x", + "22", + "23", + "11", + "x", + "x", + "0" +]; + +var dm = [ + "Dm", + "0x", + "0x", + "x", + "22", + "33", + "11", + "0" +]; + +var ff = [ + "F", + "0x", + "0x", + "33", + "22", + "11", + "11", + "0" +]; + +var b7 = [ + "B7", + "0x", + "22", + "11", + "23", + "x", + "24", + "0" +]; + +var cadd9 = [ + "Cadd9", + "0x", + "32", + "21", + "x", + "33", + "34", + "0" +]; + +var dadd11 = [ + "Dadd11", + "0x", + "33", + "22", + "x", + "11", + "x", + "3" +]; + +var csus2 = [ + "Csus2", + "0x", + "33", + "x", + "x", + "11", + "0x", + "0" +]; + +var gadd9 = [ + "Gadd9", + "32", + "0x", + "x", + "21", + "x", + "33", + "0" +]; + +var aadd9 = [ + "Aadd9", + "11", + "33", + "34", + "22", + "x", + "x", + "5" +]; + +var fsharp7add11 = [ + "F#7add11", + "21", + "43", + "44", + "32", + "x", + "x", + "0" +]; + +var d9 = [ + "D9", + "0x", + "22", + "11", + "23", + "23", + "0x", + "4" +]; + +var g7 = [ + "G7", + "33", + "22", + "x", + "x", + "34", + "11", + "0" +]; + +var bflatd = [ + "Bb/D", + "0x", + "33", + "11", + "11", + "11", + "0x", + "3" +]; + +var e7sharp9 = [ + "E7#9", + "0x", + "22", + "11", + "23", + "34", + "0x", + "6" +]; + +var a11 = [ + "A11 3rd fret", + "33", + "0x", + "34", + "22", + "11", + "0x", + "0" +]; + +var a9 = [ + "A9", + "32", + "0x", + "33", + "21", + "34", + "0x", + "3" +]; + + + +var index = 0; +var chords = []; +var menu = { + "" : { + "title" : "Guitar Chords" + }, + "C" : function() { draw(cc); }, + "D" : function() { draw(dd); }, + "E" : function() { draw(ee); }, + "Em" : function() { draw(em); }, + "A" : function() { draw(aa); }, + "Am" : function() { draw(am); }, + "F" : function() { draw(ff); }, + "G" : function() { draw(gg); }, + "Dm" : function() { draw(dm); }, + "B7" : function () { draw(b7); }, + "Cadd9" : function () { draw(cadd9); }, + "Dadd11" : function () { draw(dadd11); }, + "Csus2" : function () { draw(csus2); }, + "Gadd9" : function () { draw(gadd9); }, + "Aadd9" : function () { draw(aadd9); }, + "F#7add11" : function () { draw(fsharp7add11); }, + "D9" : function () { draw(d9); }, + "G7" : function () { draw(g7); }, + "Bb/D" : function () { draw(bflatd); }, + "E7#9" : function () { draw(e7sharp9); }, + "A11" : function () { draw(a11); }, + "A9" : function () { draw(a9); }, + "About" : function() { + E.showMessage( + "Created By:\nNovaDawn999", { + title:"About" + } + ); + } +}; + + + +function drawBase() { + for (let i = 0; i < 6; i++) { + g.drawLine(xOffset + i * stringInterval, yOffset, xOffset + i * stringInterval, yOffset + stringLength); + g.fillRect(xOffset- 1, yOffset + i * fretHeight - 1, xOffset + stringInterval * 5 + 1, yOffset + i * fretHeight + 1); + } +} + +function drawChord(chord) { + g.drawString(chord[0], g.getWidth() * 0.5 - (chord[0].length * 5), 16); + for (let i = 0; i < chord.length - 1; i++) { + if (i === 0 || chord[i][0] === "x") { + continue; + } + if (chord[i][0] === "0") { + g.drawString(chord[i][1], xOffset + (i - 1) * stringInterval - 5, yOffset + fretHeight * chord[i][0] + 2, true); + g.drawCircle(xOffset + (i - 1) * stringInterval -1, yOffset + fretHeight * chord[i][0], 10); + } + else { + g.drawString(chord[i][1], xOffset + (i - 1) * stringInterval -5, yOffset -fingerOffset + fretHeight * chord[i][0] + 2, true); + g.drawCircle(xOffset + (i - 1) * stringInterval -1, yOffset -fingerOffset + fretHeight * chord[i][0], 10); + } + } + if (chord[7] !== "0") { + g.drawString(chord[7], 9, 50); + } +} + +function buttonPress() { + setWatch(() => { + buttonPress(); + }, BTN); + E.showMenu(menu); +} + +function draw(chord) { + g.clear(); + drawBase(); + drawChord(chord); +} + + + +function main() { + E.showMenu(menu); + setWatch(() => { + buttonPress(); + }, BTN); +} + +main(); \ No newline at end of file diff --git a/apps/guitar/app.png b/apps/guitar/app.png new file mode 100644 index 000000000..6ff5d79ca Binary files /dev/null and b/apps/guitar/app.png differ diff --git a/apps/guitar/metadata.json b/apps/guitar/metadata.json new file mode 100644 index 000000000..6ab3ffc51 --- /dev/null +++ b/apps/guitar/metadata.json @@ -0,0 +1,14 @@ +{ "id": "guitar", + "name": "Guitar Chords", + "shortName":"Guitar", + "version":"0.02", + "description": "Wrist mounted guitar chords", + "icon": "app.png", + "tags": "guitar, chords", + "supports" : ["BANGLEJS2"], + "readme": "README.md", + "storage": [ + {"name":"guitar.app.js","url":"app.js"}, + {"name":"guitar.img","url":"app-icon.js","evaluate":true} + ] +} diff --git a/apps/kanawatch/ChangeLog b/apps/kanawatch/ChangeLog index b2d2bab86..a50406917 100644 --- a/apps/kanawatch/ChangeLog +++ b/apps/kanawatch/ChangeLog @@ -5,3 +5,7 @@ 0.05: Tell clock widgets to hide 0.06: Fix exception when showing missing hiragana 'WO' 0.07: Fix regression in bitmap selection on some code paths +0.08: Speedup next/prev and fix autogenerated hiragana bitmaps +0.09: Optimize loading and rendering times, introduce transition animations +0.10: Swipe up/down for Hiragana/Katakana, right/left for next/prev letter +0.11: Sort by 'AIUEO' instead of 'AEIOU', draw Widgets every minute :? diff --git a/apps/kanawatch/README.md b/apps/kanawatch/README.md index e213949dc..d13550f4d 100644 --- a/apps/kanawatch/README.md +++ b/apps/kanawatch/README.md @@ -1,19 +1,25 @@ # kanawatch -A simple watchface design with hiragana and katakana -cards for learning. +A simple watchface design perfect for learning hiragana and katakana. -## Changelog +* Interact with the interface using swipes +* Swipe up/down to switch between hiragana (H) and katakana (K) +* Swipe right/left to display the next or previous letter +* Tap to change accent color (always 24h, not configurable) +* Non-intrustive transition animations +* Low battery consumption -0.01: First release -0.02: Improve battery life, sprite resolution, fix launcher issue and unaligned text bug -0.03: Reduce code size, refresh once a minute and faster refresh -0.04: Show a random kana every minute to improve learning +## TODO + +* Only render what needs to be repainted +* Dont redraw the widgets if not necessary +* Minigame to guess kata/hira phonem ## Author -Written by pancake in 2022, powered by insomnia +Written by pancake in 2022, maintained during 2023 and powered by insomnia ## Screenshots -![hiragana and katakana](screenshot.png) +![katakana](screenshot.png) +![hiragana ](screenshot2.png) diff --git a/apps/kanawatch/app.js b/apps/kanawatch/app.js index 264058230..a81852b1b 100644 --- a/apps/kanawatch/app.js +++ b/apps/kanawatch/app.js @@ -3,182 +3,214 @@ const stripe_pos = 40; const stripe2_pos = 110; const h = g.getHeight(); const w = g.getWidth(); +const decompress = require("heatshrink").decompress; -/// ///////////////////////////////////////// -const katakana = {}; -const hiragana = {}; + +function benchStart() { + return { + now : +Date.now(), + diff: function() { + return (0+Date.now()) - this.now; + } + }; +} +const startupTime = benchStart(); function image(x,y,b) { return { bpp:1, width:x,height:y, - buffer:require('heatshrink').decompress(atob(b)) + buffer: decompress(atob(b)), }; } -katakana['A'] = image(56, 51, "v//AAfwAon//AGF/wGT/gGM/A3F/BDEn/wJQoGCj4RB//gAxUB//AAwcDAwsH/+AAwcP/4tCAwMf/wGEn/8Awl/JYYGBKQkf/I9DAwJgBGwQGDGwRlBAwJsE+42DAwPzGwYGB+J7EQIIvDQIIFEAw5DEAwRDDgCIEAxCPBKIcAR4IhER4hnCLAg9BLAgoBAwgoBcQiCBMwj0BHogGBHogGBfoooEQQREFEIgGBAokAhAGFA="); -katakana['I'] = image(54, 55, "AAkEAws+AokB/wGEg//Awk//gTE//gAwcPCYt/CYkDCYsfCYv//A0F4A0ECYg0BCYggBCYn/KwhBBGgl/EAgtBEAgMBEAZOBEAgMBEAYZB/+ABggTDBgQnDAoIaDJoIaDFgIABDQQFC74aBBgX8v4aBEwWBDQQgB/EHDQQ6BwEfGoX/+AJBDQMDWAKMBDQMPAQIaDiBFCPAgaDU4hrDDQiuDDX4acSAIaCA="); -katakana['U'] = image(52, 55, "AAMP/gGE//ABlH/AAnvAon+Bk5EDv/vIgcHBkHPBgZwBBgn/Bi8B/+PBgcf/AMFw/wBgYEDgED/6qEv4MEKYK3F8AFDj7EED4LREv/4CQn/wASEFginBDAgfEDAIfDn67BC4YABH4QXBCQcHZoQkEEoYMCHAYlBFYZEBLwk/MgpQEAAw"); -katakana['E'] = image(58, 45, "h//AAfwgYGE/0AAwn/wE/AwngDgv4DjhDCv/wJQkf/gGEg//AwkB//AA4gc/Dn4cjbAv/34GF94GF/YGF/wcjwA="); -katakana['O'] = image(57, 54, "AAcf+AGEh/8AwkH/wGEgf/AwkB/+AA4n/4AGEv/gAwk/GIsf/A4P/4AE+F/Awn4n4GE/kfAwn+h4cFg4GFwYGF4IGFKwYFBMQpxFAwJxEAwJxEAwJxEAwJxEK4JxEAwKqEMoQGE/o4En/8HAl//iqEAwKqEv/+VQgNBVQgNBcYgNBcYhLBcYhSCHAQKBAwI4CAwY4CD4IGBHASxBAYI4CAwY4CYwIGBHAQGBD4I4CBIJfCHASmDHAV/PYQ4Cj5QCHAUPLwQ4CgQGCOIgABOIgABHAIGEAAY="); -katakana['KA'] = image(54, 54, "AAMP/AGEv/gAocB/+AAwcH/wTEj4arg//AAf+j4GE/F/AwnhAon/w4aZHAMP/hTEn/wKYn/4BTDgf/KYgQCDQYQCBIQQDBIQQCBIc/DQouCDQQuCEghJBEhITBH4RTBLoRTEBIJTGCAUPNwoTCDQQWBDoIuCj4TCJIX/CYQ/BZQInBH4U//0HwBTBGgPwXAXwh4PBXAXAv4PCZIIgBEYTJBn5SBDQXABAIzBCYJcCDQXwgbOCAwIDBQgI4CgEOJwIADkAGFA"); -katakana['KI'] = image(58, 55, "AAU+Awv/4AGEn/wAwkP/gGEgf/Dkk/CAc//4ABwAGBj4GC8ATBAAf4h4GE/woBAAmAAwvgFAYcIwAcD/BFDFARFD/kBIoYACv5FBAAcfRL94DgkfHgf/95EBD4RgDD4MHLwf8AogAd+CPFGwiJCS4XHJgSGB8CJEkCJJUwYABg5pDD4amTNwKmXYbgcDLoY="); -katakana['KU'] = image(55, 55, "AAMHwAGEh/8Awkf/AGEv/wAwn/4AFDgf/EQkH/whF/4ACAwM/AoQQCBgY5BgIGDHIMHAwY5Bh4GD8AhEIAQFDIAIhBBIJACEIJpEj45CNIV/NgRpBDQIrBEoPgDQJlBEoQaDEoV/RwUP/wPBQ4Uf/gPBQ4QsBKAKSD8BvCSQXDDQYYBNYIaCGYIqBDQU//kPXoYYBj5QCEIPgj60DKoMcWga7FKoYABKogaDbojPBbojMDGob/ECYJBCbgYaDE4IaEPoIaDEAI1EbYQZECYgtBCZQGCLol/KwxxEAwJqEgIMFgIZEgA="); -katakana['KE'] = image(60, 54, "AAMcAwsD/4HFn/wBxl/8AGEg/+BxkP/gOF//ABxcB/+AA4kf/BCGAAZOBv4HEIQIOGAwgOBh4OFGYIOFn4OFEgoOBAwvgh52BKgYDBOwJUDv5nBBwY6BAYM/BwIKBJgJjBBQSbCWoQVBRgK1D/4oDBwJJBWos/WIS1CgIVCJoRGBWowCCj61HYgpRCdIjEGLgTLEIwTLEfAv/GYqtBEghyBGYjoCAwwkDAwQVEYwYjEHQt/CopeBQgQOEIIgOBPgxeFgZ7FA"); -katakana['KO'] = image(49, 46, "v//AAYFF34FE74FE94FE+4FE/IFE/gFE/w0Dgf/AocB/+AAwf/4BHE8AFDn/wAocf/AFDh/8AocHGH4w6YZf7Aon9YYoFEejBhEAAIA="); -katakana['SA'] = image(58, 53, "AAcD/wDBg4DC//AgEB/+AgE/+AKBv/ggEP/gGBj/4DgP/DnU//4A34CQ+DAIcEDAIcDDAQDDDAYDCDAYDD/4cDIgJADAAUfIAQACh4jCAAUHD4QACJwIfBAAQtBEYgGBI4QUDFQkP/4qEVYQvEAAIxCEIK5CBwV/AwsfAwocCAwYcCJogcBNIp3F"); -katakana['SI'] = image(56, 52, "gFwAwt+Awv/8AGF/gFDgP//4GGCocDAwIVDBoX/wAHCn4VFg4GB4AxEAwsfAworBEQYABv4GFj4DCjgrCBQYRFn/4JQfAIgIGD+F/JQcD/gGBMARQCOwcH/wNBCoUP/0PAwIrBj/8OwQGBn4fBGIIGCAQIlB+BcBAQKvDBIQRB8AfBIQUH4AXBP4RXBGgJmERoJsFAwv//yaFbYghBQIYaCeAi9FPQTZGdxKFCFASECFAZPBEIgNCJQaZEAwhDDAwRJDTAYGEQAiQBPIgAGA"); -katakana['SU'] = image(60, 51, "gH/AAYGBh4GD/AOG4AOF/gONDo+ABxAACgY7CAAd/+AGEg4OG//gAwkP/wGEgJCCAAcfKIQzEIQIzEIQozOj4zFEgIzFn4kHGYv/M4okIGYt/IQqXBFghuBHYs/bAY6DCwrJECod/HgYVB8ZLEcoMfLQYECCwYVB+BTBCwT7CCwYrBAYIKCCoQDC8BXBEIQSBNoQVBBYP4EAIoCOQPHCoYTB/xdBIwQ8B+6SET4N/dYn/4aCFFgKRFgC+EgPghivEAoI"); -katakana['SE'] = image(57, 53, "gEH/wGEgf/AwkB/+AA4n/4AGEv/gAwk/+AGEj/4AwkP/g4JjA4EBQQ4D/4DD4E/AwIuBv/vAoP/FwILCAAIuBv4GEBgn//wFEAwITEh//CgfwAwMfCIRGB/4BB/5xBAgJTBIQQGBwP/75CBAwOAD4JCBAwRmDDIKYBOIQGDOIQGDOIQbBAwSqBAwiqBAwiqBDYg4Cv4GCHAUfAwQ4Cg4GCHAUBbwbjnHAgADcYYADUYQxEEYq6CVwbDBdQi6CZQYqBAAZcCAwY1BEYi5DAAQ8CegfgA="); -katakana['SO'] = image(52, 52, "gGAAol8AYUD/Ef4AGCn/3/wFCg/+v/wAwV/8//Bgk//AMD8f/FoQMBj/8Bgfg//gBgcPFoYMBFocP/kHFof/4AtDBgMDFoYMBFoYMBgIIBgADBwAtDj4dBHQQMCFoYqCHQQqCFoc/BIIPCCwQtDKYIpBB4IwDIAQwCh45CBIVAFgSmDFIaaDOIYfCVgYfBRYYfCTASTCUoY1BQgZPCD4l/D4kfH4g4BH4YYBH4gFBGQd//4yDBYIyDn4SEJQIlEBgRXEHAg+BFYZRGZYQADBYgAG"); -katakana['TA'] = image(55, 56, "AAMHwAGEh/8Awkf/AGEv/gAwn/4AFDgf/EQkH/4oF/4ACAwM/AoX+FAQGCHIMBCYY5BEIIAC+AhFIAIhDHIQFDF4IhBJQMHF4JDDNIUfHIRpCv5sCn/wDQJsCDwIaBEIIKBwEf/9gOAQaB/gbBFAIPB+YsC/AaB54RBFAIaBAIOAEoJvBOgPh/+DNAJWB+//DQPBQIZyBM4f4LQSQC8EPKAIpBFAMPPgKKCgEcYIZwBiAGDbohwEZ4bdEFILxFf4ghBXwLjEDQhLBCYoaEE4IaDdIQaDBgLBCDIRQENYYTIewRkEAwJCFHYicBOIkAEAhDBS4IAJ"); -katakana['TI'] = image(57, 54, "AAkGAwsfwAGE//gAocP//wBgn//gEBgIFBAAIeBAof/wAYBAwkHAof+gEDAwf4E4YAB4AGBv4TDAAM/AwoxDKQhABLQwiCAAV/MIglBMIglBHwRwDNARbF//3Awv7Awv9Awv+Awv/MQQAD34GF74GFKAUHOIYABSAJxGaYp4Uv54FP40/P4oGHQwQGKKgt/AwrUEMIQGEVYIGLg4bMFII+Fv5TGNAsPQgsHTIoAG"); -katakana['TU'] = image(54, 53, "AAMBwAGEj4FEgf8AYPwgFgn/4BIP/g+Av/ggEP/n/gP/4EAv/v/wQBFQP/z/4CAMAg/+DAMfEIICBDAN/FgN/8YYBBAIaBw4hDDQIVBAYMAn/wDAIhCCwIhDCwIBBwAIBHAIYBEIQYDBAIuBwAjBFQghCJgQhEAIIhDEYQPBh5HBM4IhDQQQhCwYeBCwMBCoSPB/0CIQQhBAQKWDvytBCYTBDv5tBZYYTCAAQTCAAYTFHAITEj4TF/4TEh4TFv4TEg//JgIMDMYIMEO4ImD/53BAAM/AwIsEEAgFBEAZNBIIgTCFocfJwo6BPgpHEgZAEgEOAogAGA=="); -katakana['TE'] = image(57, 51, "h//AAfwg4GE/kDAwn+gIGE/8AAwuAv4GE4E/Awngj4GFNWJNF/gGF/5UF/+/AwvfAwvvAwv3Awv7GJn8IQV/4BJEv59Fn/wAwkf/DJFEAYABg/+AwjJBAxbQBwAGFH4gGBH4gGIIwgGNG4IGEg//LYjyBAwiyBAxc/EQoGGFIJTLdYJvEgF+fIsYAwo="); -katakana['TO'] = image(42, 54, "//AAgU/+AECh/8AgUD/4U/CgYPDn//wAUC/4VCCgIlDAgIKCCgIKCCgP//wUD//gCgQKCn/zBQQ+BDYP8CgMBEAQBBj4KBKYIKC54yBBQP7KYIKCG4QKB35YBBQIUCGQPjNAUD+BXDnB9Dgy8/CicAA="); -katakana['MA'] = image(57, 50, "/4AE/l/A4s/AwvfAwoAN/YGF/oxGHokf/wGLh4GN/4GSg4GChgGDwARBAw3gAwv4Awo7BAwn/4ACBAwIKB+AGDgJtBAwcAUgOPAwYLB94GDgaFCAwTBDAwcfAwoyBAwgyBAwgyCAwgcBAwgyBNgL0ENgIADn6oHDijhFW4wcB4AGDKwPwBwl/fwzUJDgZOFgAGGngGFhADCA"); -katakana['MI'] = image(52, 53, "gPwAwkf/wFDgf///gAwU/AwIVCBgX//AME//8gEHAoQGCBgYGCv4GDFIMPBggoE4A2CCoIuCAweAAwc/BghYBMwswNw0PNwkBGAIbEG4gMCOoYMCOoQMDAwRnE4BYDKYQTEKYRuCKYY8GgCjDAAV+LAtgcTMDbYhTCHobICBwbBDBghZDZwmAZoYGCAogGBCYgiBEIidCBwQ2DS4QMCVYT2CSAb2DBoLpFn72EdJAA=="); -katakana['MU'] = image(59, 54, "AAMDwAHFv/AAwkf/gVF/4VG8AGEh4VHFgoVPFdZBdRogVBgP4CokBFogVBn/wTIkHEwYrCv4ODCoMP/wVDFIP/JYQVCBwgVBGYLICCoTIDCoQCBBwQhCn5RCCoR/DNoZCDDIRRDCoQODg4+CIQYvGCoZCCCoZRDAQV//4SBRAM//4ABwEfAgQAB/ARBAAkPAwvxAwv+Dgv/8YGF/gkD/xCB543DH4P5AoaBBewsAvgGFhgGFAAQ="); -katakana['ME'] = image(55, 54, "AAcB8AGEgf/AwkP/wGEj/8Awk/+AGEv4iF//AFAuAAwcHFAsPFA34AYNwFAQvBgICCFAUHCAIoDDwQoDn4DBKIf/MYIoCDwIGB/5RBAwWDKIYGB456Dv//75RDAwP/JQQmBAwJ6Dj4GBOYYGCOYcP/5zEg//OYgGNDYw3BAwgvBAwaABAwgaBOARZC/wGDOoP8MQI1D+AGDFwPAAwJaBDAQNCJIc/AQJsBTYL3COQc/4ATBXoYdCSgU8J4SNCmCNCNQqoDAwQuBAwgFDFAITEAwK1DAAKZEAAIMFAA4="); -katakana['MO'] = image(55, 49, "j//AAfAv4GFAon/wIGFgYFE/0HAwn8h4GE/AvF8A4Bv4DCAAQzBAocB/+AAwYxBCYkH/wGEh/8MIv4Awk/+AGEGyJfFAFP9AwpOBNuikeAwxfEHoLpFNoZACAwZABIgIACJYYABIAYGCIAYwCHIoABA="); -katakana['NA'] = image(57, 55, "AAV/8AGEn/wAwkf/AGEh/8AwkH/wGEgf/AwkB/+AA4n/4A4rGoIAE/IGF/wGF/9/Awu/AwvfAwvvAwv3AwpQCOOqqEWLV/H4pGGn5GFAw0fJosfJooGGn4GGKgq6BLQoGEg4GFh4GFPoIpEDYIwFv5MFLQ4GFg6EFgaZFAAw"); -katakana['NI'] = image(56, 43, "h//AAf4A25+/AH4AuWggA5A="); -katakana['NU'] = image(55, 51, "g//AAcAh4GFj4FD/0An4GD/kAv4GD/EADQnwgIGE8EDAwnAAwuAIIgvBAAcPF4IADn4vBAAd/8AGEFAIDBAQIsBFAMDCAIoDh4eBj4oCj4GBFAd/CIJRBgBZCAQIlD/+HQIIGD54oCNwZKDPQZPDOYRdDOYqmBOYi0BOYjCBBogGGYQSAEAwimDGATdDAwQTBH4JFBLIP8AwYTB+AqBAwITB4AGBE4bADBIJyBUIJ6CVgXgJAQzBg+BAoJkCgxcBCYRIEPArlEH4YGDO4ibBeQs+AokAsAGF"); -katakana['NE'] = image(61, 55, "AAX/4AGEg/+Bws/+AGEgP/wAHEh/8Cwt/8AGEgf/Bwsf/AMEAAYnBj4GDHwQOEDAMHA4hVBn4WFJIIADHwMPA4hgCAwZkFCQKCGBwpHBPQwOFFAJyGBwt/BwozBBwpwDGYiYEEgP+iAkF4IPDCoP8j7WCUAXhbwYVB/4RBU4n4QISfD54vBS4f+FASPD+AEB+AFB/IjBFIPnA4LzCGAfAeYIjBGAP4eYQCBwZuBeYUH/EfIwJRCAoIDBg6ACnCmDR4oqBDIKfEHgKuFS4g5CBwo8CWwqOCAAQ8DcYg8Vn48FAAo="); -katakana['NO'] = image(47, 52, "AAcHAokP/gFDj/4Aod/+AFD//gAgUB//AAoUD/4oE/woJn4oLEQYoBwAoIh4oEj4oFJZ8HERU/EQhFEDgIiDH4JFDh4iEH4t/NAYcFHII/Dj4cEv4/DCwIcDCwIcDCwI5DCwhEBHIYQBKwf/GYYhBCwc/FoYKBFoYEBFoQKCE4RrBE4YFCHwQyBHAYnBJ4YFBcBN/AgcAPgYABA="); -katakana['HA'] = image(62, 52, "AAP/wEH/gGCgf/gE/+AHCh4MB//AA4QMBCIQeD4ARCDwv4Dwt/8AeEgI4BDwkH/weFj4eEAgIeF8AeEAgQeEAgQeEAgQeEAgQeGMggeCMggeCQYiACQYYbCDwgbCIogbCIoZZDIoYTCMggTCEwn/CYJFDBYZFDBYYmDv4LBEwYDDg4aCh5JCDQYiDaIQWBNAQ5CMAYLDcgYmCCwgqCGIYTBFwL7EJIIWEAgPgh4WDNAPACwgMBCwiHB/wWEFwV/CwZVB/YWEDgPHXgYuBDwLbDKQPwh60CGwWAngGDgAFBkAHEsAFEAAQA=="); -katakana['HI'] = image(47, 51, "//AAgUB/+AAoUD/4QDg/+AocP/gFDj/4Aoc/+AFDv/gFw8BwIuDj+DFwf/FwcP/4uD///FwQKB/wuBJwIFBFwM/AoP8//PAgP/+IDCAAJdBAAXwg4FDEoQKCIIIgCLoQFBKYV//5qDB4aMuF1YFDFwIRDUIQAC+YFE8YFE44FEw4FEUgn+Aon8WwhKBXggA="); -katakana['HU'] = image(49, 50, "/4AEv4FE34FE74FE94FE+4FE/YFE/oFE/w0Dg//AocD/+AAoUB//AI4ngAod/+AFDn4FEj/4Aon8AocPAokHHgg2BHhYFDHgJCLJBZCEAopIFAoxIEAoxOEApc/AojSBbwplEAoZxBAocPAojICBQhBCGYIFDBYRZCa4P/NYQuCPoYFBSoZGFZYsPAgYABA="); -katakana['HE'] = image(61, 43, "AAMH8AHF/4HFh//wAOF/wOG/AHEv4eFg//DwoOBDwgOCDwk//YeEgf/x4eEn/8n4eDgP/4AeEj/8DAIeCBwPgLgkfDYIeECYQeDh4LBIwIeC//wDIIeCBYJdCDwV/BwIwBDwIOBCQYeBn4pCDwRIBIAQeCMIJPD/AOB4CED4BhBMwf/MISbD/kHPovwj4ODDwV/UYhYBKQJ2DRoIGDHQINEcARCCWYgGEDwIOFgb+FDwL2EDwQGFIQoeCBw0YA40AA=="); -katakana['HO'] = image(61, 54, "AAV/8AGEgf/Bwsf/AHF//AAwkH/wOFn/wAwkB/+AA4kP/g8Rg//AAngv4HFCYIAE/EfA4vAAwv+Eo3wn4HFwAGFJwZ5UgfAPIJzDn/x/+PEgR/BAoJzDP4N/8JzD//D/6KDFYI8BCwYrCCAItBPQOH/wWDCgIQBCwf/4P/wIWCCQIBDWgYBCZ4KJBE4LPDEYInBh5sBBgKLBNgQ0CJoIWB4ACCBgIiBBwP8EYU/TQLXBHQQECFAI8BCwIqB8DzCDYMPAgQbCMoI3BF4IRB44OBWwQUBv4TBJIV//InBHgQCBw4OBHgUH/EfNgKOCj0A3BsCQwNgeaSdCABA="); -katakana['N'] = image(54, 50, "ggGFngFEgP+AwkPAws/AwkB/4GEh4GFn4Gaj///gNF/AGF4BEJAwITBgOAAwQTBh4GCnwJCCgVwLgRwMHAgTBHAgTGv4TEgYTFMIITEMAsHMBY0B+ClFCYiPFEAITEv//OIQMCTg3gBgggEDIIgDGYIgDMIJVDDAIABIIILCFoYYCJwZ0BHQgsBBgZnBBggnCKgYhBMIi3FgAFFgAA=="); -katakana['WA'] = image(51, 50, "/4Ay4A3E/AFCh4GBAoUBAoPgAwU///8AoUHBgOAD4nwAoUf//+AoUDGRYSBGQYSCGQd/94yDh/9GQZFB34yDn/zGQcPAgYSCG4YSBC4YSNv4SKJYJwDLwISEn5QDS4QSDDAJjDDAJ2DGIJ2DUYQ+DQYKcFFYYXBDASOCGIQFDGIQRCDwTaCG4YFBEgbHHN4hiFg6HEA="); -katakana['WO'] = image(50, 52, "/4AE34FE94FE/YFE/wYYGocB/+AAwd/8AFDn/4AocP/gFDgf/KovADAnwDB43B45EE+IFE/F/KAkfBgmHAonhAonwDAn8h4MEN5X/N4l/N4k/KwkfRwgoBDwcHOohoBOoYFBEgY2BEgYFBEgYFBJIYXBFQYpBFQZ3CAoIWCKoQQCGwQLDHgR8CAoQdCAoQvCOYYFFn5gENgKREbYgAGA"); -katakana['RA'] = image(51, 50, "n//AAcHAongAon8j4GEwYFE+F/Aof+h4ME4IFE/BYr+4FE/wFE//fAon7BgpYE//vAon9CQo3Ev/gAocP/gFDgP/wASX+ASJgYSFXwJ2ECQivBDAoSEWIs//wFDbYIrDAoI+DAoIYDQ4IYCFIIABDALlDGIJhBewS/EJQQYCG4YkED4QFDD4JJF4AFDA"); -katakana['RI'] = image(43, 53, "AAf/7/4AgMf/f/AgMD/9/8AFBv/v/gEBh/9/+AgEB/+/+AKBn/3/wEBg/+//AFX4q3v4qDh/8FQQPBz4PDAYQvBEYQvCEYI/CGYRPBB4cfIYQpBB4cH/5TCDwJjD/4kCn4EBCgN/AgIUBDoP/FIJHBAAIyCDIYjBIYYaBQ4QaBJoZHDAAoA="); -katakana['RU'] = image(61, 53, "AAUH/wHFn/wAgUB/+B/+AA4UP/gBBCgd/8ABBAwUD/4BBBwcf/ABBA4f/4ABBHQg8FHQI8/HksYHgwYBHgkPF4I8EvwlCHwOAg4gBEYI8CCIQjBHgITBCIP+HgU/CwIRBDAIgB4AMCAgMfEAIMBDAIOCBgQYCIwQMCPYJTBAQI8BBwUHEoN/8P/IYN/+AvBj4LBBwOAj/7BwZGB/4ABBwXAAQIODM4QOFHgIOC/4OBh4OCAYJGBv4OCn4OBHgJKBAYJkBIQISBaIYhCCwIOBSoTqBJQISBeYUHd4U+bYUwcAYAKA"); -katakana['RE'] = image(51, 51, "//AAocf/AFDgf/CQl/8AFDh/8AocB/+AAwc/+AFDg/+GX4ECgwyEgPgGQk+GQkP+IyDC4IyE//3GQc//gyDh//GQYYB8YyD//4GQc//wyDDAOBGQUH//gGQRvB/BlD/4DBGQU/CwIyCj4YBMoQkBBIIyBBAIYBGQIkBDAIDBGgIiD+AFBGoIyBv4eCGQIABJwQvBAAJnDEgTLCEgY8CIYLLDEgZVCAoZuBb4iaBfAj+EgE4AokAA"); -katakana['RO'] = image(50, 47, "/4AEn4FE94FE/YFE/wYF34YS4A1BgIYB+A8Cv/v/gFCj4YBAoUHDH4Y/DEbglDBQ8CAAYA=="); -katakana['YU'] = image(59, 46, "gP/AAX+A4M/A4fggEHAwf8BwIGD/4GBj4VFgYVGv4HDwEAh4GD+A+Eg46CAAf/4AGEj/4Coo6CCqJFBCot/KAIADh5QCQAhQBCrM/Myk/M3JQGh5QFMyIRBAH6NB"); -katakana['YO'] = image(50, 49, "v//AAefAonnAon5Aon+DDA1DgP/wA8E8AFDj/4AocHDFZjfDCJjxDD5WE/+/AonvAon7PgoYX/g3DAAQ"); -hiragana['A'] = image(52, 50, "gEB/wGEn/AAocD/gMcg//AAfgv4FD/wMYFIRNa54HDgYyCBgYsEBgX/+AGBHQYpBCQQaCh4JBJQPwgIdBBAP/wASB4H/j/8MIP8j5fBBIP/4P8gf+j/7/hVBj/jA4PH/C/Bn4RBv8Aj/3/Ef55FB/9/wI+D+/wj40BHwIWBL4QJB+BFBwAmB/4MBD4M/94MBD4JAB/4cBNYN/BgM//AsB/n/z4bBQgOHX4QVB/B3B/CQCAQTSC8BFCB4Q4CB4UAgIIBRQOAXojREn/gaIgAC"); -hiragana['I'] = image(58, 50, "v/gAgUggEf/AGCnkAg/+AwU/gEB/+AAwQZBDgcP/gcECQIcFCQIJCCol/4AGBgYLBj/wCokHCAIABFAIQCCon/DgQECn4cDCoItCAAI+BDggVCLoZeB+BgCCocPPQZUBwZdDJAQcEGAIcEGAIcEGQPDDghIBDggyBDggyBx4cBjxIC8aaCCAIyBLAMDM4IyBSARnC//HUIk/+IyBCASdBLAJKCGQOf/kDJQV/GQRKCJ4XgEYRPC/CoCDgOHNwl/8P/84jCDgM//5HCDgMHAwIjBgP8DwIsBQgYVBSQgVBaYZnCTIgtBbQhDCUAYkCfwYOCGIgAHA"); -hiragana['U'] = image(46, 50, "h//Aoc////8AFBAgIABgEDAofACwIAB/wWD//4CwgdBCIeAFQUfCwIADCwIAMj//+AEBv4tDAgQLBHAYFBAgf/8YFE54FECwRTB/wkCAoP7IAd/OgR2CKwcBQ4kH/hMEJYQcC4AWIh4WEn4tJg6EEj6EEVgIQDE4l/CAbABCAZqBBQgQDBQIQCXwIyCYYTIFeIhlCBQjxCLIQWBMgbdFvzYJ"); -hiragana['E'] = image(55, 50, "gF//4GE/4AB+AFBgIGC/+AgEDAwYNBg4FC/wGBh4GC/gGF/ArFFIQAD4BRVn42FLAIGEJQYGBLAhEBLAhEBLAf/8ArDBIIyEj5fCRYZYEEgJYEN4JNFDQouFDQKcBFwYGFMIIGDLQRJFAwgaBOYQuC8Y2DFwODAwcP/0HXAc//EPcQnAj5LCPAU/MwR4Cv5ECPAQ9CLoUBd4auE/guBVwf5PARaC+5qCAwXnJwSXB//HI4QGCw5ACAwUHNIn+gj/HAAg"); -hiragana['O'] = image(54, 50, "gEB/0AggGCg/4gE8AwUf8EA/gGCv+AB4QaDv/wDQn/CwIaCgP/4AaDgf/wAaCgPn/4PBAAXv/0HAwef/kfAoX+n/4v4GCAgPxCYfg/4jBAAWBGwQ1BgEDJoJQCJoJRBLYcPCAJrCgEcKAaGEHgSGDF4QPCJYYxCHoYMBn5YDBgoGBDIP8FQKiBDwabBFoIzCv/gEAJQCMwWfKAIbBh58BDQMH/l/4IaCh/xTgIaCn/P/BrD/8/4CGD/i3BDQfz/gaDv/P+AaCCAIaEHQQaDv/hGoV4h//g4VB8JnBa4ePZYRkBBwKNCbwPwCYR/C44CB4BtBfgSaD8ACBYQQWBAAYA=="); -hiragana['KA'] = image(55, 49, "gEH/AGEh/wAwkf8AGEn/AAwl/wEAhgGC/4CBngCBgP+AQP8AwMDAYIyDAYUPAwQ2CAwY2Cj/4gP/AAP4j/wgYGC/gGBg4GC/0/8EPAwsfCgd/4E/Awt/FIf/LgJmBE4IGCMwMf8JjBHwIPB4IDBgZmBv+DAYMHMwP/BQRfBOwIKCL4J2BOIQvBAgJxCGQIEBHAKPCCwIYDCwQBBQoRGBviIDIQJRC4AdCXAYdCKIcHboQ/CboY4BboghBboZKCFAYhBjAoDh/8nzME+CfBF4V/RgP/EgKVBwYGBFAMH/zIBFAQeBAwIoDboRRD4DrBJQUHAQJsDAAwA="); -hiragana['KI'] = image(48, 50, "AAMB+AFDh4FL/AFDg4FIn//AAX4ArpHC/xNEAov/LQgFCDgYAlF4UfPx8/g/8CoQbBKgQhCAoMDFAkHAoeAh4FEDgQAB4E/FgIUBwE/HwQdBn/gAoM+AoPAAoMMAohFCAqIpCgI7C4BEBI4oICAoZfE4C9BAob2EAoISCaQgACA="); -hiragana['KU'] = image(33, 45, "AAsB4ADC+ADC/wDBgf/wADMg//CYIDDh4DDD4UfAY/8AY34AZRDCh4DCg4DCgYbCgI/CgH/BgU/BgREBBgIQB8AMCFIRNDLoJ2Cv42DJwQdDFQIdDFQQdDFQIdDHYRkDgYhCgADDnwDChyzE"); -hiragana['KE'] = image(50, 49, "AAUB/0Ag/gAwN/wAICgEfBIIIBB4P4BAYPCh/wDAcD/gYE/4FBDAU/4AYEGIgOCDAQOBh//AAP+v+DAoX/7/AAof3+E/AoX9/gYD/9/gYFD/4YE/5QCGIJQDHYRvCJQU/N4JKCKAYYCKAQYWmAYEjwYEx6lDh/zUocDMgIYDv6cBKgUf/4yBBAMH/4eC4EBNQUfAQN/DYMPE4TjCAQQkCYgSJBDYLEBn7QCAQIbCE4UDDYP/PIV/CgLpD4EPP4UH+AkBAoIACCgIADh6LCAAMDAoYA=="); -hiragana['KO'] = image(52, 50, "h//AAX+gAFD//gBgn/BgvwBiWAAon4GwUBDIQACCQQFCn//4AFCg4lBCQc/DwYfBKQJdEDwYAB8CIihAFEgJJDIgQFEg5KEMgITEj/8D4hwED4JqEOIIfEv5eEg4fEFg0PHIwsEBigmFCYkOv65CJYPnbgn+ZgIAD8IMFewvgCYjRBE4IMDegQABIoUfAoK7HA=="); -hiragana['SA'] = image(51, 50, "AAMB/gFE/+AAwcf+AFDgf+DIl/4AFDg4fEgAfLgIfCj//AFQzCn/gLJYMELI5mEh6GGBgUHGAP4CAQ3COYILCBgUDIgYZBAoYmBn5REDwPgQQPgDAIVBj4fBJ4d+CQI1CgeAXhgSDKoYSEQQp1GQQpFBawXwD4IGBg42BaQngBgRlDBgmABgjzBRYZDCPIYvCv//MQoACA=="); -hiragana['SI'] = image(45, 50, "v/AAgUD/wKDj/wAof/wAECg/8BQc/8AbD/4bE/AbEFgcHFgk/FgcBFgkPDYhIgFgIKDFh8eFgn+FgcH/4sDv+/FgUD/osDn/vFgQ2BFgcf+YsD/+fFgUP/gsDv/HFgSKBLId/8IsCHgIXBSod/EIIKBwIhCv/4h4WBAQOAv/+IIP8AQIAC4AYBAAIkBn4KDJQIKDCwYpBCwRWCAoJhDAoK1DAAg="); -hiragana['SU'] = image(52, 50, "AAUf8AFDgP+BjH/AYP/AAnvAon+BjJAUgf9BgZFB/4MDn4kEg4MFGIwMED4QME+E/+AyC/x0DFgPABwIMC/gMGDIn8gYMFv/4EwcP/+AKYf/BgRACBgYRB/4mCgF/AwJ6DBgoTCRohNDTZE/VAkP/gFDE4PAUQhGCI4YeEUIgYBD4gMBEpI4GgIFEAAo"); -hiragana['SE'] = image(56, 50, "AAcP/ADB//AAwP8AwkHA34FBAAn+A1JalmAGFvinFv4GF//PXghEBAwfBAwoNGEQP/+AGDn4GFh//8AGDg5PCgF/AYP/wAGEgj/CAwQADAw4mCAwZCCAAQ8BFQgGBAAQGBj4GFJQIGEJQIGEgYGFGIIGCIQQVDHQgACA"); -hiragana['SO'] = image(53, 50, "gP/AAXggEPAweAgF/AoX+gEDBgfwgEfCYoFD/EAg4MFAAQMCAAQwBBhQpBJQozBAAU/IAIACIYJUBAAV//gsJD4IsEn4sEOAn+NIn/+4FEAA39AwvvAwqQDAAP7UYhmCx5bDuBVB4BCDg5bEJ4JoEgJ1EEQKCESwIFEg5vEEA4TFh4TFv4TGYgiLBCYrFG/5dDd4YHCOQKkBDQjbDDQQwDWgR5DAwSGEEAgAEA=="); -hiragana['TA'] = image(52, 50, "gEP+AGE/4Mjgf/AAXAgE/AoX8BjUAgP+GYkf8AFDBhHnEIQMBEQQhBn/jFAWAgYMD/AMH/gMF4f/F4UH/kQGYd/KIIACg4VBBgmAQ4gMFUJcB/8DDQZgBv6iD/wuEn/gKIJGDEIl/4KCDC4KPE/+BBgYXBBgY5BAIImCj4MBTIKFB/wMBAAKSB8EPAwXnUYIMDCwLYD95RBEAIZCFQN/AwPBKISpBwEGQAgAGA=="); -hiragana['TI'] = image(51, 49, "gED/wGEv/AAocP/AFDgP/CQk/8AFDg/8Bgn/wAFDj/wBQYAqJ4M/LBZrMJYZ+Ch5aDv/f/4bCBQIABCoMDHAYTBv4+Ej4MEg4DB4IMCAoIcCwE/TwU/+ASBEQI8BVQJLCv/gS4cP/kBMgYWBjyoEgLbJEYYSCQQkHCQg2EHASCEv4SBgYOBOQ70BQoYrBEQIABFYR/DJASRED4YFCBgJDDA="); -hiragana['TU'] = image(59, 45, "AAUP/4FFAAIGCAoX//EAg4GD//ACYYAB/kBAwgOBn4OFDgoOBAYX+BYP8j4GBwEAAgPDGwQ+C/F/BgIABCwOMLQl/+AGEg/+NIv/8BwF/gGEKwIqDAAM/HAYzDEhkfEgsDEgxJGh5JFHQPACqQrBCpkfCopXBCogcBCog5BK4jSCAwxtDDYK8EZIQcCAoQcDCYTjCJgQGCEYT0DIAYGGEgQGDEgRcEv5UEA="); -hiragana['TE'] = image(57, 50, "/4AFv4GF34GF74GF94GF+4GF/YGF/oGF/w7Cn//4BCDAwOAAwpQEj4ZDAxP8AyUPAwwiFg4GMgZFFAw0BLQqlBNAkAv4GG8AGEn/wKgv4KhZGGHALeGH4oxNh4xFOJBjGEYt/VQwVFg//BwhOBAAI7Dv4GBHYYcBCwgcB/5CEDgQyFGYgrCUwkPKAwAC"); -hiragana['TO'] = image(46, 49, "gEH/AFDj/wAod/4AECgP/Cwn8C0cICwcDBoIWC/4NBCwMfEgV/4f/BoIWBv//LAMH/4AB8AWBAoWAgE/BQYlBDYUAh4FBHwQPEEIJQDFYJhCgYwCLQQqCDYQKDDYIKDn5xEEAYQB/x8JDYkDCAkPYIk/JoQWTAol/AocZQwR6B8aNCAAOPAgf+TIZqBAongT4QfCBYY9BW4R1BA="); -hiragana['NA'] = image(55, 50, "AAd/wEAn4CBgH/BIXAgEB/wJEgf8AQIJCg/4AQIJBgEP+ACBBIMAj/gAQYsBEoIoCGwf/GwkB/8P/4AC4f+j4GDw/4n4GDj/wv4FC/0/8AMD/l/4IGD/H/wYGD+P/g4vELARtCMQRtDMQQKDL4YKCMQQKDMQQKDR4QKCTIYKCFYQ2bOoI2C4BgCGwWASAQ2BGQKJC8DNBBAIAB+DNBPYf4ZoKrDAgPwT4K7BAwRdBB4K3BVYIqCVYY6BAwKrB/0DVY3+v/hAwf8n4SBdIXwnxEBAwXgnBEBAwShBO4IbBSYSVCOYQAHA"); -hiragana['NI'] = image(57, 50, "AAMPwAGE//gAocf//wgFwgEH////kH/AZBAwP+gf+Bof/wP/gEDAwWAAIMBAwc/FgIGDj4sBv4GBE4P8HAIdBE4IqBAwYgBKAIGCKAYKBAwN/EYIGDn4jBAwZfBDAQfBLIPAAwZZBDgItENYN/CAIfBIAIGCLIRfDLIXwAwc/RQJmCHAPv/0PEoI4B+f/AwcH/P/w50D/l/wZ0CgP+j/BK4Q4Bg/gJoQ4BwIGBIwU/4EwAQI4CIYICCAYY/EJQMHHATcCbAQKEHARGBGgQqBCIc/D4IGDaITCDT4PAAQJfCQQRYDeQQGDSIIGEYYIGEE4IGEDgYFCcAQ+CGQZsCABAA="); -hiragana['NU'] = image(58, 50, "gEP/AGEgf//wHE/4ABAwc/AwIPDh4OC8AGBg4GCEwUBAwX8Dod/EgoHC4AsF+BJFjAGDg4iEFgRfF/+AAwk/IwQjDFIgjDvAjDMYJlCgRHB4ABBFIUf/ABBFIXH/0HCoUf+BcBLwQpBCogpBCYIVDv+ACohNBn/wCoRxBCohNCMoIVBOIQVBAIJNCCAIVCEYIQBCoOAb4QtDCAQtC/gjCdIIXCN4QwBC4SVBDQIXBEYUP/gXBI4QEBHwPD/8ODgR/CwZNCCYN/8P/5/4GQOf+DtBKgXv/jtBKgX5/0PAwJxB/0/DAL8CvkDJYP/IYMMgFgg//fot/VYQACgYGFAAoA=="); -hiragana['NE'] = image(67, 45, "AAXwA43/4AHFn/8A4sPCA0B//+CAt///gA4kfCA0H/4QGA4IyFn4IBGQg5BIYsD//nCAt//F/CAkf/wzBCAYFBwH//BaE8ArBwBzFCAgNBLoQQCHIPADYIQD/6dBCAk/OQIQEHIQQEHIQkCCARaBO4YUCSYQQDHIQQFHIQQERQgQCLQQQEHIKBDCAPAn5fDCAP8gbNECAaJDCAbVECAPgvj+Gg72GdoqYFCAgHFKIoQDDA0AKIjODDA0ARYQAEhwHGAAIA=="); -hiragana['NO'] = image(54, 50, "h4GFn+AAocB/0IAwcH/F//4AB+Ef8IFC//A/+PAwcD/0fAoX8h/wDQk/4ITDAgMDAwcH/hGC/EAj/wIwXggF/4AGB/+AJIIFBGQJJCDQoWBDQf/wZlBDQIWBh41Dx5kE/0/Mgn4IgIGD8f8MgYaBL4IaEPQJrD/6RCGoRkCKAR/BKAgaBKAoaFNYoWCKIIaC8BKCDQWAIYQaCgJCCDQRyDDQRXDEoOBK4ahBW4K+CAgKcBDgLcBMwIwC/1/4JHBCYP5CoQwC4aND/atBRofDAgPgdQaSBHgX4hxXBHQXAhAOBAwKXCAAJlBbIIAH"); -hiragana['HA'] = image(50, 50, "AAMH/gFDgP/Bgl/4AFDj/wDBsH/4AD/oFE/9/AwoARJVXhAon4JQn+j4MEw4YLn4YEJTIfCAooYCAoX4DgQwCwBdEBgMDHoYMB//3Bgd/8AUC4A7BJQP//kHBwQGB4JYBFoX8KgMP/gGBz/+h//AIPjGAXA//wAoXwh/4DgX4gP8IgQnCF4QFBgOAEIKIEv6SCAA4A=="); -hiragana['HI'] = image(59, 50, "gP/AAOAA4U/AwPwAwUHAwP+CwYVC4AGCj4GB/AGCgYOCCod/AwPgGokH/g8GHQY8CHQYVCHQg8CwEfCAYEBgYQDAgV/JYYEBh5LDj/4GoJKEGoJLCAwP4JYZ9C/BLCNwSGDQgSGDOoaGDAwg6BEYQHDh//EomDAIP+ToaQBEIIvCKoJyCJgPH/yDCEIIVB4BNBMwIgB+CZCn/n4f+h5jBAQMw/+BOgKyCCoN/PIICBS4I0BCoQJBJQJqCBIP5NQfgD4KACn5tDGQSDEwADBTIJaBGQKZEDISvCToR8BeAQDBAQLbCb4RSCAAcHcQYACvwGFg45BAAj/DAAw="); -hiragana['HU'] = image(55, 50, "gED/gGEg/4AwkP+EAhwGCj/ggF+AwU/4EB/wGCv+Ag4GD/4kBAwM//4AB84GBv4GC54GBAoX/x/+gIGDh/+gYFC/0P/kHAwX8AwMPAwX4j5cCGwJOBAwJIDj5jBv4QCAwIpBNoU/+AiBNoIGCJYJtBAwPhFwPANQXjAwOAgEEv+P/A2C/H+CoI2BTIIhBwY2Bh/xwH+UgUf+CwBUgSgBBYKkCn/gh/gToI1B4Ef4AvCBIM/4ZmCIAN/44oBSgKdCFAJ3CLAY0BUgQoBGgIGBEIUPAwSID+AGBQIZHBJQRECd4Q9DI4QvBJwQ2Cj4sBGATRBJwLcDFgTcDC4QGEEILqEAwIbDIARoCBgQAGA="); -hiragana['HE'] = image(55, 50, "AAUf+AGEn/gAwl/4AECBQP/wAYC4EB/4YDwED/wYDwEH/gGCCIMP/AFBgIRBGwcDCIN/GwUH/EP/4bCDAP/AAI2C+4GCHwMfAoX/JgM/AwYjBv4GI8YGCFoN/wIGBgYCBFwIiBHYJfBNAPAn/8IwIGBwAaBh/wAwOD//4R4IfBg//+B2BDoJKB+AoBg/+JQPjOwMP/n/z/nQIMf/IOB76BBn/3/gVBMgN/94nBOQX/7/gAwKbBOwSOCHoJMCEIMH/v/CAJxBh/7/hcCF4X4KYLEC5/wj5KBEIOfGwJRCL4PzF4V/JIQvBCYJJCH4JxB4AGB/xCCFQIJDDoIMBBIRNBAQJdCIwKUCeAb5CPgQACSgIFDSgIFEAAg="); -hiragana['HO'] = image(51, 50, "AAN+AokP+AFDgf+Bgl/4ASE/ASVv//AAX8h4FD/+BAonwn4FD/0HBgnAAogoBgP/HAk/8AFDg5LEgASM/gSFwADBFQIAC8E4Iof+/5FE5/wAof5/0fAwc/8YFD8f8PAYEB54MDJ4SRDJ4KRDj/gNYaoCLAYWBLAYWCLAQWCDYJvDgYSCCwV/NYQWBGQc/+AyDg4yBj4MBgYSBAQP4OwPwbIglBQAgpBBgZiBBgYYBBgY1CU4S0DFoIRCAAo="); -hiragana['MA'] = image(55, 49, "gEP+AGEj/gAwk/4EAkAGCv+AgAPD/8AgYdCgP+EgkD/gdB/AGBg4DBv4GCj/w/wGCv////8AwQFB//4AwMBAwXwEQMDAwXgAwMHAwXAAwMPAwWAG4QvBLgQGBL4X/AwRfBKgIGCL4X8n/gLARUBn5YDMwM8NQaLBQYIoCAQSIDAQRZBRYaBDRYQhBFAIJCKIYyCDwKoBToZkBOAIJBPYKLCGwMH/h2CAwMfKoKKCI4PgSIYYB4afDJQMP/gpB+AhBMgIjB/AhC4EfAwIhCEoIGCwJdBaIIZBMgSkCjhMBgakBG4LICUgKDBAwQuBPgRKCjgGE4EQAwgEBAAIbBRAQACQgIDB"); -hiragana['MI'] = image(50, 50, "h+AAocD/gFDgP/CQl/4AFDn/gv//AAOP/E/AoXj/0HAoX4/+BAoX+DAuf+EfAoXn/gYD/P/gYEBG48f+AFDg5QMMYkf8BvE/BvE/wYE/4YEKAIYYgZSCDAMBJgQYCCgYDBFoYDBj4tCDAJlDDAMBGYYYBNYYYBn4xCg/4h6ECPgIHBPgfBDwaVBQgYvBToYYCFYauBaIIwB5/wcAfz/0PAoX8cAn/IgQFC55dBAoXxFILtC/grBGgL5BYIoAGA=="); -hiragana['MU'] = image(58, 50, "AAV/4AGEj/wAwkH/gGEgP/Aod+Dgv/wAcEj/gDgkH/AcEgP+Dgt/Dg3wn4mBHwYGBDAIyCAwP/8AGBAoQODh4GC/4sBgYGD/AcCAAO/IQQcC4IkCDgI7Bj5YBg//w/8EAIjCwIEBv/gMQPgLAMPFYP//h1BgZpC/4LCNwIxB4YoBFoIxB/AjBNIMH/v+n5UB/4qBn/fIoIJBv+PLYUPQwPhOIUD/gvBGYMH/3/BAX/457CBAP/84GBDgIlB/YGBCYJwB/qECDgKREwBCC34YBDgfvLYP+HIM/+YYCIwM/MoIYB/hGBMoQEBz4nBKQfDAwODGQXwKQQMB/P4j4GBAQP+ngtBUgIRBg6aBRwKiBwOAf4TNBAobjCAogAEA"); -hiragana['ME'] = image(57, 50, "gEP+AGEg/4AwkD/gGEgP+Dgv/Awt/wAGEn/Agf/BIUf8EP/40CHAMf/4tBAYP4AQImBCIP8n4GB4EH//+AwXgEwP/v4CB/EBAYIPBg4jBAwX8BYJFBCQRKDFYIGBJQJxBIgUfAQIrBAYMPCAIfBBQR8CAwR8DMAZ8Cv4GCGIQGDGIU/AwR8BAwKqCWoU/FoS1Cj4tCHASEBWogGBUAQKBAwItBHARpB8BlBBQKuCAQIKBO4SqCBQX8AwX4h/9/wGC/kP/n/DYSlCv+P/ArB4K+B4/4SIV+j/jWIX8n0P+JSBDoMOMwJWBAwOCMwM//ZOCMwI4C75nB/5bC45nBv+DAwPhTgXAb4PAoCfCQQifBYoYAHA"); -hiragana['MO'] = image(60, 50, "AAX//4GEv4HFj4GB/wGCg4GB//4AwMBAwX/4AcEDwcPAwYWBgYGDCwQVC54tCCoX8F4PgFYP4CYI+BgE//0P/gaB/ARB4F/4ApBwAVBg4OBj/8EgITB4AiB4InBBwQgBCAIOCPQPjD4MPJ4MH/0/+ALBwARB84kBBwQ0Bv/gBwc/+5bBj5tEHAR8Bn5lBBwInBBxY2CBwcDWIQOEGwIODJwIOFIoRKC4CNCBQP3AgKwCDIIOBKIQKB8/8IQJgBj4OB8E/MAfD/ytBEgX8J4KeBZwWDIgJCBCoP4ZgIzCAYIqBeYRQB8DnCK4gGBGoIDBwAyBF4IKCCQWBAwIVBEoPgF4RFBg/4F4Q2BAAQOBTwIADHoQADbIQAIA"); -hiragana['YA'] = image(54, 50, "gEf+AGEv/AAocB/4MEg/8DUv///Aj//wEDAwIcBAwMP//8BgIGBn//+IFBAwICB54GCDQQAC/0HAgXAn45BD4IDBn45Bv4MBAYPgGYJKCFAIbB8EAgf+DQRbEv/4LYYaBOQU/4EPCwIhCCYJrCgf8CYkP+BlBCYQaBv6GDOwQaECYIaEKwIaD4JWDgP+CYIaCg/4NQYTB8Z+BFwef+4aCMgN/74aCn/z/zXCIAOH/IaCh5CB44aBJoU+a4QyBwFwDQLGBCAOBX4adBGIJMBRIQaBUYI4CDQJnDFYJ7EDQKzCDQYECAA4"); -hiragana['YU'] = image(52, 49, "AAMf+AFDgP+Bgk/8AFDgYMM/gkD/4AC+EBAof/BkA5FhEAg45Cg/AgF/AQMBBIMP/4DB//gE4Xwn5dBn4GB74IBgY0Fv4FD8AfBAoYfB/gbBIAIiBg///A7B/+A/4rBCQIxBBAISB/ghBCQeBEoIMBCQI0BBgQSCDIYSB54MBgIlB+AMCj0H/0PBgIABHQQMBOgP4BgZBBBwTDCMYIMDKIIMRWQQmDAwUMYYqyBAoaxBN4IMEV4QMCcggMBWwbZCAweA"); -hiragana['YO'] = image(55, 50, "AAMHAwsP+AGEn/gAwl/4AFDgP/BgkD/whF/AGEj4oFEIsA/+AEIgoFg/8EIooFJQ3/JRcHJSgoGJQxEEg//FIkfAws/Cgv/AwUGJQX/HwMP8AoB74GBj/gh/+IoU/4BzBBQJBCJQIKBNQRzBv+AWoIIDJAP4SoMBIgIkBOYMDHoKTBAIIRBXgQBBB4IfBEIQYBFALgCCwMP/iVCJAXwJ4QfDcAX/4JRBSoRvBEIZ2DcAQGCFQIhBPoIYBcAQGBDAJqBCgQ6Bg7rIAAY="); -hiragana['RA'] = image(48, 50, "gEP4AFDj//wAFE/gFE/4TCn4FBBgQFCBgQRC//gBgN/BYUP/EBAog3BGIIFCgH/BAIFCh4FEgQFEBoXwAqsfAoIuBAoROBEwIFBIwP+AoPnLIWALwZfBNQf/+AFE/AFBEIM/AoR6Bh/8OoIzBg4FBRgQFCL4UD/wlBAoikCAoM/W4QFBj5dCAoMGAohpDg4FEHYJ1EAog5DDgJWCb4Y/Cg7RDaARFCAoZFBAobiEeoruCAoQtCAoI+DAAgA="); -hiragana['RI'] = image(40, 49, "ngEDn/AAg9/4Ef/AEBwF//4EBwP//4HBw4EB4F/x4EB8F/z4EB+H/n4EDAQIjBCwUPAgUAAgX+gEH/n//gEDHIMDAg3wAgP+AgvgAhBeBAhmAAiJ3BAhf8AgRUBAhBXBAAJtBAgSgCVgRcBAAJXCEwIEDj5SCBoJDCBAKSBBASSBXwKICAgQmCAgIcCv4SCAgI0DeAY="); -hiragana['RU'] = image(51, 50, "gf/AAXAgF/AoX8gEPBgeAgIFD/EAn4MEg4FD8EACQoACn4lBAAUf/4FDDYOAAoQuBHwIACv/wDwgkEh/+DwoFDDw5ECDwRLDMwg5BLIZMBNgh/FGgIeB+AVB4AeBEYJmBBAJQBDgPBOocf/AoCVIU/Kwc/+5WDg/+Kwl/5/wh4mBh/4/A2CFgMOAoJDC8GBMgUHGAJQCCQKpCBgISBgf+SQMPCQN/4H/4YSBGIIwBCgMBDoTMCn/AEIROCLoKFEAIJvBTwZvCTAarFNIQFCXASyCYoYxBAoYAEA="); -hiragana['RE'] = image(56, 50, "gEf8AGF+AGigP/wAGDg//GYQGBh//C4M/AYICB/AGDv///gGC+P/AwQKB+YGB/wNC+//w4GDBYMDAwn4AwQ3BFQIGF8AGF4AGFgAGEAYMDHwIGBAYIGDn5XBAwhlBAwd/Axh6CAwSPBAwMHAxEDAwqdBAwidDAw5IBOoQGDU4QGDUAIGE//fAwufCgrmCh4iCAwk4nwGE/EcAwbSBjAGFegReCUgIGJOYIUEQIYGCIYOAAwPgAwIAIA="); -hiragana['RO'] = image(50, 50, "AAf4gEB/4AC8EAv4FC/kAj4MDwEHAofwDAgSBDAoACn/+AocfAokP/4FDE4OAApED//AAohJBAAI5BAocAIQIFEHghFCD4QFCBoU/KIQMBNQZ9BOAhOCQYYFE/B8CE4QFBM4JGB4YuDj/7AocD/xIE/+fP4c/84FDh/8QoZyBj5mE4aFDn5yEDAIFDGIIFDIgIXDDwKREv4eEv4eBiAFCDwMH+A8BIQLnEEgLnDSooqBQYQFCDgQ2DAoolCJAgAD"); -hiragana['WA'] = image(51, 50, "AAV/4AFDh/4AocB/4DBj/ggE/AQMD/0Ag/8DgWAgH/AQMP+ASB//AgISBAoIDC4Ef///+ASBh4FB/4SBgYFC+E/4IFC/8H/F///9//g/8f/3/x/+j/nAQPwv/j/H/wf+I4N/KAJlBv+P9/4MoMP/f9/xlBAIIqBwAUBn/vFwIdBg40BNIIOBIIR7B+BbC8B7BKoX4uAyCAwM+GQX5//f8IyCn/z/hHCK4N/4/8h/8/4EB/4lBF4P/z5wB8f+RYJjBPoPAFwO/BQP4IQX/wJkCTAUfVYf4gf4BgS4BbQRiCcgbSCAAILEcALkCAAM/DoYeCC4ZLBfoIeD/ASEDAhoBAoYlBDwcAg/ABggAEA="); -hiragana['WO'] = image(50, 52, "/4AE34FE94FE/YFE/wYYGocB/+AAwd/8AFDn/4AocP/gFDgf/KovADAnwDB43B45EE+IFE/F/KAkfBgmHAonhAonwDAn8h4MEN5X/N4l/N4k/KwkfRwgoBDwcHOohoBOoYFBEgY2BEgYFBEgYFBJIYXBFQYpBFQZ3CAoIWCKoQQCGwQLDHgR8CAoQdCAoQvCOYYFFn5gENgKREbYgAGA"); // XXX there's no WO in hiragana, so we fill it with a copy of the katakana char -hiragana['N'] = image(54, 50, "AAVgAYUP8EHwAGCv/Av4RD/8D/wFCgf8g/8DQf4j/4AwU/8E/+AaDwF//4VBgIfB/4GCD4MPAwcf+YFB/4jBn4FC/4jBAof/4AYC//n/+DBYeD/wZC/f/FgIrCGIQsCKYU/444CKYP/z4xCvxOBv+/8EBQQP4B4KFCCoJeCNIYPBQgQKBj53CAYSbBCYQDBHgJbCTYUDOQZHBM4QTBTYX/GQQxBP4Y8BDQRGBTYY4Eh5MDHgZTDAojdEbAYGEHgIGEv7/DHgIhFfAh1EEIg8GEIg8GTYYhDHhYAF"); -/// ///////////////////////////////////////// +const katakana = { +A: image(56, 51, "v//AAfwAon//AGF/wGT/gGM/A3F/BDEn/wJQoGCj4RB//gAxUB//AAwcDAwsH/+AAwcP/4tCAwMf/wGEn/8Awl/JYYGBKQkf/I9DAwJgBGwQGDGwRlBAwJsE+42DAwPzGwYGB+J7EQIIvDQIIFEAw5DEAwRDDgCIEAxCPBKIcAR4IhER4hnCLAg9BLAgoBAwgoBcQiCBMwj0BHogGBHogGBfoooEQQREFEIgGBAokAhAGFA="), +I: image(54, 55, "AAkEAws+AokB/wGEg//Awk//gTE//gAwcPCYt/CYkDCYsfCYv//A0F4A0ECYg0BCYggBCYn/KwhBBGgl/EAgtBEAgMBEAZOBEAgMBEAYZB/+ABggTDBgQnDAoIaDJoIaDFgIABDQQFC74aBBgX8v4aBEwWBDQQgB/EHDQQ6BwEfGoX/+AJBDQMDWAKMBDQMPAQIaDiBFCPAgaDU4hrDDQiuDDX4acSAIaCA="), +U: image(52, 55, "AAMP/gGE//ABlH/AAnvAon+Bk5EDv/vIgcHBkHPBgZwBBgn/Bi8B/+PBgcf/AMFw/wBgYEDgED/6qEv4MEKYK3F8AFDj7EED4LREv/4CQn/wASEFginBDAgfEDAIfDn67BC4YABH4QXBCQcHZoQkEEoYMCHAYlBFYZEBLwk/MgpQEAAw"), +E: image(58, 45, "h//AAfwgYGE/0AAwn/wE/AwngDgv4DjhDCv/wJQkf/gGEg//AwkB//AA4gc/Dn4cjbAv/34GF94GF/YGF/wcjwA="), + O: image(57, 54, "AAcf+AGEh/8AwkH/wGEgf/AwkB/+AA4n/4AGEv/gAwk/GIsf/A4P/4AE+F/Awn4n4GE/kfAwn+h4cFg4GFwYGF4IGFKwYFBMQpxFAwJxEAwJxEAwJxEAwJxEK4JxEAwKqEMoQGE/o4En/8HAl//iqEAwKqEv/+VQgNBVQgNBcYgNBcYhLBcYhSCHAQKBAwI4CAwY4CD4IGBHASxBAYI4CAwY4CYwIGBHAQGBD4I4CBIJfCHASmDHAV/PYQ4Cj5QCHAUPLwQ4CgQGCOIgABOIgABHAIGEAAY="), +KA: image(54, 54, "AAMP/AGEv/gAocB/+AAwcH/wTEj4arg//AAf+j4GE/F/AwnhAon/w4aZHAMP/hTEn/wKYn/4BTDgf/KYgQCDQYQCBIQQDBIQQCBIc/DQouCDQQuCEghJBEhITBH4RTBLoRTEBIJTGCAUPNwoTCDQQWBDoIuCj4TCJIX/CYQ/BZQInBH4U//0HwBTBGgPwXAXwh4PBXAXAv4PCZIIgBEYTJBn5SBDQXABAIzBCYJcCDQXwgbOCAwIDBQgI4CgEOJwIADkAGFA"), +KI: image(58, 55, "AAU+Awv/4AGEn/wAwkP/gGEgf/Dkk/CAc//4ABwAGBj4GC8ATBAAf4h4GE/woBAAmAAwvgFAYcIwAcD/BFDFARFD/kBIoYACv5FBAAcfRL94DgkfHgf/95EBD4RgDD4MHLwf8AogAd+CPFGwiJCS4XHJgSGB8CJEkCJJUwYABg5pDD4amTNwKmXYbgcDLoY="), +KU: image(55, 55, "AAMHwAGEh/8Awkf/AGEv/wAwn/4AFDgf/EQkH/whF/4ACAwM/AoQQCBgY5BgIGDHIMHAwY5Bh4GD8AhEIAQFDIAIhBBIJACEIJpEj45CNIV/NgRpBDQIrBEoPgDQJlBEoQaDEoV/RwUP/wPBQ4Uf/gPBQ4QsBKAKSD8BvCSQXDDQYYBNYIaCGYIqBDQU//kPXoYYBj5QCEIPgj60DKoMcWga7FKoYABKogaDbojPBbojMDGob/ECYJBCbgYaDE4IaEPoIaDEAI1EbYQZECYgtBCZQGCLol/KwxxEAwJqEgIMFgIZEgA="), + KE: image(60, 54, "AAMcAwsD/4HFn/wBxl/8AGEg/+BxkP/gOF//ABxcB/+AA4kf/BCGAAZOBv4HEIQIOGAwgOBh4OFGYIOFn4OFEgoOBAwvgh52BKgYDBOwJUDv5nBBwY6BAYM/BwIKBJgJjBBQSbCWoQVBRgK1D/4oDBwJJBWos/WIS1CgIVCJoRGBWowCCj61HYgpRCdIjEGLgTLEIwTLEfAv/GYqtBEghyBGYjoCAwwkDAwQVEYwYjEHQt/CopeBQgQOEIIgOBPgxeFgZ7FA"), +KO: image(49, 46, "v//AAYFF34FE74FE94FE+4FE/IFE/gFE/w0Dgf/AocB/+AAwf/4BHE8AFDn/wAocf/AFDh/8AocHGH4w6YZf7Aon9YYoFEejBhEAAIA="), +SA: image(58, 53, "AAcD/wDBg4DC//AgEB/+AgE/+AKBv/ggEP/gGBj/4DgP/DnU//4A34CQ+DAIcEDAIcDDAQDDDAYDCDAYDD/4cDIgJADAAUfIAQACh4jCAAUHD4QACJwIfBAAQtBEYgGBI4QUDFQkP/4qEVYQvEAAIxCEIK5CBwV/AwsfAwocCAwYcCJogcBNIp3F"), +SI: image(56, 52, "gFwAwt+Awv/8AGF/gFDgP//4GGCocDAwIVDBoX/wAHCn4VFg4GB4AxEAwsfAworBEQYABv4GFj4DCjgrCBQYRFn/4JQfAIgIGD+F/JQcD/gGBMARQCOwcH/wNBCoUP/0PAwIrBj/8OwQGBn4fBGIIGCAQIlB+BcBAQKvDBIQRB8AfBIQUH4AXBP4RXBGgJmERoJsFAwv//yaFbYghBQIYaCeAi9FPQTZGdxKFCFASECFAZPBEIgNCJQaZEAwhDDAwRJDTAYGEQAiQBPIgAGA"), +SU: image(60, 51, "gH/AAYGBh4GD/AOG4AOF/gONDo+ABxAACgY7CAAd/+AGEg4OG//gAwkP/wGEgJCCAAcfKIQzEIQIzEIQozOj4zFEgIzFn4kHGYv/M4okIGYt/IQqXBFghuBHYs/bAY6DCwrJECod/HgYVB8ZLEcoMfLQYECCwYVB+BTBCwT7CCwYrBAYIKCCoQDC8BXBEIQSBNoQVBBYP4EAIoCOQPHCoYTB/xdBIwQ8B+6SET4N/dYn/4aCFFgKRFgC+EgPghivEAoI"), +SE: image(57, 53, "gEH/wGEgf/AwkB/+AA4n/4AGEv/gAwk/+AGEj/4AwkP/g4JjA4EBQQ4D/4DD4E/AwIuBv/vAoP/FwILCAAIuBv4GEBgn//wFEAwITEh//CgfwAwMfCIRGB/4BB/5xBAgJTBIQQGBwP/75CBAwOAD4JCBAwRmDDIKYBOIQGDOIQGDOIQbBAwSqBAwiqBAwiqBDYg4Cv4GCHAUfAwQ4Cg4GCHAUBbwbjnHAgADcYYADUYQxEEYq6CVwbDBdQi6CZQYqBAAZcCAwY1BEYi5DAAQ8CegfgA="), +SO: image(52, 52, "gGAAol8AYUD/Ef4AGCn/3/wFCg/+v/wAwV/8//Bgk//AMD8f/FoQMBj/8Bgfg//gBgcPFoYMBFocP/kHFof/4AtDBgMDFoYMBFoYMBgIIBgADBwAtDj4dBHQQMCFoYqCHQQqCFoc/BIIPCCwQtDKYIpBB4IwDIAQwCh45CBIVAFgSmDFIaaDOIYfCVgYfBRYYfCTASTCUoY1BQgZPCD4l/D4kfH4g4BH4YYBH4gFBGQd//4yDBYIyDn4SEJQIlEBgRXEHAg+BFYZRGZYQADBYgAG"), +TA: image(55, 56, "AAMHwAGEh/8Awkf/AGEv/gAwn/4AFDgf/EQkH/4oF/4ACAwM/AoX+FAQGCHIMBCYY5BEIIAC+AhFIAIhDHIQFDF4IhBJQMHF4JDDNIUfHIRpCv5sCn/wDQJsCDwIaBEIIKBwEf/9gOAQaB/gbBFAIPB+YsC/AaB54RBFAIaBAIOAEoJvBOgPh/+DNAJWB+//DQPBQIZyBM4f4LQSQC8EPKAIpBFAMPPgKKCgEcYIZwBiAGDbohwEZ4bdEFILxFf4ghBXwLjEDQhLBCYoaEE4IaDdIQaDBgLBCDIRQENYYTIewRkEAwJCFHYicBOIkAEAhDBS4IAJ"), +TI: image(57, 54, "AAkGAwsfwAGE//gAocP//wBgn//gEBgIFBAAIeBAof/wAYBAwkHAof+gEDAwf4E4YAB4AGBv4TDAAM/AwoxDKQhABLQwiCAAV/MIglBMIglBHwRwDNARbF//3Awv7Awv9Awv+Awv/MQQAD34GF74GFKAUHOIYABSAJxGaYp4Uv54FP40/P4oGHQwQGKKgt/AwrUEMIQGEVYIGLg4bMFII+Fv5TGNAsPQgsHTIoAG"), +TU: image(54, 53, "AAMBwAGEj4FEgf8AYPwgFgn/4BIP/g+Av/ggEP/n/gP/4EAv/v/wQBFQP/z/4CAMAg/+DAMfEIICBDAN/FgN/8YYBBAIaBw4hDDQIVBAYMAn/wDAIhCCwIhDCwIBBwAIBHAIYBEIQYDBAIuBwAjBFQghCJgQhEAIIhDEYQPBh5HBM4IhDQQQhCwYeBCwMBCoSPB/0CIQQhBAQKWDvytBCYTBDv5tBZYYTCAAQTCAAYTFHAITEj4TF/4TEh4TFv4TEg//JgIMDMYIMEO4ImD/53BAAM/AwIsEEAgFBEAZNBIIgTCFocfJwo6BPgpHEgZAEgEOAogAGA=="), +TE: image(57, 51, "h//AAfwg4GE/kDAwn+gIGE/8AAwuAv4GE4E/Awngj4GFNWJNF/gGF/5UF/+/AwvfAwvvAwv3Awv7GJn8IQV/4BJEv59Fn/wAwkf/DJFEAYABg/+AwjJBAxbQBwAGFH4gGBH4gGIIwgGNG4IGEg//LYjyBAwiyBAxc/EQoGGFIJTLdYJvEgF+fIsYAwo="), +TO: image(42, 54, "//AAgU/+AECh/8AgUD/4U/CgYPDn//wAUC/4VCCgIlDAgIKCCgIKCCgP//wUD//gCgQKCn/zBQQ+BDYP8CgMBEAQBBj4KBKYIKC54yBBQP7KYIKCG4QKB35YBBQIUCGQPjNAUD+BXDnB9Dgy8/CicAA="), +MA: image(57, 50, "/4AE/l/A4s/AwvfAwoAN/YGF/oxGHokf/wGLh4GN/4GSg4GChgGDwARBAw3gAwv4Awo7BAwn/4ACBAwIKB+AGDgJtBAwcAUgOPAwYLB94GDgaFCAwTBDAwcfAwoyBAwgyBAwgyCAwgcBAwgyBNgL0ENgIADn6oHDijhFW4wcB4AGDKwPwBwl/fwzUJDgZOFgAGGngGFhADCA"), +MI: image(52, 53, "gPwAwkf/wFDgf///gAwU/AwIVCBgX//AME//8gEHAoQGCBgYGCv4GDFIMPBggoE4A2CCoIuCAweAAwc/BghYBMwswNw0PNwkBGAIbEG4gMCOoYMCOoQMDAwRnE4BYDKYQTEKYRuCKYY8GgCjDAAV+LAtgcTMDbYhTCHobICBwbBDBghZDZwmAZoYGCAogGBCYgiBEIidCBwQ2DS4QMCVYT2CSAb2DBoLpFn72EdJAA=="), +MU: image(59, 54, "AAMDwAHFv/AAwkf/gVF/4VG8AGEh4VHFgoVPFdZBdRogVBgP4CokBFogVBn/wTIkHEwYrCv4ODCoMP/wVDFIP/JYQVCBwgVBGYLICCoTIDCoQCBBwQhCn5RCCoR/DNoZCDDIRRDCoQODg4+CIQYvGCoZCCCoZRDAQV//4SBRAM//4ABwEfAgQAB/ARBAAkPAwvxAwv+Dgv/8YGF/gkD/xCB543DH4P5AoaBBewsAvgGFhgGFAAQ="), +ME: image(55, 54, "AAcB8AGEgf/AwkP/wGEj/8Awk/+AGEv4iF//AFAuAAwcHFAsPFA34AYNwFAQvBgICCFAUHCAIoDDwQoDn4DBKIf/MYIoCDwIGB/5RBAwWDKIYGB456Dv//75RDAwP/JQQmBAwJ6Dj4GBOYYGCOYcP/5zEg//OYgGNDYw3BAwgvBAwaABAwgaBOARZC/wGDOoP8MQI1D+AGDFwPAAwJaBDAQNCJIc/AQJsBTYL3COQc/4ATBXoYdCSgU8J4SNCmCNCNQqoDAwQuBAwgFDFAITEAwK1DAAKZEAAIMFAA4="), +MO: image(55, 49, "j//AAfAv4GFAon/wIGFgYFE/0HAwn8h4GE/AvF8A4Bv4DCAAQzBAocB/+AAwYxBCYkH/wGEh/8MIv4Awk/+AGEGyJfFAFP9AwpOBNuikeAwxfEHoLpFNoZACAwZABIgIACJYYABIAYGCIAYwCHIoABA="), +NA: image(57, 55, "AAV/8AGEn/wAwkf/AGEh/8AwkH/wGEgf/AwkB/+AA4n/4A4rGoIAE/IGF/wGF/9/Awu/AwvfAwvvAwv3AwpQCOOqqEWLV/H4pGGn5GFAw0fJosfJooGGn4GGKgq6BLQoGEg4GFh4GFPoIpEDYIwFv5MFLQ4GFg6EFgaZFAAw"), +NI: image(56, 43, "h//AAf4A25+/AH4AuWggA5A="), +NU: image(55, 51, "g//AAcAh4GFj4FD/0An4GD/kAv4GD/EADQnwgIGE8EDAwnAAwuAIIgvBAAcPF4IADn4vBAAd/8AGEFAIDBAQIsBFAMDCAIoDh4eBj4oCj4GBFAd/CIJRBgBZCAQIlD/+HQIIGD54oCNwZKDPQZPDOYRdDOYqmBOYi0BOYjCBBogGGYQSAEAwimDGATdDAwQTBH4JFBLIP8AwYTB+AqBAwITB4AGBE4bADBIJyBUIJ6CVgXgJAQzBg+BAoJkCgxcBCYRIEPArlEH4YGDO4ibBeQs+AokAsAGF"), +NE: image(61, 55, "AAX/4AGEg/+Bws/+AGEgP/wAHEh/8Cwt/8AGEgf/Bwsf/AMEAAYnBj4GDHwQOEDAMHA4hVBn4WFJIIADHwMPA4hgCAwZkFCQKCGBwpHBPQwOFFAJyGBwt/BwozBBwpwDGYiYEEgP+iAkF4IPDCoP8j7WCUAXhbwYVB/4RBU4n4QISfD54vBS4f+FASPD+AEB+AFB/IjBFIPnA4LzCGAfAeYIjBGAP4eYQCBwZuBeYUH/EfIwJRCAoIDBg6ACnCmDR4oqBDIKfEHgKuFS4g5CBwo8CWwqOCAAQ8DcYg8Vn48FAAo="), +NO: image(47, 52, "AAcHAokP/gFDj/4Aod/+AFD//gAgUB//AAoUD/4oE/woJn4oLEQYoBwAoIh4oEj4oFJZ8HERU/EQhFEDgIiDH4JFDh4iEH4t/NAYcFHII/Dj4cEv4/DCwIcDCwIcDCwI5DCwhEBHIYQBKwf/GYYhBCwc/FoYKBFoYEBFoQKCE4RrBE4YFCHwQyBHAYnBJ4YFBcBN/AgcAPgYABA="), +HA: image(62, 52, "AAP/wEH/gGCgf/gE/+AHCh4MB//AA4QMBCIQeD4ARCDwv4Dwt/8AeEgI4BDwkH/weFj4eEAgIeF8AeEAgQeEAgQeEAgQeEAgQeGMggeCMggeCQYiACQYYbCDwgbCIogbCIoZZDIoYTCMggTCEwn/CYJFDBYZFDBYYmDv4LBEwYDDg4aCh5JCDQYiDaIQWBNAQ5CMAYLDcgYmCCwgqCGIYTBFwL7EJIIWEAgPgh4WDNAPACwgMBCwiHB/wWEFwV/CwZVB/YWEDgPHXgYuBDwLbDKQPwh60CGwWAngGDgAFBkAHEsAFEAAQA=="), +HI: image(47, 51, "//AAgUB/+AAoUD/4QDg/+AocP/gFDj/4Aoc/+AFDv/gFw8BwIuDj+DFwf/FwcP/4uD///FwQKB/wuBJwIFBFwM/AoP8//PAgP/+IDCAAJdBAAXwg4FDEoQKCIIIgCLoQFBKYV//5qDB4aMuF1YFDFwIRDUIQAC+YFE8YFE44FEw4FEUgn+Aon8WwhKBXggA="), +HU: image(49, 50, "/4AEv4FE34FE74FE94FE+4FE/YFE/oFE/w0Dg//AocD/+AAoUB//AI4ngAod/+AFDn4FEj/4Aon8AocPAokHHgg2BHhYFDHgJCLJBZCEAopIFAoxIEAoxOEApc/AojSBbwplEAoZxBAocPAojICBQhBCGYIFDBYRZCa4P/NYQuCPoYFBSoZGFZYsPAgYABA="), +HE: image(61, 43, "AAMH8AHF/4HFh//wAOF/wOG/AHEv4eFg//DwoOBDwgOCDwk//YeEgf/x4eEn/8n4eDgP/4AeEj/8DAIeCBwPgLgkfDYIeECYQeDh4LBIwIeC//wDIIeCBYJdCDwV/BwIwBDwIOBCQYeBn4pCDwRIBIAQeCMIJPD/AOB4CED4BhBMwf/MISbD/kHPovwj4ODDwV/UYhYBKQJ2DRoIGDHQINEcARCCWYgGEDwIOFgb+FDwL2EDwQGFIQoeCBw0YA40AA=="), +HO: image(61, 54, "AAV/8AGEgf/Bwsf/AHF//AAwkH/wOFn/wAwkB/+AA4kP/g8Rg//AAngv4HFCYIAE/EfA4vAAwv+Eo3wn4HFwAGFJwZ5UgfAPIJzDn/x/+PEgR/BAoJzDP4N/8JzD//D/6KDFYI8BCwYrCCAItBPQOH/wWDCgIQBCwf/4P/wIWCCQIBDWgYBCZ4KJBE4LPDEYInBh5sBBgKLBNgQ0CJoIWB4ACCBgIiBBwP8EYU/TQLXBHQQECFAI8BCwIqB8DzCDYMPAgQbCMoI3BF4IRB44OBWwQUBv4TBJIV//InBHgQCBw4OBHgUH/EfNgKOCj0A3BsCQwNgeaSdCABA="), +N: image(54, 50, "ggGFngFEgP+AwkPAws/AwkB/4GEh4GFn4Gaj///gNF/AGF4BEJAwITBgOAAwQTBh4GCnwJCCgVwLgRwMHAgTBHAgTGv4TEgYTFMIITEMAsHMBY0B+ClFCYiPFEAITEv//OIQMCTg3gBgggEDIIgDGYIgDMIJVDDAIABIIILCFoYYCJwZ0BHQgsBBgZnBBggnCKgYhBMIi3FgAFFgAA=="), +WA: image(51, 50, "/4Ay4A3E/AFCh4GBAoUBAoPgAwU///8AoUHBgOAD4nwAoUf//+AoUDGRYSBGQYSCGQd/94yDh/9GQZFB34yDn/zGQcPAgYSCG4YSBC4YSNv4SKJYJwDLwISEn5QDS4QSDDAJjDDAJ2DGIJ2DUYQ+DQYKcFFYYXBDASOCGIQFDGIQRCDwTaCG4YFBEgbHHN4hiFg6HEA="), +WO: image(50, 52, "/4AE34FE94FE/YFE/wYYGocB/+AAwd/8AFDn/4AocP/gFDgf/KovADAnwDB43B45EE+IFE/F/KAkfBgmHAonhAonwDAn8h4MEN5X/N4l/N4k/KwkfRwgoBDwcHOohoBOoYFBEgY2BEgYFBEgYFBJIYXBFQYpBFQZ3CAoIWCKoQQCGwQLDHgR8CAoQdCAoQvCOYYFFn5gENgKREbYgAGA"), +RA: image(51, 50, "n//AAcHAongAon8j4GEwYFE+F/Aof+h4ME4IFE/BYr+4FE/wFE//fAon7BgpYE//vAon9CQo3Ev/gAocP/gFDgP/wASX+ASJgYSFXwJ2ECQivBDAoSEWIs//wFDbYIrDAoI+DAoIYDQ4IYCFIIABDALlDGIJhBewS/EJQQYCG4YkED4QFDD4JJF4AFDA"), +RI: image(43, 53, "AAf/7/4AgMf/f/AgMD/9/8AFBv/v/gEBh/9/+AgEB/+/+AKBn/3/wEBg/+//AFX4q3v4qDh/8FQQPBz4PDAYQvBEYQvCEYI/CGYRPBB4cfIYQpBB4cH/5TCDwJjD/4kCn4EBCgN/AgIUBDoP/FIJHBAAIyCDIYjBIYYaBQ4QaBJoZHDAAoA="), +RU: image(61, 53, "AAUH/wHFn/wAgUB/+B/+AA4UP/gBBCgd/8ABBAwUD/4BBBwcf/ABBA4f/4ABBHQg8FHQI8/HksYHgwYBHgkPF4I8EvwlCHwOAg4gBEYI8CCIQjBHgITBCIP+HgU/CwIRBDAIgB4AMCAgMfEAIMBDAIOCBgQYCIwQMCPYJTBAQI8BBwUHEoN/8P/IYN/+AvBj4LBBwOAj/7BwZGB/4ABBwXAAQIODM4QOFHgIOC/4OBh4OCAYJGBv4OCn4OBHgJKBAYJkBIQISBaIYhCCwIOBSoTqBJQISBeYUHd4U+bYUwcAYAKA"), +RE: image(51, 51, "//AAocf/AFDgf/CQl/8AFDh/8AocB/+AAwc/+AFDg/+GX4ECgwyEgPgGQk+GQkP+IyDC4IyE//3GQc//gyDh//GQYYB8YyD//4GQc//wyDDAOBGQUH//gGQRvB/BlD/4DBGQU/CwIyCj4YBMoQkBBIIyBBAIYBGQIkBDAIDBGgIiD+AFBGoIyBv4eCGQIABJwQvBAAJnDEgTLCEgY8CIYLLDEgZVCAoZuBb4iaBfAj+EgE4AokAA"), +RO: image(50, 47, "/4AEn4FE94FE/YFE/wYF34YS4A1BgIYB+A8Cv/v/gFCj4YBAoUHDH4Y/DEbglDBQ8CAAYA=="), +YU: image(59, 46, "gP/AAX+A4M/A4fggEHAwf8BwIGD/4GBj4VFgYVGv4HDwEAh4GD+A+Eg46CAAf/4AGEj/4Coo6CCqJFBCot/KAIADh5QCQAhQBCrM/Myk/M3JQGh5QFMyIRBAH6NB"), +YO: image(50, 49, "v//AAefAonnAon5Aon+DDA1DgP/wA8E8AFDj/4AocHDFZjfDCJjxDD5WE/+/AonvAon7PgoYX/g3DAAQ"), + +}; +const hiragana = { + +// hiragana + +A: image(52, 50, "gEB/wGEn/AAocD/gMcg//AAfgv4FD/wMYFIRNa54HDgYyCBgYsEBgX/+AGBHQYpBCQQaCh4JBJQPwgIdBBAP/wASB4H/j/8MIP8j5fBBIP/4P8gf+j/7/hVBj/jA4PH/C/Bn4RBv8Aj/3/Ef55FB/9/wI+D+/wj40BHwIWBL4QJB+BFBwAmB/4MBD4M/94MBD4JAB/4cBNYN/BgM//AsB/n/z4bBQgOHX4QVB/B3B/CQCAQTSC8BFCB4Q4CB4UAgIIBRQOAXojREn/gaIgAC"), +I:image(58, 50, "v/gAgUggEf/AGCnkAg/+AwU/gEB/+AAwQZBDgcP/gcECQIcFCQIJCCol/4AGBgYLBj/wCokHCAIABFAIQCCon/DgQECn4cDCoItCAAI+BDggVCLoZeB+BgCCocPPQZUBwZdDJAQcEGAIcEGAIcEGQPDDghIBDggyBDggyBx4cBjxIC8aaCCAIyBLAMDM4IyBSARnC//HUIk/+IyBCASdBLAJKCGQOf/kDJQV/GQRKCJ4XgEYRPC/CoCDgOHNwl/8P/84jCDgM//5HCDgMHAwIjBgP8DwIsBQgYVBSQgVBaYZnCTIgtBbQhDCUAYkCfwYOCGIgAHA"), +U: image(46, 50, "h//Aoc////8AFBAgIABgEDAofACwIAB/wWD//4CwgdBCIeAFQUfCwIADCwIAMj//+AEBv4tDAgQLBHAYFBAgf/8YFE54FECwRTB/wkCAoP7IAd/OgR2CKwcBQ4kH/hMEJYQcC4AWIh4WEn4tJg6EEj6EEVgIQDE4l/CAbABCAZqBBQgQDBQIQCXwIyCYYTIFeIhlCBQjxCLIQWBMgbdFvzYJ"), +E:image(55, 50, "gF//4GE/4AB+AFBgIGC/+AgEDAwYNBg4FC/wGBh4GC/gGF/ArFFIQAD4BRVn42FLAIGEJQYGBLAhEBLAhEBLAf/8ArDBIIyEj5fCRYZYEEgJYEN4JNFDQouFDQKcBFwYGFMIIGDLQRJFAwgaBOYQuC8Y2DFwODAwcP/0HXAc//EPcQnAj5LCPAU/MwR4Cv5ECPAQ9CLoUBd4auE/guBVwf5PARaC+5qCAwXnJwSXB//HI4QGCw5ACAwUHNIn+gj/HAAg"), +O: image(54, 50, "gEB/0AggGCg/4gE8AwUf8EA/gGCv+AB4QaDv/wDQn/CwIaCgP/4AaDgf/wAaCgPn/4PBAAXv/0HAwef/kfAoX+n/4v4GCAgPxCYfg/4jBAAWBGwQ1BgEDJoJQCJoJRBLYcPCAJrCgEcKAaGEHgSGDF4QPCJYYxCHoYMBn5YDBgoGBDIP8FQKiBDwabBFoIzCv/gEAJQCMwWfKAIbBh58BDQMH/l/4IaCh/xTgIaCn/P/BrD/8/4CGD/i3BDQfz/gaDv/P+AaCCAIaEHQQaDv/hGoV4h//g4VB8JnBa4ePZYRkBBwKNCbwPwCYR/C44CB4BtBfgSaD8ACBYQQWBAAYA=="), + +KA: image(55, 49, "gEH/AGEh/wAwkf8AGEn/AAwl/wEAhgGC/4CBngCBgP+AQP8AwMDAYIyDAYUPAwQ2CAwY2Cj/4gP/AAP4j/wgYGC/gGBg4GC/0/8EPAwsfCgd/4E/Awt/FIf/LgJmBE4IGCMwMf8JjBHwIPB4IDBgZmBv+DAYMHMwP/BQRfBOwIKCL4J2BOIQvBAgJxCGQIEBHAKPCCwIYDCwQBBQoRGBviIDIQJRC4AdCXAYdCKIcHboQ/CboY4BboghBboZKCFAYhBjAoDh/8nzME+CfBF4V/RgP/EgKVBwYGBFAMH/zIBFAQeBAwIoDboRRD4DrBJQUHAQJsDAAwA="), +KI: image(48, 50, "AAMB+AFDh4FL/AFDg4FIn//AAX4ArpHC/xNEAov/LQgFCDgYAlF4UfPx8/g/8CoQbBKgQhCAoMDFAkHAoeAh4FEDgQAB4E/FgIUBwE/HwQdBn/gAoM+AoPAAoMMAohFCAqIpCgI7C4BEBI4oICAoZfE4C9BAob2EAoISCaQgACA="), +KU: image(33, 45, "AAsB4ADC+ADC/wDBgf/wADMg//CYIDDh4DDD4UfAY/8AY34AZRDCh4DCg4DCgYbCgI/CgH/BgU/BgREBBgIQB8AMCFIRNDLoJ2Cv42DJwQdDFQIdDFQQdDFQIdDHYRkDgYhCgADDnwDChyzE"), +KE: image(50, 49, "AAUB/0Ag/gAwN/wAICgEfBIIIBB4P4BAYPCh/wDAcD/gYE/4FBDAU/4AYEGIgOCDAQOBh//AAP+v+DAoX/7/AAof3+E/AoX9/gYD/9/gYFD/4YE/5QCGIJQDHYRvCJQU/N4JKCKAYYCKAQYWmAYEjwYEx6lDh/zUocDMgIYDv6cBKgUf/4yBBAMH/4eC4EBNQUfAQN/DYMPE4TjCAQQkCYgSJBDYLEBn7QCAQIbCE4UDDYP/PIV/CgLpD4EPP4UH+AkBAoIACCgIADh6LCAAMDAoYA=="), +KO: image(52, 50, "h//AAX+gAFD//gBgn/BgvwBiWAAon4GwUBDIQACCQQFCn//4AFCg4lBCQc/DwYfBKQJdEDwYAB8CIihAFEgJJDIgQFEg5KEMgITEj/8D4hwED4JqEOIIfEv5eEg4fEFg0PHIwsEBigmFCYkOv65CJYPnbgn+ZgIAD8IMFewvgCYjRBE4IMDegQABIoUfAoK7HA=="), + +SA:image(51, 50, "AAMB/gFE/+AAwcf+AFDgf+DIl/4AFDg4fEgAfLgIfCj//AFQzCn/gLJYMELI5mEh6GGBgUHGAP4CAQ3COYILCBgUDIgYZBAoYmBn5REDwPgQQPgDAIVBj4fBJ4d+CQI1CgeAXhgSDKoYSEQQp1GQQpFBawXwD4IGBg42BaQngBgRlDBgmABgjzBRYZDCPIYvCv//MQoACA=="), +SI: image(45, 50, "v/AAgUD/wKDj/wAof/wAECg/8BQc/8AbD/4bE/AbEFgcHFgk/FgcBFgkPDYhIgFgIKDFh8eFgn+FgcH/4sDv+/FgUD/osDn/vFgQ2BFgcf+YsD/+fFgUP/gsDv/HFgSKBLId/8IsCHgIXBSod/EIIKBwIhCv/4h4WBAQOAv/+IIP8AQIAC4AYBAAIkBn4KDJQIKDCwYpBCwRWCAoJhDAoK1DAAg="), +SU: image(52, 50, "AAUf8AFDgP+BjH/AYP/AAnvAon+BjJAUgf9BgZFB/4MDn4kEg4MFGIwMED4QME+E/+AyC/x0DFgPABwIMC/gMGDIn8gYMFv/4EwcP/+AKYf/BgRACBgYRB/4mCgF/AwJ6DBgoTCRohNDTZE/VAkP/gFDE4PAUQhGCI4YeEUIgYBD4gMBEpI4GgIFEAAo"), +SE: image(56, 50, "AAcP/ADB//AAwP8AwkHA34FBAAn+A1JalmAGFvinFv4GF//PXghEBAwfBAwoNGEQP/+AGDn4GFh//8AGDg5PCgF/AYP/wAGEgj/CAwQADAw4mCAwZCCAAQ8BFQgGBAAQGBj4GFJQIGEJQIGEgYGFGIIGCIQQVDHQgACA"), +SO: image(53, 50, "gP/AAXggEPAweAgF/AoX+gEDBgfwgEfCYoFD/EAg4MFAAQMCAAQwBBhQpBJQozBAAU/IAIACIYJUBAAV//gsJD4IsEn4sEOAn+NIn/+4FEAA39AwvvAwqQDAAP7UYhmCx5bDuBVB4BCDg5bEJ4JoEgJ1EEQKCESwIFEg5vEEA4TFh4TFv4TGYgiLBCYrFG/5dDd4YHCOQKkBDQjbDDQQwDWgR5DAwSGEEAgAEA=="), + +TA: image(52, 50, "gEP+AGE/4Mjgf/AAXAgE/AoX8BjUAgP+GYkf8AFDBhHnEIQMBEQQhBn/jFAWAgYMD/AMH/gMF4f/F4UH/kQGYd/KIIACg4VBBgmAQ4gMFUJcB/8DDQZgBv6iD/wuEn/gKIJGDEIl/4KCDC4KPE/+BBgYXBBgY5BAIImCj4MBTIKFB/wMBAAKSB8EPAwXnUYIMDCwLYD95RBEAIZCFQN/AwPBKISpBwEGQAgAGA=="), +TI: image(51, 49, "gED/wGEv/AAocP/AFDgP/CQk/8AFDg/8Bgn/wAFDj/wBQYAqJ4M/LBZrMJYZ+Ch5aDv/f/4bCBQIABCoMDHAYTBv4+Ej4MEg4DB4IMCAoIcCwE/TwU/+ASBEQI8BVQJLCv/gS4cP/kBMgYWBjyoEgLbJEYYSCQQkHCQg2EHASCEv4SBgYOBOQ70BQoYrBEQIABFYR/DJASRED4YFCBgJDDA="), +TU: image(59, 45, "AAUP/4FFAAIGCAoX//EAg4GD//ACYYAB/kBAwgOBn4OFDgoOBAYX+BYP8j4GBwEAAgPDGwQ+C/F/BgIABCwOMLQl/+AGEg/+NIv/8BwF/gGEKwIqDAAM/HAYzDEhkfEgsDEgxJGh5JFHQPACqQrBCpkfCopXBCogcBCog5BK4jSCAwxtDDYK8EZIQcCAoQcDCYTjCJgQGCEYT0DIAYGGEgQGDEgRcEv5UEA="), +TE: image(57, 50, "/4AFv4GF34GF74GF94GF+4GF/YGF/oGF/w7Cn//4BCDAwOAAwpQEj4ZDAxP8AyUPAwwiFg4GMgZFFAw0BLQqlBNAkAv4GG8AGEn/wKgv4KhZGGHALeGH4oxNh4xFOJBjGEYt/VQwVFg//BwhOBAAI7Dv4GBHYYcBCwgcB/5CEDgQyFGYgrCUwkPKAwAC"), +TO: image(46, 49, "gEH/AFDj/wAod/4AECgP/Cwn8C0cICwcDBoIWC/4NBCwMfEgV/4f/BoIWBv//LAMH/4AB8AWBAoWAgE/BQYlBDYUAh4FBHwQPEEIJQDFYJhCgYwCLQQqCDYQKDDYIKDn5xEEAYQB/x8JDYkDCAkPYIk/JoQWTAol/AocZQwR6B8aNCAAOPAgf+TIZqBAongT4QfCBYY9BW4R1BA="), + +NA: image(55, 49, "gEP+AGEj/gAwk/4EAkAGCv+AgAPD/8AgYdCgP+EgkD/gdB/AGBg4DBv4GCj/w/wGCv////8AwQFB//4AwMBAwXwEQMDAwXgAwMHAwXAAwMPAwWAG4QvBLgQGBL4X/AwRfBKgIGCL4X8n/gLARUBn5YDMwM8NQaLBQYIoCAQSIDAQRZBRYaBDRYQhBFAIJCKIYyCDwKoBToZkBOAIJBPYKLCGwMH/h2CAwMfKoKKCI4PgSIYYB4afDJQMP/gpB+AhBMgIjB/AhC4EfAwIhCEoIGCwJdBaIIZBMgSkCjhMBgakBG4LICUgKDBAwQuBPgRKCjgGE4EQAwgEBAAIbBRAQACQgIDB"), +NI: image(50, 50, "h+AAocD/gFDgP/CQl/4AFDn/gv//AAOP/E/AoXj/0HAoX4/+BAoX+DAuf+EfAoXn/gYD/P/gYEBG48f+AFDg5QMMYkf8BvE/BvE/wYE/4YEKAIYYgZSCDAMBJgQYCCgYDBFoYDBj4tCDAJlDDAMBGYYYBNYYYBn4xCg/4h6ECPgIHBPgfBDwaVBQgYvBToYYCFYauBaIIwB5/wcAfz/0PAoX8cAn/IgQFC55dBAoXxFILtC/grBGgL5BYIoAGA=="), +NU: image(58, 50, "AAV/4AGEj/wAwkH/gGEgP/Aod+Dgv/wAcEj/gDgkH/AcEgP+Dgt/Dg3wn4mBHwYGBDAIyCAwP/8AGBAoQODh4GC/4sBgYGD/AcCAAO/IQQcC4IkCDgI7Bj5YBg//w/8EAIjCwIEBv/gMQPgLAMPFYP//h1BgZpC/4LCNwIxB4YoBFoIxB/AjBNIMH/v+n5UB/4qBn/fIoIJBv+PLYUPQwPhOIUD/gvBGYMH/3/BAX/457CBAP/84GBDgIlB/YGBCYJwB/qECDgKREwBCC34YBDgfvLYP+HIM/+YYCIwM/MoIYB/hGBMoQEBz4nBKQfDAwODGQXwKQQMB/P4j4GBAQP+ngtBUgIRBg6aBRwKiBwOAf4TNBAobjCAogAEA"), +NE: image(57, 50, "gEP+AGEg/4AwkD/gGEgP+Dgv/Awt/wAGEn/Agf/BIUf8EP/40CHAMf/4tBAYP4AQImBCIP8n4GB4EH//+AwXgEwP/v4CB/EBAYIPBg4jBAwX8BYJFBCQRKDFYIGBJQJxBIgUfAQIrBAYMPCAIfBBQR8CAwR8DMAZ8Cv4GCGIQGDGIU/AwR8BAwKqCWoU/FoS1Cj4tCHASEBWogGBUAQKBAwItBHARpB8BlBBQKuCAQIKBO4SqCBQX8AwX4h/9/wGC/kP/n/DYSlCv+P/ArB4K+B4/4SIV+j/jWIX8n0P+JSBDoMOMwJWBAwOCMwM//ZOCMwI4C75nB/5bC45nBv+DAwPhTgXAb4PAoCfCQQifBYoYAHA"), +NO: image(60, 50, "AAX//4GEv4HFj4GB/wGCg4GB//4AwMBAwX/4AcEDwcPAwYWBgYGDCwQVC54tCCoX8F4PgFYP4CYI+BgE//0P/gaB/ARB4F/4ApBwAVBg4OBj/8EgITB4AiB4InBBwQgBCAIOCPQPjD4MPJ4MH/0/+ALBwARB84kBBwQ0Bv/gBwc/+5bBj5tEHAR8Bn5lBBwInBBxY2CBwcDWIQOEGwIODJwIOFIoRKC4CNCBQP3AgKwCDIIOBKIQKB8/8IQJgBj4OB8E/MAfD/ytBEgX8J4KeBZwWDIgJCBCoP4ZgIzCAYIqBeYRQB8DnCK4gGBGoIDBwAyBF4IKCCQWBAwIVBEoPgF4RFBg/4F4Q2BAAQOBTwIADHoQADbIQAIA"), + +HA: image(55, 50, "AAd/wEAn4CBgH/BIXAgEB/wJEgf8AQIJCg/4AQIJBgEP+ACBBIMAj/gAQYsBEoIoCGwf/GwkB/8P/4AC4f+j4GDw/4n4GDj/wv4FC/0/8AMD/l/4IGD/H/wYGD+P/g4vELARtCMQRtDMQQKDL4YKCMQQKDMQQKDR4QKCTIYKCFYQ2bOoI2C4BgCGwWASAQ2BGQKJC8DNBBAIAB+DNBPYf4ZoKrDAgPwT4K7BAwRdBB4K3BVYIqCVYY6BAwKrB/0DVY3+v/hAwf8n4SBdIXwnxEBAwXgnBEBAwShBO4IbBSYSVCOYQAHA"), +HI: image(57, 50, "AAMPwAGE//gAocf//wgFwgEH////kH/AZBAwP+gf+Bof/wP/gEDAwWAAIMBAwc/FgIGDj4sBv4GBE4P8HAIdBE4IqBAwYgBKAIGCKAYKBAwN/EYIGDn4jBAwZfBDAQfBLIPAAwZZBDgItENYN/CAIfBIAIGCLIRfDLIXwAwc/RQJmCHAPv/0PEoI4B+f/AwcH/P/w50D/l/wZ0CgP+j/BK4Q4Bg/gJoQ4BwIGBIwU/4EwAQI4CIYICCAYY/EJQMHHATcCbAQKEHARGBGgQqBCIc/D4IGDaITCDT4PAAQJfCQQRYDeQQGDSIIGEYYIGEE4IGEDgYFCcAQ+CGQZsCABAA="), +HU: image(58, 50, "gEP/AGEgf//wHE/4ABAwc/AwIPDh4OC8AGBg4GCEwUBAwX8Dod/EgoHC4AsF+BJFjAGDg4iEFgRfF/+AAwk/IwQjDFIgjDvAjDMYJlCgRHB4ABBFIUf/ABBFIXH/0HCoUf+BcBLwQpBCogpBCYIVDv+ACohNBn/wCoRxBCohNCMoIVBOIQVBAIJNCCAIVCEYIQBCoOAb4QtDCAQtC/gjCdIIXCN4QwBC4SVBDQIXBEYUP/gXBI4QEBHwPD/8ODgR/CwZNCCYN/8P/5/4GQOf+DtBKgXv/jtBKgX5/0PAwJxB/0/DAL8CvkDJYP/IYMMgFgg//fot/VYQACgYGFAAoA=="), +HE: image(67, 45, "AAXwA43/4AHFn/8A4sPCA0B//+CAt///gA4kfCA0H/4QGA4IyFn4IBGQg5BIYsD//nCAt//F/CAkf/wzBCAYFBwH//BaE8ArBwBzFCAgNBLoQQCHIPADYIQD/6dBCAk/OQIQEHIQQEHIQkCCARaBO4YUCSYQQDHIQQFHIQQERQgQCLQQQEHIKBDCAPAn5fDCAP8gbNECAaJDCAbVECAPgvj+Gg72GdoqYFCAgHFKIoQDDA0AKIjODDA0ARYQAEhwHGAAIA=="), +HO: image(53, 49, "h4GFv4FEg/4kAGDn/D/4ACwP+j4FC/kf+IMD8H/w4GDEAM/AoQEB4IMD4f+g4FCEoPwGIXggH/wEAgP/IIP8KQX4B4PAKQXAgP+AoMDAYMPEAQkC/+DEIIkBEAJVD/8/8IFD/P/h4GD5/wv5IDv+DBgfz/gTEz/gCYf4KIIABGgRRBLIZVDNIJVDNIRVDNIRlBNIZlCKwIDC+EDGYJpCwClCNIQMCCYIwBBgX8GAIBBJwRIBPofwJAIeBLwKCBBwIiCx/4H4IVCv/BFYIFB/f+KYIMCx6RD94YBwLfDwYTBGYV8LgJICgI5CBgUCgaGBLYQACAwLVBgA"), + +MA: image(50, 49, "AAMH/gFDgP/Bgl/4AFDj/wDBsH/4AD/oFE/9/AwoARJVXhAon4JQn+j4MEw4YLn4YEJTIfCAooYCAoX4DgQwCwBdEBgMDHoYMB//3Bgd/8AUC4A7BJQP//kHBwQGB4JYBFoX8KgMP/gGBz/+h//AIPjGAXA//wAoXwh/4DgX4gP8IgQnCF4QFBgOAEIKIEv6SCAAIA=="), +MI: image(58, 49, "gP/AAOAA4V/AwPgAwUfAwP4AwUHAwP+DjAABgYcDDwYcDDwQcDDwQcFg/8gAXDDgMAn4XDv/Ah4XDj/wGgkPDgpQBDghPB+AcDMoXjDgQGCNwZsCNwYGEDgM/AwYcBPQQAC/kP/4IEw//MgIYC+f/wZHBCAP8//AGwMDEgKGBRAQVBz/4NYI2C44sBNYMP/PxFQI9BAQMY/+BFQKvCOoIsBEYKSCFQU/SQP8WYQCCGYIqCEwI0BFQQmBMgIDBJwOAfgXAAYItBRAJVCKIIVBAYN/FQIYBAYN/FoIrBTQSzCdgRfCAAg0BAAkfbwQACgY4BAAgGDA"), +MU: image(55, 49, "gED/gGEg/4AwkP+EAhwGCj/ggF+AwU/4EB/wGCv+Ag4GD/4kBAwM//4AB84GBv4GC54GBAoX/x/+gIGDh/+gYFC/0P/kHAwX8AwMPAwX4j5cCGwJOBAwJIDj5jBv4QCAwIpBNoU/+AiBNoIGCJYJtBAwPhFwPANQXjAwOAgEEv+P/A2C/H+CoI2BTIIhBwY2Bh/xwH+UgUf+CwBUgSgBBYKkCn/gh/gToI1B4Ef4AvCBIM/4ZmCIAN/44oBSgKdCFAJ3CLAY0BUgQoBGgIGBEIUPAwSID+AGBQIZHBJQRECd4Q9DI4QvBJwQ2Cj4sBGATRBJwLcDFgTcDC4QGEEILqEAwIbDIARoCBgQ"), +ME: image(55, 49, "AAUf+AGEn/gAwl/4AECBQP/wAYC4EB/4YDwED/wYDwEH/gGCCIMP/AFBgIRBGwcDCIN/GwUH/EP/4bCDAP/AAI2C+4GCHwMfAoX/JgM/AwYjBv4GI8YGCFoN/wIGBgYCBFwIiBHYJfBNAPAn/8IwIGBwAaBh/wAwOD//4R4IfBg//+B2BDoJKB+AoBg/+JQPjOwMP/n/z/nQIMf/IOB76BBn/3/gVBMgN/94nBOQX/7/gAwKbBOwSOCHoJMCEIMH/v/CAJxBh/7/hcCF4X4KYLEC5/wj5KBEIOfGwJRCL4PzF4V/JIQvBCYJJCH4JxB4AGB/xCCFQIJDDoIMBBIRNBAQJdCIwKUCeAb5CPgQACSgIFDSgIDC"), +MO: image(50, 49, "AAN+Aokf8AFDh/4AocD/wSE/+AAod/4AeE+AFDg/8CAf/AAX8j4FD/8HAonBAonwDBY3OKwkBKxc/N5M/GwcHh42D3/DAofn/AFD/P+DAf+v/PBgeP+YFD8f+NAuAG4axBU4ZaCKAUBOAJQDOYIYE+AYEVYKFCDAaICDASICDAsPDAQxBgYYBj4rBAoOAYQPwPQPgE4JYDRQo6BAoglBPoQ0CAogMCAoYvBIwQA="), + + +YA: image(53, 49, "AAVgAYUf4EPAoUB/8B/gGCg/4j/wAwU/4F/4ATDgf/BgUP/EPDQYRBn///wTBAQP//4OBCYMfAwP4CYPPAoP/8AnBAAeAh4FD/gMD/n/+ALD8H/z4EB/v/wf+CIUH/kP+4+CLoN/CYJhBCYmAgfwCYP7CYMeIwOcOoYiBBAOAPYXggZuCIwIrCTgQrCCYIMBFYP8gYZBC4Mf8B3CTQIPBQgYwBg4MDGAKYBGITABBgZnCL4QTCj5EFAAbUBAwgTBAoYTGYAITFcwQTPfQYTCTAITYMAQTDVgUAA="), +YU: image(51, 49, "AAV/4AFDh/4AocB/4DBj/ggE/AQMD/0Ag/8DgWAgH/AQMP+ASB//AgISBAoIDC4Ef///+ASBh4FB/4SBgYFC+E/4IFC/8H/F///9//g/8f/3/x/+j/nAQPwv/j/H/wf+I4N/KAJlBv+P9/4MoMP/f9/xlBAIIqBwAUBn/vFwIdBg40BNIIOBIIR7B+BbC8B7BKoX4uAyCAwM+GQX5//f8IyCn/z/hHCK4N/4/8h/8/4EB/4lBF4P/z5wB8f+RYJjBPoPAFwO/BQP4IQX/wJkCTAUfVYf4gf4BgS4BbQRiCcgbSCAAILEcALkCAAM/DoYeCC4ZLBfoIeD/ASEDAhoBAoYlBDwcAg/AAoY"), +YO: image(49, 49, "AAMP/AFDg/8AocD/wFDgP/DAn/wAFDv/AAoc/8AFDj/wGCH/AAIwDAAImCAoQmCv4FBEwU/AoImCj4FBEwUPAoJXCMO4wEM4IWDI4IwCKYQwCL4oFCDAQFDCQIXCNgQFDEoMP/iSC+EHEIJ5CAoSSCwYaBEwXhFoMf8Y4BEAJnBCYN/+Ef/AuBz41CLoPPUQd/4YFDj/AAocD4AuBPIXgDQJ/En6REA="), + +RA: image(47, 49, "gEP4AFDn//Aod///wAoX///+AgMDAoP/DIMHAoX4AowjC//gh/4gIXCj4mBj4wBn/gEoP8GYI/CvAzBwAFBkAaBIgYTCAAUHGARcCJ4YrBFAJcD4AZDFAI/CFAMPJYQOBK4XwLgZdBJwIFDMIQFCQod/+AIBOIXzO4nnRIQRB55dDDYJdDHgQEBIgM/OgUD/0+Nof8jBtDOYk/OYgyDYgQhCPwLOCFoQ4DMwIcCPYSBCAATkECwKBDCwIVCFoQFCIgSNCHASNBGIQA=="), +RI: image(39, 49, "ngEDv+AAgX/AYUD/wGB4EH/EH//wh/wn4EBj/h/4EBn/HAgV/z4EB+P+v4EB8YCB4F/8//E4N/54VBFgIWB4AEB346BgP/v/8AgP+//4IQP9//ggBABC4UPAgJRBj4qCgBKBC4IwBF4QrBDgQrB/5vBgYcDEwIcCEwI5BEwP3EIU/94hCv/fEIImBn4+BRYKWCg/8EwSLBTQU/CwScCUYSoDj4zCBoIzCHoIuDKARjBJYJUCQAR7DQAQbDEASABbgU/BATqE"), +RU: image(51, 49, "gf/AAXAgF/AoX8gEPBgeAgIFD/EAn4MEg4FD8EACQoACn4lBAAUf/4FDDYOAAoQuBHwIACv/wDwgkEh/+DwoFDDw5ECDwRLDMwg5BLIZMBNgh/FGgIeB+AVB4AeBEYJmBBAJQBDgPBOocf/AoCVIU/Kwc/+5WDg/+Kwl/5/wh4mBh/4/A2CFgMOAoJDC8GBMgUHGAJQCCQKpCBgISBgf+SQMPCQN/4H/4YSBGIIwBCgMBDoTMCn/AEIROCLoKFEAIJvBTwZvCTAarFNIQFCXASyCYoYxBAoQ"), +RE: image(55, 49, "gEf8AGEn4GFv/AAwn/wAFDgP/BgkD/wGEg/8DoIkCh/4gf/+A2C+EPAwV///gAQIGB///4ICB+AuB/+PAQPgg4DBn4GE/wSB//AEoIABwABBj4FB/hODA4PwJwYgB4BOCHwROCNoQDBJwJtCLoM/PwJdBPYN/AQMPEoQvDDQMBBIV/DwMDF4QhCg4QBEIIlBh4QBLIIlBWoRRBWol/F4eAIYIlBMwR7BEoQQBUIYvCNgIlBF4SBBEoLsBHgI2DSwP9GwaWB+ZmEj/HGwIvCj+PFgKWBjk+RgSWB/E4Lgn4sBcCIII+CGwTjDWoZFBSYYRBYYgDBYYa5CLgIGBAAI"), +RO: image(50, 49, "AAf4gEB/4AC8EAv4FC/kAj4MDwEHAofwDAgSBDAoACn/+AocfAokP/4FDE4OAApED//AAohJBAAI5BAocAIQIFEHghFCD4QFCBoU/KIQMBNQZ9BOAhOCQYYFE/B8CE4QFBM4JGB4YuDj/7AocD/xIE/+fP4c/84FDh/8QoZyBj5mE4aFDn5yEDAIFDGIIFDIgIXDDwKREv4eEv4eBiAFCDwMH+A8BIQLnEEgLnDSooqBQYQFCDgQ2DAoolCJAQA="), +WA: image(54, 49, "gEf+AGEv/AAocB/4MEg/8DUv///Aj//wEDAwIcBAwMP//8BgIGBn//+IFBAwICB54GCDQQAC/0HAgXAn45BD4IDBn45Bv4MBAYPgGYJKCFAIbB8EAgf+DQRbEv/4LYYaBOQU/4EPCwIhCCYJrCgf8CYkP+BlBCYQaBv6GDOwQaECYIaEKwIaD4JWDgP+CYIaCg/4NQYTB8Z+BFwef+4aCMgN/74aCn/z/zXCIAOH/IaCh5CB44aBJoU+a4QyBwFwDQLGBCAOBX4adBGIJMBRIQaBUYI4CDQJnDFYJ7EDQKzCDQYECgA="), +WO: image(52, 49, "AAMf+AFDgP+Bgk/8AFDgYMM/gkD/4AC+EBAof/BkA5FhEAg45Cg/AgF/AQMBBIMP/4DB//gE4Xwn5dBn4GB74IBgY0Fv4FD8AfBAoYfB/gbBIAIiBg///A7B/+A/4rBCQIxBBAISB/ghBCQeBEoIMBCQI0BBgQSCDIYSB54MBgIlB+AMCj0H/0PBgIABHQQMBOgP4BgZBBBwTDCMYIMDKIIMRWQQmDAwUMYYqyBAoaxBN4IMEV4QMCcggMBWwbZCAweA"), +N: image(54, 49, "AAMHAwsf8AGE/+AAocD/wTF+AGEv/ACZUP/ATKgP/CYv8Awk/IQgTBIQkHCYxCFCYxWTIQxWGFAhCBAwkPAwJCE/5KDCYQiBhhCBAwJlBn+Aj/+/49BDoP/8IDBgf8IQIDBKgUf/EPLAJUBv/gn/AFgKZCAIMHCIP4DQSXBAIIaC/+BCIIaBYwKZCLwIuBCYLRCFwIKBEYX/CYUfEYP4TIRACCYQ+BwZUBDwIYBOgITCRAQVCEIP//0BYISjB+CtDUYRNBAwQ5Bg7gDBQIA="), + +}; +const keys = [ + "A","I","U","E","O", + "HA","HI","HU","HE","HO", + "KA","KI","KU","KE","KO", + "MA","MI","MU","ME","MO", + "NA","NI","NU","NE","NO", + "RA","RI","RU","RE","RO", + "SA","SI","SU","SE","SO", + "TA","TI","TU","TE","TO", + "WA","WO","YO","YU","N", + ]; let kana = katakana.KA; let scroll = 0; - +// const keys = Object.keys(katakana).sort(); +// console.log(keys); let hiramode = false; let curkana = 'KA'; + +console.log("StartupTime: "+startupTime.diff()); + function next () { - let found = false; - for (const k of Object.keys(katakana).sort()) { - if (found) { - kana = hiramode ? hiragana[k] : katakana[k]; - curkana = k; - return; - } - if (curkana === k) { - found = true; - } + const off = keys.indexOf(curkana); + if (off !== -1 && off + 1 < keys.length) { + return keys[off + 1]; } - curkana = 'KA'; - updateWatch(ohhmm); + return keys[0]; } function randKana() { try { - const keys = Object.keys(katakana); - const total = keys.length; - let index = 0 | (Math.random() * total); + let index = 0 | (Math.random() * keys.length); curkana = keys[index]; } catch (e) { randKana(); } } +// const bench = benchStart(); +// console.log("-->" + bench.diff()); function prev () { - let oldk = ''; - let count = 0; - for (const k of Object.keys(katakana).sort()) { - if (curkana === k) { - if (count > 0) { - curkana = oldk; - return; - } - } - oldk = k; - count++; + const off = keys.indexOf(curkana); + if (off > 0) { + return keys[off - 1]; + } + return keys[keys.length - 1]; +} +let color = 0; +const colors = [ + () => g.setColor(0,1,0), + () => g.setColor(1,1,0), + () => g.setColor(0,1,1), + () => g.setColor(1,1,1), + // too dark + () => g.setColor(0,0,1), + () => g.setColor(0,0,0), + () => g.setColor(1,0,0), +]; + +function nextColor() { + if (color + 1 >= colors.length) { + color = 0; + } else { + color++; + } +} +function prevColor() { + if (color < 1) { + color = colors.length - 1; + } else { + color--; } - curkana = oldk; - updateWatch(ohhmm); } -const kanacolors = { - A: [] -}; - - -function updateWatch (hhmm) { +function render(hhmm) { g.setFontAlign(-1, -1, 0); g.setBgColor(0, 0, 0); g.setColor(0, 0, 0); - var whitecolor = false; - if (curkana.indexOf('A') != -1) { - g.setColor(1, 0, 0); - whitecolor = true; - } else if (curkana.indexOf('I') != -1) { - g.setColor(0, 1, 0); - } else if (curkana.indexOf('U') != -1) { - g.setColor(0, 0, 1); - whitecolor = true; - } else if (curkana.indexOf('E') != -1) { - g.setColor(1, 1, 0); - } else { - g.setColor(0, 1, 1); - } - g.fillRect(0, 0, w, h); + const whitecolor = color > 3; + colors[color](); + g.fillRect(0, 30, w, h); g.setFont('Vector', 50); if (whitecolor) { @@ -196,9 +228,9 @@ function updateWatch (hhmm) { } g.drawString(hhmm, x, y - 1); - drawKana(4 + (g.getWidth() / 6), 60); + drawKana(); drawMonthDay(); - Bangle.drawWidgets(); + // Bangle.drawWidgets(); // :? always draw? } function drawMonthDay() { @@ -219,21 +251,47 @@ function getPhoneme(k) { } return k; } - + +var ohhmm = ''; +var ypos = 0; +var xpos = 0; +var zpos = 1; function drawKana (x, y) { + if (!x) { + x = 4 + (g.getWidth() / 6); + } + if (!y) { + y = 40; + } + x += xpos; + y += ypos; g.setColor(0, 0, 0); - g.fillRect(0, 0, g.getWidth(), 6 * (h / 8) + 1); + g.fillRect(0, 30, g.getWidth(), 6 * (h / 8) + 1); g.setColor(1, 1, 1); + x -= ((zpos) - 1)*50; + y -= (zpos - 1)*50; kana = hiramode ? hiragana[curkana] : katakana[curkana]; - g.drawImage(kana, x + 20, 40, { scale: 1.6 }); + if (guard) { + g.setColor(0.8,0.8,0.8); + } + g.drawImage(kana, x + 20, y, { scale: 1.6 * zpos }); g.setColor(1, 1, 1); g.setFont('Vector', 24); g.drawString(getPhoneme(curkana), 4, 32); - g.drawString(hiramode ? 'H' : 'K', w - 20, 32); + if (hiramode) { + g.setColor(0.2,0.2,0.2) + g.drawString('K', w - 20, 32); + g.setColor(1, 1, 1); + g.drawString('H', w - 20, 32+24); + } else { + g.setColor(1, 1, 1); + g.drawString('K', w - 20, 32); + g.setColor(0.2,0.2,0.2) + g.drawString('H', w - 20, 32+24); + } + // g.drawString(hiramode ? 'H' : 'K', w - 20, 32); } -var ohhmm = ''; - function tickWatch () { const now = Date(); month = now.getMonth() + 1; @@ -243,27 +301,127 @@ function tickWatch () { } const hhmm = zpad(now.getHours()) + ':' + zpad(now.getMinutes()); if (hhmm !== ohhmm) { - randKana(); - updateWatch(hhmm); ohhmm = hhmm; + randKana(); + render(hhmm); } } +let guard = false; +function hiraPush(d,dx) { + if (guard) { + return; + } + xpos = 0; + ypos = 0; + zpos = 1; + guard = true; + var count = 2; + function paint() { + count--; + if (count < 0) { + guard = false; + xpos = 0; + ypos = 0; + zpos = 1; + render(ohhmm); + return; + } + zpos -= 0.04; + render(ohhmm); + setTimeout(paint, 100); + } + setTimeout (paint, 5); +} + +function hiraSwipe(d,dx, dostuff) { + if (guard) { + return; + } + if (dx) { + ypos = 0; + } else { + ypos = (d * 4); + } + xpos = 0; + guard = true; + var count = 2; + function paint() { + count--; + if (count < 0) { + if (dx) { + curkana = d>0?prev():next(); + } else { + if (dostuff) { + hiramode = !hiramode; + } + } + guard = false; + xpos = 0; + ypos = 0; + render(ohhmm); + return; + } + if (dx) { + xpos += (8*d); + } else { + ypos -= (4*d); + } + render(ohhmm); + setTimeout(paint, 5); + } + setTimeout (paint, 5); +} Bangle.on('touch', function (tap, top) { - if (top.x < w / 4) { - prev(); - } else if (top.x > (w - (w / 4))) { - next(); + if (top.y < (h / 1.5)) { + if (top.x > w /2) { + //hiramode = !hiramode; + if (hiramode) { + hiraSwipe(1,0, hiramode); + } else { + hiraSwipe(-1,0, !hiramode); + } + } else { + hiraSwipe(1,1,1); + } + } else if (top.x < w / 2) { + nextColor(); + hiraPush(); + // curkana = prev(); } else { - hiramode = !hiramode; + prevColor(); + hiraPush(); + // curkana = next(); } - kana = hiramode ? hiragana[curkana] : katakana[curkana]; - updateWatch(ohhmm); + render(ohhmm); +}); +Bangle.on('swipe', function (x,y) { + if (x > 0) { + // nextColor(); + hiraSwipe(1, 1); + } else if (x < 0) { + // prevColor(); + hiraSwipe(-1,1); + } else if (y < 0) { + hiraSwipe(1, 0, hiramode); + } else if (y > 0) { + hiraSwipe(-1, 0, !hiramode); + } + render(ohhmm); }); g.clear(true); // show launcher when button pressed Bangle.setUI('clock'); Bangle.loadWidgets(); +Bangle.drawWidgets(); + +// redraw widgets every 10 minutes +setInterval(function() { + // maybe not always necessary + Bangle.drawWidgets(); +}, 1000 * 60 * 10); tickWatch(); setInterval(tickWatch, 1000 * 60); + + diff --git a/apps/kanawatch/metadata.json b/apps/kanawatch/metadata.json index d7a6f8c23..f3aaeae92 100644 --- a/apps/kanawatch/metadata.json +++ b/apps/kanawatch/metadata.json @@ -2,7 +2,7 @@ "id": "kanawatch", "name": "Kanawatch", "shortName": "Kanawatch", - "version": "0.07", + "version": "0.11", "type": "clock", "description": "Learn Hiragana and Katakana", "icon": "app.png", @@ -26,6 +26,9 @@ "screenshots": [ { "url": "screenshot.png" + }, + { + "url": "screenshot2.png" } ] } diff --git a/apps/kanawatch/screenshot.old.png b/apps/kanawatch/screenshot.old.png new file mode 100644 index 000000000..b1ed879aa Binary files /dev/null and b/apps/kanawatch/screenshot.old.png differ diff --git a/apps/kanawatch/screenshot.png b/apps/kanawatch/screenshot.png index b1ed879aa..4ef0ecbf2 100644 Binary files a/apps/kanawatch/screenshot.png and b/apps/kanawatch/screenshot.png differ diff --git a/apps/kanawatch/screenshot2.png b/apps/kanawatch/screenshot2.png new file mode 100644 index 000000000..3e85485de Binary files /dev/null and b/apps/kanawatch/screenshot2.png differ diff --git a/apps/kbmulti/ChangeLog b/apps/kbmulti/ChangeLog index 4ef8f7bda..defae902b 100644 --- a/apps/kbmulti/ChangeLog +++ b/apps/kbmulti/ChangeLog @@ -3,3 +3,4 @@ 0.03: Use default Bangle formatter for booleans 0.04: Allow moving the cursor 0.05: Switch swipe directions for Caps Lock and moving cursor. +0.06: Add ability to auto-lowercase after a capital letter insertion. diff --git a/apps/kbmulti/README.md b/apps/kbmulti/README.md index b6754711d..88b91fa80 100644 --- a/apps/kbmulti/README.md +++ b/apps/kbmulti/README.md @@ -12,6 +12,6 @@ Uses the multitap keypad logic originally from here: http://www.espruino.com/Mor ![](screenshot_2.png) ![](screenshot_3.png) -Written by: [Sir Indy](https://github.com/sir-indy) and [Thyttan](https://github.com/thyttan) +Written by: [Sir Indy](https://github.com/sir-indy), [Thyttan](https://github.com/thyttan) and [bobrippling](https://github.com/bobrippling). For support and discussion please post in the [Bangle JS Forum](http://forum.espruino.com/microcosms/1424/) diff --git a/apps/kbmulti/lib.js b/apps/kbmulti/lib.js index 9b642a132..505132040 100644 --- a/apps/kbmulti/lib.js +++ b/apps/kbmulti/lib.js @@ -9,6 +9,7 @@ exports.input = function(options) { if (settings.firstLaunch===undefined) { settings.firstLaunch = true; } if (settings.charTimeout===undefined) { settings.charTimeout = 500; } if (settings.showHelpBtn===undefined) { settings.showHelpBtn = true; } + if (settings.autoLowercase===undefined) { settings.autoLowercase = true; } var fontSize = "6x15"; var Layout = require("Layout"); @@ -89,19 +90,21 @@ exports.input = function(options) { } function newCharacter(ch) { - displayText(); + displayText(false); if (ch && textIndex < text.length) textIndex ++; charCurrent = ch; charIndex = 0; } function onKeyPad(key) { + var retire = 0; deactivateTimeout(charTimeout); // work out which char was pressed if (key==charCurrent) { charIndex = (charIndex+1) % letters[charCurrent].length; text = text.slice(0, -1); } else { + retire = charCurrent !== undefined; newCharacter(key); } var newLetter = letters[charCurrent][charIndex]; @@ -109,13 +112,22 @@ exports.input = function(options) { let post = text.slice(textIndex, text.length); text = pre + (caps ? newLetter.toUpperCase() : newLetter.toLowerCase()) + post; + + if(retire) + retireCurrent(); // set a timeout charTimeout = setTimeout(function() { charTimeout = undefined; newCharacter(); + retireCurrent(); }, settings.charTimeout); - displayText(charTimeout); + displayText(true); + } + + function retireCurrent(why) { + if (caps && settings.autoLowercase) + setCaps(); } var moveMode = false; diff --git a/apps/kbmulti/metadata.json b/apps/kbmulti/metadata.json index 510454f79..0b44b0306 100644 --- a/apps/kbmulti/metadata.json +++ b/apps/kbmulti/metadata.json @@ -1,6 +1,6 @@ { "id": "kbmulti", "name": "Multitap keyboard", - "version":"0.05", + "version":"0.06", "description": "A library for text input via multitap/T9 style keypad", "icon": "app.png", "type":"textinput", diff --git a/apps/kbmulti/settings.js b/apps/kbmulti/settings.js index 96e72b290..08dbb1925 100644 --- a/apps/kbmulti/settings.js +++ b/apps/kbmulti/settings.js @@ -3,6 +3,7 @@ var settings = require('Storage').readJSON("kbmulti.settings.json", true) || {}; if (settings.showHelpBtn===undefined) { settings.showHelpBtn = true; } if (settings.charTimeout===undefined) { settings.charTimeout = 500; } + if (settings.autoLowercase===undefined) { settings.autoLowercase = true; } return settings; } @@ -21,6 +22,10 @@ format: v => v, onchange: v => updateSetting("charTimeout", v), }, + /*LANG*/'Lowercase after first uppercase': { + value: !!settings().autoLowercase, + onchange: v => updateSetting("autoLowercase", v) + }, /*LANG*/'Show help button?': { value: !!settings().showHelpBtn, onchange: v => updateSetting("showHelpBtn", v) diff --git a/apps/kbswipe/ChangeLog b/apps/kbswipe/ChangeLog index a7b2d44c2..38d71986e 100644 --- a/apps/kbswipe/ChangeLog +++ b/apps/kbswipe/ChangeLog @@ -5,3 +5,4 @@ 0.05: Keep drag-function in ram, hopefully improving performance and input reliability somewhat. 0.06: Support input of numbers and uppercase characters. 0.07: Support input of symbols. +0.08: Redone patterns a,e,m,w,z. diff --git a/apps/kbswipe/lib.js b/apps/kbswipe/lib.js index ea6d78255..7d05d7a8e 100644 --- a/apps/kbswipe/lib.js +++ b/apps/kbswipe/lib.js @@ -10,11 +10,11 @@ on the left of the IDE, then do a stroke and copy out the Uint8Array line */ exports.getStrokes = function(mode, cb) { if (mode === exports.INPUT_MODE_ALPHA) { - cb("a", new Uint8Array([58, 159, 58, 155, 62, 144, 69, 127, 77, 106, 86, 90, 94, 77, 101, 68, 108, 62, 114, 59, 121, 59, 133, 61, 146, 70, 158, 88, 169, 107, 176, 124, 180, 135, 183, 144, 185, 152])); + cb("a", new Uint8Array([31, 157, 33, 149, 37, 131, 42, 112, 46, 97, 49, 83, 52, 72, 56, 64, 59, 59, 63, 53, 68, 48, 74, 47, 80, 47, 88, 50, 98, 63, 109, 94, 114, 115, 116, 130, 117, 141])); cb("b", new Uint8Array([51, 47, 51, 77, 56, 123, 60, 151, 65, 163, 68, 164, 68, 144, 67, 108, 67, 76, 72, 43, 104, 51, 121, 74, 110, 87, 109, 95, 131, 117, 131, 140, 109, 152, 88, 157])); cb("c", new Uint8Array([153, 62, 150, 62, 145, 62, 136, 62, 123, 62, 106, 65, 85, 70, 65, 75, 50, 82, 42, 93, 37, 106, 36, 119, 36, 130, 40, 140, 49, 147, 61, 153, 72, 156, 85, 157, 106, 158, 116, 158])); cb("d", new Uint8Array([57, 178, 57, 176, 55, 171, 52, 163, 50, 154, 49, 146, 47, 135, 45, 121, 44, 108, 44, 97, 44, 85, 44, 75, 44, 66, 44, 58, 44, 48, 44, 38, 46, 31, 48, 26, 58, 21, 75, 20, 99, 26, 120, 35, 136, 51, 144, 70, 144, 88, 137, 110, 124, 131, 106, 145, 88, 153])); - cb("e", new Uint8Array([150, 72, 141, 69, 114, 68, 79, 69, 48, 77, 32, 81, 31, 85, 46, 91, 73, 95, 107, 100, 114, 103, 83, 117, 58, 134, 66, 143, 105, 148, 133, 148, 144, 148])); + cb("e", new Uint8Array([107, 50, 101, 46, 94, 42, 85, 40, 75, 40, 65, 40, 58, 40, 51, 40, 47, 40, 44, 43, 45, 54, 52, 68, 63, 79, 70, 84, 70, 85, 59, 89, 52, 96, 45, 108, 39, 119, 37, 126, 37, 132, 37, 137, 41, 143, 48, 147, 60, 148, 69, 148, 78, 148, 84, 148, 89, 148])); cb("f", new Uint8Array([157, 52, 155, 52, 148, 52, 137, 52, 124, 52, 110, 52, 96, 52, 83, 52, 74, 52, 67, 52, 61, 52, 57, 52, 55, 52, 52, 52, 52, 54, 52, 58, 52, 64, 54, 75, 58, 97, 59, 117, 60, 130])); cb("g", new Uint8Array([160, 66, 153, 62, 129, 58, 90, 56, 58, 57, 38, 65, 31, 86, 43, 125, 69, 152, 116, 166, 145, 154, 146, 134, 112, 116, 85, 108, 97, 106, 140, 106, 164, 106])); cb("h", new Uint8Array([58, 50, 58, 55, 58, 64, 58, 80, 58, 102, 58, 122, 58, 139, 58, 153, 58, 164, 58, 171, 58, 177, 58, 179, 58, 181, 58, 180, 58, 173, 58, 163, 59, 154, 61, 138, 64, 114, 68, 95, 72, 84, 80, 79, 91, 79, 107, 82, 123, 93, 137, 111, 145, 130, 149, 147, 150, 154, 150, 159])); @@ -22,7 +22,7 @@ exports.getStrokes = function(mode, cb) { cb("j", new Uint8Array([130, 57, 130, 61, 130, 73, 130, 91, 130, 113, 130, 133, 130, 147, 130, 156, 130, 161, 130, 164, 130, 166, 129, 168, 127, 168, 120, 168, 110, 168, 91, 167, 81, 167, 68, 167])); cb("k", new Uint8Array([149, 63, 147, 68, 143, 76, 136, 89, 126, 106, 114, 123, 100, 136, 86, 147, 72, 153, 57, 155, 45, 152, 36, 145, 29, 131, 26, 117, 26, 104, 27, 93, 30, 86, 35, 80, 45, 77, 62, 80, 88, 96, 113, 116, 130, 131, 140, 142, 145, 149, 148, 153])); cb("l", new Uint8Array([42, 55, 42, 59, 42, 69, 44, 87, 44, 107, 44, 128, 44, 143, 44, 156, 44, 163, 44, 167, 44, 169, 45, 170, 49, 170, 59, 169, 76, 167, 100, 164, 119, 162, 139, 160, 163, 159])); - cb("m", new Uint8Array([49, 165, 48, 162, 46, 156, 44, 148, 42, 138, 42, 126, 42, 113, 43, 101, 45, 91, 47, 82, 49, 75, 51, 71, 54, 70, 57, 70, 61, 74, 69, 81, 75, 91, 84, 104, 94, 121, 101, 132, 103, 137, 106, 130, 110, 114, 116, 92, 125, 75, 134, 65, 139, 62, 144, 66, 148, 83, 151, 108, 155, 132, 157, 149])); + cb("m", new Uint8Array([36, 139, 36, 120, 36, 99, 36, 79, 36, 61, 41, 45, 56, 43, 71, 46, 77, 66, 77, 93, 77, 97, 84, 69, 93, 51, 107, 47, 118, 53, 123, 79, 124, 115, 124, 140])); cb("n", new Uint8Array([50, 165, 50, 160, 50, 153, 50, 140, 50, 122, 50, 103, 50, 83, 50, 65, 50, 52, 50, 45, 50, 43, 52, 52, 57, 67, 66, 90, 78, 112, 93, 131, 104, 143, 116, 152, 127, 159, 135, 160, 141, 150, 148, 125, 154, 96, 158, 71, 161, 56, 162, 49])); cb("o", new Uint8Array([107, 58, 104, 58, 97, 61, 87, 68, 75, 77, 65, 88, 58, 103, 54, 116, 53, 126, 55, 135, 61, 143, 75, 149, 91, 150, 106, 148, 119, 141, 137, 125, 143, 115, 146, 104, 146, 89, 142, 78, 130, 70, 116, 65, 104, 62])); cb("p", new Uint8Array([29, 47, 29, 55, 29, 75, 29, 110, 29, 145, 29, 165, 29, 172, 29, 164, 30, 149, 37, 120, 50, 91, 61, 74, 72, 65, 85, 61, 103, 61, 118, 63, 126, 69, 129, 76, 130, 87, 126, 98, 112, 108, 97, 114, 87, 116])); @@ -32,10 +32,10 @@ exports.getStrokes = function(mode, cb) { cb("t", new Uint8Array([45, 55, 48, 55, 55, 55, 72, 55, 96, 55, 120, 55, 136, 55, 147, 55, 152, 55, 155, 55, 157, 55, 158, 56, 158, 60, 156, 70, 154, 86, 151, 102, 150, 114, 148, 125, 148, 138, 148, 146])); cb("u", new Uint8Array([35, 52, 35, 59, 35, 73, 35, 90, 36, 114, 38, 133, 42, 146, 49, 153, 60, 157, 73, 158, 86, 156, 100, 152, 112, 144, 121, 131, 127, 114, 132, 97, 134, 85, 135, 73, 136, 61, 136, 56])); cb("v", new Uint8Array([36, 55, 37, 59, 40, 68, 45, 83, 51, 100, 58, 118, 64, 132, 69, 142, 71, 149, 73, 156, 76, 158, 77, 160, 77, 159, 80, 151, 82, 137, 84, 122, 86, 111, 90, 91, 91, 78, 91, 68, 91, 63, 92, 61, 97, 61, 111, 61, 132, 61, 150, 61, 162, 61])); - cb("w", new Uint8Array([25, 46, 25, 82, 25, 119, 33, 143, 43, 153, 60, 147, 73, 118, 75, 91, 76, 88, 85, 109, 96, 134, 107, 143, 118, 137, 129, 112, 134, 81, 134, 64, 134, 55])); + cb("w", new Uint8Array([35, 37, 35, 44, 35, 58, 35, 81, 35, 110, 35, 129, 39, 136, 45, 140, 51, 141, 60, 137, 70, 121, 76, 99, 78, 79, 78, 70, 78, 69, 83, 89, 89, 112, 93, 127, 97, 135, 102, 136, 108, 131, 115, 116, 119, 93, 122, 72, 123, 55, 123, 43])); cb("x", new Uint8Array([56, 63, 56, 67, 57, 74, 60, 89, 66, 109, 74, 129, 85, 145, 96, 158, 107, 164, 117, 167, 128, 164, 141, 155, 151, 140, 159, 122, 166, 105, 168, 89, 170, 81, 170, 73, 169, 66, 161, 63, 141, 68, 110, 83, 77, 110, 55, 134, 47, 145])); cb("y", new Uint8Array([30, 41, 30, 46, 30, 52, 30, 63, 30, 79, 33, 92, 38, 100, 47, 104, 54, 107, 66, 105, 79, 94, 88, 82, 92, 74, 94, 77, 96, 98, 96, 131, 94, 151, 91, 164, 85, 171, 75, 171, 71, 162, 74, 146, 84, 130, 95, 119, 106, 113])); - cb("z", new Uint8Array([29, 62, 35, 62, 43, 62, 63, 62, 87, 62, 110, 62, 125, 62, 134, 62, 138, 62, 136, 63, 122, 68, 103, 77, 85, 91, 70, 107, 59, 120, 50, 132, 47, 138, 43, 143, 41, 148, 42, 151, 53, 155, 80, 157, 116, 158, 146, 158, 163, 158])); + cb("z", new Uint8Array([39, 38, 45, 38, 53, 38, 62, 38, 72, 38, 82, 38, 89, 38, 96, 38, 99, 39, 95, 48, 82, 68, 70, 87, 60, 100, 50, 117, 42, 132, 42, 140, 45, 143, 53, 143, 67, 143, 81, 143])); cb("SHIFT", new Uint8Array([100, 160, 100, 50])); } else if (mode === exports.INPUT_MODE_NUM) { cb("0", new Uint8Array([82, 50, 76, 50, 67, 50, 59, 50, 50, 51, 43, 57, 38, 68, 34, 83, 33, 95, 33, 108, 34, 121, 42, 136, 57, 148, 72, 155, 85, 157, 98, 155, 110, 149, 120, 139, 128, 127, 134, 119, 137, 114, 138, 107, 138, 98, 138, 88, 138, 77, 137, 71, 134, 65, 128, 60, 123, 58])); @@ -210,7 +210,7 @@ exports.input = function(options) { if (o.stroke!==undefined && o.xy.length >= 6 && isStrokeInside(R, o.xy)) { var ch = o.stroke; if (ch=="\b") text = text.slice(0,-1); - else if (ch==="SHIFT") { shift=!shift; Bangle.drawWidgets(); } + else if (ch==="SHIFT") { shift=!shift; WIDGETS.kbswipe.draw(); } else text += shift ? ch.toUpperCase() : ch; } lastDrag = undefined; @@ -226,7 +226,7 @@ exports.input = function(options) { shift = false; setupStrokes(); show(); - Bangle.drawWidgets(); + WIDGETS.kbswipe.draw(); } Bangle.on('stroke',strokeHandler); @@ -239,7 +239,7 @@ exports.input = function(options) { area:"tl", width: 36, // 3 chars, 6*2 px/char draw: function() { - g.reset(); + g.reset().clearRect(this.x, this.y, this.x + this.width-1, this.y + 23); g.setFont("6x8:2x3"); g.setColor("#f00"); if (input_mode === exports.INPUT_MODE_ALPHA) { @@ -251,6 +251,7 @@ exports.input = function(options) { } } }; + Bangle.drawWidgets(); return new Promise((resolve,reject) => { Bangle.setUI({mode:"custom", drag:e=>{ diff --git a/apps/kbswipe/metadata.json b/apps/kbswipe/metadata.json index 6b597a371..3f3fbffa3 100644 --- a/apps/kbswipe/metadata.json +++ b/apps/kbswipe/metadata.json @@ -1,6 +1,6 @@ { "id": "kbswipe", "name": "Swipe keyboard", - "version":"0.07", + "version":"0.08", "description": "A library for text input via PalmOS style swipe gestures (beta!)", "icon": "app.png", "type":"textinput", diff --git a/apps/locale/locale.html b/apps/locale/locale.html index 6eb0d94ea..72c862da0 100644 --- a/apps/locale/locale.html +++ b/apps/locale/locale.html @@ -156,7 +156,7 @@ exports = { name : "en_GB", currencySym:"£", "%-m": "d.getMonth()+1", "%d": "('0'+d.getDate()).slice(-2)", "%-d": "d.getDate()", - "%HH": "('0'+getHours(d)).slice(-2)", + "%HH": "getHours(d)", "%MM": "('0'+d.getMinutes()).slice(-2)", "%SS": "('0'+d.getSeconds()).slice(-2)", "%A": `${js(locale.day)}.split(',')[d.getDay()]`, @@ -191,9 +191,9 @@ function round(n, dp) { var is12; function getHours(d) { var h = d.getHours(); - if (is12===undefined) is12 = (require('Storage').readJSON('setting.json',1)||{})["12hour"]; - if (!is12) return h; - return (h%12==0) ? 12 : h%12; + if (is12 === undefined) is12 = (require('Storage').readJSON('setting.json', 1) || {})["12hour"]; + if (!is12) return ('0' + h).slice(-2); + return ((h % 12 == 0) ? 12 : h % 12).toString(); } exports = { name: ${js(locale.lang)}, diff --git a/apps/locale/locales.js b/apps/locale/locales.js index 7b3146e15..cd720ba62 100644 --- a/apps/locale/locales.js +++ b/apps/locale/locales.js @@ -1,6 +1,7 @@ /* jshint esversion: 6 */ const distanceUnits = { // how many meters per X? "m": 1, + "ft": 0.3048, "yd": 0.9144, "mi": 1609.34, "km": 1000, @@ -160,7 +161,7 @@ var locales = { currency_symbol: "$", currency_first: true, int_curr_symbol: "USD", speed: "mph", - distance: { 0: "m", 1: "mi" }, + distance: { 0: "ft", 1: "mi" }, temperature: "°F", ampm: { 0: "am", 1: "pm" }, timePattern: { 0: "%HH:%MM:%SS ", 1: "%HH:%MM" }, diff --git a/apps/messagegui/app.js b/apps/messagegui/app.js index 7ce568f82..ccc6acec6 100644 --- a/apps/messagegui/app.js +++ b/apps/messagegui/app.js @@ -84,8 +84,8 @@ function showMapMessage(msg) { if (msg.distance!==undefined) distance = require("locale").distance(msg.distance); if (msg.instr) { - if (msg.instr.includes("towards")) { - m = msg.instr.split("towards"); + if (msg.instr.includes("towards") || msg.instr.includes("toward")) { + m = msg.instr.split(/towards|toward/); target = m[0].trim(); street = m[1].trim(); }else diff --git a/apps/notify/ChangeLog b/apps/notify/ChangeLog index d7b754ff9..f40490488 100644 --- a/apps/notify/ChangeLog +++ b/apps/notify/ChangeLog @@ -9,3 +9,4 @@ 0.10: Improvements to help notifications work with themes 0.11: Fix regression that caused no notifications and corrupted background 0.12: Add Bangle.js 2 support with Bangle.setLCDOverlay +0.13: Add a default title background for the dark theme diff --git a/apps/notify/metadata.json b/apps/notify/metadata.json index 1cc8f52c1..bab68127c 100644 --- a/apps/notify/metadata.json +++ b/apps/notify/metadata.json @@ -2,7 +2,7 @@ "id": "notify", "name": "Notifications (default)", "shortName": "Notifications", - "version": "0.12", + "version": "0.13", "description": "Provides the default `notify` module used by applications to display notifications on the screen. This module is installed by default by client applications such as the Gadgetbridge app. Installing `Fullscreen Notifications` replaces this module with a version that displays the notifications using the full screen", "icon": "notify.png", "type": "notify", diff --git a/apps/notify/notify_bjs1.js b/apps/notify/notify_bjs1.js index fb56e4bbc..4ff8f88e9 100644 --- a/apps/notify/notify_bjs1.js +++ b/apps/notify/notify_bjs1.js @@ -103,7 +103,7 @@ exports.show = function(options) { b -= 2;h -= 2; // title bar if (options.title || options.src) { - g.setColor(options.titleBgColor||0x39C7).fillRect(x,y, r,y+20); + g.setColor("titleBgColor" in options ? options.titleBgColor : g.theme.dark ? 0x1 : 0x39C7).fillRect(x,y, r,y+20); const title = options.title||options.src; g.setColor(g.theme.fg).setFontAlign(-1, -1, 0).setFont("6x8", 2); g.drawString(title.trim().substring(0, 13), x+25,y+3); diff --git a/apps/notify/notify_bjs2.js b/apps/notify/notify_bjs2.js index c202e8c55..456c4e929 100644 --- a/apps/notify/notify_bjs2.js +++ b/apps/notify/notify_bjs2.js @@ -100,7 +100,7 @@ exports.show = function(options) { gg.clearRect(x,y, r,b); // title bar if (options.title || options.src) { - gg.setColor(options.titleBgColor||0x39C7).fillRect(x,y, r,y+20); + gg.setColor("titleBgColor" in options ? options.titleBgColor : g.theme.dark ? 0x1 : 0x39C7).fillRect(x,y, r,y+20); const title = options.title||options.src; gg.setColor(g.theme.fg).setFontAlign(-1, -1, 0).setFont("6x8", 2); gg.drawString(title.trim().substring(0, 13), x+25,y+3); diff --git a/apps/ohmcalc/ChangeLog b/apps/ohmcalc/ChangeLog new file mode 100644 index 000000000..af752b896 --- /dev/null +++ b/apps/ohmcalc/ChangeLog @@ -0,0 +1,3 @@ +0.01: New App! +0.02: New Results menu item to show the formula and values used. +0.03: Adds haptics to Input screen and long press "C" to go back. diff --git a/apps/ohmcalc/README.md b/apps/ohmcalc/README.md new file mode 100644 index 000000000..3add03736 --- /dev/null +++ b/apps/ohmcalc/README.md @@ -0,0 +1,22 @@ +# Ohm's Law Calculator + +This is a simple and intuitive Ohm's Law Calculator application designed for the Bangle.js 2 Smartwatch. The calculator focuses on providing an easy-to-navigate user interface with automatic calculation logic, allowing you to quickly and efficiently calculate electrical measurements, including Voltage, Current, Resistance, and Power. + +## Usage Guide + +* __Select the Electrical Measurement:__ On the main menu, select the electrical measurement that you wish to calculate (Voltage, Current, Resistance, or Power). +* __Enter Known Values:__ You will then be prompted to enter the known values. The application will present a menu listing the remaining electrical measurements. Select one and you will be taken to a numeric input screen. Repeat this step for the second known value. +* __View Results:__ Once the two known values have been entered, the application will automatically calculate and display the value of the selected electrical measurement. The Results menu will show the calculated value, the unit of measurement, and the formula used for calculation. +* __Navigation:__ Whether you're deep in calculations or perusing the results, the power to navigate the app remains in your hands. While the 'Back' button is present in most scenarios, it isn't available in the Results menu and the Input screen. However, you can always return to the beginning by selecting 'Main Menu' from the Results menu. And if you're on the numeric Input screen, simply hold down the 'Clear' button to navigate back. + +## Compatibility + +This app was built and tested with Bangle.js 2. + +## Feedback + +If you have any issues or suggestions, please open an issue on this GitHub repository. Contributions to improve the application are also welcomed. + +## Creator + +[stweedo](https://github.com/stweedo) diff --git a/apps/ohmcalc/app-icon.js b/apps/ohmcalc/app-icon.js new file mode 100644 index 000000000..091392f44 --- /dev/null +++ b/apps/ohmcalc/app-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEwxH+AH4AyQBIvmnM/AAk5F8pWBFwoABMDweGgEHF48HCAw2VE4MGDAgvCRIIACSoIvFgEGG4wuPKAYZCgENn8UgFVqsAik/hoNDC4ouUAAMOLAYoBF4Ok0gGEFwUOTJQuPAAM4DIIJCFAI2GRYM4ZRguNg7pIh0NhpXDAwIVIGBguFdwJlHABbTCgwwPCIgQB1V6dYSXGQ4zzCvWqIwhNBMBwvBvVPbokUKQQADg7BBSQUAp5FBDojANFAQmCAoTxCgEsmc0mkzlgxCMoQwBFwYFBFxgvBgAaCcYcUgEBmdfAA0zCoJiCegc4BIIvNeQwuCg4qEg8rAwowDdhwvLI4IuFLIIHFGAT4EF5rdEJAkHgImHF41fI4p2BAAYtIg8HhpGECwK7FXAQvHBQJIEikNEYIxGgE5UQh2FLw0rlYvHMAwAEnIvDGgIJDigABAwUAlhTGwAvBmgABnQMDlgfDDwRVDMAYvETocOAwSOFLwPVp6wEGAY8BC4MOBgYvMqukgENXwU0Lw3V5+kAALDFBoLaBhsA0lVF6U4F4rFBFgXP6uANoovVR5U6RQlP6ryGR6jvMnTqCYQJeGd6AwBBIYAFRIIiEeQheGr7FBDxE5FwZgCCQMHhqkBIwZTGYYYKGRwJ4DDIMNEYIoCF4YxEAAIWEMBAIBLxhIBAAYtFGYwXEnAmHF4JeFA4J4EAwIrLF5JICGAq9GE4J2EF6JsBI4UNhx5EYY67CFwcOhp3DGB0AFQTQCAoU4AwUsmYAClgHBg5EChwGCAoaNQE4N6p4wDMQIxCAAcHRYYoBp96DoouLgyjE1QZBPYQAEnDmEAAUNIoOqbYkGGBTsFCIL0GABhsCJoZgOFAkHFxEOAASZDNwYVFFxgwHdogJCii+DXoIGCgyXGFxwwHboIoG0mkAwgWBgBnDFyIwFVYQICQgIoBqtVF4TrBBoQXFFyAwDeAI4GnJmDnImGSYIuUJQbKMKxAXGAC4eBF44oeGBCJBAAiVBF0hgCAA4vlGI4tnADg=")) diff --git a/apps/ohmcalc/app.js b/apps/ohmcalc/app.js new file mode 100644 index 000000000..aa470401c --- /dev/null +++ b/apps/ohmcalc/app.js @@ -0,0 +1,373 @@ +let Layout = require("Layout"); + +// Definitions for units and formulas for electrical measurements. +const UNITS = { + "Voltage (V)": "Volts", + "Current (I)": "Amps", + "Resistance (R)": "Ohms", + "Power (P)": "Watts", +}; + +const FORMULAS = { + 'Voltage (V)': { + 'Current (I), Resistance (R)': { equation: "{0} * {1}", display: "V = I * R" }, + 'Power (P), Current (I)': { equation: "{0} / {1}", display: "V = P / I" }, + 'Power (P), Resistance (R)': { equation: "Math.sqrt({0} * {1})", display: "V = sqrt(P * R)" } + }, + 'Current (I)': { + 'Voltage (V), Resistance (R)': { equation: "{0} / {1}", display: "I = V / R" }, + 'Power (P), Voltage (V)': { equation: "{0} / {1}", display: "I = P / V" }, + 'Power (P), Resistance (R)': { equation: "Math.sqrt({0} / {1})", display: "I = sqrt(P / R)" } + }, + 'Resistance (R)': { + 'Voltage (V), Current (I)': { equation: "{0} / {1}", display: "R = V / I" }, + 'Power (P), Current (I)': { equation: "{0} / (Math.pow({1}, 2))", display: "R = P / I^2" }, + 'Power (P), Voltage (V)': { equation: "(Math.pow({1}, 2)) / {0}", display: "R = V^2 / P" } + }, + 'Power (P)': { + 'Voltage (V), Current (I)': { equation: "{0} * {1}", display: "P = V * I" }, + 'Current (I), Resistance (R)': { equation: "(Math.pow({0}, 2)) * {1}", display: "P = I^2 * R" }, + 'Voltage (V), Resistance (R)': { equation: "(Math.pow({0}, 2)) / {1}", display: "P = V^2 / R" } + }, +}; + +// Screen positioning settings +let btnSize = 23; +let xCenter = g.getWidth() / 2; +let yCenter = g.getHeight() / 2; + +// Variables to hold state +let lastStringWidth = 0; +let halfStringWidth = lastStringWidth / 2; +let calculatedVariable; +let selectedVariable; +let variableValues = {}; +let inputStr = ""; +let invalidInput = false; + +// Function references +let handleEnter; +let showVariableSelectionMenu; +let showInputMenu; +let resultsMenu; +let mainMenu; + +// Setup the layout for input buttons +let layout = new Layout({ + type: "v", + c: [ + { type: "txt", font: "6x8:3", label: "", id: "label" }, + { type: "h", c: "123".split("").map(i => ({ type: "btn", font: "6x8:3", label: i, cb: () => { handleButtonPress(i); }, fillx: 1, filly: 1 })) }, + { type: "h", c: "456".split("").map(i => ({ type: "btn", font: "6x8:3", label: i, cb: () => { handleButtonPress(i); }, fillx: 1, filly: 1 })) }, + { type: "h", c: "789".split("").map(i => ({ type: "btn", font: "6x8:3", label: i, cb: () => { handleButtonPress(i); }, fillx: 1, filly: 1 })) }, + { type: "h", c: ".0C".split("").map(i => ({ type: "btn", font: "6x8:3", label: i, cb: () => { handleButtonPress(i); }, cbl: i === "C" ? () => { showVariableSelectionMenu(); Bangle.buzz(20); } : undefined, fillx: 1, filly: 1 })) }, + { type: "h", c: [{ type: "btn", font: "6x8:2", label: "Enter", cb: () => { handleEnter(); }, fillx: 1, filly: 1 }] } + ] +}, { lazy: false }); + +// Clears area at the top of the screen where the text is displayed +function clearTextArea() { // Except Back Button + let x2 = g.getWidth(); + g.clearRect(0, 0, x2, btnSize); +} + +// Function to clear the entire screen, except for the Back button +function clearScreen() { + let x2 = g.getWidth(); + let y2 = g.getHeight(); + g.clearRect(btnSize, 0, x2, btnSize); + g.clearRect(0, btnSize, x2, y2); +} + +// Function to set up and display the calculator input screen +function showCalculatorInputScreen(variable) { + selectedVariable = variable; + layout.setUI(); + layout.render(); +} + +// Function to set and display the current value of the input +// Adjusts the font size to fit the screen width +function setValue(newStr) { + clearTextArea(); + inputStr = newStr; + + // Here we check the width of the string and adjust the font size + // Start with a standard font size and increase if the string is too wide + let fontSize = "6x8:3"; // start with a standard size + g.setFont(fontSize); + + // If the string is too long to fit on the screen, adjust the font size + while (g.stringWidth(inputStr) > g.getWidth() - 10 && fontSize !== "6x8:1") { + fontSize = "6x8:" + (Number(fontSize.split(":")[1]) - 1).toString(); + g.setFont(fontSize); + } + + layout.label.label = inputStr; + g.drawString(inputStr, layout.label.x, layout.label.y + 11); + lastStringWidth = g.stringWidth(inputStr); +} + +// Function to handle the press of a button and append its value to the current input +function handleButtonPress(value) { + Bangle.buzz(20); + if (invalidInput) { + return; // Don't allow input if an invalid input error message is displayed + } + inputStr = value === 'C' ? '' : inputStr + value; + setValue(inputStr); +} + +// Function to format the unit of measurement +function formatUnit(unit, value) { + return parseFloat(value) === 1 ? unit.slice(0, -1) : unit; +} + +// Calculates the value of the selected variable based on the entered values +// Also handles rounding and trimming of long decimal numbers +function calculateValue(calculatedVariable, variableValues) { + let formulas = FORMULAS[calculatedVariable]; + let formulaKeys = Object.keys(formulas); + for (let i = 0; i < formulaKeys.length; i++) { + let formulaKey = formulaKeys[i]; + let variables = formulaKey.split(', '); + if (variables.every(variable => variableValues.hasOwnProperty(variable))) { + let formulaData = formulas[formulaKey]; + let formula = formulaData.equation; + let formulaDisplay = formulaData.display; + let formulaValues = variables.map(variable => variableValues[variable]); + let calculatedValue = eval(formula.replace(/\{(\d+)\}/g, (_, index) => formulaValues[index])); + + // Check if the number is an integer + let isInteger = Math.floor(calculatedValue) === calculatedValue; + + // Round and trim long decimal numbers + if (!isInteger) { + calculatedValue = calculatedValue.toFixed(3); + calculatedValue = calculatedValue.replace(/\.0+$/, '').replace(/(\.\d*[1-9])0+$/, '$1'); + } + + let result = Object.entries(variableValues).map(function (entry) { + let variable = entry[0]; + let value = entry[1]; + return [variable, `${value} ${formatUnit(UNITS[variable], value.toString())}`]; + }); + result.push([calculatedVariable, `${calculatedValue} ${formatUnit(UNITS[calculatedVariable], calculatedValue.toString())}`]); + return { + formula: formulaDisplay.replace(/\{(\d+)\}/g, (_, index) => formulaValues[index]), + value: calculatedValue, + unit: formatUnit(UNITS[calculatedVariable], calculatedValue), + result: result, + }; + } + } +} + +// Main function to initialize the application and setup the main menu +(function () { + mainMenu = { + '': { 'title': 'Ohm\'s Law Calc' }, + '< Back': () => Bangle.showClock() + }; + + Object.keys(UNITS).forEach(unit => { + mainMenu[unit] = () => handleUnitSelection(unit); + }); + + // Function to present the menu for selecting the variable + // Filters out the calculated variable and already set variables from the menu + showVariableSelectionMenu = function () { + clearScreen(); + let variableSelectionMenu = { + '': { 'title': 'Select Variable' }, + '< Back': () => { + E.showMenu(mainMenu); + variableValues = {}; + } + }; + let variables = Object.keys(UNITS); + let remainingVariables = variables.filter(v => v !== calculatedVariable && !variableValues[v]); + remainingVariables.forEach(variable => { + variableSelectionMenu[variable] = function () { + showInputMenu(variable); + }; + }); + E.showMenu(variableSelectionMenu); + }; + + // Function to handle the input of variable values + // It sets the current selected variable and displays the calculator input screen + showInputMenu = function (variable) { + setValue(""); + selectedVariable = variable; + let inputMenu = { + '': { 'title': variable }, + }; + E.showMenu(inputMenu); + showCalculatorInputScreen(variable); + }; + + // Function to handle the event of pressing 'Enter' + // It checks if the input is valid, if so, it saves the value and + // either calculates the result (if enough variables are present) or opens variable selection menu + handleEnter = function () { + // Check if the input is valid + if (inputStr === "" || isNaN(inputStr) || (inputStr.match(/\./g) || []).length > 1) { + // Show error message + setValue("Invalid Input"); + invalidInput = true; // Prevent further input + // Clear error message after 2 seconds + setTimeout(() => { + setValue(''); + invalidInput = false; // Allow input again + }, 2000); + return; + } + + if (calculatedVariable === null) { + return; + } + variableValues[selectedVariable] = parseFloat(inputStr); + if (Object.keys(variableValues).length === 2) { + let result = calculateValue(calculatedVariable, variableValues); + showResultsScreen(result); + calculatedVariable = null; + variableValues = {}; + } else { + setValue(""); + showVariableSelectionMenu(); + return; + } + return; + }; + + // Function to handle the selection of a unit of electical measurement + function handleUnitSelection(unit) { + calculatedVariable = unit; + showVariableSelectionMenu(); + } + + // Function to display the results screen with the calculated value + function drawValueScreen + (result) { + let drawPage = function () { + clearScreen(); + let fontSize = g.getHeight() / 3; + g.setFontVector(fontSize); + + // Reduce the font size until both the value and unit fit on the screen + while (g.stringWidth(result.value) > g.getWidth() - 10 || g.getFontHeight() > g.getHeight() / 2) { + fontSize--; + g.setFontVector(fontSize); + } + + let valueY = yCenter - g.getFontHeight() / 2; + let unitY = yCenter + g.getFontHeight() / 2; + let valueWidth = g.stringWidth(result.value); + let unitWidth = g.stringWidth(result.unit); + let valueX = (g.getWidth() - valueWidth) / 2; + let unitX = (g.getWidth() - unitWidth) / 2; + + g.drawString(result.value, valueX, valueY); + g.drawString(result.unit, unitX, unitY); + g.flip(); + }; + + // Shows the back button on the value screen + return function () { + clearScreen(); + let valueMenu = { + '': { 'title': 'Results' }, + '< Back': function () { + E.showMenu(resultsMenu); + } + }; + E.showMenu(valueMenu); + drawPage(); + }; + } + + // Function to display the results screen with the values from the result.result array + function drawResultScreen(result) { + let drawPage = function() { + clearScreen(); + let fontSize = 30; // Initial font size + let lineSpacing = 15; // Space between lines + + // Define the vertical positions of the titles + let titlePositions = [10, 72, 132]; + + for (let i = 0; i < result.result.length; i++) { + let currentResult = result.result[i]; + let resultTitle = currentResult[0]; + let resultValue = currentResult[1]; + + // Draw title + g.setFontVector(fontSize / 2); // Small font for title + let titleX = (g.getWidth() - g.stringWidth(resultTitle)) / 2; + let titleY = titlePositions[i]; // Get the vertical position for the title + g.drawString(resultTitle, titleX, titleY); // Draw at the desired position + + let underlineYPosition = titleY + g.getFontHeight() - 3; + g.drawLine(titleX, underlineYPosition, titleX + g.stringWidth(resultTitle), underlineYPosition); // Draw underline + + let valueX; + let valueY = titleY + g.getFontHeight(); // Draw below the title + + // Calculate the font size for value dynamically + g.setFontVector(fontSize); + while (g.stringWidth(resultValue) > g.getWidth() - 10) { + fontSize--; // Reduce the font size by 1 + g.setFontVector(fontSize); + } + + valueY += g.getFontHeight() / 2 + 2; + valueX = (g.getWidth() - g.stringWidth(resultValue)) / 2; + g.drawString(resultValue, valueX, valueY); // Draw at the desired position + + // Move down for the next entry + let nextTitleY = (i + 1 < titlePositions.length) ? titlePositions[i + 1] : titleY + 1.5 * fontSize + lineSpacing; + yPosition = nextTitleY; + } + g.flip(); + }; + + // Shows the back button on the result screen + return function() { + clearScreen(); + let resultMenu = { + '': { 'title': 'Results' }, + '< Back': function() { + E.showMenu(resultsMenu); + } + }; + E.showMenu(resultMenu); + drawPage(); + }; + } + + // Shows the results menu with the calculated results and options + function showResultsScreen(result) { + let backButton = function () { + clearScreen(); + E.showMenu(resultsMenu); + }; + g.clear(); + resultsMenu = { + '': { 'title': 'Results' }, + 'Main Menu': function () { + E.showMenu(mainMenu); + }, + }; + resultsMenu[result.value + ' ' + result.unit] = drawValueScreen(result); + resultsMenu[result.formula] = drawResultScreen(result); + resultsMenu['Exit'] = function () { + Bangle.showClock(); + }; + E.showMenu(resultsMenu); + } + + clearTextArea(); + E.showMenu(mainMenu); + +})(); diff --git a/apps/ohmcalc/app.png b/apps/ohmcalc/app.png new file mode 100644 index 000000000..65d6c2bf2 Binary files /dev/null and b/apps/ohmcalc/app.png differ diff --git a/apps/ohmcalc/metadata.json b/apps/ohmcalc/metadata.json new file mode 100644 index 000000000..de81ec6d7 --- /dev/null +++ b/apps/ohmcalc/metadata.json @@ -0,0 +1,23 @@ +{ + "id": "ohmcalc", + "name": "Ohm's Law Calculator", + "shortName": "Ohm's Law Calc", + "version": "0.03", + "description": "A smart and simple calculator for Ohm's Law calculations, designed specifically for Bangle.js 2 smartwatches. Handles voltage, current, resistance, and power calculations with smart logic to prevent invalid inputs.", + "icon": "app.png", + "type": "app", + "tags": "calculator, utilities, electric", + "supports": ["BANGLEJS2"], + "readme": "README.md", + "allow_emulator": true, + "storage": [{ + "name": "ohmcalc.app.js", + "url": "app.js" + }, + { + "name": "ohmcalc.img", + "url": "app-icon.js", + "evaluate": true + } + ] +} diff --git a/apps/rescalc/ChangeLog b/apps/rescalc/ChangeLog index 5560f00bc..43f41aa76 100644 --- a/apps/rescalc/ChangeLog +++ b/apps/rescalc/ChangeLog @@ -1 +1,2 @@ 0.01: New App! +0.02: Fixes colors not matching user input from color menu in some cases, 3 bands are now shown larger, various code improvements. \ No newline at end of file diff --git a/apps/rescalc/app.js b/apps/rescalc/app.js index cfb88e015..566809837 100644 --- a/apps/rescalc/app.js +++ b/apps/rescalc/app.js @@ -3,75 +3,19 @@ // https://icons8.com/icon/ISAVBnskZod0/resistor let colorData = { - black: { - value: 0, - multiplier: Math.pow(10, 0), - hex: '#000' - }, - brown: { - value: 1, - multiplier: Math.pow(10, 1), - tolerance: 1, - hex: '#8B4513' - }, - red: { - value: 2, - multiplier: Math.pow(10, 2), - tolerance: 2, - hex: '#f00' - }, - orange: { - value: 3, - multiplier: Math.pow(10, 3), - hex: '#FF9900' - }, - yellow: { - value: 4, - multiplier: Math.pow(10, 4), - hex: '#ff0' - }, - green: { - value: 5, - multiplier: Math.pow(10, 5), - tolerance: 0.5, - hex: '#0f0' - }, - blue: { - value: 6, - multiplier: Math.pow(10, 6), - tolerance: 0.25, - hex: '#00f' - }, - violet: { - value: 7, - multiplier: Math.pow(10, 7), - tolerance: 0.1, - hex: '#f0f' - }, - grey: { - value: 8, - multiplier: Math.pow(10, 8), - tolerance: 0.05, - hex: '#808080' - }, - white: { - value: 9, - multiplier: Math.pow(10, 9), - hex: '#fff' - }, - gold: { - multiplier: Math.pow(10, -1), - tolerance: 5, - hex: '#FFD700' - }, - silver: { - multiplier: Math.pow(10, -2), - tolerance: 10, - hex: '#C0C0C0' - }, - none: { - tolerance: 20 - }, + black: { value: 0, multiplier: 1, hex: '#000' }, + brown: { value: 1, multiplier: 10, tolerance: 1, hex: '#8B4513' }, + red: { value: 2, multiplier: 100, tolerance: 2, hex: '#f00' }, + orange: { value: 3, multiplier: 1000, hex: '#FF9900' }, + yellow: { value: 4, multiplier: 10000, hex: '#ff0' }, + green: { value: 5, multiplier: 100000, tolerance: 0.5, hex: '#0f0' }, + blue: { value: 6, multiplier: 1000000, tolerance: 0.25, hex: '#00f' }, + violet: { value: 7, multiplier: 10000000, tolerance: 0.1, hex: '#f0f' }, + grey: { value: 8, multiplier: 100000000, tolerance: 0.05, hex: '#808080' }, + white: { value: 9, multiplier: 1000000000, hex: '#fff' }, + gold: { multiplier: 0.1, tolerance: 5, hex: '#FFD700' }, + silver: { multiplier: 0.01, tolerance: 10, hex: '#C0C0C0' }, + none: { tolerance: 20 }, }; function clearScreen() { // Except Back Button @@ -91,38 +35,55 @@ function colorBandsToResistance(colorBands) { return [resistance, tolerance]; } -function log10(val) { - return Math.log(val) / Math.log(10); -} - function resistanceToColorBands(resistance, tolerance) { - let multiplier = Math.floor(log10(resistance)); - let firstDigit = Math.floor(resistance / Math.pow(10, multiplier)); - resistance -= firstDigit * Math.pow(10, multiplier); - multiplier--; // for the next digit - let secondDigit = Math.floor(resistance / Math.pow(10, multiplier)); - resistance -= secondDigit * Math.pow(10, multiplier); - console.log("First Digit: " + firstDigit); - console.log("Second Digit: " + secondDigit); - console.log("Multiplier: " + multiplier); - let firstBandEntry = Object.entries(colorData).find(function(entry) { + let firstDigit, secondDigit, multiplier; + if (resistance < 1) { + // The resistance is less than 1, so we need to handle this case specially + let count = 0; + while (resistance < 1) { + resistance *= 10; + count++; + } + // Now, resistance is a whole number and count is how many times we had to multiply by 10 + let resistanceStr = resistance.toString(); + firstDigit = 0; // Set the first band color to be black + secondDigit = Number(resistanceStr.charAt(0)); // Set the second band color to be the significant digit + // Use count to determine the multiplier + multiplier = count === 1 ? 0.1 : 0.01; + } else { + // Convert the resistance to a string so we can manipulate it easily + let resistanceStr = resistance.toString(); + if (resistanceStr.length === 1) { // Check if resistance is a single digit + firstDigit = 0; + secondDigit = Number(resistanceStr.charAt(0)); + multiplier = 1; // Set multiplier to 1 for single digit resistance values + } else { + // Extract the first two digits from the resistance value + firstDigit = Number(resistanceStr.charAt(0)); + secondDigit = Number(resistanceStr.charAt(1)); + // Calculate the multiplier by matching it directly with the length of digits + multiplier = resistanceStr.length - 2 >= 0 ? Math.pow(10, resistanceStr.length - 2) : Math.pow(10, resistanceStr.length - 1); + } + } + let firstBandEntry = Object.entries(colorData).find(function (entry) { return entry[1].value === firstDigit; }); let firstBand = firstBandEntry ? firstBandEntry[1].hex : undefined; - let secondBandEntry = Object.entries(colorData).find(function(entry) { + let secondBandEntry = Object.entries(colorData).find(function (entry) { return entry[1].value === secondDigit; }); let secondBand = secondBandEntry ? secondBandEntry[1].hex : undefined; - let multiplierBandEntry = Object.entries(colorData).find(function(entry) { - return entry[1].multiplier === Math.pow(10, multiplier); + let multiplierBandEntry = Object.entries(colorData).find(function (entry) { + return entry[1].multiplier === multiplier; }); let multiplierBand = multiplierBandEntry ? multiplierBandEntry[1].hex : undefined; - let toleranceBandEntry = Object.entries(colorData).find(function(entry) { + let toleranceBandEntry = Object.entries(colorData).find(function (entry) { return entry[1].tolerance === tolerance; }); let toleranceBand = toleranceBandEntry ? toleranceBandEntry[1].hex : undefined; - console.log("Color bands: " + [firstBand, secondBand, multiplierBand, toleranceBand]); - return [firstBand, secondBand, multiplierBand, toleranceBand]; + let bands = [firstBand, secondBand, multiplierBand]; + if (toleranceBand) bands.push(toleranceBand); + return bands; } function drawResistor(colorBands, tolerance) { @@ -130,32 +91,27 @@ function drawResistor(colorBands, tolerance) { let resistorBodyWidth = 51; let resistorBodyHeight = 43; let resistorStartX = 52; - var bandColors = colorBands; - var numcolorBands = bandColors.length; - var resistorStartY = ((g.getHeight() - resistorBodyHeight) / 2) + 48; + let bandColors = colorBands; + let numColorBands = bandColors.length; + let resistorStartY = ((g.getHeight() - resistorBodyHeight) / 2) + 48; clearScreen(); g.drawImage(img, 0, 112); - var bandWidth = (resistorBodyWidth - (numcolorBands * 2 - 1)) / numcolorBands; // width of each band, accounting for the spacing - var bandHeight = resistorBodyHeight; // height of each band - var currentX = resistorStartX; // starting point for the first band + let bandWidth = (resistorBodyWidth - (numColorBands * 2 - 1)) / numColorBands; // width of each band, accounting for the spacing + let bandHeight = resistorBodyHeight; // height of each band + let currentX = resistorStartX; // starting point for the first band // Define the tolerance values that will trigger the fourth band - var validTolerances = [1, 2, 0.5, 0.25, 0.1, 0.05, 5, 10]; - - for (var i = 0; i < numcolorBands; i++) { + let validTolerances = [1, 2, 0.5, 0.25, 0.1, 0.05, 5, 10]; + for (let i = 0; i < numColorBands; i++) { // Skip the fourth band and its outlines if the tolerance is not in the valid list if (i === 3 && !validTolerances.includes(tolerance)) continue; - - var bandX = currentX; // calculate the x-coordinate of the band - var bandY = resistorStartY; // y-coordinate of the band - + let bandX = currentX; // calculate the x-coordinate of the band + let bandY = resistorStartY; // y-coordinate of the band g.setColor(bandColors[i]); // set the color for the band g.fillRect(bandX, bandY, bandX + bandWidth, bandY + bandHeight); - // Draw band outlines g.setColor('#000'); // set the color for the outline g.drawLine(bandX, bandY, bandX, bandY + bandHeight); // left outline g.drawLine(bandX + bandWidth, bandY, bandX + bandWidth, bandY + bandHeight); // right outline - // if it's the fourth band, shift it over by an additional 12 pixels if (i === 2) { currentX = bandX + bandWidth + 5 + 12; // update the current X position for the next band, accounting for the spacing @@ -170,14 +126,14 @@ function omega() { } function formatResistance(resistance) { - var units = ["", "k", "M", "G"]; - var unitIndex = 0; + let units = ["", "k", "M", "G"]; + let unitIndex = 0; while (resistance >= 1000 && unitIndex < units.length - 1) { resistance /= 1000; unitIndex++; } // Convert to string and truncate unnecessary zeroes - var resistanceStr = String(resistance); + let resistanceStr = String(resistance); if (resistanceStr.length > 5) { // if length is more than 5 including decimal point resistanceStr = resistance.toFixed(2); } @@ -188,49 +144,40 @@ function formatResistance(resistance) { } function drawResistance(resistance, tolerance) { - var x = g.getWidth() / 2; - var y = 40; - var formattedResistance = formatResistance(resistance); - var resistanceStr = formattedResistance.value; - var unit = formattedResistance.unit; + let x = g.getWidth() / 2; + let y = 40; + let formattedResistance = formatResistance(resistance); + let resistanceStr = formattedResistance.value; + let unit = formattedResistance.unit; g.reset(); - // draw resistance value g.setFontAlign(0, 0).setFont("Vector", 54); g.clearRect(0, y - 15, g.getWidth(), y + 25); // clear the background g.drawString(resistanceStr, x + 4, y); - - // draw unit, symbol and tolerance + // draw unit, symbol, and tolerance y += 46; g.setFontAlign(-1, 0).setFont("Vector", 27); - - var toleranceShift = tolerance.toString().replace('.', '').length > 2 ? 8 : 0; - var unitX = ((unit === "M" || unit === "G") ? 0 : 8) - toleranceShift; - var omegaX = (unit ? 46 : 36) - toleranceShift; // Shift the Omega symbol to the left if there is no unit - + let toleranceShift = tolerance.toString().replace('.', '').length > 2 ? 8 : 0; + let unitX = ((unit === "M" || unit === "G") ? 0 : 8) - toleranceShift; + let omegaX = (unit ? 46 : 36) - toleranceShift; // Shift the Omega symbol to the left if there is no unit g.drawString(unit.padStart(3), unitX, y); - // Draw the Ohm symbol to the right of the unit - g.drawImage(omega(), omegaX, y - 13, { - scale: 0.45 - }); - + g.drawImage(omega(), omegaX, y - 13, { scale: 0.45 }); g.setFontAlign(1, 0).setFont("Vector", 27); - // Define the tolerance values that will trigger the fourth band - var validTolerances = [1, 2, 0.5, 0.25, 0.1, 0.05, 5, 10]; - + let validTolerances = [1, 2, 0.5, 0.25, 0.1, 0.05, 5, 10]; // Check if the tolerance is not in the valid list, and if it's not, set it to 20 if (!validTolerances.includes(tolerance)) { tolerance = 20; } - - var toleranceStr = "±" + tolerance + "%"; - var toleranceX = tolerance.toString().replace('.', '').length > 2 ? 10 : 14; + let toleranceStr = "±" + tolerance + "%"; + let toleranceX = tolerance.toString().replace('.', '').length > 2 ? 10 : 14; g.drawString(toleranceStr.padEnd(4), 176 - toleranceX, y); } -(function() { +(function () { + let colorBands; + let inputColorBands; let settings = { resistance: 0, tolerance: 0, @@ -244,6 +191,8 @@ function drawResistance(resistance, tolerance) { colorBands: ["", "", "", ""] }; settings = emptySettings; + colorBands = null; + inputColorBands = null; } function showColorBandMenu(bandNumber) { @@ -251,7 +200,7 @@ function drawResistance(resistance, tolerance) { '': { 'title': `Band ${bandNumber}` }, - '< Back': function() { + '< Back': function () { E.showMenu(colorEntryMenu); }, }; @@ -260,24 +209,24 @@ function drawResistance(resistance, tolerance) { for (let color in colorData) { if (bandNumber === 1 || bandNumber === 2) { if (color !== 'none' && color !== 'gold' && color !== 'silver') { - (function(color) { - colorBandMenu[color.charAt(0).toUpperCase() + color.slice(1)] = function() { + (function (color) { + colorBandMenu[color.charAt(0).toUpperCase() + color.slice(1)] = function () { setBandColor(bandNumber, color); }; })(color); } } else if (bandNumber === 3) { if (color !== 'none') { - (function(color) { - colorBandMenu[color.charAt(0).toUpperCase() + color.slice(1)] = function() { + (function (color) { + colorBandMenu[color.charAt(0).toUpperCase() + color.slice(1)] = function () { setBandColor(bandNumber, color); }; })(color); } } else if (bandNumber === 4) { if (colorData[color].hasOwnProperty('tolerance')) { - (function(color) { - colorBandMenu[color.charAt(0).toUpperCase() + color.slice(1)] = function() { + (function (color) { + colorBandMenu[color.charAt(0).toUpperCase() + color.slice(1)] = function () { setBandColor(bandNumber, color); }; })(color); @@ -289,11 +238,8 @@ function drawResistance(resistance, tolerance) { function setBandColor(bandNumber, color) { settings.colorBands[bandNumber - 1] = color; // arrays are 0-indexed - console.log(`Band ${bandNumber} color set to ${color}`); - // Update the color band in the colorEntryMenu colorEntryMenu[`${bandNumber}:`].value = color; - showColorEntryMenu(); } @@ -302,7 +248,7 @@ function drawResistance(resistance, tolerance) { '': { 'title': 'Band Color' }, - '< Back': function() { + '< Back': function () { clearScreen(); E.showMenu(mainMenu); }, @@ -338,9 +284,9 @@ function drawResistance(resistance, tolerance) { setTimeout(() => showColorBandMenu(4), 5); } }, - 'Draw Resistor': function() { - let colorBands = settings.colorBands; - let values = colorBandsToResistance(colorBands); + 'Draw Resistor': function () { + inputColorBands = settings.colorBands; + let values = colorBandsToResistance(inputColorBands); settings.resistance = values[0]; settings.tolerance = values[1]; showDrawingMenu(); @@ -355,7 +301,7 @@ function drawResistance(resistance, tolerance) { '': { 'title': 'Multiplier' }, - '< Back': function() { + '< Back': function () { showResistanceEntryMenu(); } }; @@ -367,9 +313,8 @@ function drawResistance(resistance, tolerance) { let formattedMultiplier = formatMultiplier(multiplierValue); multiplierMenu[`${formattedMultiplier}`] = () => { settings.multiplier = multiplierValue; - console.log(`Multiplier changed to: ${settings.multiplier}`); // Update the value of 'Multiplier' in resistanceEntryMenu - resistanceEntryMenu["Multiplier"] = function() { + resistanceEntryMenu["Multiplier"] = function () { showMultiplierMenu(); }; showResistanceEntryMenu(); @@ -395,7 +340,7 @@ function drawResistance(resistance, tolerance) { '': { 'title': 'Tolerance' }, - '< Back': function() { + '< Back': function () { showResistanceEntryMenu(); } }; @@ -406,22 +351,29 @@ function drawResistance(resistance, tolerance) { let tolerance = parseFloat(colorData[color].tolerance); // Parse the tolerance as a float toleranceMenu[`${tolerance}%`] = () => { settings.tolerance = tolerance; - console.log(settings.tolerance); // Update the value of 'Tolerance (%)' in resistanceEntryMenu - resistanceEntryMenu["Tolerance (%)"] = function() { + resistanceEntryMenu["Tolerance (%)"] = function () { showToleranceMenu(); }; showResistanceEntryMenu(); }; } } - E.showMenu(toleranceMenu); } - function drawResistorAndResistance(resistance, tolerance, multipliedResistance) { - console.log('Draw Resistor clicked'); - let colorBands = resistanceToColorBands(multipliedResistance || resistance, tolerance); + function drawResistorAndResistance(resistance, tolerance) { + if (inputColorBands) { + colorBands = inputColorBands.map(color => { + if (colorData.hasOwnProperty(color)) { + return colorData[color].hex; + } else { + return; + } + }); + } else { + colorBands = resistanceToColorBands(resistance, tolerance); + } drawResistor(colorBands, tolerance); drawResistance(resistance, tolerance); resetSettings(); @@ -431,7 +383,7 @@ function drawResistance(resistance, tolerance) { '': { 'title': 'Resistance' }, - '< Back': function() { + '< Back': function () { clearScreen(); E.showMenu(mainMenu); }, @@ -441,15 +393,15 @@ function drawResistance(resistance, tolerance) { max: 99, wrap: true, format: v => '', - onchange: v => {} + onchange: v => { } }, - 'Multiplier': function() { + 'Multiplier': function () { showMultiplierMenu(); }, - 'Tolerance (%)': function() { + 'Tolerance (%)': function () { showToleranceMenu(); }, - 'Draw Resistor': function() { + 'Draw Resistor': function () { showDrawingMenu(); } }; @@ -469,25 +421,21 @@ function drawResistance(resistance, tolerance) { }; resistanceEntryMenu['Ohms'].onchange = v => { settings.resistance = v || 0; - console.log('Resistance changed to: ', settings.resistance); }; - E.showMenu(resistanceEntryMenu); } function showDrawingMenu() { let drawingMenu = { '': { - 'title': 'Resistor Drawing' + 'title': '' }, - '< Back': function() { + '< Back': function () { clearScreen(); E.showMenu(mainMenu); }, }; - E.showMenu(drawingMenu); - let resistance = settings.resistance * (settings.multiplier || 1); let tolerance = settings.tolerance; drawResistorAndResistance(resistance, tolerance); @@ -498,14 +446,14 @@ function drawResistance(resistance, tolerance) { 'title': 'Resistor Calc' }, '< Back': () => Bangle.showClock(), // return to the clock app - 'Resistance': function() { + 'Resistance': function () { resetSettings(); showResistanceEntryMenu(); }, - 'Colors': function() { + 'Colors': function () { resetSettings(); showColorEntryMenu(); }, }; E.showMenu(mainMenu); -})(); +})(); \ No newline at end of file diff --git a/apps/rescalc/metadata.json b/apps/rescalc/metadata.json index c14bbd6e5..aaa5f2acf 100644 --- a/apps/rescalc/metadata.json +++ b/apps/rescalc/metadata.json @@ -3,7 +3,7 @@ "name": "Resistor Calculator", "shortName": "Resistor Calc", "icon": "rescalc.png", - "version":"0.01", + "version":"0.02", "screenshots": [ {"url": "screenshot.png"}, {"url": "screenshot-1.png"}, diff --git a/apps/run/ChangeLog b/apps/run/ChangeLog index e79696c78..ab2803ec6 100644 --- a/apps/run/ChangeLog +++ b/apps/run/ChangeLog @@ -14,4 +14,4 @@ 0.13: Revert #1578 (stop duplicate entries) as with 2v12 menus it causes other boxes to be wiped (fix #1643) 0.14: Fix Bangle.js 1 issue where after the 'overwrite track' menu, the start/stop button stopped working 0.15: Keep run state between runs (allowing you to exit and restart the app) -0.16: Added ability to resume a run that was stopped previously (fix #1907) \ No newline at end of file +0.16: Added ability to resume a run that was stopped previously (fix #1907) diff --git a/apps/runplus/ChangeLog b/apps/runplus/ChangeLog index d920a3eca..05d24b96d 100644 --- a/apps/runplus/ChangeLog +++ b/apps/runplus/ChangeLog @@ -13,10 +13,12 @@ 0.12: Fix for recorder not stopping at end of run. Bug introduced in 0.11 0.13: Revert #1578 (stop duplicate entries) as with 2v12 menus it causes other boxes to be wiped (fix #1643) 0.14: Fix Bangle.js 1 issue where after the 'overwrite track' menu, the start/stop button stopped working -0.15: Diverge from the standard "Run" app. Swipe to intensity interface a la Karvonen (curtesy of FTeacher at https://github.com/f-teacher) - Keep run state between runs (allowing you to exit and restart the app) -0.16: Don't clear zone 2b indicator segment when updating HRM reading. - Write to correct settings file, fixing settings not working. -0.17: Fix typo in variable name preventing starting a run. -0.18: Tweak HRM min/max defaults. Extend min/max intervals in settings. Fix - another typo. +0.15: Keep run state between runs (allowing you to exit and restart the app) +0.16: Added ability to resume a run that was stopped previously (fix #1907) +0.17: Diverge from the standard "Run" app. Swipe to intensity interface a la Karvonen (curtesy of FTeacher at https://github.com/f-teacher) +0.18: Don't clear zone 2b indicator segment when updating HRM reading. +Write to correct settings file, fixing settings not working. +0.19: Fix typo in variable name preventing starting a run +0.20: Tweak HRM min/max defaults. Extend min/max intervals in settings. Fix + another typo. +0.21: Rebase on "Run" app ver. 0.16. diff --git a/apps/runplus/app.js b/apps/runplus/app.js index 7cb5d4381..41fab7ae2 100644 --- a/apps/runplus/app.js +++ b/apps/runplus/app.js @@ -61,36 +61,47 @@ function setStatus(running) { // Called to start/stop running function onStartStop() { - let running = !exs.state.active; - let prepPromises = []; + var running = !exs.state.active; + var shouldResume = false; + var promise = Promise.resolve(); + + if (running && exs.state.duration > 10000) { // if more than 10 seconds of duration, ask if we should resume? + promise = promise. + then(() => { + isMenuDisplayed = true; + return E.showPrompt("Resume run?",{title:"Run"}); + }).then(r => { + isMenuDisplayed=false;shouldResume=r; + }); + } + // start/stop recording // Do this first in case recorder needs to prompt for // an overwrite before we start tracking exstats if (settings.record && WIDGETS["recorder"]) { if (running) { isMenuDisplayed = true; - prepPromises.push( - WIDGETS["recorder"].setRecording(true).then(() => { + promise = promise. + then(() => WIDGETS["recorder"].setRecording(true, { force : shouldResume?"append":undefined })). + then(() => { isMenuDisplayed = false; layout.setUI(); // grab our input handling again layout.forgetLazyState(); layout.render(); - }) - ); + }); } else { - prepPromises.push( - WIDGETS["recorder"].setRecording(false) + promise = promise.then( + () => WIDGETS["recorder"].setRecording(false) ); } } - if (!prepPromises.length) // fix for Promise.all bug in 2v12 - prepPromises.push(Promise.resolve()); - - Promise.all(prepPromises) - .then(() => { + promise = promise.then(() => { if (running) { - exs.start(); + if (shouldResume) + exs.resume() + else + exs.start(); } else { exs.stop(); } diff --git a/apps/runplus/metadata.json b/apps/runplus/metadata.json index 60860dc07..40256e595 100644 --- a/apps/runplus/metadata.json +++ b/apps/runplus/metadata.json @@ -1,7 +1,7 @@ { "id": "runplus", "name": "Run+", - "version": "0.18", + "version": "0.21", "description": "Displays distance, time, steps, cadence, pace and more for runners. Based on the Run app, but extended with additional screen for heart rate interval training.", "icon": "app.png", "tags": "run,running,fitness,outdoors,gps,karvonen,karvonnen", diff --git a/apps/setting/ChangeLog b/apps/setting/ChangeLog index 42bac0ea7..d090add58 100644 --- a/apps/setting/ChangeLog +++ b/apps/setting/ChangeLog @@ -66,4 +66,5 @@ of 'Select Clock' 0.58: On/Off settings items now use checkboxes 0.59: Preserve BLE whitelist even when disabled 0.60: Moved LCD calibration to top of menu, and use 12 taps (not 8) - LCD calibration will now error if the calibration is obviously wrong \ No newline at end of file + LCD calibration will now error if the calibration is obviously wrong +0.61: Permit temporary bypass of the BLE whitelist diff --git a/apps/setting/metadata.json b/apps/setting/metadata.json index 20213e81f..b2b19dd6b 100644 --- a/apps/setting/metadata.json +++ b/apps/setting/metadata.json @@ -1,7 +1,7 @@ { "id": "setting", "name": "Settings", - "version": "0.60", + "version": "0.61", "description": "A menu for setting up Bangle.js", "icon": "settings.png", "tags": "tool,system", diff --git a/apps/setting/settings.js b/apps/setting/settings.js index ffea3ddbb..d22f28412 100644 --- a/apps/setting/settings.js +++ b/apps/setting/settings.js @@ -658,6 +658,7 @@ function showUtilMenu() { function makeConnectable() { try { NRF.wake(); } catch (e) { } Bluetooth.setConsole(1); + NRF.ignoreWhitelist = 1; var name = "Bangle.js " + NRF.getAddress().substr(-5).replace(":", ""); E.showPrompt(name + /*LANG*/"\nStay Connectable?", { title: /*LANG*/"Connectable" }).then(r => { if (settings.ble != r) { @@ -665,6 +666,7 @@ function makeConnectable() { updateSettings(); } if (!r) try { NRF.sleep(); } catch (e) { } + delete NRF.ignoreWhitelist; showMainMenu(); }); } diff --git a/apps/shadowclk/ChangeLog b/apps/shadowclk/ChangeLog index 7ba343b2f..ce2933f0c 100644 --- a/apps/shadowclk/ChangeLog +++ b/apps/shadowclk/ChangeLog @@ -1,3 +1,4 @@ 0.01: New App! 0.02: New 'Settings Menu' to choose your favorite color and switch between light or dark themes 0.03: New 'Leading Zero' and 'Date Suffix' options in 'Settings Menu' +0.04: Updated settings menu to better maintain app settings and system settings diff --git a/apps/shadowclk/app.js b/apps/shadowclk/app.js index 7ffdc4683..dd47caf7b 100644 --- a/apps/shadowclk/app.js +++ b/apps/shadowclk/app.js @@ -37,7 +37,6 @@ let color = appSettings.color !== undefined ? appSettings.color : "#0ff"; let enableLeadingZero = appSettings.enableLeadingZero !== undefined ? appSettings.enableLeadingZero : false; let enableSuffix = appSettings.enableSuffix !== undefined ? appSettings.enableSuffix : true; -// Draw the time and date (function () { let drawTimeout; @@ -46,30 +45,17 @@ let enableSuffix = appSettings.enableSuffix !== undefined ? appSettings.enableSu var y = g.getHeight() / 2; g.reset().clearRect(Bangle.appRect); var date = new Date(); - var hour = date.getHours(); - var minutes = String(date.getMinutes()).padStart(2, '0'); + var locale = require("locale"); + var timeStr = locale.time(date, 1); - // Handle 12-hour format - if (is12Hour) { - hour = hour % 12 || 12; // Convert 0 to 12 for 12-hour format - } else { - // If the leading zero option is enabled and hour is less than 10, add leading zero - if (enableLeadingZero && hour < 10) { - hour = '0' + hour; - } - } - - var timeStr = hour + ':' + minutes; - - // Handle midnight in 12-hour format specifically - if (is12Hour && hour === 0) { - timeStr = '12' + timeStr.substring(2); + // If 24-hour format and leading zero should be removed + if (!is12Hour && !enableLeadingZero && timeStr.charAt(0) === '0') { + timeStr = timeStr.substr(1); } g.setFontAlign(0, 0).setFont("LondrinaSolid").setColor(color).drawString(timeStr, x - 1, y); g.reset().setFontAlign(0, 0).setFont("LondrinaShadow").drawString(timeStr, x - 1, y); - var locale = require("locale"); var dayOfMonth = date.getDate(); var month = locale.month(date, 1).slice(0, 1).toUpperCase() + locale.month(date, 1).slice(1).toLowerCase(); var year = date.getFullYear(); @@ -110,4 +96,4 @@ let enableSuffix = appSettings.enableSuffix !== undefined ? appSettings.enableSu Bangle.loadWidgets(); draw(); setTimeout(Bangle.drawWidgets, 0); -})(); \ No newline at end of file +})(); diff --git a/apps/shadowclk/metadata.json b/apps/shadowclk/metadata.json index 4e47b9845..432558b95 100644 --- a/apps/shadowclk/metadata.json +++ b/apps/shadowclk/metadata.json @@ -1,7 +1,7 @@ { "id": "shadowclk", "name": "Shadow Clock", - "version": "0.03", + "version": "0.04", "description": "A simple clock using the Londrina font in color with a shadowed outline. Based on the Anton Clock.", "icon": "app.png", "screenshots": [{ @@ -32,4 +32,4 @@ "data": [{ "name": "shadowclk.json" }] -} \ No newline at end of file +} diff --git a/apps/shadowclk/settings.js b/apps/shadowclk/settings.js index 21b4826a5..3fb774892 100644 --- a/apps/shadowclk/settings.js +++ b/apps/shadowclk/settings.js @@ -9,9 +9,18 @@ theme: 'light', enableSuffix: true, enableLeadingZero: false, - enable12Hour: false // default time mode + enable12Hour: false }, require('Storage').readJSON("shadowclk.json", true) || {}); + // Check if shadowclk is the selected clock + if (sysSettings.clock === "shadowclk.app.js") { + // Sync app settings with system settings + appSettings.theme = sysSettings.theme.dark ? 'dark' : 'light'; + if (sysSettings['12hour'] !== undefined) { + appSettings.enable12Hour = sysSettings['12hour']; + } + } + // Colors from 'Light BW' and 'Dark BW' themes function createThemeColors(mode) { let cl = x => g.setColor(x).getColor(); @@ -37,9 +46,10 @@ // Switch theme and save to storage function switchTheme(mode) { if (mode === g.theme.dark) return; - let s = require('Storage').readJSON("setting.json", 1) || {}; - s.theme = createThemeColors(mode); - require('Storage').writeJSON("setting.json", s); + sysSettings.theme = createThemeColors(mode); + if (sysSettings.clock === "shadowclk.app.js") { + require('Storage').writeJSON("setting.json", sysSettings); + } updateTheme(mode); } @@ -85,8 +95,10 @@ } function writeTimeModeSetting() { - sysSettings['12hour'] = appSettings.enable12Hour; - require('Storage').writeJSON("setting.json", sysSettings); + if (sysSettings.clock === "shadowclk.app.js") { + sysSettings['12hour'] = appSettings.enable12Hour; + require('Storage').writeJSON("setting.json", sysSettings); + } } function showMenu() { diff --git a/apps/sleeplogalarm/ChangeLog b/apps/sleeplogalarm/ChangeLog index 80f8bd7e4..286221777 100644 --- a/apps/sleeplogalarm/ChangeLog +++ b/apps/sleeplogalarm/ChangeLog @@ -1,4 +1,5 @@ 0.01: New App! 0.02: Add "from Consec."-setting 0.03: Correct how to ignore last triggered alarm -0.04: Make "disable alarm" possible on next day; correct alarm filtering; improve settings \ No newline at end of file +0.04: Make "disable alarm" possible on next day; correct alarm filtering; improve settings +0.05: Correct hide function + replace all `var` with `let`. diff --git a/apps/sleeplogalarm/README.md b/apps/sleeplogalarm/README.md index 005377fb1..8da369eb3 100644 --- a/apps/sleeplogalarm/README.md +++ b/apps/sleeplogalarm/README.md @@ -1,6 +1,6 @@ # Sleep Log Alarm -This widget searches for active alarms and raises an own alarm event up to the defined time earlier, if in light sleep or awake phase. Optional the earlier alarm will only be triggered if comming from or in consecutive sleep. The settings of the earlier alarm can be adjusted and it is possible to filter the targeting alarms by time and message. By default the time of the targeting alarm is displayed inside the widget which can be adjusted, too. +This widget searches for active alarms and raises an own alarm event up to the defined time earlier, if in light sleep or awake phase. Optional the earlier alarm will only be triggered if comming from or in consecutive sleep. The settings of the earlier alarm can be adjusted and it is possible to filter the targeting alarms by time and message. The widget is only displayed if an active alarm is detected. The time of the targeting alarm is displayed inside the widget, too. The time or the complete widget can be hidden in the options. _This widget does not detect sleep on its own and can not create alarms. It requires the [sleeplog](/apps/?id=sleeplog) app and any alarm app that uses [sched](/apps/?id=sched) to be installed._ @@ -30,7 +30,7 @@ _This widget does not detect sleep on its own and can not create alarms. It requ - __msg includes__ | include only alarms including this string in msg __""__ / ... - __Widget__ submenu - - __hide__ | completely hide the widget + - __hide always__ | completely hide the widget _on_ / __off__ - __show time__ | show the time of the targeting alarm __on__ / _off_ diff --git a/apps/sleeplogalarm/lib.js b/apps/sleeplogalarm/lib.js index 343e811af..609a45fde 100644 --- a/apps/sleeplogalarm/lib.js +++ b/apps/sleeplogalarm/lib.js @@ -1,5 +1,5 @@ // load library -var sched = require("sched"); +let sched = require("sched"); // find next active alarm in range function getNextAlarm(allAlarms, fo, withId) { @@ -10,7 +10,7 @@ function getNextAlarm(allAlarms, fo, withId) { // return next active alarms in range, filter for // active && not timer && not own alarm && // after from && before to && includes msg - var ret = allAlarms.filter( + let ret = allAlarms.filter( a => a.on && !a.timer && a.id !== "sleeplog" && a.t >= fo.from && a.t < fo.to && (!fo.msg || a.msg.includes(fo.msg)) ).map(a => { // add time to alarm @@ -21,7 +21,7 @@ function getNextAlarm(allAlarms, fo, withId) { ).sort((a, b) => a.tTo - b.tTo); // prevent triggering for an already triggered alarm again if available if (fo.lastDate) { - var toLast = fo.lastDate - new Date().valueOf() + 1000; + let toLast = fo.lastDate - new Date().valueOf() + 1000; if (toLast > 0) ret = ret.filter(a => a.tTo > toLast); } // return first entry @@ -59,7 +59,7 @@ exports = { if (typeof (global.sleeplog || {}).trigger !== "object") return; // read settings to calculate alarm range - var settings = exports.getSettings(); + let settings = exports.getSettings(); // set the alarm time this.time = getNextAlarm(sched.getAlarms(), settings.filter).t; @@ -68,7 +68,7 @@ exports = { if (!this.time) return; // set widget width if not hidden - if (!this.hidden) this.width = 8; + if (!settings.wid.hide) this.width = 8; // insert sleeplogalarm conditions and function sleeplog.trigger.sleeplogalarm = { @@ -87,22 +87,22 @@ exports = { // trigger function trigger: function() { // read settings - var settings = exports.getSettings(); + let settings = exports.getSettings(); // read all alarms - var allAlarms = sched.getAlarms(); + let allAlarms = sched.getAlarms(); // find first active alarm - var alarm = getNextAlarm(sched.getAlarms(), settings.filter, settings.disableOnAlarm); + let alarm = getNextAlarm(sched.getAlarms(), settings.filter, settings.disableOnAlarm); // return if no alarm is found if (!alarm) return; // get now - var now = new Date(); + let now = new Date(); // get date of the alarm - var aDate = new Date(now + alarm.tTo); + let aDate = new Date(now + alarm.tTo); // disable earlier triggered alarm if set if (settings.disableOnAlarm) { diff --git a/apps/sleeplogalarm/metadata.json b/apps/sleeplogalarm/metadata.json index fd85507e6..30d3dcda7 100644 --- a/apps/sleeplogalarm/metadata.json +++ b/apps/sleeplogalarm/metadata.json @@ -2,7 +2,7 @@ "id":"sleeplogalarm", "name":"Sleep Log Alarm", "shortName": "SleepLogAlarm", - "version": "0.04", + "version": "0.05", "description": "Enhance your morning and let your alarms wake you up when you are in light sleep.", "icon": "app.png", "type": "widget", diff --git a/apps/sleeplogalarm/settings.js b/apps/sleeplogalarm/settings.js index 1f3a13272..d797ae6bc 100644 --- a/apps/sleeplogalarm/settings.js +++ b/apps/sleeplogalarm/settings.js @@ -1,6 +1,6 @@ (function(back) { // read settings - var settings = require("sleeplogalarm").getSettings(); + let settings = require("sleeplogalarm").getSettings(); // write change to storage function writeSetting() { @@ -23,7 +23,7 @@ // show widget menu function showFilterMenu() { // set menu - var filterMenu = { + let filterMenu = { "": { title: "Filter Alarm" }, @@ -64,22 +64,22 @@ }) } }; - var menu = E.showMenu(filterMenu); + let menu = E.showMenu(filterMenu); } // show widget menu function showWidMenu() { // define color values and names - var colName = ["red", "yellow", "green", "cyan", "blue", "magenta", "black", "white"]; - var colVal = [63488, 65504, 2016, 2047, 31, 63519, 0, 65535]; + let colName = ["red", "yellow", "green", "cyan", "blue", "magenta", "black", "white"]; + let colVal = [63488, 65504, 2016, 2047, 31, 63519, 0, 65535]; // set menu - var widgetMenu = { + let widgetMenu = { "": { title: "Widget Settings" }, /*LANG*/"< Back": () => showMain(9), - /*LANG*/"hide": { + /*LANG*/"hide always": { value: settings.wid.hide, onchange: v => { settings.wid.hide = v; @@ -105,13 +105,13 @@ } } }; - var menu = E.showMenu(widgetMenu); + let menu = E.showMenu(widgetMenu); } // show main menu function showMain(selected) { // set menu - var mainMenu = { + let mainMenu = { "": { title: "Sleep Log Alarm", selected: selected @@ -184,7 +184,7 @@ } } }; - var menu = E.showMenu(mainMenu); + let menu = E.showMenu(mainMenu); } // draw main menu diff --git a/apps/sleeplogalarm/widget.js b/apps/sleeplogalarm/widget.js index e3171751f..a62782604 100644 --- a/apps/sleeplogalarm/widget.js +++ b/apps/sleeplogalarm/widget.js @@ -1,7 +1,7 @@ // check if enabled in settings if ((require("Storage").readJSON("sleeplogalarm.settings.json", true) || {enabled: true}).enabled) { // read settings - settings = require("sleeplogalarm").getSettings(); // is undefined if used with var + let settings = require("sleeplogalarm").getSettings(); // insert neccessary settings into widget WIDGETS.sleeplogalarm = { @@ -10,10 +10,13 @@ if ((require("Storage").readJSON("sleeplogalarm.settings.json", true) || {enable time: 0, earlier: settings.earlier, draw: function () { - // draw zzz - g.reset().setColor(settings.wid.color).drawImage(atob("BwoBD8SSSP4EEEDg"), this.x + 1, this.y); - // call function to draw the time of alarm if a alarm is found - if (this.time) this.drawTime(this.time + 1); + // draw if width is set + if (this.width) { + // draw zzz + g.reset().setColor(settings.wid.color).drawImage(atob("BwoBD8SSSP4EEEDg"), this.x + 1, this.y); + // call function to draw the time of alarm if a alarm is found + if (this.time) this.drawTime(this.time + 1); + } }, drawTime: () => {}, reload: require("sleeplogalarm").widReload