CINXE.COM
PICO-8 Fantasy Console
<html> <head> <title>PICO-8 Fantasy Console</title> <meta charset="utf-8"/><meta property="og:image" content="https://www.lexaloffle.com/gfx/p8tv_preview_orul.png"/> <meta property="og:title" content="PICO-8 Fantasy Console"/> <meta property="og:description" content="PICO-8 is a fantasy console for making, sharing and playing tiny games and other computer programs."/> <meta name="viewport" content="width=device-width"> <script type="text/javascript"> function microAjax(B,A){this.bindFunction=function(E,D){return function(){return E.apply(D,[D])}};this.stateChange=function(D){if(this.request.readyState==4){this.callbackFunction(this.request.responseText)}};this.getRequest=function(){if(window.ActiveXObject){return new ActiveXObject("Microsoft.XMLHTTP")}else{if(window.XMLHttpRequest){return new XMLHttpRequest()}}return false};this.postBody=(arguments[2]||"");this.callbackFunction=A;this.url=B;this.request=this.getRequest();if(this.request){var C=this.request;C.onreadystatechange=this.bindFunction(this.stateChange,this);if(this.postBody!==""){C.open("POST",B,true);C.setRequestHeader("X-Requested-With","XMLHttpRequest");C.setRequestHeader("Content-type","application/x-www-form-urlencoded");C.setRequestHeader("Connection","close")}else{C.open("GET",B,true)}C.send(this.postBody)}}; </script> <STYLE TYPE="text/css"> <!-- @font-face { font-family: 'proggyvec'; src: url('/proggyvec.woff') format('woff'); } .banner_div{display:block} .topmenu_div{display:table} .lilmenu_div{display:none} .desktop_div{display:inline} .mobile_div{display:none} .avatar{ border-radius:10%; box-shadow: 4px 4px 6px rgba(0.2, 0.0, 0.4, 0.1); } .avatar_tiny{ border-radius:20%; box-shadow: 4px 4px 6px rgba(0.2, 0.0, 0.4, 0.1); } .scrollable_with_touch{ max-width:95vw; overflow-x:auto;overflow-y:hidden; -webkit-overflow-scrolling:touch; } .info_box { display:table; float:left; margin-left:8px; margin-right:16px; margin-top:0px; margin-bottom:16px; text-align:left; } .info_box_right_old {display:table; margin-left:16px; margin-right:8px; margin-top:-10px; margin-bottom:16px; float:right; text-align:left; } .info_box_right { margin-top:-10px; } .info_box h2 {margin-top:-16px;} h2 {margin-top:0px;} .info_group { display:table; max-width:800px; margin-bottom:20px; text-align:left } .info_group img{ min-width:192px; } .info_group h2{ text-decoration: none; color:#fab; font-weight: normal; } .lexchex{ padding:8px; color:#444; font-size:12pt; } .lexchex input{ margin-right:6px; height: 24px; width: 24px; background-color: #eee; position:relative; top:4px; } .pip_form_input{ background:#222; color:#ccc; } .form_button{ padding:6px; padding-left:10px; padding-right:10px; display:table; margin-top:12px; border:2px solid; border-color:#999; border-radius:6px; background:#222; color:#ccc; } .form_button:hover{ background-color:#fab; color:#222; cursor:pointer; } .form_button_span{ padding:2px; padding-left:4px; padding-right:4px; display:inline; border:1px solid; border-color:#999; } .form_button_span:hover{ background-color:#fab; color:#222; cursor:pointer; } .edit_tool_button{ padding:4px; display:table; padding-left:8px; padding-right:8px; border-radius:1; margin-right:4px; margin-bottom:2px; border:1px solid; border-color:#999; margin-bottom:8px; } .edit_tool_button:hover{ background-color:#fab; color:#222; cursor:pointer; } .form_tab{ padding:12px; padding-right:20px; padding-top:8px; padding-bottom:8px; display: table; float:left; font-size:14pt; border-radius:0px; margin-right:2px; color:#ccc; background-color:#444; } .form_tab:hover{ background-color:#f8a; color:#fff; cursor:pointer; } zzz_canvas{ image-rendering: optimizeSpeed; image-rendering: -moz-crisp-edges; image-rendering: -webkit-optimize-contrast; image-rendering: optimize-contrast; image-rendering: pixelated; -ms-interpolation-mode: nearest-neighbor; border: 0px; width:512px; height:512px; } .favlike{ min-width:160px; } .pixel_perfect{ image-rendering: optimizeSpeed; image-rendering: -moz-crisp-edges; image-rendering: -webkit-optimize-contrast; image-rendering: optimize-contrast; image-rendering: pixelated; -ms-interpolation-mode: nearest-neighbor; border: 0px; } .subnav{float:right; max-width:400px; margin-top:10px} .snb{ padding:6px; margin:2px; display:flex; } @media screen and (max-width: 800px) { .subnav{float:left; width:100%;} .snb{padding:5px;} } @media screen and (max-width: 920px) { .banner_div{display:none;} .topmenu_div{display:none;} .lilmenu_div{display:table;} .desktop_div{display:none;} .mobile_div{display:inline;} .info_box{float:left;width:100%; margin:0px; margin-bottom:10px} .info_box_right{float:left; width:100%; margin:0px; margin-bottom:10px} .favlike{ min-width:10px; } } .block { text-align: center; margin: 20px; } .centered { display: inline-block; vertical-align: middle; width: 300px; } img{ max-width: 100%; height:auto; } body{ font-size:11pt; font-family : proggyvec; line-height: 1.4; color:#ccc; background-color:#303030; margin:0px; padding:0px; } code { padding:2px; margin:2px; padding-left:4px; padding-right:4px; background-color:#ddd; color: #222; } TD{font-size:11pt;} p{ font-size:11pt; } pre{ font-size:11pt; } code{ font-size:11pt; } a:hover { cursor:pointer; } b{color: #ffffff} .tag { float:left; display:table; text-align:left; font-size:8pt; margin-right:4px; margin-bottom:4px; padding:3px; padding-left:6px; padding-right:6px; background-color:#aaa; color:#444; border-radius:2px; } .tag:hover { cursor:pointer; color:#fff; background-color:#f8a; } .tagblue { font-size:8pt; padding:4px; padding-left:6px; padding-right:6px; background-color:#8ad; color:#222; margin-top:6px; display:inline-block; border-radius:2px; } .tagblue:hover { cursor:pointer; color:#fff; background-color:#f8a; } .bright_hover{ cursor:pointer; opacity: 0.3; } .bright_hover:hover{ opacity: 1.0; } .menu_icon_lil { display:inline-block; padding-top:4px; padding-left:2px; padding-right:2px; } .menu_icon { display:inline-block; padding-top:4px; padding-left:2px; padding-right:2px; } .menu_icon a{ display:inline-block; opacity:0.5; } .menu_icon a:hover{ opacity:1.0; background: #ff80a0; } .menu_icon hover{ opacity:1.0; background: #ff80a0; } .thread_preview{ width:100%; min-height:64px; margin-top:2px; background-color:#444; } .stitched { padding: 10px; padding-left: 20px; padding-right: 20px; margin: 10px; margin-right: 50px; background: #f8f8f8; color: #404040; font-size: 21px; font-weight: bold; line-height: 0.5em; border: 2px dashed #d0e0ff; border-radius: 5px; box-shadow: 0 0 0 4px #f8f8f8, 2px 1px 6px 4px rgba(5, 5, 0, 0.2); font-weight: normal; } .social_button{ opacity:0.5; } .social_button:hover{ opacity:1.0; } .support_button{ padding:8px; background-color:#fab; color:#333; font-size:10pt; display:table; opacity:1.0; } .support_button:hover{ background-color:#fab; opacity:1.0; } .pinkhover{ cursor:pointer; } .pinkhover:hover{ background:#fab; color:#222; } .pink-hover_t a{ opacity: 0.5; } .pink-hover_t2 a{ } .pink-hover a:hover { display: block; background: #ff80a0; opacity: 1.0; } .pink-hover_t a:hover { display: block; background: #ff80a0; opacity: 1.0; } .post_title{ font-size: 9pt; } .post_title2{ font-size: 20pt; color : #ffffff; margin: 0px; margin-bottom: 8px; margin-top:0px; } @media screen and (max-width: 800px) { .post_title2{font-size:16pt}; } .breadcrumbs{ margin-left:8px; font-size:12px; color:#888; border-radius:0px; } .breadcrumbs a:link { color: #ccc; padding: 4px; display:inline-block; } .breadcrumbs a:visited { color: #ccc; background-color: #666; } .breadcrumbs a:hover { color: #fff; background-color: #888; } .breadcrumbs2{ margin-left:8px; font-size:12px; color:#888; border-radius:0px; } .breadcrumbs2 a:link { color: #999; padding: 4px; padding-left: 8px; padding-right: 8px; display:inline-block; } .breadcrumbs2 a:visited { color: #999; } .breadcrumbs2 a:hover { color: #fab; } .post_title3{ font-size: 12pt; font-weight : bold; color : #ff3070; } .blog_title2{ font-size: 20pt; color : #ff3070; } .blog_title{ font-size: 16pt; color : #202020; } .blog_num_comments{ font-size: 14pt; color : #303030; font-weight : bold; } .user_title{ font-size: 12pt; color : #ffe0d0; } h1{color:#ffffff;font-size:22pt;padding-top:8px} h2{color:#ffffff;font-size:18pt;padding-top:16px} h3{color:#f7a; font-size:14pt;padding-top:8px} A:link { text-decoration: none; color: #fff; } A:visited { text-decoration: none; color: #fff; } p A:link { zzz_text-decoration: underline; color:#fbc; } p A:hover{ color:#f0f; } p A:visited { zzz_text-decoration: underline; color:#fbc; } p A:visited:hover { color:#fff; } .link_with_image_unused{ background-image:url("/gfx/ext_link.png"); background-repeat: no-repeat; background-position: right; padding-right:20px;color:#fff; } A:hover { text-decoration: none; color: #fff; } A:active { text-decoration: none; color: #ffffff } .navstring a { color: #808080; font-weight : bold; } .navstring a:hover { color: #faa; font-weight : bold; } .navstring{ color: #c0c0c0; font-weight : bold; } .linkydiv{ display: block; text-align: center; vertical-align: middle; margin: 0px; background-color : #ff80a0; } .linkydiv:hover { background-color : #ff80a0; color: #ffffff; } .cartfile{ float:left; width:160px; height:200px; margin: 4px; padding: 4px; text-align: center; } .cartlabel{ background:#333; width:124; padding:2px; margin-top:8px; padding-top:4px; padding-bottom:4px; font-size:10px; } .cartlabelbig{ background:#333; width:252; padding-top:14px; padding-bottom:14px; margin-top:16px; font-size:16px; } .linkybutton { float: left; font-size: 14px; color: #808080; display: table-cell; text-align: center; vertical-align: middle; padding-top: 10px; padding-bottom: 10px; padding-left: 12px; padding-right: 12px; margin: 0px; height: 16px; } @media screen and (max-width: 800px) { .linkybutton{ float:none; display:flex; padding:12px; align-text: left; margin-left:50px; font-size:11pt; } } .linkbutton.img img{ display:inline-block; vertical-align:middle; } .linkybutton:hover { background-color : #ff80a0; color: #ffffff; } .linkybutton_lil0 { float:left; background-color : #555; padding: 6px; color: #aaa; font-size: 12px; height:20px; vertical-align:center; display:inline-table; margin:2px; } .linkybutton_lil { float:left; background-color : #555; padding: 6px; color: #aaa; font-size: 16px; height:20px; vertical-align:center; display:inline-table; margin:2px; } .linkybutton_lil:hover { background-color : #ff80a0; color: #ffffff; } .pinkbutton{ background:#505050; padding:6px; } .pinkbutton:hover { background-color : #808080; } .pbutton{ padding: 0px; margin: 0px; background-color : #505050; color : #a0a0a0; } .pbutton:hover{ background-color : #707070; } .logininput { font-size: 12px; color: #000000; line-height: 14px; border: 0; margin: 2px; padding: 2px; background-color : #ccc; height: 30px; } .logininput:hover { background-color : #ff80a0; } .logininput2 { font-size: 12px; color: #fff; border-width: 1px; border-style: solid; border-color:#fff; border-radius:3px; margin: 4px; padding: 8px; padding-left:16px; padding-right:16px; background-color : #333; height: 32px; cursor:pointer; } .logininput2:hover { background-color : #ff80a0; } input { background-color:#ffffe0; color:#202020; border-width: 1px; border-style: solid; border-color: #999999; font-size: 12pt; color: #404040; } .lexinput_search { background-color:#999; color:#202020; font-size: 10pt; color: #111; height:20px; margin-right: 8px; } .lexinput { background-color:#ffffe0; color:#202020; border-width: 1px; border-style: solid; border-color: #999999; font-size: 12pt; color: #404040; height:32px; margin-left:8px; } .result_msg { color:#888; } #login_button { width: 48px; height: 16px; margin: 0; padding: 0; border: 0; background: transparent url(img/login_button.png) no-repeat center top; text-indent: -1000em; } #signup_button { width: 48px; height: 16px; margin: 0; padding: 0; border: 0; background: transparent url(img/signup_button.png) no-repeat center top; text-indent: -1000em; } --> </STYLE> <script> var current_time="2024-12-01 22:08:15"; var s_uid=0; function $(name) { return document.getElementById(name); } // globals var p_page = 1; var p_sub = 0; var p_cat = 0; var p_pid = 0; var p_mode = 0; var search_url_base = ""; var tag_url_base = ""; var pip_form_data = {}; var touch_detected = false; var undefined = "undefined"; // :/ var current_playing_cart_id = -1; function microAjax(B,A){this.bindFunction=function(E,D){return function(){return E.apply(D,[D])}};this.stateChange=function(D){if(this.request.readyState==4){this.callbackFunction(this.request.responseText)}};this.getRequest=function(){if(window.ActiveXObject){return new ActiveXObject("Microsoft.XMLHTTP")}else{if(window.XMLHttpRequest){return new XMLHttpRequest()}}return false};this.postBody=(arguments[2]||"");this.callbackFunction=A;this.url=B;this.request=this.getRequest();if(this.request){var C=this.request;C.onreadystatechange=this.bindFunction(this.stateChange,this);if(this.postBody!==""){C.open("POST",B,true);C.setRequestHeader("X-Requested-With","XMLHttpRequest");C.setRequestHeader("Content-type","application/x-www-form-urlencoded");C.setRequestHeader("Connection","close")}else{C.open("GET",B,true)}C.send(this.postBody)}}; // from: https://plainjs.com/javascript/ajax/send-ajax-get-and-post-requests-47/ function post_ajax(url, data, success) { var params = typeof data == 'string' ? data : Object.keys(data).map( function(k){ return encodeURIComponent(k) + '=' + encodeURIComponent(data[k]) } ).join('&'); var xhr = window.XMLHttpRequest ? new XMLHttpRequest() : new ActiveXObject("Microsoft.XMLHTTP"); xhr.open('POST', url); xhr.onreadystatechange = function() { if (xhr.readyState>3 && xhr.status==200) { success(xhr.responseText); } }; xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest'); //xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded'); xhr.setRequestHeader('Content-Type', 'text/plain; charset=UTF-8'); xhr.send(params); return xhr; } // in ms. 1000, 20 means 50fps for 1 second. function poll_function(duration, interval, func) { var start_t = (new Date()).getTime(); (function p() { tt = (new Date).getTime() - start_t; if (tt <= duration) { func(tt/duration); setTimeout(p, interval); } })(); } function insertTextAtCursor(el, text, overwrite) { if (typeof el == "string") { el_str = el; el = document.getElementById(el); if (!el){ console.log("@ could not find element to insert text: "+el_str); } } var val = el.value, endIndex, range, doc = el.ownerDocument; if (overwrite) { el.focus(); el.value = text; return; } if (typeof el.selectionStart == "number" && typeof el.selectionEnd == "number") { endIndex = el.selectionEnd; el.value = val.slice(0, endIndex) + text + val.slice(endIndex); el.selectionStart = el.selectionEnd = endIndex + text.length; } else if (doc.selection != "undefined" && doc.selection.createRange) { el.focus(); range = doc.selection.createRange(); range.collapse(false); range.text = text; range.select(); } } function set_enter_to_save(el, save_el) { if (!el) return; // experimental: only auto-save when value has changed (can tab through unchanged fields) var start_val = el.value; if (!el.installed_enter_to_save) // only install once { console.log("@installing set_enter_to_save: "+el+", "+save_el); el.installed_enter_to_save = true; el.addEventListener("keyup", function(event){ //console.log(event.keyCode); if (event.keyCode == 13) //if (el.value != start_val) // commented -- don't need / confusing. save_el.click(); //if (event.keyCode == 9) // tab // commented -- use blur if want to do this // save_el.click(); } ); // also auto-save on tab / click off // careful not to introduce double sends (hard to get right -- maybe not worth it) el.addEventListener("blur", function(event){ var x = event.clientX, y = event.clientY; if (typeof x != 'number') return; if (typeof y != 'number') return; var el0 = document.elementFromPoint(x, y); if (!el0 || el0.name != "cancel_button") //if (el.value != start_val) save_el.click(); save_el.click(); } ); } } function toggle_set_button(el_name, post_id, which, sid) { var el = document.getElementsByClassName(el_name); var eli = document.getElementsByClassName('i_'+el_name); if (s_uid == 0) { window.alert("Please log in first."); return; } // window.alert(el_name); for(var i = 0; i < eli.length; i++) { eli[i].innerHTML = '<center><img src=/gfx/load16.gif width=20 height=20></center>'; } microAjax("/bbs/set.php?pid="+post_id+"&sid="+sid+"&which="+which, function (retdata) { var el = document.getElementsByClassName(el_name); for(var i = 0; i < el.length; i++) { el[i].innerHTML = json_parse(retdata)['printable']; } if (which == "tick") { // location.reload(true); // bleh } } ); } function hide_save_draft_button() { var el = document.getElementById("save_draft_button"); if (el) el.style.display = "none"; var el = document.getElementById("preview_button"); if (el) el.style.display = ""; var el = document.getElementById("publish_post_button"); if (el) el.style.innerHTML = "Publish Changes"; } function save_attr(val_id, div_id, url, post_save, extract_snippets) { if (extract_snippets) url = url+'&extract_snippets=1'; //console.log("@ save_attr("+val_id+", "+div_id+", "+url+")"); //console.log(" @ extract_snippets: "+extract_snippets); var el1 = document.getElementById(div_id); var el = document.getElementById(val_id); if (el) { //console.log(" @ sending:" + el.value); value = el.value; post_ajax(url, el.value, function (retdata) { dat = json_parse(retdata); el1.innerHTML = dat['printable']; // set value field if (dat['modified'] && dat['status'] == "ok") el.value = dat['modified']; //else // console.log(" @ no modified text"); if (post_save) post_save(); } ); } else { // grab value from url (val=) instead of body. e.g. catsub buttons microAjax(url, function (retdata) { el1.innerHTML = json_parse(retdata)['printable']; } ) } //el1.innerHTML = '<img src=/gfx/load16.gif>'; el1.innerHTML = '<div style="padding:8px"><img src=/gfx/load16.gif></div>'; } function cancel_attr(val_id, div_id, url) { console.log("@ cancel_attr("+val_id+", "+div_id+", "+url); var el = document.getElementById(val_id); if (el) { var el1 = document.getElementById(div_id); el1.innerHTML = '<div style="padding:8px"><img src=/gfx/load16.gif></div>'; microAjax(url, function (retdata) { el1.innerHTML = json_parse(retdata)['printable'];; } ); } } function edit_attr(val_id, div_id, save_id, url, has_autosave) { //console.log("@ edit_attr("+val_id+", "+div_id+", "+url+", "+has_autosave); var el = document.getElementById(div_id); if (el) { //el.innerHTML='<img src=/gfx/load16.gif>';//+el.innerHTML; el.innerHTML = '<div style="padding:8px"><img src=/gfx/load16.gif></div>'; microAjax(url, function (retdata) { el.innerHTML = json_parse(retdata)['printable']; var el2 = document.getElementById(val_id); if (el2) if (has_autosave) { el2.focus(); //el2.select(); var el3 = document.getElementById(save_id); set_enter_to_save(el2, el3); } } ); } } // enter data function submit_pip_form_data(form) { // for each class=pip_form_input element, add to global pip_form_data var els = document.getElementsByClassName('pip_form_input'); console.log("@ submit form: adding "+els.length+" values"); for(var i = 0; i < els.length; i++) { el = els[i]; if (el.checked) { pip_form_data[el.id] = "checked"; } else if (el.value) { pip_form_data[el.id] = el.value; } console.log("@ set "+el.id+" to "+pip_form_data[el.id]); if (el.files && el.files[0]) { console.log("@ file: "+el.files[0].name); pip_form_data["uploaded_file"] = JSON.stringify(el.files[0]); pip_form_data["uploaded_file_size"] = el.files[0].size; pip_form_data["uploaded_name"] = el.files[0].name; pip_form_data["uploaded_type"] = el.files[0].type; } } } function reload_pip_form(form, step) { var el = document.getElementById(form); console.log("reloading form: "+form+" // step "+step); if (el) { var file_size = 0; if (pip_form_data.file_contents) file_size = pip_form_data.file_contents.length * 6 / 8; if (form == "cart_submit" && file_size > 1024*1024) // 1MB for pico-8 / voxatron carts { window.alert('Sorry, that file is too large. ('+Math.floor(file_size/1024)+'k / 1024k)'); return; } if (file_size > 1024*1024*8) { window.alert('Sorry, that file is too large. ('+Math.floor(file_size/(1024*1024))+'MB / 8MB)'); return; } // upload stringified_data = JSON.stringify(pip_form_data); var el1 = document.getElementById("form_loading_icon"); if (el1) el1.innerHTML='<img src=/gfx/load16.gif>'; //window.alert('pip_form_data.target_field:'+pip_form_data["target_field"]); // pass along some context post_ajax("/form.php?form="+form+"&step="+step+"&cat="+p_cat+"&sub="+p_sub+"&pid="+ p_pid+"&page="+p_page+"&mode="+p_mode, stringified_data, function (retdata) { el.innerHTML = retdata; // not JSON! } ); } else { console.log("@@ could not find form element"); } } function save_open_pip_form_fields() { // click all the save buttons var els = document.getElementsByClassName('form_button'); //for(var i = 0; i < els.length; i++) // invalidates iterator on first save! ha! for(var i = els.length-1; i >= 0; i--) { var el = els[i]; suffix = el.id.substr(el.id.length-5); if (suffix == "_save") el.click(); } } function submit_pip_form(form, step) { // save any open fields submit_pip_form_data(form); reload_pip_form(form, step); } function clear_pip_form() { pip_form_data = {}; } function randomize_cart_id_field() { el = document.getElementById("cart_lid"); c = "bdfghjkmnprstwyz"; v = "aiueo"; val = ""; for (i = 0; i < 5; i++) { val = val + c.substr(Math.floor(Math.random()*16), 1); val = val + v.substr(Math.floor(Math.random()*6), 1); } el.value = val; } // https://stackoverflow.com/questions/12710001/how-to-convert-uint8-array-to-base64-encoded-string function Uint8ToBase64(u8Arr){ var CHUNK_SIZE = 0x8000; //arbitrary number var index = 0; var length = u8Arr.length; var result = ''; var slice; while (index < length) { slice = u8Arr.subarray(index, Math.min(index + CHUNK_SIZE, length)); result += String.fromCharCode.apply(null, slice); index += CHUNK_SIZE; } return btoa(result); } function reader_onload(evt) { // evt.target.result is a ArrayBuffer var bytes = new Uint8Array( evt.target.result ); pip_form_data["file_contents"] = Uint8ToBase64(bytes); // show submit button var el = document.getElementById("uploaded_button"); el.style.display = "table"; } function filechooser_onchange(files) { var file = files[0]; var reader = new FileReader(); reader.onload = function(evt) { reader_onload(evt); }; reader.readAsArrayBuffer(file); } function toggle_visible(el) { //console.log("@toggle_visible: "+el); if (typeof el == 'string') { el = document.getElementById(el); } if (!el){ return; } if (el.style.display == "") { el.style.display="none"; return false; } else { el.style.display=""; return true; } } /* show active tab under tab_name, hide others assume only one set of tabs on page */ function show_tab(tab_name, which, tab_group) { var el; console.log("toggle tab "+tab_name+"_"+which); if (!tab_group) tab_group = "form_tab"; var els = document.getElementsByClassName(tab_group); //console.log("found "+els.length); for(var i = 0; i < els.length; i++) { el = els[i]; el2 = document.getElementById(el.id+"_content"); if (el.id == tab_name+"_"+which) { // open tab el.style["background-color"] = "#f8a"; el.style.color="#222"; if (el2) console.log(" opening "+el2.id); if (el2) el2.style.display="block"; } else { // hide tab el.style["background-color"] = ""; el.style.color="#fff"; if (el2) console.log(" hiding "+el2.id); if (el2) el2.style.display="none"; } } } function get_cart_url(lid, cat) { //console.log("@get_cart_url lid:"+lid+" cat:"+cat); /* console.log("@number: "+Number(lid)); console.log("@string: "+String(lid)); console.log("@equality:"+((Number(lid) == String(lid)) ? "true" : "false")); */ if (Number(lid) == String(lid) && Number(lid) > 0) { num = Math.floor(lid / 10000); url = `/bbs/cposts/`+num+`/`; if (cat == 6) return url + `cpost` + lid + `.png`; if (cat == 7) return url + lid + `.p8.png`; if (cat == 8) return url + lid + `.p64.png`; } else { lid = String(lid); if (cat == 6) return `/bbs/cposts/`+lid.substr(0,2)+`/`+lid+`.vx.png`; if (cat == 7) return `/bbs/cposts/`+lid.substr(0,2)+`/`+lid+`.p8.png`; if (cat == 8) return `/bbs/cposts/`+lid.substr(0,2)+`/`+lid+`.p64.png`; } return ''; } function element_dismisser(el, event) { var inner = el.childNodes[1]; if (!inner) return; var rect = inner.getBoundingClientRect(); if (!el.real_dismiss) { //console.log(' @@ element_dismisser: not real click -- return early'); el.real_dismiss = true; // next time! return; } if (rect && rect.bottom > 0) if (event.clientX < rect.left || event.clientX > rect.right || event.clientY > rect.bottom || event.clientY < rect.top) { //console.log(' @@ element_dismisser: dismissing'); console.log(rect); el.style.display="none"; document.removeEventListener('click', dismisser_func); } } var dismisser_func = null; function install_dismisser(el1) { //console.log('@@ installing dismisser'); // just in case document.removeEventListener('click', dismisser_func); dismisser_func = function(event){ element_dismisser(el1, event); } document.addEventListener('click', dismisser_func); el1.real_dismiss = false; // hack -- first click is to open pulldown } function install_search_listener(el) { el.addEventListener("keyup", function(event){ if (event.keyCode == 13) window.location.hash = "" + search_url_base + el.value; } ); } function update_checkout() { var checkboxes = document.getElementsByClassName('checkout_checkbox'); var items = document.getElementsByClassName('checkout_item'); var show_which = "buy"; if (document.getElementById("buy_p8") && document.getElementById("buy_p8").checked == true) show_which = show_which + "_p8"; if (document.getElementById("buy_vx") && document.getElementById("buy_vx").checked == true) show_which = show_which + "_vx"; if (document.getElementById("buy_p64") && document.getElementById("buy_p64").checked == true) show_which = show_which + "_p64"; console.log("show_which: "+show_which); // show only the selected bundle / product for (i = 0; i < items.length; i++) { if (items[i].id == show_which) items[i].style.display = ""; else items[i].style.display = "none"; } } function json_parse(data){ if (data[0] != '{'){ window.alert('Error // Bad Data Format: ' + data); return {"printable" : "[error]", "status" : "bad_data"}; } return JSON.parse(data); }</script> <script> var cart_info = {}; // from https://github.com/kripken/emscripten/issues/2053 function fullscreenable_canvas(canEl) { // var canEl = document.getElementById("canvas"); var reqFuncName = (canEl.mozRequestFullScreen || canEl.webkitRequestFullScreen || {"name": "requestFullScreen"}).name; document.addEventListener(reqFuncName.slice(0, -"requestFullScreen".length) + "fullscreenchange", function (evt) { if(document.mozFullScreen || document.webkitIsFullScreen || document.fullScreen || document.isFullScreen) { var w = canEl.clientWidth; var h = canEl.clientHeight; var factor = Math.min(screen.width / w, screen.height / h); canEl.setAttribute("style", "width: " + Math.round(w * factor) + "px !important; height: " + Math.round(h * factor) + "px !important;"); } else { canEl.removeAttribute("style"); } }, false); // in Firefox full screen must be set to the parent of the canvas to allow resizing the canvas for correct aspect ratio if(reqFuncName == "mozRequestFullScreen") { canEl[reqFuncName] = function() { this.parentNode[reqFuncName].apply(this.parentNode, arguments); }; } } function pip_requestFullscreen( element ) { if ( element.requestFullscreen ) { element.requestFullscreen(); } else if ( element.mozRequestFullScreen ) { element.mozRequestFullScreen(); } else if ( element.webkitRequestFullScreen ) { element.webkitRequestFullScreen( Element.ALLOW_KEYBOARD_INPUT ); } } window.onload = function() { //fullscreenable_canvas(); } </script> <script> function validate_signup (form) { if (form.su_agree.checked) { return true; } else { alert ("If you wish to sign up, you need to agree to the Terms of Use") return false; } } </script> </head><body><div id="body_0"> <div class=mobile_div> <div class = "lilmenu_div" style = " padding:0px; margin:0px; top:0px; width:100%; height:128px; background:url('/gfx/h800_pico8.png') no-repeat center; -webkit-background-size:cover; -moz-background-size:cover; -o-background-size:cover; background-size:cover; "> <div style=" position:relative; height:48px; top: 76px; margin:0px; "><div style="float:left; height:48px; display:flex; align-items:center; "> <a onclick=" var el = document.getElementById('lil_menu_pulldown'); toggle_visible(el); var el = document.getElementById('account_pulldown'); el.style.display='none'; "> <div style="background-color:rgba(0,0,0,0.5); margin-left:8px; padding:12px"> <img src="/gfx/top_pulldown.png" width=20 height=20> </div> </a> </div> <div style="float:right; margin-right:12px; height:48px; display:flex; align-items:center; background-color:rgba(0,0,0,0.5); "> <script> function click_account_pulldown() { el1 = document.getElementById('account_pulldown'); if (toggle_visible(el1)) { install_dismisser(el1); el2 = document.getElementById('lil_menu_pulldown'); el2.style.display='none'; } } </script> <a onclick="click_account_pulldown();"> <div style="display:flex; align-items:center; height:100%; float:right; "> <div style=" padding:8px; display:flex; align-items:center; "> Log In  <img style="margin-left:8px;" src="/gfx/top_drop.png"> </div> </div> </a></div></div></div> <style> .pdmi{ display:flex; align-items:center; padding:4px; font-size:12pt; }; </style> <div id="lil_menu_pulldown" style=" z-index: 400; position:absolute; display:table; padding-top:20px; padding-bottom:20px; background-color:rgba(0,0,0,0.8); width:100%; display:none"><div style="width:auto;padding:16px"><div class="menu_icon" title="Lexaloffle Games"><a href="/"><img src="/gfx/top_info.png"></a></div><div class="menu_icon" title="PICO-8"><a href="/pico-8.php"><img src="/gfx/p8b_pico8.png"></a></div><div class="menu_icon" title="Voxatron"><a href="/voxatron.php" style="padding-right:4px;padding-left:4px;"><img src="/gfx/p8b_vox.png"></a></div><div class="menu_icon" title="Picotron"><a href="/picotron.php"><img src="/gfx/p8b_picotron.png"></a></div><div class="menu_icon" title="BBS" ><a href="/bbs"><img src="/gfx/bbs_cube.gif" width=40></a></div><div class="menu_icon" title="Superblog"><a href="/bbs/superblog.php"><img src="/gfx/top_blog.png"></a></div></div><a href=/pico-8.php ><div class="linkybutton" style="background-color : #a0a0a0; color:#fff">PICO-8</div></a><a href=/pico-8.php?page=faq ><div class="linkybutton">FAQ</div></a><a href=/pico-8.php?page=resources ><div class="linkybutton">Resources</div></a><a href=/pico-8.php?page=schools ><div class="linkybutton">Schools</div></a><a href=/pico-8.php?page=submit ><div class="linkybutton">Submit</div></a><a href=/bbs/?cat=7 ><div class="linkybutton">Forum</div></a><a href=/bbs/?cat=7&carts_tab=1#mode=carts&sub=2 ><div class="linkybutton">Carts</div></a></div></div> <div class = "topmenu_div" id = "banner" style=" margin:0px; top:0px; background-color:#111; width:100%; height:25vh; background:url('/gfx/p8tv_header.png') no-repeat center; -webkit-background-size:cover; -moz-background-size:cover; -o-background-size:cover; background-size:cover; "> <div id="banner_info" style="width:400px; height:300px; position:absolute"> <table cellpadding=0 cellspacing=0 width=100%><tr><td width=48%> <a href="#m"><img src="/gfx/pico8_logo_vector.png" style="width:100%;"></a> </td><td width=2%> </td><td width=50% valign=bottom> <div style="width:100%; height:100%; display:table; background-color:#f69; "> <a href="/games.php?page=updates"> <div style="padding:3%; color:#fff; margin-left:2%;"> <table cellspacing=0 cellpadding=0><tr><td width=12%> <img src="/gfx/filedisk.png" width=20 height=20> </td><td style="font-size:0.8vw; color:#fff">   Download v0.2.6b </td></table> </div> </a> </div> </td></tr></table> <div style="width:100%; max-height:30%; margin-top:5%; display:table"> <div id="p8tv_cart_info" style="padding:1%; display:table; width:100%; height:100%; font-size:1vw; "> Make your own cartridges or browse the cartverse in SPLORE! <a href="#m" style="color:#999;cursor:pointer">[learn more]</a> </div> </div> </div> <script> var last_pico8_player_layout_hash = 0; var pico8_player_button_w = 24; function update_pico8_player_layout() { var canvas = document.getElementById("canvas"); var banner = document.getElementById("banner"); var p8_frame = document.getElementById("p8_frame") var p8_menu_buttons = $("p8_menu_buttons"); if (!canvas || !banner || !p8_frame || !p8_menu_buttons) { last_pico8_player_layout_hash = -55; // should still be invalid though requestAnimationFrame(update_pico8_player_layout); // console.log("@@ skipping update_pico8_player_layout (not ready)"); return; } // console.log("@@ calling update_pico8_player_layout"); // calculate scale var scale = 8; while (scale > 1 && (scale*480 > window.innerWidth || scale*210 > window.innerHeight*0.95)) // scale -= 1/8; //!! favor size and layout over pixel perfectness scale -= 1/2; var docWidth = document.documentElement.clientWidth || document.body.clientWidth; // https://developer.mozilla.org/en-US/docs/Web/API/Element.clientWidth var banner_w = docWidth; // window.innerWidth; // window.innerWidth includes scrollbar var banner_h = 210*scale; var csize = scale * 128; var csize_w = scale * 144; var csize_h = scale * 136; var cart_w = scale * 160/4; var cart_h = scale * 205/4; var layout_hash = banner_w + banner_h * 1000; if (typeof(p8_is_running) !== 'undefined' && p8_is_running) layout_hash += 10001; //if (p8_is_running) layout_hash += 10001; // hrrm // last_pico8_player_layout_hash = 0; // !!!!!!!!!!!!!! every frame while debugging if (last_pico8_player_layout_hash == layout_hash) { // no need to update this frame requestAnimationFrame(update_pico8_player_layout); //console.log("@@ skipping update_pico8_player_layout (no change)"); return; } console.log("@ regenerating layout "+last_pico8_player_layout_hash); last_pico8_player_layout_hash = layout_hash; // set sizes and positions if (banner) { banner.style.width = banner_w; banner.style.height = banner_h; } pico8_player_button_w = 24 * csize_w / 512; if (canvas) { canvas.style.width = csize_w; canvas.style.height = csize_h; canvas.style.left = banner_w/2 - csize_w/2; canvas.style.top = banner_h/2 - csize_h/2 + 10*scale; } // p8_frame: match canvas if (p8_frame) { p8_frame.style.width = csize_w; p8_frame.style.height = csize_h; p8_frame.style.left = banner_w/2 - csize_w/2; p8_frame.style.top = banner_h/2 - csize_h/2 + 10*scale; } // power-on gif start_player_gif = document.getElementById("start_player_gif"); if (start_player_gif) { start_player_gif.width = csize; start_player_gif.height = csize; start_player_gif.style.left = banner_w/2 - csize/2; start_player_gif.style.top = banner_h/2 - csize/2 + 10*scale; } // adjust vertical position of menu buttons p8_menu_buttons = document.getElementById("p8_menu_buttons"); if (p8_menu_buttons) { p8_menu_buttons.style.marginTop = csize_h - (pico8_player_button_w*5); } // carts var tv_left = banner_w/2 - 90*scale; var tv_right = banner_w/2 + 90*scale; var tv_bottom = banner_h - 15*scale; var els = document.getElementsByClassName("p8cart"); var lels = document.getElementsByClassName("p8cart_label"); for (i = 0; i < els.length; i++) { var el = els[i]; if (el && el.style) // sanity { el.style.width = cart_w; el.style.height = cart_h; // label if (lels && lels.length == els.length) { lel = lels[i]; // to do: don't rely on this lel.style.width = cart_w * 128 / 160; lel.style.height = cart_w * 128 / 160; lel.style.left = 16 * cart_w/160; lel.style.top = -181 * cart_w/160; } } else last_pico8_player_layout_hash = -77; } // first 6: grid on left for (y = 0; y < 2; y++) for (x = 0; x < 3; x++) { var el = els[x + y*3]; if (el){ el.style.left = tv_left - (x+1)*(cart_w*1.15); el.style.top = tv_bottom - ((1-y)+1)*(cart_h*1.1); }else last_pico8_player_layout_hash = -77; } // last 9: grid on right for (y = 0; y < 3; y++) for (x = 0; x < 3; x++) { var el = els[6+x + y*3]; if (el){ el.style.left = tv_right + cart_w*0.15 + (x)*(cart_w*1.15); el.style.top = tv_bottom - ((2-y)+1)*(cart_h*1.1); } else last_pico8_player_layout_hash = -77; } // top left: pico-8 logo, download button, cart info. all inside banner_info // occupies 3x1 carts var banner_info = document.getElementById("banner_info"); if (banner_info) { banner_info.style.left = tv_left - (3 * cart_w*1.15); banner_info.style.top = tv_bottom - (3 * cart_h*1.1); banner_info.style.width = cart_w*3.3; banner_info.style.height = cart_h*1.5; } else last_pico8_player_layout_hash = -77; // p8_menu_buttons position if (p8_menu_buttons && p8_menu_buttons.style) // sanity { p8_menu_buttons.style = ""; p8_menu_buttons.style.zIndex = 50000; } else last_pico8_player_layout_hash = -77; els = document.getElementsByClassName("p8_menu_button"); for (i = 0; i < els.length; i++) { var el = els[i]; if (el) { el.style = "position:absolute;margin-left:"+(144*scale)+"px"; el.style.top = (75 + i * 10) * scale; } else last_pico8_player_layout_hash = -77; } // invalidate buttons layout p8_buttons_hash = -33; requestAnimationFrame(update_pico8_player_layout); } requestAnimationFrame(update_pico8_player_layout); // called from p8_run_cart() function p8_run_cart_onrun(cid) { last_pico8_player_layout_hash = 0; // update layout // hide start gif var start_player_gif = document.getElementById("start_player_gif"); if (start_player_gif) start_player_gif.style.display="none"; // update cart info var el = document.getElementById("p8tv_cart_info"); for (i = 0; i < 15; i++) if (p8tv_dat[i][1] == cid) { var user_url="/bbs/?uid="+p8tv_dat[i][5]; el.innerHTML = ` <table cellpadding=0 cellspacing=0 width=100% height=100%><tr> <td width=54><a href="`+user_url+`"><img src="`+p8tv_dat[i][7]+`" width=48 height=48 style="max-height:90%"></a></td> <td width style="color:#fff; font-size:1.7vh"> <div style=""> <span style="color:#99a">Now playing </span><a href="`+p8tv_dat[i][3]+`">`+p8tv_dat[i][8]+`</a><br> </div><div style="margin-top:2%"> <div style="float:left"> <span style="color:#99a;">by </span><a href="`+user_url+`">@`+p8tv_dat[i][6]+`</a> </div> <div style="display:inline;">`+p8tv_dat[i][9]+`</div> </div> </td> </tr></table> `; } } </script> <script> // globals var p8_is_running = false; var p8_script = null; var Module = null; var codo_textarea = null; var menu_buttons_extra_hack = 0; var is_voxatron = false; var is_picotron = false; var p8_current_playing_lid = null; function p8_document() { /* if (p8_current_playing_lid) { var playing_div = document.getElementById("cart_player_"+p8_current_playing_lid); if (playing_div) return playing_div; } */ return document; } // Default shell for PICO-8 0.1.12 // options // p8_autoplay true to boot the cartridge automatically after page load when possible // if the browser can not create an audio context outside of a user gesture (e.g. on iOS), p8_autoplay has no effect var p8_autoplay = false; // When pico8_state is defined, PICO-8 will set .is_paused, .sound_volume and .frame_number each frame // (used for determining button icons) var pico8_state = []; // use to send keypresses // var codo_key_buffer = []; var codo_key_buffer = []; var p8_keyboard_state = 0; // mode (toggle with shift) // When pico8_buttons is defined, PICO-8 reads each int as a bitfield holding that player's button states // 0x1 left, 0x2 right, 0x4 up, 0x8 right, 0x10 O, 0x20 X, 0x40 menu // (used by p8_update_gamepads) var pico8_buttons = [0, 0, 0, 0, 0, 0, 0, 0]; // max 8 players // picotron var picotron_buttons = []; // max 8 players // When pico8_mouse is defined and .length>0, PICO-8 reads the 3 integers as X, Y and a bitfield for buttons: 0x1 LMB, 0x2 RMB var pico8_mouse = []; // used to display number of detected joysticks var pico8_gamepads = {}; pico8_gamepads.count = 0; // When pico8_gpio is defined, reading and writing to gpio pins will read and write to these values var pico8_gpio = new Array(128); // When pico8_audio_context context is defined, the html shell (this file) is responsible for creating and managing it // Otherwise, PICO-8 will create its own one var pico8_audio_context; p8_gfx_dat={ "p8b_pause1": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAOUlEQVRIx2NgGPbg/8cX/0F46FtAM4vobgHVLRowC6hm0YBbQLFFoxaM4FQ0dHPy0C1Nh26NNugBAAnizNiMfvbGAAAAAElFTkSuQmCC", "p8b_controls":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAQ0lEQVRIx2NgGAXEgP8fX/ynBaap4XBLhqcF1IyfYWQBrZLz0LEAlzqqxQFVLcAmT3MLqJqTaW7B4CqLaF4fjIIBBwBL/B2vqtPVIwAAAABJRU5ErkJggg==", "p8b_full":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAN0lEQVRIx2NgGPLg/8cX/2mJ6WcBrUJm4CwgOSgGrQVEB8WoBaMWDGMLhm5OHnql6dCt0YY8AAA9oZm+9Z9xQAAAAABJRU5ErkJggg==", "p8b_pause0":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAKUlEQVRIx2NgGHbg/8cX/7FhctWNWjBqwagFoxaMWjBqwagF5Fkw5AAAPaGZvsIUtXUAAAAASUVORK5CYII=", "p8b_sound0":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAANklEQVRIx2NgGDHg/8cX/5Hx0LEA3cChYwEugwavBcRG4qgFoxYMZwuGfk4efqXp8KnRBj0AAMz7cLDnG4FeAAAAAElFTkSuQmCC", "p8b_sound1":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAPUlEQVRIx2NgGDHg/8cX/5Hx0LEA3cChYwEugwhZQLQDqG4BsZFIKMhGLRi1YChbMPRz8vArTYdPjTboAQCSVgpXUWQAMAAAAABJRU5ErkJggg==", "p8b_close":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAU0lEQVRIx2NkoDFgpJsF/z+++I8iwS9BkuW49A+cBcRaREgf/Swg1SJi1dHfAkIG4EyOOIJy4Cwg1iJCiWDUAvItGLqpaOjm5KFfmg79Gm3ItioAl+mAGVYIZUUAAAAASUVORK5CYII=", "p8b_cart":'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAZ0lEQVR4Ae2dsQrAMAhEa+ie1f//uq79Ajs1OKRyiGDTeuAQAr4cp5lpU5LzEH32ijqPvi2ioaUpgDqTp2BApHbrEs3k6fV5GRSgAD8DmJtsbegaDpb4iz6eAao7q5njHAfo9Lxiii5mqxbMNtaN0wAAABB0RVh0TG9kZVBORwAyMDExMDIyMeNZtsEAAAAASUVORK5CYII=', "controls_left_panel":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAASwAAAEsCAYAAAB5fY51AAAEI0lEQVR42u3dMU7DQBCG0Tjam9DTcP8jpEmfswS5iHBhAsLxev/hvQY6pGXyZRTQ+nQCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHqbHEEtl+vt7hS+fLy/mXHBQqxEi/6aI/AiFW9SnB2BWDkDBAtAsADBAhAsAMECBAtAsAAECxAsAMECECxAsAAEC0CwONJ8tYvrXRAsImK19j0IFsPGSrQQLCJiNV+et7xAT7QQLIaN1dr3ooVgMWysRAvBIipWooVgERUr0UKwiIqVaCFYRMVKtBAsomIlWggWUbESLQSLqFiJFoJFVKxEC8EiKlaihWARFSvRQrDYJSSVfhaCBSBYAIIFCBbAHpoj4Bl/scOGBWDD4lX8iwE2LADBAgQLQLAABAsQLADBAhAsQLAABAtAsADBAhAsAMECBAtAsAAECxAsAMECECxAsAAECxAsAMECECxAsMh1ud7uTsHZVDcZyFo8Yt5sVJ6NyUAaSNEyIymaXwZepIKd4mwoQbAFC0CwAMECECwAwQIEC0CwAAQLECwAwQIQLECwAAQLQLAAwQI4UHME2/10QZq7usyBObBhRQwpmBUb1nADuPbuaUD/p2ezMH+1admwhosVfBcxb2SCJVaIlmAhVoiWYIkVoiVagiVWiJZgiZVYIVqCJVaIlmgJllghWoIlViBagiVWiJZoCZZYIVqCJVYgWoIlViBaggUIlnc0sPELlmghVmIlWKKFWAmWaIFYCZZoIVYIlmghVoIlWiBWgiVaiJVgIVqIlWCJFoiVYIkWYiVYiBZiJViihViJ1XbNEWyL1mMQRYvfvIGJlQ1rmE0LzIoNyyBiDrBhAYIFIFiAYAEIFoBgAYIFIFgAggUIFoBgAQgWIFgAggUgWIBgDc+Nn1D/tdH8YupwgZy5qG4ykKIlVmZDsDjshSlazqQqH7p793Q2CBaAYAGCBSBYAIIFCBaAYAEIFiBYAIIFIFiAYAEIFoBgAYIFIFgAggUIFoBgAQgWIFgAggUgWIBgAQgWwENzBKxZPub9CJ7WjA0LsGFRV+9N5+jNDhsWgGABggUgWACCxW56fgjuA3cEiz9Z/nWwR0iWP8P/YCFYDBstsUKwiIiWWCFYRERLrBAsIqIlVggWEdESKwSLiGiJFYJFRLTECsEiIlpihWARES2xQrCIiJZYIVhEREusECwioiVWCBYx0RIrBIuoaIkVr+YhFHTZtMCGBQgWgGABCBYgWACCBSBYgGABCBaAYAGCBSBYAIIFCBbj2uOR8s6AEbhexgsWYri3SKhKczcXAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMA2n+e0UMDzh3yTAAAAAElFTkSuQmCC", "controls_right_panel":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAASwAAAFeCAYAAAA/lyK/AAAKHklEQVR42u3dAZKaWBAGYE3tvfBmMCfDnGzWJLhLHHBGBt7rhu+rSiWbbAk8p3+7UeF0AgAAAAAAAAAAAOAQzpaAzN5vDlOsNwILhJXQSuIfP/YoZMGcxQ9LgLByfAILQGABAgtAYAEILEBgAQgsAIEFCCwAgQUgsACBBSCwAAQWILAABBYst/cL3LmA3/9ccRRFTRquZIigylKsrjwKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMZ0tAXz0/v7eLi6q8/nNCgos2CKYmttvl+E/uw02cX/M6y3IflpxgQVLu6fuScC8HDIP4ff08XVhwNMwuf3q3z9qvzP+fTUgh1+P+iHkAP4Li6mQairtTzO3T54tEFRhu5mZrk9wwYGDqo0+ds10XYILjhRUjgOI2J30ezqRvcdjAmH1dzeyu6KeCC7dFiQt5sMU8mMwe/YhV9cx1jhuQKehswRWCKvm4GvRCC3I0VUYhT6GlvNaIKyEFiCshBYIK6EltKBuAQorawYKz9oBaxWct+uXraGPf0ChYuudh7GOkKkzUGTrhpZOFTYcBY0x1hR0A7pWQFF5MYDDFJSxpdBoaDVgp93Vk3sJzmmjdjF76rLc+Zmq3dXvH8KbKCF1+nPn5svDP12HX1Om/v9fukh3d4621pC1u2oD7cv4+vDtwscJeZ/BSOsNKbur2udVtrqlVtT7DDqXBQlf7aduo1UoFPsjrzvorpaFVdGbOUwEZHPEtYeMYdXU6jZqXzcqQmiN9sHHSOCFsaQpvN0mSIdT9WoKo3UwFkLEkSTaZWtqh6exEIK+uke9xta40zpKlwvGwc+32Qf+NH2VfTMWQsBRJMMXq2t9bcZYCF8rkrZ0UUYefWp9Ofke5tl+hn4oI0oVSOnOZfjjr+/0/Yy6LsO+XWusUa1tQorAKjwOphp5KnVZzmNB7YLM+BWUGvvsPBY8L45eIc7uc/FvANxP+GdaJ+ewKOm602192+hc1sUaCSwqjzsVtnVNuFTX0utVY3sCiyxdxNset5V1nzOukcBibzrHsF8CC6EVcCxEYIHAElgAAgtAYAECC0BgAQgsiOdiCQQWx9IJLIEFwsoxCCxYW8YL07mYnsDiYAU5+kJvxtHq8nAMAhIqhVWxq2m6gN/XA8sF/OCTDqKALmEHcV+b6w6fD0jZYbkJRaD9zdiJ6rAopSu8vWuWLmt8S7IDPC+QooNo3Uh1ch+r3kjViXd4HiBthaJ0q/qZtfFTCZ90PJUCoQ+4HtX2zT0J4esdT1Nwm81oNGwDrsV7hW03xkEIWijRQuthf5oK22+jn9uDw46FEUJiqrOqtR/GQUjw6v4QWjXOG/UBwso4CAsKpq+8/WLBMWyzD9Lh9cZBSDSSTARIv+G22ppdnXEQ1iviNsh+rHpCfgjETR57D+sOuqx1g6tfUtTD4/TRgmpP3dVZ6VArJE5/vsfWlbr+0xf36XL6eBWD62n+KgpT//8p0nFFXW+BRbou6/cP4U3QQD2dvv7l4G44ljdrDTvtsqJ/128n69w7dwUrvfJ7m33T9W28Mwi6LN0VKCq8GECSscVoaE1BN6BrBTYqMqFlHSHVGKMz+F6nahSEwqGl4KwdKDxrBqxZgL0CXBRWzluB0BJWgNASViC0hBVQr0C9XT8dVj7+AQlCqz/oGvTCCnJ2F4fpto563KDT0FkCtQt5b13HxO3IjICws6JOH1x7PCZgvttK243s5TiAhQUfvTuJeuNVoF5whRurJkY/QQWC64NqXddMNyWogE+7mXt4tRtvu50JKSfTX+QusByy6xr+2E388/jvrufz+ecroXj6+7b1s4+f+XbxAmv/hfH6E+MHuljnNQqZboNNdEvCD4Hlhx4vNgLLWGGsAEJ2Uk7cAuG7KW+NA9mCyocPgfBB5esdQPygchxAxO7EJUqAVN2Ii8ABYYvZZXaBFF2HGxkYEUGnobME1g4rN+MUWpCiqzAKndzuHISV0AKEldACYYXQgmAFKKysGSg8awesVXDerl+2hj7+AYWKrXcexjpCps5Aka0bWjpV2HAUNMZYU9AN6FoBReXFAA5TUMaWQqOh1YBA3dWeinLNY9FlwYrdVdTH28u67GltyOtH9u5q+GO31mOeb7J3Wvd9vx/LirqHdQcivOJn7Sa23m9dFjqsIN1V9k5rw85KlwUZXumzdBQl91OXhQ7rtYK5f3zhuvW2MnRahTqrsevD8wAC64nLluNgptCqEFbjdb8oIQg6kkQbhWruj7EQHdZr42BXetuROq1KndWHLstYiMD62jh4rbHxCKEVIKzG628shOijiLHUWIgO66VxpKYanVaQzirU84DAitxdhfqwYsnQChhWYZ8XBFYot5p9O1JoRQ2rSM8DROywwp4z2Wrfop8nch4LHdZz16Bd3+qdVuQxMPrzgcBSIAVDK0lYCSwE1kwBpzixu0ZoJQqrdM8PAqt0ILwl2MfFoZUtrJx4R2DtwJLQythZgcA6YGgJKxBYKUJLWIHAShFawgoEVorQElYgsFKElrACgZUmtIQVCKzwpkZCQGCFDavzQGiBwAofVo8jodACgRU6rIQWCKxUYSW0YOeBlemqAK98dCFraLlKAwJruqDfkhXyy5+zytxpuWoDAmvaZY9hlTi0LsoIZoIgeiGvtY9ZrpXumu7osOZ1e+2skndanVJCYM0HQxtwn1b/bmD00HLCHYH1vIDfghbuZl9kztBpOeEOT8IhUvGW2p+I54qcv0KH9bluKJZmz51V9E5rtP6dMkJgzbsOv1+OElZBQ+vy8HwAEUeRo2/fOIgOK8lYGOFKobU7LeMgvFgwwwt8f+Suotb+/Fr3YdONn0YIWKxRR6Aa+2UcxEi4fCxsSxRo7TEwyng4Wm/jIER7pfedPt0VOqwUXVamW3GV6LR0VxD0FT9rJ7Hlfuuu0GGt12X1axZmls6qVKc1Wl/dFazxyr/G2+x76SLWPI7Rx0h0V7BCQbVrfS5rT0W5YmDdP3flcjKgqI7xYgBMjC0+gW1NQTegawU2KjKhZR0h1RijM/hep2oUhMKhpeCsHSg8awasWYC9AlwUVs5bgdASVoDQElYgtIQVUK9AvV0/HVY+/gEJQqs/6Br0wgpydheH6baOetyg09BZArULeW9dx9BVGQFhx0WdPrj2eEzAfLeVthvZy3EACws+encydFSCCgRX3LFqYvQTVCC4PqjWdc10U4IK+LSbuYdXu/G225mQcjKdwzhbguUBMvyxm/jn8d9dz+fzz1dC8fbbZeax/vq72+O+eSYQWLzceY1CpttgE92S8AOBxZIu7PUnRvcEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACwwL/cvBIh09+hJAAAAABJRU5ErkJggg==", }; // added w/ pico-8 0.2.1: dummys listeners on document required to allow touch events inside iframe (e.g. itch.io player) document.addEventListener('touchstart', {}); document.addEventListener('touchmove', {}); document.addEventListener('touchend', {}); // -------------------------------------------------------------------------------------------------------------------------------- // pico-8 0.2.2: allow dropping files var p8_dropped_cart = null; var p8_dropped_cart_name = ""; function p8_drop_file(e) { // console.log("@@ dropping file..."); e.stopPropagation(); e.preventDefault(); let file = null; // dropped file if (e.dataTransfer && e.dataTransfer.files && e.dataTransfer.files[0]) file = e.dataTransfer.files[0] // file selected via chooser if (!file && e.target && e.target.files && e.target.files[0]) file = e.target.files[0]; if (file) { // read from file reader = new FileReader(); let dropped_filename = 'dropped.p8.png'; try { if (typeof file.fileName !== 'undefined') dropped_filename = file.fileName; if (typeof file.name !== 'undefined') dropped_filename = file.name; } catch(err) { // was happening when inside reader.onload -- files[] becomes an empty set by that stage under Chrome on Chromebook (eh?) console.log("@@ failed to set dropped file name: "+err.message+" files:"+JSON.stringify(e.dataTransfer.files)); } console.log("@@ fetching dropped file: "+dropped_filename); reader.onload = function (event) { p8_dropped_cart = reader.result; p8_dropped_cart_name = dropped_filename; console.log("@@ finished reading dropped file: "+dropped_filename); // data:image/png;base64 e.stopPropagation(); e.preventDefault(); codo_command = 9; // read directly from p8_dropped_cart with libb64 decoder }; reader.readAsDataURL(file); } else if (e.dataTransfer) { // read from url (or data url) txt = e.dataTransfer.getData('Text'); if (txt){ p8_dropped_cart_name = "untitled.p8.png"; p8_dropped_cart = txt; codo_command = 9; } } } function nop(evt) { evt.stopPropagation(); evt.preventDefault(); } function dragover(evt) { evt.stopPropagation(); evt.preventDefault(); Module.pico8DragOver(); } function dragstop(evt) { evt.stopPropagation(); evt.preventDefault(); Module.pico8DragStop(); } // -------------------------------------------------------------------------------------------------------------------------------- var p8_buttons_hash = -1; function p8_update_button_icons() { var w = 24; var bottom_margin = 12; var padding = 4; var left = 44; var p8tv_mode = false; // buttons only appear when running if (!p8_is_running) { requestAnimationFrame(p8_update_button_icons); return; } // p8tv font page player if (typeof(pico8_player_button_w) !== 'undefined') { p8tv_mode = true; w = Math.floor(pico8_player_button_w); // console.log("@@ player_button_w: "+w); bottom_margin = Math.floor(w*3/4) - 4; padding = Math.floor(w / 4); left = Math.floor(w * 2 / 3); } var is_fullscreen=(document.fullscreenElement || document.mozFullScreenElement || document.webkitIsFullScreen || document.msFullscreenElement); // hash based on: pico8_state.sound_volume pico8_state.is_paused bottom_margin left is_fullscreen p8_touch_detected var hash = 0; hash = pico8_state.sound_volume; if (pico8_state.is_paused) hash += 0x100; if (p8_touch_detected) hash += 0x200; if (is_fullscreen) hash += 0x400; hash += bottom_margin * 0.001; hash += left * 1001.3; if (p8_buttons_hash == hash) { requestAnimationFrame(p8_update_button_icons); return; } p8_buttons_hash = hash; // console.log("@@ updating button icons"); // regenerate every frame (shouldn't be expensive?) els = p8_document().getElementsByClassName('p8_menu_button'); for (i = 0; i < els.length; i++) { el = els[i]; index = el.id; if (p8tv_mode) // cludge button positions { el.style.marginBottom = bottom_margin; el.style.paddingBottom = bottom_margin; el.style.padding = 0; el.style.left = left; } else { // arrrrgh //el.style.marginLeft = menu_buttons_extra_hack; } if (index == 'p8b_sound') index += (pico8_state.sound_volume == 0 ? "0" : "1"); // 1 if undefined if (index == 'p8b_pause') index += (pico8_state.is_paused > 0 ? "1" : "0"); // 0 if undefined new_str = '<img width='+w+' height='+w+' style="display:table; pointer-events:none;" src="'+p8_gfx_dat[index]+'">'; if (el.innerHTML != new_str) // :/ el.innerHTML = new_str; // hide all buttons for touch mode (can pause with menu buttons) var is_visible = p8_is_running; if (!p8_touch_detected && el.parentElement.id == "p8_menu_buttons_touch") // if (el.parentElement.id == "p8_menu_buttons_touch") is_visible = false; if (p8_touch_detected && el.parentElement.id == "p8_menu_buttons") is_visible = false; if (is_fullscreen) is_visible = false; if (is_visible) el.style.display=""; else el.style.display="none"; } requestAnimationFrame(p8_update_button_icons); } function abs(x) { return x < 0 ? -x : x; } function pico8_buttons_event(e, step) { if (!p8_is_running) return; if (!document.getElementById("touch_controls_gfx")) return; // console.log("button event step ",step); if (step == 2 && typeof(pico8_mouse) !== 'undefined') { pico8_mouse[2] = 0; } // on canvas var num = 0; if (e.touches) num = e.touches.length; if (num == 0) { // no active touches: release mouse button from anywhere on page if (typeof(pico8_mouse) !== 'undefined') pico8_mouse[2] = 0; } else { let touch = e.touches[0]; var x = touch.clientX; var y = touch.clientY; var w = window.innerWidth; var h = window.innerHeight; let canvas = p8_document().getElementById("canvas"); if (p8_touch_detected) if (typeof(pico8_mouse) !== 'undefined') if (canvas) { var rect = canvas.getBoundingClientRect(); //console.log(rect.top, rect.right, rect.bottom, rect.left, x, y); if (x >= rect.left && x < rect.right && y >= rect.top && y < rect.bottom) { // only define pico8_mouse once it is needed (otherwise codo mouse is clobbered on desktop) if (is_picotron) pico8_mouse = [ Math.floor((x - rect.left) * 480 / (rect.right - rect.left)), Math.floor((y - rect.top) * 270 / (rect.bottom - rect.top)), step < 2 ? 1 : 0 ]; else pico8_mouse = [ Math.floor((x - rect.left) * 128 / (rect.right - rect.left)), Math.floor((y - rect.top) * 128 / (rect.bottom - rect.top)), step < 2 ? 1 : 0 ]; //console.log("-> x y b ", pico8_mouse[0], pico8_mouse[1], pico8_mouse[2]); // return; // commented -- blocks overlapping buttons }else { pico8_mouse[2] = 0; } } } if (document.getElementById("touch_controls_gfx").style.display != "none") pico8_buttons_event_virtual_dpad(e, step); else pico8_buttons_event_virtual_keyboard(e, step); } // ** dupe ** (virtual_dpad) function pico8_buttons_event_virtual_keyboard(e, step) { if (!p8_is_running) return; if (step != 0) return; var num = 0; if (e.touches) num = e.touches.length; for (var i = 0; i < num; i++) if (e.touches[i]) { var touch = e.touches[i]; var x = touch.clientX; var y = touch.clientY; var w = window.innerWidth; var h = window.innerHeight; var r = Math.min(w,h) / 12; if (r > 40) r = 40; var keybd_h = (r*12)*132.0/200.0 // console.log("x:",x," y:",y," keybd_h:",keybd_h," r:",r); if (y < h - r*9) { // no controller buttons up here; includes canvas and menu buttons at top in touch mode } else { e.preventDefault(); var y1 = Math.floor((y - (h - keybd_h)) * 6 / keybd_h); if (y1 == 3) x -= (r*12.0/10.0)*3.0/20.0; if (y1 == 4) x -= (r*12.0/10.0)*6.0/20.0; var x1 = Math.floor(x * 10 / (r*12)); if (x1 >= 0 && x1 < 10 && y1 >= 0 && y1 < 6) { // send keypress signal to pico-8 let key_chars=[ [ "X{[(*-=_+X", "1234567890", "qwertyuiop", "asdfghjklX", "zxcvbnm,.X", "XXXX <>/" ], [ "XXXXX[]`~X", `!"#$%^&@()`, "QWERTYUIOP", "ASDFGHJKLX", "ZXCVBNM;:X", `XXXX ?'\\\\` ] ]; let val = key_chars[p8_keyboard_state][y1].charCodeAt(x1); if ((y1==3 || y1==4) && x1==9) val = 13; // enter //if (y1==0 && x1==9) val = 9; // del if (y1==0 && x1==9) val = 8; // backspace if (y1==0 && x1==0) val = 27; if (y1==5 && x1>=0 && x1 < 4) val = -1; // shift, alt, left, right codo_key_buffer.push(val); // macros if (p8_keyboard_state == 0) if (y1 == 0 && x1 >= 1 && x1 <= 3) { if (x1 == 1) codo_key_buffer.push("}".charCodeAt(0)); if (x1 == 2) codo_key_buffer.push("]".charCodeAt(0)); if (x1 == 3) codo_key_buffer.push(")".charCodeAt(0)); // to do: push left-cursor here } // special: shift key (show alt keys set) if (y1 == 5 && x1 == 0){ // toggle p8_keyboard_state = p8_keyboard_state ? 0 : 1; el = document.getElementById("controls_keyboard_panel"); if (el) el.setAttribute("src", p8_keyboard_state ? "/gfx/controls_keyboard2.png" : "/gfx/controls_keyboard.png"); } p8_give_focus(); // ** hrm. } } } } // step 0 down 1 drag 2 up (not used) function pico8_buttons_event_virtual_dpad(e, step) { if (!p8_is_running) return; pico8_buttons[0] = 0; var num = 0; if (e.touches) num = e.touches.length; for (var i = 0; i < num; i++) if (e.touches[i]) { var touch = e.touches[i]; var x = touch.clientX; var y = touch.clientY; var w = window.innerWidth; var h = window.innerHeight; //console.log("dpad touch ",x,y); var r = Math.min(w,h) / 12; if (r > 40) r = 40; b = 0; if (y < h - r*8) { // no controller buttons up here; includes canvas and menu buttons at top in touch mode } else { e.preventDefault(); if ((y < h - r*6) && y > (h - r*8)) { // menu button: half as high as X O button // stretch across right-hand half above X O buttons if (x > w - r*3) b |= 0x40; // escape button for pwa (doesn't do anything otherwise) if (x < r*3){ codo_key_buffer.push(27); p8_give_focus(); } } else if (x < w/2 && x < r*6) { // stick mask = 0xf; // dpad var cx = 0 + r*3; var cy = h - r*3; deadzone = r/3; var dx = x - cx; var dy = y - cy; if (abs(dx) > abs(dy) * 0.6) // horizontal { if (dx < -deadzone) b |= 0x1; if (dx > deadzone) b |= 0x2; } if (abs(dy) > abs(dx) * 0.6) // vertical { if (dy < -deadzone) b |= 0x4; if (dy > deadzone) b |= 0x8; } } else if (x > w - r*6) { // button; diagonal split from bottom right corner mask = 0x30; // one or both of [X], [O] if ( (h-y) > (w-x) * 0.8) b |= 0x10; if ( (w-x) > (h-y) * 0.8) b |= 0x20; } } pico8_buttons[0] |= b; } } var p8_update_layout_hash = -1; var last_windowed_container_height = 512; var last_windowed_container_width = 512; function p8_update_layout() { var canvas = p8_document().getElementById("canvas"); var p8_playarea = p8_document().getElementById("p8_playarea"); var p8_container = p8_document().getElementById("p8_container"); var p8_frame = p8_document().getElementById("p8_frame"); var csize = 512; var margin_top = 0; var margin_left = 0; var aspect = p8_aspect; // page didn't load yet? first call should be after p8_frame is created if (!canvas || !p8_playarea || !p8_container || !p8_frame) { p8_update_layout_hash = -1; requestAnimationFrame(p8_update_layout); return; } // assumes frame doesn't have padding var is_fullscreen=(document.fullscreenElement || document.mozFullScreenElement || document.webkitIsFullScreen || document.msFullscreenElement); var frame_width = p8_frame.offsetWidth; var frame_height = p8_frame.offsetHeight; if (is_fullscreen) { // same as window frame_width = window.innerWidth; frame_height = window.innerHeight; } else{ // never larger than window // (happens when address bar is down in portraight mode on phone) frame_width = Math.min(frame_width, window.innerWidth); frame_height = Math.min(frame_height, window.innerHeight); } // as big as will fit in a frame.. csize = Math.min(frame_width,frame_height); // .. but never more than 2/3 of longest side for touch (e.g. leave space for controls on iPad) if (p8_touch_detected && p8_is_running) { var longest_side = Math.max(window.innerWidth,window.innerHeight); csize = Math.min(csize, longest_side * 2/3); } // pixel perfect: quantize to closest multiple of 128 // only when large display (desktop) if (aspect == 1.0) // pico-8 if (frame_width >= 512 && frame_height >= 512) { csize = (csize+1) & ~0x7f; } // csize should never be wider / taller than parent frame // (otherwise stretched large when fullscreen and then return) // update: also -- picotron pushes out width of page and messes up touch controls formatting if (!is_fullscreen && p8_frame) { // p8_frame_0 parent csize = Math.min(csize, last_windowed_container_height); csize = Math.min(csize, last_windowed_container_width / aspect); } if (is_fullscreen) { // always center horizontally margin_left = (frame_width - (csize * aspect))/2; if (p8_touch_detected) { if (window.innerWidth < window.innerHeight) { // portrait: keep at y=40 (avoid rounded top corners / camer num thing etc.) margin_top = Math.min(40, frame_height - csize); } else { // landscape: put a little above vertical center margin_top = (frame_height - csize)/4; } } else{ // non-touch: center vertically margin_top = (frame_height - csize)/2; } // turn off temp hack /* deleteme canvas.style.position = ""; canvas.style.left = 0.0; */ } /* deleteme else { // temp hack: had to remove margin:auto from p8_container tostop blurry scale under chrome p8_container.style.margin = 0; // WHHHHY let left = (frame_width - (csize * aspect))/2; canvas.style.position = "relative"; canvas.style.left = left; menu_buttons_extra_hack = left; // p8_menu_buttons.style.marginLeft = 40;//10.0 + Math.floor(left); } */ // temporary voxatron hacks // if (p8_aspect > 1.0) // margin_left -= 40; // skip if relevant state has not changed var update_hash = csize + margin_top * 1000.3 + margin_left * 0.001 + frame_width * 333.33 + frame_height * 772.15134; if (is_fullscreen) update_hash += 0.1237; if (!is_fullscreen) // fullscreen: update every frame for safety. should be cheap! if (!p8_touch_detected) // mobile: update every frame because nothing can be trusted if (p8_update_layout_hash == update_hash) { //console.log("p8_update_layout(): skipping"); requestAnimationFrame(p8_update_layout); return; } p8_update_layout_hash = update_hash; // record this for returning to original size after fullscreen pushes out container height (argh) if (!is_fullscreen && p8_frame) { last_windowed_container_height = p8_frame.parentNode.parentNode.offsetHeight; last_windowed_container_width = p8_frame.parentNode.parentNode.offsetWidth; } //console.log("@@ p8_update_layout(): updating "+(is_fullscreen ? "fullscreen" : "windowed")+" csize: " + csize); // mobile in portrait mode: put screen at top (w / a little space for fullscreen button) // (don't cart about buttons overlapping screen) if (p8_touch_detected && p8_is_running && document.body.clientWidth < document.body.clientHeight) p8_playarea.style.marginTop = 32; else if (p8_touch_detected && p8_is_running) // landscape: slightly above vertical center (only relevant for iPad / highres devices) p8_playarea.style.marginTop = (document.body.clientHeight - csize) / 4; else p8_playarea.style.marginTop = ""; canvas.style.width = csize * aspect; canvas.style.height = csize; // to do: this should just happen from css layout. used in fullscreen canvas.style.marginLeft = margin_left; canvas.style.marginTop = margin_top; // console.log("margin_left: "+margin_left+" width: "+(csize * aspect)); p8_container.style.width = csize * aspect; p8_container.style.height = csize; if (p8_touch_detected && p8_is_running) { // turn off pointer events to prevent double-tap zoom etc (works on Android) // don't want this for desktop because breaks mouse input & click-to-focus when using codo_textarea canvas.style.pointerEvents = "none"; p8_container.style.marginTop = "0px"; // buttons // same as touch event handling var w = window.innerWidth; var h = window.innerHeight; var r = Math.min(w,h) / 12; if (r > 40) r = 40; el = document.getElementById("controls_right_panel"); el.style.left = w-r*6; el.style.top = h-r*7; el.style.width = r*6; el.style.height = r*7; if (el.getAttribute("src") != p8_gfx_dat["controls_right_panel"]) // optimisation: avoid reload? (browser should handle though) el.setAttribute("src", p8_gfx_dat["controls_right_panel"]); el = document.getElementById("controls_left_panel"); el.style.left = 0; el.style.top = h-r*6; el.style.width = r*6; el.style.height = r*6; if (el.getAttribute("src") != p8_gfx_dat["controls_left_panel"]) // optimisation: avoid reload? (browser should handle though) el.setAttribute("src", p8_gfx_dat["controls_left_panel"]); el = document.getElementById("controls_keyboard_panel"); el.style.left = 0; el.style.top = h-r*12*(132.0/200.0); el.style.width = r*12; el.style.height = r*12*(132.0/200.0); if (el.getAttribute("src") == "") el.setAttribute("src", "/gfx/controls_keyboard.png"); // scroll to cart (need to stop running with X) p8_frame.scrollIntoView(true); if (pico8_state.show_dpad == 0 && w < h) // not true when undefined { // virtual keyboard document.getElementById("touch_controls_gfx").style.display="none"; document.getElementById("touch_keyboard_gfx").style.display="table"; // hide touch menu bottons //document.getElementById("p8_menu_buttons_touch").style.display="none"; } else{ // virtual dpad document.getElementById("touch_controls_gfx").style.display="table"; document.getElementById("touch_keyboard_gfx").style.display="none"; } // don't use background -- just hide body div //document.getElementById("touch_controls_background").style.display="table"; } else{ // no touch document.getElementById("touch_controls_gfx").style.display="none"; document.getElementById("touch_keyboard_gfx").style.display="none"; //document.getElementById("touch_controls_background").style.display="none"; } if (!p8_is_running) { p8_playarea.style.display="none"; p8_container.style.display="flex"; p8_container.style.marginTop="auto"; el = p8_document().getElementById("p8_start_button"); if (el) el.style.display="flex"; } requestAnimationFrame(p8_update_layout); } var p8_touch_detected = false; //addEventListener("click", function(event){alert(pico8_state.show_dpad);}); addEventListener("touchstart", function(event) { p8_touch_detected = true; // hide textarea, so that virtual mobile keyboard doesn't come up // (and fall back to internal copy/paste -- can't paste from other apps, but can ctrl-c,v within PICO-8) if (codo_textarea && codo_textarea.style.display != "none") codo_textarea.style.display="none"; /* // deleteme if (typeof(pico8_state.show_dpad) === 'undefined' || pico8_state.show_dpad) { if (codo_textarea && codo_textarea.style.display != "none") codo_textarea.style.display="none"; } else { if (codo_textarea && codo_textarea.style.display != "") codo_textarea.style.display=""; } */ }, {passive: true}); function p8_create_audio_context() { // console.log("p8_create_audio_context()"); if (pico8_audio_context) { try { pico8_audio_context.resume(); } catch(err) { console.log("** pico8_audio_context.resume() failed"); } return; } var webAudioAPI = window.AudioContext || window.webkitAudioContext || window.mozAudioContext || window.oAudioContext || window.msAudioContext; if (webAudioAPI) { pico8_audio_context = new webAudioAPI; // wake up iOS if (pico8_audio_context) { try { var dummy_source_sfx = pico8_audio_context.createBufferSource(); dummy_source_sfx.buffer = pico8_audio_context.createBuffer(1, 1, 22050); // dummy dummy_source_sfx.connect(pico8_audio_context.destination); dummy_source_sfx.start(1, 0.25); // gives InvalidStateError -- why? hasn't been played before //dummy_source_sfx.noteOn(0); // deleteme } catch(err) { console.log("** dummy_source_sfx.start(1, 0.25) failed"); } } } } // just hides. can reopen in a paused state. // used only by mobile X button after touch_detected function p8_close_cart() { p8_is_running = false; p8_touch_detected = false; Module.pico8SetPaused(1); // hide stuff el = document.getElementById("p8_frame_0"); if (el) el.style.display="none"; // show page el = document.getElementById("body_0"); if (el) el.style.display=""; el = document.getElementById("lex_footer"); if (el) el.style.display=""; // (re-)show dormant players els = document.getElementsByClassName("dormant_player"); for (i = 0; i < els.length; i++) els[i].style.display = ''; // show } function p8_run_cart(player_url, cart_lid, cart_url) { console.log("p8_run_cart: "+player_url+" "+cart_lid+" "+cart_url); p8_current_playing_lid = cart_lid; //codo_textarea = document.getElementById("codo_textarea_global"); codo_textarea = document.getElementById("codo_textarea_"+cart_lid); // e.g. for "currently playing" update if (typeof(p8_run_cart_onrun) !== 'undefined') { p8_run_cart_onrun(cart_lid); } if (p8_is_running) { if (cart_lid != 0){ _cartname=[String(cart_lid)]; codo_command = 6; } return; } p8_is_running = true; // create audio context and wake it up (for iOS -- needs happen inside touch event) p8_create_audio_context(); // show touch elements els = document.getElementsByClassName('p8_controller_area'); for (i = 0; i < els.length; i++) els[i].style.display=""; // install touch events. These also serve to block scrolling / pinching / zooming on phones when p8_is_running // moved event.preventDefault(); calls into pico8_buttons_event (want to let top buttons pass through) addEventListener("touchstart", function(event){ pico8_buttons_event(event, 0); }, {passive: false}); addEventListener("touchmove", function(event){ pico8_buttons_event(event, 1); }, {passive: false}); addEventListener("touchend", function(event){ pico8_buttons_event(event, 2); }, {passive: false}); // load and run script e = document.createElement("script"); p8_script = e; e.onload = function(){ //window.alert("loaded "+p8_update_layout_hash); // show canvas / menu buttons only after loading el = document.getElementById("p8_playarea"); if (el) el.style.display="table"; if (typeof(last_pico8_player_layout_hash) !== 'undefined') // p8tv last_pico8_player_layout_hash = -1; if (typeof(p8_update_layout_hash) !== 'undefined') p8_update_layout_hash = -77; if (typeof(p8_buttons_hash) !== 'undefined') p8_buttons_hash = -1; /* // happens outside; when generating player. Module = {}; Module.canvas = document.getElementById("canvas"); Module.arguments = [cart_url.toString()]; p8_update_button_icons(); */ // Module.arguments = [cart_url.toString()]; // doesn't work // hack: use command 6 instead. will load as soon as codo_update is spinning // allows starting player AND choosing cart by clicking on p8tv cart // could just always do this anyway; is cleaner. if (cart_lid != 0){ _cartname=[String(cart_lid)]; codo_command = 6; } // install drag and drop thing function noopHandler(evt) { evt.stopPropagation(); evt.preventDefault(); } var canvas = p8_document().getElementById("canvas"); canvas.addEventListener('dragenter', noopHandler, false); canvas.addEventListener('dragover', noopHandler, false); canvas.addEventListener('dragleave', noopHandler, false); canvas.addEventListener('drop', noopHandler, false); canvas.addEventListener('drop', p8_drop_file, false); } e.type = "application/javascript"; e.src = player_url; e.id = "e_script"; document.body.appendChild(e); // load and run // hide start button and show canvas / menu buttons. hide start button el = document.getElementById("p8_start_button"); if (el) el.style.display="none"; // add #playing for touchscreen devices (allows back button to close) if (p8_touch_detected) { window.location.hash = "#playing"; window.onhashchange = function() { if (window.location.hash.search("playing") < 0) p8_close_cart(); } } // install drag&drop listeners { let canvas = p8_document().getElementById("canvas"); if (canvas) { canvas.addEventListener('dragenter', dragover, false); canvas.addEventListener('dragover', dragover, false); canvas.addEventListener('dragleave', dragstop, false); canvas.addEventListener('drop', nop, false); canvas.addEventListener('drop', p8_drop_file, false); } } // install "sure you'd like to navigate away?" thing window.onbeforeunload = function() { if (pico8_state.require_page_navigate_confirmation) return "Are you sure you want to navigate away?"; else return null; // ok to close immediately } } // Gamepad code // from @weeble's mod var P8_BUTTON_O = {action:'button', code: 0x10}; var P8_BUTTON_X = {action:'button', code: 0x20}; var P8_DPAD_LEFT = {action:'button', code: 0x1}; var P8_DPAD_RIGHT = {action:'button', code: 0x2}; var P8_DPAD_UP = {action:'button', code: 0x4}; var P8_DPAD_DOWN = {action:'button', code: 0x8}; var P8_MENU = {action:'menu'}; var P8_NO_ACTION = {action:'none'}; var P8_BUTTON_MAPPING = [ // ref: https://w3c.github.io/gamepad/#remapping P8_BUTTON_O, // Bottom face button P8_BUTTON_X, // Right face button P8_BUTTON_X, // Left face button P8_BUTTON_O, // Top face button P8_NO_ACTION, // Near left shoulder button (L1) P8_NO_ACTION, // Near right shoulder button (R1) P8_NO_ACTION, // Far left shoulder button (L2) P8_NO_ACTION, // Far right shoulder button (R2) P8_MENU, // Left auxiliary button (select) P8_MENU, // Right auxiliary button (start) P8_NO_ACTION, // Left stick button P8_NO_ACTION, // Right stick button P8_DPAD_UP, // Dpad up P8_DPAD_DOWN, // Dpad down P8_DPAD_LEFT, // Dpad left P8_DPAD_RIGHT, // Dpad right ]; // Track which player is controller by each gamepad. Gamepad index i controls the // player with index pico8_gamepads_mapping[i]. Gamepads with null player are // currently unassigned - they get assigned to a player when a button is pressed. var pico8_gamepads_mapping = []; function p8_unassign_gamepad(gamepad_index) { if (pico8_gamepads_mapping[gamepad_index] == null) { return; } pico8_buttons[pico8_gamepads_mapping[gamepad_index]] = 0; pico8_gamepads_mapping[gamepad_index] = null; } function p8_first_player_without_gamepad(max_players) { var allocated_players = pico8_gamepads_mapping.filter(function(x) { return x != null; }); var sorted_players = Array.from(allocated_players).sort(); for (var desired = 0; desired < sorted_players.length && desired < max_players; ++desired) { if (desired != sorted_players[desired]) { return desired; } } if (sorted_players.length < max_players) { return sorted_players.length; } return null; } function p8_assign_gamepad_to_player(gamepad_index, player_index) { p8_unassign_gamepad(gamepad_index); pico8_gamepads_mapping[gamepad_index] = player_index; } function p8_convert_standard_gamepad_to_button_state(gamepad, axis_threshold, button_threshold) { // Given a gamepad object, return: // { // button_state: the binary encoded Pico 8 button state // menu_button: true if any menu-mapped button was pressed // any_button: true if any button was pressed, including d-pad // buttons and unmapped buttons // } if (!gamepad || !gamepad.axes || !gamepad.buttons) { return { button_state: 0, menu_button: false, any_button: false, picotron_button_state: [0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0] }; } function button_state_from_axis(axis, low_state, high_state, default_state) { if (axis && axis < -axis_threshold) return low_state; if (axis && axis > axis_threshold) return high_state; return default_state; } var axes_actions = [ button_state_from_axis(gamepad.axes[0], P8_DPAD_LEFT, P8_DPAD_RIGHT, P8_NO_ACTION), button_state_from_axis(gamepad.axes[1], P8_DPAD_UP, P8_DPAD_DOWN, P8_NO_ACTION), ]; var button_actions = gamepad.buttons.map(function (button, index) { var pressed = button.value > button_threshold || button.pressed; if (!pressed) return P8_NO_ACTION; return P8_BUTTON_MAPPING[index] || P8_NO_ACTION; }); var all_actions = axes_actions.concat(button_actions); var menu_button = button_actions.some(function (action) { return action.action == 'menu'; }); var button_state = (all_actions .filter(function (a) { return a.action == 'button'; }) .map(function (a) { return a.code; }) .reduce(function (result, code) { return result | code; }, 0) ); var any_button = gamepad.buttons.some(function (button) { return button.value > button_threshold || button.pressed; }); any_button |= button_state; //jww: include axes 0,1 as might be first intended action // picotron var picotron_button_state = [ -gamepad.axes[0], gamepad.axes[0], -gamepad.axes[1], gamepad.axes[1], gamepad.buttons[1].value, gamepad.buttons[0].value, gamepad.buttons.length > 9 ? Math.max(gamepad.buttons[8].value, gamepad.buttons[9].value) : 0, // menu button 0, // 7: reserved -gamepad.axes[2], gamepad.axes[2], -gamepad.axes[3], gamepad.axes[3], // secondary stick gamepad.buttons[2].value, // diamond-L gamepad.buttons[3].value, // diamond-U gamepad.buttons[4].value, gamepad.buttons[5].value // shoulder L, R ]; // dpad if (gamepad.buttons.length >= 16){ picotron_button_state[0] = Math.max(picotron_button_state[0], gamepad.buttons[14].value); picotron_button_state[1] = Math.max(picotron_button_state[1], gamepad.buttons[15].value); picotron_button_state[2] = Math.max(picotron_button_state[2], gamepad.buttons[12].value); picotron_button_state[3] = Math.max(picotron_button_state[3], gamepad.buttons[13].value); } // extra menu button: big one in the middle if (gamepad.buttons.length >= 17) picotron_button_state[6] = Math.max(picotron_button_state[6], gamepad.buttons[16].value); // picotron expects integer values 0..32767 for (var j=0; j < 16; j++) { picotron_button_state[j] = Math.max(0, Math.min(Math.floor(picotron_button_state[j] * 32767.0), 32767)); if (picotron_button_state[j] >= 8192){ any_button |= 0xff; //console.log("btn "+j+": "+picotron_button_state[j]); } } return { button_state, menu_button, any_button, picotron_button_state }; } // jww: pico-8 0.2.1 version for unmapped gamepads, following p8_convert_standard_gamepad_to_button_state // axes 0,1 & buttons 0,1,2,3 are reasonably safe. don't try to read dpad. // menu buttons are unpredictable, but use 6..8 anyway (better to have a weird menu button than none) function p8_convert_unmapped_gamepad_to_button_state(gamepad, axis_threshold, button_threshold) { if (!gamepad || !gamepad.axes || !gamepad.buttons) { return { button_state: 0, menu_button: false, any_button: false, picotron_button_state : [0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0] }; } var button_state = 0; if (gamepad.axes[0] && gamepad.axes[0] < -axis_threshold) button_state |= 0x1; if (gamepad.axes[0] && gamepad.axes[0] > axis_threshold) button_state |= 0x2; if (gamepad.axes[1] && gamepad.axes[1] < -axis_threshold) button_state |= 0x4; if (gamepad.axes[1] && gamepad.axes[1] > axis_threshold) button_state |= 0x8; // buttons: first 4 taken to be O/X, 6..8 taken to be menu button for (j = 0; j < gamepad.buttons.length; j++) if (gamepad.buttons[j].value > 0 || gamepad.buttons[j].pressed) { if (j < 4) button_state |= (0x10 << (((j+1)/2)&1)); // 0 1 1 0 -- A,X -> O,X on xbox360 else if (j >= 6 && j <= 8) button_state |= 0x40; } var menu_button = button_state & 0x40; var any_button = gamepad.buttons.some(function (button) { return button.value > button_threshold || button.pressed; }); any_button |= button_state; //jww: include axes 0,1 as might be first intended action // picotron: use pico-8 var picotron_button_state = [ (button_state & 1) ? 0x7fff : 0, (button_state & 2) ? 0x7fff : 0, (button_state & 4) ? 0x7fff : 0, (button_state & 8) ? 0x7fff : 0, (button_state & 16) ? 0x7fff : 0, (button_state & 32) ? 0x7fff : 0, (button_state & 64) ? 0x7fff : 0, 0, 0,0,0,0, 0,0,0,0 ]; return { button_state, menu_button, any_button, picotron_button_state }; } // gamepad https://developer.mozilla.org/en-US/docs/Web/API/Gamepad_API/Using_the_Gamepad_API // (sets bits in pico8_buttons[]) function p8_update_gamepads() { var axis_threshold = 0.3; var button_threshold = 0.5; // Should be unnecessary, we should be able to trust .pressed var max_players = 8; var gps = navigator.getGamepads() || navigator.webkitGetGamepads(); if (!gps) return; // In Chrome, gps is iterable but it's not an array. gps = Array.from(gps); pico8_gamepads.count = gps.length; while (gps.length > pico8_gamepads_mapping.length) { pico8_gamepads_mapping.push(null); } var menu_button = false; var gamepad_states = gps.map(function (gp) { return (gp && gp.mapping == "standard") ? p8_convert_standard_gamepad_to_button_state(gp, axis_threshold, button_threshold) : p8_convert_unmapped_gamepad_to_button_state(gp, axis_threshold, button_threshold); }); // Unassign disconnected gamepads. // gps.forEach(function (gp, i) { if (gp && !gp.connected) { p8_unassign_gamepad(i); }}); gps.forEach(function (gp, i) { if (!gp || !gp.connected) { p8_unassign_gamepad(i); }}); // https://www.lexaloffle.com/bbs/?pid=87132#p // Assign unassigned gamepads when any button is pressed. gamepad_states.forEach(function (state, i) { if (state.any_button && pico8_gamepads_mapping[i] == null) { var first_free_player = p8_first_player_without_gamepad(max_players); p8_assign_gamepad_to_player(i, first_free_player); } }); // Update pico8_buttons array. gamepad_states.forEach(function (gamepad_state, i) { if (pico8_gamepads_mapping[i] != null) { pico8_buttons[pico8_gamepads_mapping[i]] = gamepad_state.button_state; picotron_buttons[pico8_gamepads_mapping[i]] = gamepad_state.picotron_button_state; } }); // Update menu button. // Pico 8 only recognises the menu button on the first player, so we // press it when any gamepad has pressed a button mapped to menu. if (gamepad_states.some(function (state) { return state.menu_button; })) { pico8_buttons[0] |= 0x40; if (picotron_buttons[0]) picotron_buttons[0][6] = 0xff; // ditto for picotron } requestAnimationFrame(p8_update_gamepads); } requestAnimationFrame(p8_update_gamepads); // End of gamepad code // key blocker. prevent cursor keys from scrolling page while playing cart. // also don't act on M, R so that can mute / reset cart document.addEventListener('keydown', function (event) { event = event || window.event; if (!p8_is_running) return; //console.log(event.keyCode+":"+([17,88,67,86].indexOf(event.keyCode))); if (pico8_state.has_focus == 1) // commented -- catch /all/ keypresses to support editor, and because using codo_textfield focus method // if ([32, 37, 38, 39, 40, 77, 82, 80, 9].indexOf(event.keyCode) > -1) // allow: cursors, M R P, tab if ([17,88,67,86].indexOf(event.keyCode) < 0) // block all keypresses except ctrl,x,c,v (need for codo_textfield clipboard) if (event.preventDefault) event.preventDefault(); //if (is_picotron) // picotron: block everything // if ([86].indexOf(event.keyCode) == -1) // ** except V to paste -- need native browser behaviour on that one ** // if (event.preventDefault) event.preventDefault(); },{passive: false}); // same as in codo_update_js_textfield() function p8_give_focus() { el = (typeof codo_textarea === 'undefined') ? document.getElementById("codo_textarea") : codo_textarea; if (el) { el.focus(); el.select(); } } function p8_request_fullscreen() { var is_fullscreen=(document.fullscreenElement || document.mozFullScreenElement || document.webkitIsFullScreen || document.msFullscreenElement); if (is_fullscreen) { if (document.exitFullscreen) { document.exitFullscreen(); } else if (document.webkitExitFullscreen) { document.webkitExitFullscreen(); } else if (document.mozCancelFullScreen) { document.mozCancelFullScreen(); } else if (document.msExitFullscreen) { document.msExitFullscreen(); } return; } var el = document.getElementById("p8_playarea"); if ( el.requestFullscreen ) { el.requestFullscreen(); } else if ( el.mozRequestFullScreen ) { el.mozRequestFullScreen(); } else if ( el.webkitRequestFullScreen ) { el.webkitRequestFullScreen( Element.ALLOW_KEYBOARD_INPUT ); } } var p8_aspect = 1.0; function activate_p8_player(player_url, cart_lid, cart_url, new_parent_id, dormant_player_id) { var p8_frame_0 = document.getElementById("p8_frame_0"); var new_parent = document.getElementById(new_parent_id); var dormant_player = document.getElementById(dormant_player_id); p8_aspect = 1.0; if (player_url.indexOf("vox") >= 0) is_voxatron = true; if (player_url.indexOf("picotron") >=0 || player_url.indexOf("playground")>= 0) is_picotron = true; if (is_voxatron) p8_aspect = 820.0/512.0; if (is_picotron) p8_aspect = 480.0/270.0; if (!p8_frame_0) { console.log("@@ could not find p8_frame_0"); return; } p8_frame_0.parentNode.removeChild(p8_frame_0); new_parent.appendChild(p8_frame_0); //p8_frame_0.style.display="table"; p8_frame_0.style="display:table; width:100%;height:100%; max-width:100vw;max-height:100vh; min-width:256px;min-height:256px;margin:0px;background-color:#111" dormant_player.style.display = 'none'; // bbs player: can remove entire page and move player div to front // (differs from exported player approach if (p8_touch_detected) { el = document.getElementById("body_0"); if (el) el.style.display="none"; el.parentNode.appendChild(document.getElementById("p8_frame_0")); el = document.getElementById("lex_footer"); if (el) el.style.display="none"; } // run! p8_run_cart(player_url, cart_lid, cart_url); // load cart menu embedded = (window.parent && window.parent != window) ? 1 : 0; // to do: decide cab (and make cab system clearer. count as bbs play -- need for voxatron embeds) //var el = document.getElementById("more_carts_global"); //if (!el) console.log("@@ could not find "+"more_carts_"+cart_lid); //if (el) // only load if can find element for it { microAjax("/bbs/on_play.php?id="+cart_lid+"&embedded="+embedded+"&cab=0", function (retdata){ var el = document.getElementById("more_carts_"+cart_lid); //var el = document.getElementById("more_carts_global"); if (el) el.innerHTML = retdata; } ); } // show dormant frames on other carts // was set_active_widget() els = document.getElementsByClassName("dormant_player"); for (i = 0; i < els.length; i++) if (els[i] != dormant_player) els[i].style.display = ''; // show // safety. should be unnecessary :| // when running wasm version, canvas seems right size, but then shrinks. sdl? to do: investigate setTimeout(function(){ p8_update_layout_hash = -56; },200); setTimeout(function(){ p8_update_layout_hash = -56; },300); setTimeout(function(){ p8_update_layout_hash = -57; },500); setTimeout(function(){ p8_update_layout_hash = -58; },1000); setTimeout(function(){ p8_update_layout_hash = -59; },2000); } function toggle_cart_menu(div_id) { var el=$(div_id); if (el) { el.style.display = (el.style.display=='none') ? 'table' : 'none'; var slider_el = el.childNodes[1]; if (true) { left_target = 0; slider_el.style.left = (left_target + 540)+'px'; poll_function(200, 10, function(q){ q= 1 - (1-q)*(1-q); slider_el.style.left = (left_target + (1-q)*540)+'px'; }); } codo_running = (el.style.display == 'none'); // running if hidden codo_command = 5; codo_command_p = !codo_running; } } // for downloading saved carts function download_browser_file(filename, contents) { var element = document.createElement('a'); if (filename.substr(filename.length - 7) == ".p8.png") element.setAttribute('href', 'data:image/png;base64,' + encodeURIComponent(contents)); else if (filename.substr(filename.length - 4) == ".wav") element.setAttribute('href', 'data:audio/x-wav;base64,' + encodeURIComponent(contents)); else element.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(contents)); element.setAttribute('download', filename); element.style.display = 'none'; document.body.appendChild(element); element.click(); document.body.removeChild(element); } </script><STYLE TYPE="text/css"> <!--.p8_menu_button{ opacity:0.2; padding:4px; display:table; width:24px; height:24px; } .p8_menu_button:hover{ opacity:1.0; cursor:pointer; } canvas{ image-rendering: optimizeSpeed; image-rendering: -moz-crisp-edges; image-rendering: -webkit-optimize-contrast; image-rendering: optimize-contrast; image-rendering: pixelated; -ms-interpolation-mode: nearest-neighbor; border: 0px; cursor: none; } .button_gfx{ stroke-width:2; stroke: #ffffff; stroke-opacity:0.4; fill-opacity:0.2; fill:black; } .button_gfx_icon{ stroke-width:3; stroke: #909090; stroke-opacity:0.7; fill:none; } .p8_start_button{ cursor:pointer; -repeat center; -webkit-background-size:cover; -moz-background-size:cover; -o-background-size:cover; background-size:cover; } .p8_menu_buttons{ display: flex; flex-direction: column; height:512px; background-color:#f8a; } @media screen and (max-width: 800px) { .cart_player_wrapper{ width:384px; height:384px; } .p8_menu_buttons{ margin-top:0px; display:none; } } --> </STYLE> <div> <!-- optional div to limit max size --> <div id="p8_frame" style="display:flex; position:absolute;"> <textarea id="codo_textarea_amstradchips1-1" class="emscripten" style="display:none; position:absolute; left:-9999px; height:0px; overflow:hidden"></textarea><input id="p8_file_chooser" type="file" name="name" style="display: none;" onchange='p8_drop_file(event)'/><div style="position:absolute"> <div id="cart_menu_amstradchips1-1" style=" display:none; position:absolute; width:696px; height:640px; max-width:696px; max-height:640px; z-index:200; overflow:hidden; padding:0px; " > <div style=" background: rgba(24.0,24.0,42.0,0.95); position:relative; top:0px; left:0px; margin:0px; display:block; width:688px; height:632px; padding-left:8px; padding-top:8px; overflow:hidden; " > <div style="margin:8px; margin-top:12px; margin-bottom:20px; width:100%; height:128px; float:left"><div style="margin-top:8px; margin-left:px; width:128px; height:128px; float:left; background:url('/bbs/thumbs/pico8_amstradchips1-1.png') no-repeat center; -webkit-background-size:cover; -moz-background-size:cover; -o-background-size:cover; background-size:cover; "> </div><div style="padding:8px; padding-left:32px; display:table"><a target="_parent" href="https://www.lexaloffle.com/bbs/?pid=amstradchips1#p"> <div style="font-size:16pt; color:#fff; margin-bottom:8px">amstradchips1</div> </a><div style="padding-bottom:12px"><a target="_parent" style="color:#fab" href="/bbs/cposts/am/amstradchips1-1.p8.png">Cart File</a> | <a target="_parent" style="color:#fab" href="https://www.lexaloffle.com/bbs/?pid=amstradchips1-1#p">Forum Post</a> | </div> <table cellpadding=0 cellspacing=8><tr><td rowspan=2> <a target="_parent" href=https://www.lexaloffle.com/bbs/?uid=23375><img src="/bimg/pi/pi15.png" width=48></a> </td><td> <a target="_parent" style="color:#fab" href="https://www.lexaloffle.com/bbs/?uid=23375&mode=carts">More cartridges</a> by <a target="_parent" href=https://www.lexaloffle.com/bbs/?uid=23375><b>carlc27843</b></a> </td></tr><tr><td> <a target=_rss href="/bbs/feed.php?uid=23375" class=social_button> <img src="/gfx/so_rss.png" width=24 height=24 style="float:left; margin-left:4px; margin-top:0px; margin-bottom:4px"> </a><a target=_twitter href="https://twitter.com/carlc27843" class=social_button> <img src="/gfx/so_twitter.png" width=24 height=24 style="float:left; margin-left:4px; margin-top:0px; margin-bottom:4px"> </a><a target=_itch href="https://carlc27843.itch.io/" class=social_button> <img src="/gfx/so_itch.png" width=24 height=24 style="float:left; margin-left:4px; margin-top:0px; margin-bottom:4px"> </a> </td></tr></table> </div></div><div style="display:table; font-size:14pt; color:#fff; width:100%; padding:4px; padding-top:24px; ">More Cartridges <a target="_parent" style="font-size:10pt; color:#aaa" href="https://www.lexaloffle.com/bbs/?cat=7#sub=2&mode=carts">[View All]</a> </div><div id="more_carts_amstradchips1-1"></div></div></div></div> <div id="p8_menu_buttons_touch" style="position:absolute; width:100%; z-index:10; left:0px; top:0px;"> <div class="p8_menu_button" id="p8b_full" style="float:left;margin-left:10px" onClick="p8_give_focus(); p8_request_fullscreen();"></div> <div class="p8_menu_button" id="p8b_sound" style="float:left;margin-left:10px" onClick="p8_give_focus(); p8_create_audio_context(); Module.pico8ToggleSound();"></div> <div class="p8_menu_button" id="p8b_close" style="float:right; margin-right:10px" onClick="p8_close_cart();"></div> </div> <div id="p8_container" style="margin:auto; display:table;" onclick="if (!p8_is_running) {p8_create_audio_context(); p8_run_cart('/play/pico8_0206c_dev8.js', 'amstradchips1-1', '/bbs/cposts/am/amstradchips1-1.p8.png');}"> <div id="p8_start_button" class="p8_start_button" style="width:100%; height:100%; display:flex; cursor:pointer; background:url(''); -repeat center; -webkit-background-size:cover; -moz-background-size:cover; -o-background-size:cover; background-size:cover; "> <img width=80 height=80 style="margin:auto;" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAFAAAABQCAYAAACOEfKtAAABpklEQVR42u3au23DQBCEYUXOXIGKcujQXUgFuA0XIKgW90Q9oEAg+Ljd27vd2RsCf058gEDqhofPj+OB6SMCAQlIQAIyAhKQgARkBAQDnM6XSRsB7/2e/tSA0//12fCAKsQX3ntDA4oRFwBRIc0AixE38BAhTQGLEAsBUSDNAXcRhYDRIZsAPlp99VECRoXsDpgN0g0wC6Q7IDpkGEBUyG6A0+vKBtkdMBukG2AWSHdAdMgwgKiQ4QDRIMMCokCGB4wOCQPYFVKw2cABNocUjl6wgE0gFashPKAZpHJ2TQNYBVmxW6cDFENWDv9pAUshCVgJScBKSAISkD9hPkT4GkNAMdzepyj8Kye852EBLe51CZHHWQK4JcThD1SlcHPEYY/0a+A0n6SkGZV6w6WZNb3g4Id1b7hwgGhwYQBR4dwB0eHcALPAdQfMBhcOEA0uDCAqnDsgOpwbYBa4poA/31+rZYFrBriFpwGMCtcEcA9PAhgdzhywBK8EEQXOFFCCtwaIBmcGKMWbI6LCmQBq8R6hw5kAMgISkIAEJCAjIAEJSEBGQI9ukV7lRn9nD+gAAAAASUVORK5CYII="/> </div> <div id="p8_playarea" style="display:none; margin:auto; -webkit-user-select:none; -moz-user-select: none; user-select: none; -webkit-touch-callout:none; "> <div id="touch_controls_background" style=" pointer-events:none; display:none; background-color:#000; opacity:0.5; position:fixed; top:0px; left:0px; border:0; width:100vw; height:200vh; overflow:hidden">   </div> <div id="p8_playarea_flex" style="display:flex; position:"> <canvas class="emscripten" id="canvas" oncontextmenu="event.preventDefault();"> </canvas> <div class=p8_menu_buttons id="p8_menu_buttons" style="margin-left:10px;"> <div class="p8_menu_button" style="position:absolute; bottom:125px" id="p8b_controls" onClick="p8_give_focus(); Module.pico8ToggleControlMenu();"></div> <div class="p8_menu_button" style="position:absolute; bottom:90px" id="p8b_pause" onClick="p8_give_focus(); Module.pico8TogglePaused(); p8_update_layout_hash = -22;"></div> <div class="p8_menu_button" style="position:absolute; bottom:55px" id="p8b_sound" onClick="p8_give_focus(); p8_create_audio_context(); Module.pico8ToggleSound();"></div> </div> </div> <!-- display after first layout update --> <div id="touch_controls_gfx" style=" pointer-events:none; display:table; position:fixed; top:0px; left:0px; border:0; width:100vw; height:100vh"> <img src="" id="controls_right_panel" style="position:absolute; opacity:0.5;"> <img src="" id="controls_left_panel" style="position:absolute; opacity:0.5;"> </div> <!-- touch_controls_gfx --> <div id="touch_keyboard_gfx" style=" pointer-events:none; display:table; position:fixed; top:0px; left:0px; border:0; width:100vw; height:100vh"> <img src="" id="controls_keyboard_panel" style="position:absolute; opacity:0.5;"> </div> <!-- touch_keyboard_gfx --> </div> <!--p8_playarea --> </div> <!-- p8_container --> </div> <!-- p8_frame --> </div> <!-- size limit --> <script type="text/javascript"> p8_update_button_icons(); var canvas = document.getElementById("canvas"); Module = {}; Module.canvas = canvas; // pointer lock request needs to be inside a canvas interaction event // pico8_state.request_pointer_lock is true when 0x5f2d bit 0 and bit 2 are set -- poke(0x5f2d,0x5) // note on mouse acceleration for future: // https://github.com/w3c/pointerlock/pull/49 canvas.addEventListener("click", function() { if (!p8_touch_detected) if (pico8_state.request_pointer_lock) { // console.log("requesting lock"); canvas.requestPointerLock(); } }); Module.arguments = ["-tv_frame","1","/bbs/cposts/am/amstradchips1-1.p8.png"];</script></div><div class="desktop_div"> <div onclick = 'p8_run_cart("/play/pico8_0206c_dev8.js", "trichromat-0","/bbs/cposts/tr/trichromat-0.p8.png")' class="p8cart" id="p8cart_0" style="cursor:pointer; width:160px; height:205px; position:absolute; top: -1000px; left:-1000px;"> <img src="/bbs/cposts/tr/trichromat-0.p8.png" style="width:100%; height:100%;"> </div> <div onclick = 'p8_run_cart("/play/pico8_0206c_dev8.js", "build_a_jetpack-1","/bbs/cposts/bu/build_a_jetpack-1.p8.png")' class="p8cart" id="p8cart_1" style="cursor:pointer; width:160px; height:205px; position:absolute; top: -1000px; left:-1000px;"> <img src="/bbs/cposts/bu/build_a_jetpack-1.p8.png" style="width:100%; height:100%;"> </div> <div onclick = 'p8_run_cart("/play/pico8_0206c_dev8.js", "super_world_of_goo-6","/bbs/cposts/su/super_world_of_goo-6.p8.png")' class="p8cart" id="p8cart_2" style="cursor:pointer; width:160px; height:205px; position:absolute; top: -1000px; left:-1000px;"> <img src="/bbs/cposts/su/super_world_of_goo-6.p8.png" style="width:100%; height:100%;"> </div> <div onclick = 'p8_run_cart("/play/pico8_0206c_dev8.js", "dontdig-1","/bbs/cposts/do/dontdig-1.p8.png")' class="p8cart" id="p8cart_3" style="cursor:pointer; width:160px; height:205px; position:absolute; top: -1000px; left:-1000px;"> <img src="/bbs/cposts/do/dontdig-1.p8.png" style="width:100%; height:100%;"> </div> <div onclick = 'p8_run_cart("/play/pico8_0206c_dev8.js", "gemdredging-21","/bbs/cposts/ge/gemdredging-21.p8.png")' class="p8cart" id="p8cart_4" style="cursor:pointer; width:160px; height:205px; position:absolute; top: -1000px; left:-1000px;"> <img src="/bbs/cposts/ge/gemdredging-21.p8.png" style="width:100%; height:100%;"> </div> <div onclick = 'p8_run_cart("/play/pico8_0206c_dev8.js", "blood_of_vladula-0","/bbs/cposts/bl/blood_of_vladula-0.p8.png")' class="p8cart" id="p8cart_5" style="cursor:pointer; width:160px; height:205px; position:absolute; top: -1000px; left:-1000px;"> <img src="/bbs/cposts/bl/blood_of_vladula-0.p8.png" style="width:100%; height:100%;"> </div> <div onclick = 'p8_run_cart("/play/pico8_0206c_dev8.js", "seinsim-0","/bbs/cposts/se/seinsim-0.p8.png")' class="p8cart" id="p8cart_6" style="cursor:pointer; width:160px; height:205px; position:absolute; top: -1000px; left:-1000px;"> <img src="/bbs/cposts/se/seinsim-0.p8.png" style="width:100%; height:100%;"> </div> <div onclick = 'p8_run_cart("/play/pico8_0206c_dev8.js", "amstradchips1-1","/bbs/cposts/am/amstradchips1-1.p8.png")' class="p8cart" id="p8cart_7" style="cursor:pointer; width:160px; height:205px; position:absolute; top: -1000px; left:-1000px;"> <img src="/bbs/cposts/am/amstradchips1-1.p8.png" style="width:100%; height:100%;"> </div> <div onclick = 'p8_run_cart("/play/pico8_0206c_dev8.js", "sonic_25_sage-0","/bbs/cposts/so/sonic_25_sage-0.p8.png")' class="p8cart" id="p8cart_8" style="cursor:pointer; width:160px; height:205px; position:absolute; top: -1000px; left:-1000px;"> <img src="/bbs/cposts/so/sonic_25_sage-0.p8.png" style="width:100%; height:100%;"> </div> <div onclick = 'p8_run_cart("/play/pico8_0206c_dev8.js", "38515","/bbs/cposts/3/38515.p8.png")' class="p8cart" id="p8cart_9" style="cursor:pointer; width:160px; height:205px; position:absolute; top: -1000px; left:-1000px;"> <img src="/bbs/cposts/3/38515.p8.png" style="width:100%; height:100%;"> </div> <div onclick = 'p8_run_cart("/play/pico8_0206c_dev8.js", "43646","/bbs/cposts/4/43646.p8.png")' class="p8cart" id="p8cart_10" style="cursor:pointer; width:160px; height:205px; position:absolute; top: -1000px; left:-1000px;"> <img src="/bbs/cposts/4/43646.p8.png" style="width:100%; height:100%;"> </div> <div onclick = 'p8_run_cart("/play/pico8_0206c_dev8.js", "24981","/bbs/cposts/2/24981.p8.png")' class="p8cart" id="p8cart_11" style="cursor:pointer; width:160px; height:205px; position:absolute; top: -1000px; left:-1000px;"> <img src="/bbs/cposts/2/24981.p8.png" style="width:100%; height:100%;"> </div> <div onclick = 'p8_run_cart("/play/pico8_0206c_dev8.js", "42930","/bbs/cposts/4/42930.p8.png")' class="p8cart" id="p8cart_12" style="cursor:pointer; width:160px; height:205px; position:absolute; top: -1000px; left:-1000px;"> <img src="/bbs/cposts/4/42930.p8.png" style="width:100%; height:100%;"> </div> <div onclick = 'p8_run_cart("/play/pico8_0206c_dev8.js", "28861","/bbs/cposts/2/28861.p8.png")' class="p8cart" id="p8cart_13" style="cursor:pointer; width:160px; height:205px; position:absolute; top: -1000px; left:-1000px;"> <img src="/bbs/cposts/2/28861.p8.png" style="width:100%; height:100%;"> </div> <a href="/bbs/?cat=7#sub=2&mode=carts&orderby=featured"> <div class="p8cart" id="p8cart_15" style="cursor:pointer; width:160px; height:205px; position:absolute; top: -1000px; left:-1000px;"> <img src="/gfx/p8_carts_cart.png" style="width:100%; height:100%;"> <img src="/gfx/cart32.png" class="p8cart_label" id="p8cart_label_15" style="display:none; top:-80px; left:20px"> </div> </a> <script> var p8tv_dat = [[0,`trichromat-0`, `/bbs/cposts/tr/trichromat-0.p8.png`, `/bbs/?pid=trichromat-0#p`, `/bbs/thumbs/pico8_trichromat-0.png`, `60255`, `spratt`, `/media/60255/zoombini_big.png`, `trichromat`, `<a target=_itch href="https://spratt.itch.io/" class=social_button> <img src="/gfx/so_itch.png" width=16 height=16 style="float:left; margin-left:4px; margin-top:0px; margin-bottom:4px"> </a><a target=_bluesky href="https://bsky.app/profile/sprattgames.bsky.social" class=social_button> <img src="/gfx/so_bluesky.png" width=16 height=16 style="float:left; margin-left:4px; margin-top:0px; margin-bottom:4px"> </a>`],[1,`build_a_jetpack-1`, `/bbs/cposts/bu/build_a_jetpack-1.p8.png`, `/bbs/?pid=build_a_jetpack-1#p`, `/bbs/thumbs/pico8_build_a_jetpack-1.png`, `103223`, `Yoshiip`, `/media/103223/aymeris100pico8.png`, `Build a Jetpack`, `<a target=_home href="https://aymeri100.fr" class=social_button> <img src="/gfx/so_home.png" width=16 height=16 style="float:left; margin-left:4px; margin-top:0px; margin-bottom:4px"> </a><a target=_mastodon href="https://mastodon.gamedev.place/@aymeri" class=social_button> <img src="/gfx/so_mastodon.png" width=16 height=16 style="float:left; margin-left:4px; margin-top:0px; margin-bottom:4px"> </a><a target=_twitter href="https://twitter.com/aymeri100" class=social_button> <img src="/gfx/so_twitter.png" width=16 height=16 style="float:left; margin-left:4px; margin-top:0px; margin-bottom:4px"> </a><a target=_youtube href="https://youtube.com/@Aymeri" class=social_button> <img src="/gfx/so_youtube.png" width=16 height=16 style="float:left; margin-left:4px; margin-top:0px; margin-bottom:4px"> </a><a target=_itch href="https://yoshiip.itch.io/" class=social_button> <img src="/gfx/so_itch.png" width=16 height=16 style="float:left; margin-left:4px; margin-top:0px; margin-bottom:4px"> </a>`],[2,`super_world_of_goo-6`, `/bbs/cposts/su/super_world_of_goo-6.p8.png`, `/bbs/?pid=super_world_of_goo-6#p`, `/bbs/thumbs/pico8_super_world_of_goo-6.png`, `79281`, `primary/convergence`, `/media/79281/REM_Monkey_Icon_BLACK.png`, `SUPER World of Goo!`, `<a target=_home href="https://discord.gg/jRkuQh2nr8" class=social_button> <img src="/gfx/so_home.png" width=16 height=16 style="float:left; margin-left:4px; margin-top:0px; margin-bottom:4px"> </a>`],[3,`dontdig-1`, `/bbs/cposts/do/dontdig-1.p8.png`, `/bbs/?pid=dontdig-1#p`, `/bbs/thumbs/pico8_dontdig-1.png`, `12806`, `morningtoast`, `https://www.lexaloffle.com/bbs/files/12806/santo.jpg`, `Don't Dig Up the Dead`, `<a target=_home href="https://morningtoast.itch.io/" class=social_button> <img src="/gfx/so_home.png" width=16 height=16 style="float:left; margin-left:4px; margin-top:0px; margin-bottom:4px"> </a><a target=_twitter href="https://twitter.com/morningtoast" class=social_button> <img src="/gfx/so_twitter.png" width=16 height=16 style="float:left; margin-left:4px; margin-top:0px; margin-bottom:4px"> </a><a target=_itch href="https://morningtoast.itch.io/" class=social_button> <img src="/gfx/so_itch.png" width=16 height=16 style="float:left; margin-left:4px; margin-top:0px; margin-bottom:4px"> </a><a target=_bluesky href="https://bsky.app/profile/morningtoast" class=social_button> <img src="/gfx/so_bluesky.png" width=16 height=16 style="float:left; margin-left:4px; margin-top:0px; margin-bottom:4px"> </a>`],[4,`gemdredging-21`, `/bbs/cposts/ge/gemdredging-21.p8.png`, `/bbs/?pid=gemdredging-21#p`, `/bbs/thumbs/pico8_gemdredging-21.png`, `78688`, `thesailor`, `/media/78688/Diver_lowpoly.gif`, `Gemstone Dredging 1.9`, `<a target=_mastodon href="https://mastodon.social/@thesailordev" class=social_button> <img src="/gfx/so_mastodon.png" width=16 height=16 style="float:left; margin-left:4px; margin-top:0px; margin-bottom:4px"> </a><a target=_itch href="https://the-sailor.itch.io/" class=social_button> <img src="/gfx/so_itch.png" width=16 height=16 style="float:left; margin-left:4px; margin-top:0px; margin-bottom:4px"> </a>`],[5,`blood_of_vladula-0`, `/bbs/cposts/bl/blood_of_vladula-0.p8.png`, `/bbs/?pid=blood_of_vladula-0#p`, `/bbs/thumbs/pico8_blood_of_vladula-0.png`, `58525`, `slumma`, `/media/58525/13_lex-avatar.png`, `Blood of Vladula jr.`, `<a target=_mastodon href="https://mastodon.gamedev.place/@slumma" class=social_button> <img src="/gfx/so_mastodon.png" width=16 height=16 style="float:left; margin-left:4px; margin-top:0px; margin-bottom:4px"> </a>`],[6,`seinsim-0`, `/bbs/cposts/se/seinsim-0.p8.png`, `/bbs/?pid=seinsim-0#p`, `/bbs/thumbs/pico8_seinsim-0.png`, `40503`, `Rangee27`, `/media/40503/11_profilepic.png`, `Seinfeld Simulator 1.0`, ``],[7,`amstradchips1-1`, `/bbs/cposts/am/amstradchips1-1.p8.png`, `/bbs/?pid=amstradchips1-1#p`, `/bbs/thumbs/pico8_amstradchips1-1.png`, `23375`, `carlc27843`, `/bimg/pi/pi15.png`, `Emulated Amstrad CPC Chiptunes`, `<a target=_twitter href="https://twitter.com/carlc27843" class=social_button> <img src="/gfx/so_twitter.png" width=16 height=16 style="float:left; margin-left:4px; margin-top:0px; margin-bottom:4px"> </a><a target=_itch href="https://carlc27843.itch.io/" class=social_button> <img src="/gfx/so_itch.png" width=16 height=16 style="float:left; margin-left:4px; margin-top:0px; margin-bottom:4px"> </a>`],[8,`sonic_25_sage-0`, `/bbs/cposts/so/sonic_25_sage-0.p8.png`, `/bbs/?pid=sonic_25_sage-0#p`, `/bbs/thumbs/pico8_sonic_25_sage-0.png`, `38130`, `BoneVolt`, `https://www.lexaloffle.com/bbs/files/38130/PICO-8_2.png`, `Sonic 2.5 SAGE 2020`, `<a target=_twitter href="https://twitter.com/bone_volt" class=social_button> <img src="/gfx/so_twitter.png" width=16 height=16 style="float:left; margin-left:4px; margin-top:0px; margin-bottom:4px"> </a><a target=_youtube href="https://youtube.com/glmaass" class=social_button> <img src="/gfx/so_youtube.png" width=16 height=16 style="float:left; margin-left:4px; margin-top:0px; margin-bottom:4px"> </a><a target=_itch href="https://bonevolt.itch.io/" class=social_button> <img src="/gfx/so_itch.png" width=16 height=16 style="float:left; margin-left:4px; margin-top:0px; margin-bottom:4px"> </a>`],[9,`38515`, `/bbs/cposts/3/38515.p8.png`, `/bbs/?pid=38515#p`, `/bbs/thumbs/pico38515.png`, `16247`, `FunFetched`, `https://www.lexaloffle.com/bbs/files/16247/avatar.png`, `Nanoman`, ``],[10,`43646`, `/bbs/cposts/4/43646.p8.png`, `/bbs/?pid=43646#p`, `/bbs/thumbs/pico43646.png`, `13822`, `Liquidream`, `/media/13822/avatar-large-cropped-trans-blinking.gif`, `Portal - Still Alive`, `<a target=_home href="https://www.liquidream.co.uk" class=social_button> <img src="/gfx/so_home.png" width=16 height=16 style="float:left; margin-left:4px; margin-top:0px; margin-bottom:4px"> </a><a target=_mastodon href="https://mastodon.gamedev.place/@Liquidream" class=social_button> <img src="/gfx/so_mastodon.png" width=16 height=16 style="float:left; margin-left:4px; margin-top:0px; margin-bottom:4px"> </a><a target=_twitter href="https://twitter.com/Liquidream" class=social_button> <img src="/gfx/so_twitter.png" width=16 height=16 style="float:left; margin-left:4px; margin-top:0px; margin-bottom:4px"> </a><a target=_youtube href="https://youtube.com/liquidreamuk" class=social_button> <img src="/gfx/so_youtube.png" width=16 height=16 style="float:left; margin-left:4px; margin-top:0px; margin-bottom:4px"> </a><a target=_itch href="https://liquidream.itch.io/" class=social_button> <img src="/gfx/so_itch.png" width=16 height=16 style="float:left; margin-left:4px; margin-top:0px; margin-bottom:4px"> </a><a target=_kofi href="https://ko-fi.com/liquidream" class=social_button> <img src="/gfx/so_kofi.png" width=16 height=16 style="float:left; margin-left:4px; margin-top:0px; margin-bottom:4px"> </a><a target=_bluesky href="https://bsky.app/profile/liquidream.bsky.social" class=social_button> <img src="/gfx/so_bluesky.png" width=16 height=16 style="float:left; margin-left:4px; margin-top:0px; margin-bottom:4px"> </a>`],[11,`24981`, `/bbs/cposts/2/24981.p8.png`, `/bbs/?pid=24981#p`, `/bbs/thumbs/pico24981.png`, `11048`, `NuSan`, `https://www.lexaloffle.com/bimg/pi/pi8.png`, `P.Craft`, `<a target=_mastodon href="https://peoplemaking.games/@nusan" class=social_button> <img src="/gfx/so_mastodon.png" width=16 height=16 style="float:left; margin-left:4px; margin-top:0px; margin-bottom:4px"> </a><a target=_itch href="https://nusan.itch.io/" class=social_button> <img src="/gfx/so_itch.png" width=16 height=16 style="float:left; margin-left:4px; margin-top:0px; margin-bottom:4px"> </a>`],[12,`42930`, `/bbs/cposts/4/42930.p8.png`, `/bbs/?pid=42930#p`, `/bbs/thumbs/pico42930.png`, `13966`, `DragonXVI`, `https://www.lexaloffle.com/bbs/files/13966/Twit.png`, `Charge! (LD39)`, ``],[13,`28861`, `/bbs/cposts/2/28861.p8.png`, `/bbs/?pid=28861#p`, `/bbs/thumbs/pico28861.png`, `15740`, `managore`, `https://i.imgur.com/fqksat0.pngfiles/15740/logo`, `PERISHER, a CELESTE mod`, ``],[14,`37086`, `/bbs/cposts/3/37086.p8.png`, `/bbs/?pid=37086#p`, `/bbs/thumbs/pico37086.png`, `12489`, `kometbomb`, `https://www.lexaloffle.com/bbs/files/12489/avatar.png`, `OMEGA ZONE`, `<a target=_mastodon href="https://mastodon.social/@kometbomb" class=social_button> <img src="/gfx/so_mastodon.png" width=16 height=16 style="float:left; margin-left:4px; margin-top:0px; margin-bottom:4px"> </a><a target=_twitter href="https://twitter.com/kometbomb" class=social_button> <img src="/gfx/so_twitter.png" width=16 height=16 style="float:left; margin-left:4px; margin-top:0px; margin-bottom:4px"> </a><a target=_itch href="https://kometbomb.itch.io/" class=social_button> <img src="/gfx/so_itch.png" width=16 height=16 style="float:left; margin-left:4px; margin-top:0px; margin-bottom:4px"> </a>`],];</script><div class = "topmenu_div" id="m" style=" position:relative; margin:0px; background-color:rgba(10,0,20,0.8); width:100%; height:48px; "><div style="float:left; margin-left:0px;"><div class="menu_icon" title="Lexaloffle Games"><a href="/"><img src="/gfx/top_info.png"></a></div><div class="menu_icon" title="PICO-8"><a href="/pico-8.php"><img src="/gfx/p8b_pico8.png"></a></div><div class="menu_icon" title="Voxatron"><a href="/voxatron.php" style="padding-right:4px;padding-left:4px;"><img src="/gfx/p8b_vox.png"></a></div><div class="menu_icon" title="Picotron"><a href="/picotron.php"><img src="/gfx/p8b_picotron.png"></a></div><div class="menu_icon" title="BBS" ><a href="/bbs"><img src="/gfx/bbs_cube.gif" width=40></a></div><div class="menu_icon" title="Superblog"><a href="/bbs/superblog.php"><img src="/gfx/top_blog.png"></a></div><div style="display:inline-block;background-color:#555;width:1px;height:40px; margin-left:10px;margin-right:10px"></div><div style="display:inline-block;margin:4px" class="navstring"><a href=/pico-8.php ><div class="linkybutton" style="background-color : #a0a0a0; color:#fff">PICO-8</div></a><a href=/pico-8.php?page=faq ><div class="linkybutton">FAQ</div></a><a href=/pico-8.php?page=resources ><div class="linkybutton">Resources</div></a><a href=/pico-8.php?page=schools ><div class="linkybutton">Schools</div></a><a href=/pico-8.php?page=submit ><div class="linkybutton">Submit</div></a><a href=/bbs/?cat=7 ><div class="linkybutton">Forum</div></a><a href=/bbs/?cat=7&carts_tab=1#mode=carts&sub=2 ><div class="linkybutton">Carts</div></a></div></div> <script> function click_account_pulldown() { el1 = document.getElementById('account_pulldown'); if (toggle_visible(el1)) { install_dismisser(el1); el2 = document.getElementById('lil_menu_pulldown'); el2.style.display='none'; } } </script> <a onclick="click_account_pulldown();"> <div style="display:flex; align-items:center; height:100%; float:right; "> <div style=" padding:8px; display:flex; align-items:center; "> Log In  <img style="margin-left:8px;" src="/gfx/top_drop.png"> </div> </div> </a></div></div><div id = "account_pulldown" style="position:relative; left: -28px; float:right; width: 240px; height:0px; z-index: 400; display:none;" > <div id = "account_pulldown_inner" style=" padding:12px; background-color:rgba(0,0,0,0.8); width:100%; border-width: 1px; border-style: solid; border-color: #000; "> <div style="margin-right:20px"> <form style="margin:8px;" name="loginsubmit" method="post" action="/account.php?page=login"> <table><tr> <td>User:</td><td> <INPUT name="user" class="logininput" size=16 ><br> </td></tr><tr><td> Password:</td><td> <INPUT name="pass" type="password" class="logininput" size=16 type=text><br> <INPUT type="hidden" name="go" value="//pico-8.php"> </td></tr><tr><td></td><td> <input type=submit class="logininput2" style="margin-top:8px" value="LOG IN"> </td></tr></table> <br> <a href="/account.php?page=new_user" style="color:#fab">New User</a> | <a href="/account.php" style="color:#fab">Account Help</a> </td></tr></table> </form></div></div></div> <div style="max-width:96vw; padding:2vw"> <div id="main_div" style=" max-width:920px; min-height:50vh; margin:auto; display:block; padding-top:16px; padding-bottom:16px; overflow:hidden; " ><center> <div style="display:table"> <div style="float:left; "> <div class="info_group"> <div class="desktop_div"> <div class="info_box"> <img src="/gfx/jelpi_demo.gif" style="width:256px; margin-right:10px"> </div> </div> <div> <h2 style="margin-top:-20px">Welcome to PICO-8!</h2> <font size=3> PICO-8 is a <a href="?page=faq" style="color:#fab">fantasy console</a> for making, sharing and playing tiny games and other computer programs. It <i style="color:#fc3">feels</i> like a regular console, but runs on Windows / Mac / Linux. When you turn it on, the machine greets you with a commandline, a suite of cartridge creation tools, and an online cartridge browser called <a style="color:#7cf">SPLORE</a>. </font> <br><br> <div class="desktop_div"> <a href="#getpico8" style="display:none"> <div style="margin-left:0px; margin-top:-10px;style="float:left"> <div class="form_button" style="padding:6px; padding-left:8px; padding-right:8px"> Get PICO-8 </div> </div> </a> <a href="?#getpico8"> <div style=" display:table; background-color:#fed; border-radius:4px; box-shadow: 2px 2px 8px rgba(16, 0, 48, 0.4); font-size:16pt; padding:12px; color:#333; "> Get PICO-8 <span style="top:-4px; position:relative; margin:4px; color:#fff; font-size:10pt; padding:4px; background-color:#f8a; border-radius:8px; padding-left:12px; padding-right:12px">$14.99</span> <div style="font-size:9pt; margin-top:6px; "> Windows | Mac | Linux | Raspberry Pi </div> </div></a> </div> </div> </div> <div class="mobile_div"><center><img src="/gfx/jelpi_demo.gif" style="width:100%; max-width:384px"></center> <br><br> <a href="?#getpico8"> <div style=" display:table; background-color:#fed; border-radius:4px; box-shadow: 2px 2px 8px rgba(16, 0, 48, 0.4); font-size:20pt; padding:16px; width:90%; margin-top:8px; color:#333; "> <center> Get PICO-8 <span style="top:-4px; position:relative; margin:4px; color:#222; font-size:12pt; padding:6px; background-color:#fab; border-radius:8px;">$14.99</span> <div style="font-size:9pt; margin-top:6px; "> Windows | Mac | Linux | Raspberry Pi </div> </center> </div></a></div> <div class=mobile_div> <a href="/bbs/?cat=7#mode=carts&sub=2&orderby=featured"> <div style="display:table; width:100%; margin-top:20px; font-size:16pt; padding:8px; color:#fab">Featured Cartridges <span style="color:#aaa; font-size:10pt">[View All]</span> </div></a><br> </div><div class=mobile_div><div style="padding-bottom:10px; overflow-x:auto; overflow-y:hidden; -webkit-overflow-scrolling:touch; height:274px; width:94vw; display:flex; flex-wrap:nowrap; white-space:nowrap; "><hr><a href="/bbs/?pid=142886#p"><div style="padding:8px; background-color:rgba(0,0,16,0.6); width:272px; display:table"><img class=pixel_perfect style="padding:0px; width:256px" src="/bbs/thumbs/pico8_trichromat-0.png"></div></a><a href="/bbs/?pid=155669#p"><div style="padding:8px; background-color:rgba(0,0,16,0.6); width:272px; display:table"><img class=pixel_perfect style="padding:0px; width:256px" src="/bbs/thumbs/pico8_build_a_jetpack-1.png"></div></a><a href="/bbs/?pid=148834#p"><div style="padding:8px; background-color:rgba(0,0,16,0.6); width:272px; display:table"><img class=pixel_perfect style="padding:0px; width:256px" src="/bbs/thumbs/pico8_super_world_of_goo-6.png"></div></a><a href="/bbs/?pid=125072#p"><div style="padding:8px; background-color:rgba(0,0,16,0.6); width:272px; display:table"><img class=pixel_perfect style="padding:0px; width:256px" src="/bbs/thumbs/pico8_dontdig-1.png"></div></a><a href="/bbs/?pid=152679#p"><div style="padding:8px; background-color:rgba(0,0,16,0.6); width:272px; display:table"><img class=pixel_perfect style="padding:0px; width:256px" src="/bbs/thumbs/pico8_gemdredging-21.png"></div></a><a href="/bbs/?pid=139797#p"><div style="padding:8px; background-color:rgba(0,0,16,0.6); width:272px; display:table"><img class=pixel_perfect style="padding:0px; width:256px" src="/bbs/thumbs/pico8_blood_of_vladula-0.png"></div></a><a href="/bbs/?pid=86298#p"><div style="padding:8px; background-color:rgba(0,0,16,0.6); width:272px; display:table"><img class=pixel_perfect style="padding:0px; width:256px" src="/bbs/thumbs/pico8_manbomber-0.png"></div></a><a href="/bbs/?pid=154661#p"><div style="padding:8px; background-color:rgba(0,0,16,0.6); width:272px; display:table"><img class=pixel_perfect style="padding:0px; width:256px" src="/bbs/thumbs/pico8_exterra-1.png"></div></a><a href="/bbs/?pid=140954#p"><div style="padding:8px; background-color:rgba(0,0,16,0.6); width:272px; display:table"><img class=pixel_perfect style="padding:0px; width:256px" src="/bbs/thumbs/pico8_pony9000_1_3_3-0.png"></div></a></div></div><hr> <div class="info_group"> <h2>Specifications</h2> <div class="info_box" style="display:table; width:90%; max-width:600px; float:left;"> <center> <table cellspacing=2 width=100% border=0> <tr><td><b>Display</b></td> <td width=10></td><td>128x128 16 colours</td></tr> <tr><td><b>Cart Size</b></td> <td width=10></td><td>32k</td></tr> <tr><td><b>Sound</b></td> <td width=10></td><td>4 channel chip blerps</td></tr> <tr><td><b>Code</b></td> <td width=10></td><td>P8 Lua</td></tr> <tr><td><b>CPU</b></td> <td width=10></td><td>4M vm insts/sec</td></tr> <tr><td><b>Sprites</b></td> <td width=10></td><td>256 8x8 sprites</td></tr> <tr><td><b>Map</b></td> <td width=10></td><td>128x32 tiles</td></tr> </tr><td colspan=3> <br> </td></tr> </table> </center> </div> <div class=info_box> <font size=3> The harsh limitations of PICO-8 are carefully chosen to be fun to work with, to encourage small but expressive designs, and to give cartridges made with PICO-8 their own particular look and feel. </font> </div> </div> <div class="info_group"> <div class="info_box"> <div class="mobile_div"> <center><a href="/gfx/p8_tracker.gif"><img src="/gfx/p8_tracker.gif" style="max-width:384px"> </a></center> </div> <div class="desktop_div"> <a href="/gfx/p8_tracker.gif"><img src="/gfx/p8_tracker.gif" width=136></a> </div> </div> <div class="info_box_right"> <h2> Creative Tools </h2> <font size=3> PICO-8 has tools for editing <b>code</b>, <b>music</b>, <b>sound</b>, <b>sprites</b>, <b>maps</b> built right into the console. Create a whole game or program in one sitting without needing to leave the cosy development environment! </font> </div> </div> <div class="info_group"> <div class="info_box"> <div class="mobile_div"> <center><a href="/gfx/jelpi.p8.png"><img src="/gfx/jelpi.p8.png" style="width:50%" ></a></center> </div> <div class="desktop_div"> <a href="/gfx/jelpi.p8.png"><img src="/gfx/jelpi.p8.png" width=138 ></a> </div> </div> <div class="info_box_right"> <h2> Shareable Cartridges </h2> <p><font size=3> PICO-8 cartridges can be saved in a special .png format and sent directly to other users, shared with anyone via a web cart player, or exported to stand-alone HTML5, Windows, Mac and Linux apps. </font></p> <p><font size=3> Any cartridge can be opened again in PICO-8, letting you peek inside to modify or study the code, graphics and sound. </font></p> </font> </div> </div> <div class="info_group"> <div class="info_box"> <div class="mobile_div"> <center><a href="/gfx/p8_splore.gif"><img src="/gfx/p8_splore.gif" style="width:100%; max-width:384px"> </a></center> </div> <div class="desktop_div" style="background-color:#000"> <center><a href="/gfx/p8_splore.gif"> <img style="padding:5px" src="/gfx/p8_splore.gif" width=128 height=128></a></center> </div> </div> <div class="info_box_right"> <h2> Explore the Cartverse</h2> <font size=3> PICO-8 comes with a built-in cartridge browser called SPLORE, for searching and favouriting carts from the online collection. </font> <a target=_play href="/bbs/?cat=7#sub=2" style="display:none"> <div style="margin-left:0px; margin-top:-10px;style="float:left"> <div class="form_button" style="padding:6px; padding-left:8px; padding-right:8px"> Play Cartridges Online </div> </div> </a> </div> </div> <div class="info_group"> <div class="info_box"> <div class="mobile_div" style="display:none"> <center> <a href="/bbs/?cat=7"> <img class=pixel_perfect src="/gfx/bbs_cube.gif" style="width:100%; max-width:384px"></a></center> </div> <div class="desktop_div"> <center><a href="/bbs/?cat=7"> <img class=pixel_perfect src="/gfx/bbs_cube.gif" style="width:128px; max-width:384px;"> </a></center> </div> </div> <div class="info_box_right"> <h2> Community Resources </h2> <p><font size=3> PICO-8 has a friendly community of users collaborating, sharing knowledge and creating tools, snippets and tutorials. Check out what people are up to on the <a target=_bbs href="/bbs/?cat=7">forums</a>, <a target=_twitter href="https://twitter.com/search?q=%23pico8&src=typd">twitter</a>, and <a target=_communities href="?page=resources#community">other places</a>. </font></p> </div> </div> <div class="info_group"> <div class="info_box"> <div class="mobile_div"> <center><a href="/bbs/?cat=7"><img src="/gfx/pi_tv_1.jpg" style="width:100%; max-width:384px"></a></center> </div> <div class="desktop_div"> <center><a href="/gfx/pi_tv_1.jpg"> <img src="/gfx/pi_tv_1.jpg" style="width:128px; max-width:384px; "><br> </a></center> </div> </div> <div class="info_box_right"> <h2> Just Add Hardware </h2> <p><font size=3> PICO-8 is tiny to download, easy to install, and will run on almost anything! To use PICO-8, you'll need either Windows, a Mac, Linux (i386 / amd64), or a Raspbery Pi (pictured) with ~700MHz CPU. Turn your old unused netbooks or microcomputers into PICO-8s! </font></p> </div> </div> </center> <div id="getpico8"></div> <br> <div style="width:100%; padding:16px; background-color:#f68; border-radius:0px; font-size:24pt;color:#fff;" > Get PICO-8 <div style="position:relative; top:-6px; display:inline; padding:4px; font-size:14pt;background-color:#fff;border-radius:8px;color:#f68">$14.99</div> </div> <div style="width:100%; display:table; background-color:#edd; padding:12px; border-radius:0px; padding-top:20px"> <font size=3 style="color:#444"> <div style="padding:8px; padding-left:7%; padding-right:7%;"> Enter your email address below, choose a payment method, and you will be emailed a download link. Purchasing PICO-8 also gives you access to all future updates. If you have any trouble, please feel free to <a href="/cdn-cgi/l/email-protection#83ebe6fac3efe6fbe2efece5e5efe6ade0ecee" style="color:#f58">email us</a>. <br><br> <div style="display:none"><input type="checkbox" class="checkout_checkbox" id="buy_p8" checked=true> </div> <div class="lexchex"><input type="checkbox" class="checkout_checkbox" id="buy_p64" onchange="update_checkout();"> Add <a href="picotron.php" style="color:#44f">Picotron</a> for <strike style="color:#777; font-size:9pt">$19.99</strike> $11.99 <div style="display:inline;padding:5px; font-size:11pt; background-color:#f69; border-radius:6px; color:#fff"><b>SAVE $8!</b></div> </div> <div class="lexchex"><input type="checkbox" class="checkout_checkbox" id="buy_vx" onchange="update_checkout();"> Add <a href="voxatron.php" style="color:#44f">Voxatron</a> for <strike style="color:#777; font-size:9pt">$19.99</strike> $11.99 <div style="display:inline;padding:5px; font-size:11pt; background-color:#f69; border-radius:6px; color:#fff"><b>SAVE $8!</b></div> </div> </div> </font> <br> <center><div style="height:250px"><div class="checkout_item" id="buy_p8" style="display:"> <div class="desktop_div"> <iframe src="https://www.humblebundle.com/widget/v2/product/pico8/098fd23?theme=light" width=820 height=290 style="border: none;" scrolling="no" frameborder="0"></iframe> </div> <div class="mobile_div"> <iframe src="https://www.humblebundle.com/widget/v2/product/pico8/098fd23?theme=light" width=320 height=165 style="border: none;" scrolling="no" frameborder="0"></iframe> </div> </div><div class="checkout_item" id="buy_p8_p64" style="display:none"> <div class="desktop_div"> <iframe src="https://www.humblebundle.com/widget/v2/product/pico-8_picotron/KynWvCDlDk?theme=light" width=820 height=290 style="border: none;" scrolling="no" frameborder="0"></iframe> </div> <div class="mobile_div"> <iframe src="https://www.humblebundle.com/widget/v2/product/pico-8_picotron/KynWvCDlDk?theme=light" width=320 height=165 style="border: none;" scrolling="no" frameborder="0"></iframe> </div> </div><div class="checkout_item" id="buy_p8_vx" style="display:none"> <div class="desktop_div"> <iframe src="https://www.humblebundle.com/widget/v2/product/voxatron?theme=light" width=820 height=290 style="border: none;" scrolling="no" frameborder="0"></iframe> </div> <div class="mobile_div"> <iframe src="https://www.humblebundle.com/widget/v2/product/voxatron?theme=light" width=320 height=165 style="border: none;" scrolling="no" frameborder="0"></iframe> </div> </div><div class="checkout_item" id="buy_p8_vx_p64" style="display:none"> <div class="desktop_div"> <iframe src="https://www.humblebundle.com/widget/v2/product/pico-8_voxatron_picotron/QXTXj8qjtj?theme=light" width=820 height=290 style="border: none;" scrolling="no" frameborder="0"></iframe> </div> <div class="mobile_div"> <iframe src="https://www.humblebundle.com/widget/v2/product/pico-8_voxatron_picotron/QXTXj8qjtj?theme=light" width=320 height=165 style="border: none;" scrolling="no" frameborder="0"></iframe> </div> </div></div></center><br><br><br></div></div></div><script data-cfasync="false" src="/cdn-cgi/scripts/5c5dd728/cloudflare-static/email-decode.min.js"></script><script> //inject_cart_data(); </script> </div></div> <div id="lex_footer" style=" width:100%; min-height:300px; background-color:#111; "><div style="padding:20px"> <center><div style="display:table"> <div style="display:block; margin-bottom:10px;"> <center> <a style="margin:8px" href="/info.php">About</a> | <a style="margin:8px" href="/cdn-cgi/l/email-protection#e8808d91a8848d908984878e8e848dc68b8785">Contact</a> | <a style="margin:8px" href="/games.php?page=updates">Updates</a> | <a style="margin:8px" href="/info.php?page=tos">Terms of Use</a> | <a style="margin:8px" href="/picotron.php">Picotron</a> </center> </div> <center> <div style="display:table; margin-bottom:10px"> <div style="font-size:10pt; display:flex; align-items:center"> Follow Lexaloffle:   <a href="https://mastodon.social/@zep" class=social_button><img class=pixel_perfect src="/gfx/so_mastodon.png" width=32 height=32 border=0></a>   <a href="https://twitter.com/lexaloffle" class=social_button><img class=pixel_perfect src="/gfx/so_twitter.png" width=32 height=32 border=0></a>   <a href="https://www.youtube.com/user/lexaloffletv" class=social_button> <img class=pixel_perfect src="/gfx/so_youtube.png" width=32 height=32 border=0></a>   <a href="https://www.lexaloffle.com/bbs/feed.php" class=social_button><img class=pixel_perfect src="/gfx/so_rss.png" width=32 height=32></a> </div> </div> <div style="color:#777;font-size:8pt;"> Generated 2024-12-01 22:08:15 | 0.229s | Q:789 <div id=cache_info style="display:inline;"> </div> </div> </center> </div></center> </div></div> </div> <script data-cfasync="false" src="/cdn-cgi/scripts/5c5dd728/cloudflare-static/email-decode.min.js"></script></body> </html>