Files
picoGraph/pico8-edu-page.html

3416 lines
92 KiB
HTML

<html>
<head>
<title>PICO-8 Education Edition</title>
<meta charset="utf-8"/><meta property="og:title"
content="PICO-8 Education Edition"/>
<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}
h4{color:#f7a; font-size:12pt;padding-top:6px; font-weight:bold}
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 : #444;
padding: 6px;
color: #444;
font-size: 14px;
height:24px;
vertical-align:center;
display:inline-table;
margin:2px;
}
.linkybutton_lil {
float:left;
background-color : #555;
padding: 8px;
color: #aaa;
font-size: 14px;
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="2026-03-20 17:11:50";
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 style="background-color:#111;">
<style type="text/css">
A:link {
text-decoration: none;
color: #937;
font-weight:bold;
}
A:visited {
text-decoration: none;
color: #937;
font-weight:bold;
}
</style>
<script>
function hide_speech()
{
el = document.getElementById('speech_bubble');
if (el) el.style.display = "none";
el = document.getElementById('zoot');
if (el) el.src = '/gfx/zoot_idle.gif';
// el = document.getElementById('top_container');
// if (el) el.style.maxHeight = "";
}
var bunny_timeout_id;
function bunny_hide_or_say(str, show_back)
{
el = document.getElementById('speech_bubble');
if (el && el.style.display == "")
return hide_speech();
else
return bunny_say(str, show_back);
}
function bunny_say(str, show_back)
{
el = document.getElementById('speech_bubble');
if (show_back === true) show_back = 'BACK';
if (el){
el.style.display = "";
str = str + '<div><div style="float:left; display:table; border-radius:5px; margin-top:20px; padding:5px; padding-left:10px; padding-right:10px; background-color:#ffd0c0; cursor:pointer; color:#555; font-size:9pt; border-style:solid; border-width:2px; border-color:#ffa080" onclick="hide_speech();">CLOSE</div>';
if (show_back)
str = str + '<div style="float:left; margin-left:15px; display:table; border-radius:5px; margin-top:20px; padding:5px; padding-left:10px; padding-right:10px; background-color:#ffd0c0; cursor:pointer; color:#555; font-size:9pt; border-style:solid; border-width:2px; border-color:#ffa080" onclick="bunny_say(help_index);">'+show_back+'</div>';
el.innerHTML = str + '</div>';
}
// el = document.getElementById('top_container');
// if (el) el.style.maxHeight = "45vw";
el = document.getElementById('zoot');
if (el) el.src = '/gfx/zoot_talking.gif';
clearTimeout(bunny_timeout_id);
bunny_timeout_id = setTimeout(function(){
el = document.getElementById('zoot');
if (el) el.src = '/gfx/zoot_idle.gif';
}, 4000);
}
var help_about=`
Welcome to <span style="font-weight:bold; color:#09f">PICO-8 Education Edition</span>!<br>
<br>
This version includes everything needed to follow tutorials and make cartridges for the <a target="_new" href="https://www.pico-8.com"><span style="font-weight:bold; color:#f49">PICO-8 Fantasy Console</span></a>: an imaginary machine with built-in creation tools.
<br><br>
For the regular version of PICO-8 that runs natively and includes a cartridge browser and exporters, please visit: <br><br>
<a target="_new" href="https://www.pico-8.com">www.pico-8.com</a>
`;
var showed_touch_message = false;
var help_start=`
After pressing play &#9655; to boot PICO-8, you can type commands in like this:
<pre>
CIRCFILL(64,64,20,12)
</pre>
Type without shift/capslock and press the Enter key to enter each command.
To try out some demos, use the following commands:
<pre>
INSTALL_DEMOS
CD DEMOS
LOAD HELLO
RUN
</pre>
To stop a running program, press ESC, and ESC once more to jump to the editing tools.
<br><br>
`;
var help_saving=`
To save a cartridge, type:
<pre>
SAVE MYCART
</pre>
(or whatever you would like to call it). This web version of PICO-8 will download the cartridge each time it is saved for safekeeping.
<br><br>
Cartridges can be loaded by dragging and dropping the .p8 cartridge file into the browser window, or by using the LOAD command.
`;
/*
`
<br><br>
To save a cartridge in .png format, first capture use .p8.png in the filename:
<pre>
SAVE MYCART.P8.PNG
</pre>
`;
*/
var help_first=`
<p>
Type REBOOT to start a new cartridge, and then ESC for the code editor.
At the top right you can switch between: CODE, GFX, MAP, SFX, and MUSIC.
Try drawing a sprite in slot 1 (the default in the GFX editor), and then type the following program
into the CODE editor:
</p>
<pre>
X = 60
FUNCTION _DRAW()
CLS(1)
SPR(1,X,60)
IF (BTN(0)) X -= 2
IF (BTN(1)) X += 2
END
</pre>
Then press ESC again to get back to the command prompt, and type RUN to see the program in action.
If everything is working, you can press left and right to control the sprite. Congratulations, you are now a PICO-8 programmer!
`;
var help_resources=`
Open PICO-8 resources in a new tab:
<br><br>
<a target="_manual" href="https://www.lexaloffle.com/dl/docs/pico-8_manual.html">PICO-8 Manual</a>
<br><br>
<a target="_forum" href="https://www.lexaloffle.com/bbs/?cat=7">PICO-8 Forum & Cartridges</a>
<br><br>
<a target="_resources" href="https://www.lexaloffle.com/pico-8.php?page=resources">Zines, Tutorials & More Resources</a>
`;
var help_shortcuts = `
<pre>
ESC stop program / toggle editor
CTRL-R run / restart
CTRL-C copy
CTRL-6 save a screenshot
CTRL-8 start recording a gif
CTRL-9 end recording a gif
</pre>
Handy commands:
<pre>
REBOOT to start an empty cartridge
LS to list the files in the current directory
INSTALL_DEMOS to install some demos
CD to change directory (e.g. CD DEMOS)
LOAD to load a cartridge
SAVE to save a cartridge
</pre>
`;
var help_index=`
<div style="cursor:pointer; margin-top:15px; font-size:11pt" onclick="bunny_say(help_about, true);"><span style="color:#f8a;font-size:10pt;position:relative;top:-2px; margin-right:6px">&#11044;</span> About PICO-8</div>
<div style="cursor:pointer; margin-top:15px; font-size:11pt" onclick="bunny_say(help_start, true);"><span style="color:#f48;font-size:10pt;position:relative;top:-2px; margin-right:6px">&#11044;</span> Getting Started</div>
<div style="cursor:pointer; margin-top:15px; font-size:11pt" onclick="bunny_say(help_first, true);"><span style="color:#f91;font-size:10pt;position:relative;top:-2px; margin-right:6px">&#11044;</span> First Program</div>
<div style="cursor:pointer; margin-top:15px; font-size:11pt" onclick="bunny_say(help_saving, true);"><span style="color:#4e4;font-size:10pt;position:relative;top:-2px; margin-right:6px">&#11044;</span> Saving Cartridges</div>
<div style="cursor:pointer; margin-top:15px; font-size:11pt" onclick="bunny_say(help_resources, true);"><span style="color:#0af;font-size:10pt;position:relative;top:-2px; margin-right:6px">&#11044;</span> Manual & Tutorials</div>
`;
</script>
<div style="width:100%; height:30%; position:absolute; top:0px; left:0px">
<div id="p8imgholder" style="display:table; min-width:160px; float:right; margin-right:20px; margin-top:80px">
</div>
</div>
<div style="width:100%; position:absolute; left:0px; top:0px"><div style="font-size:10pt; color:#999; float:right; padding:4px; padding-right:16px;">
// Made by <a target="_twitter" href="https://www.twitter.com/lexaloffle" style="color:#f8a">lexaloffle</a> &
<a target="_superblog" href="https://www.lexaloffle.com/bbs/superblog.php?cat=7" style="color:#f8a">co</a></div></div><div id="top_container" style="margin-top:32px"><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;
if (pico8_state.has_focus == 1)
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();
// extra keys to block from picotron shell
// block: cursors(37..40), M (77) R (82) P (80), tab (9) pageup/down(33,34) slash (191)
if (false) // delete this -- handle above
if (is_picotron)
if (pico8_state.has_focus == 1)
if ([32, 37, 38, 39, 40, 77, 82, 80, 9, 33, 34, 191].indexOf(event.keyCode) > -1)
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 id="p8_frame_0" style="display:; width:100%;height:100%; max-width:95vw;max-height:95vh; min-width:256px;min-height:256px;margin:auto;background-color:#111">
<div id="p8_frame" style="display:flex; width:100%;height:100%; min-width:256px;min-height:256px;margin:auto;">
<textarea id="codo_textarea_0" class="emscripten" style="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 id="p8_container"
style="margin:auto; display:table;"
onclick="if (!p8_is_running) {p8_create_audio_context(); p8_run_cart('/play/pico8_edu_0207.js', '0', '/bbs/cposts/bad_lid_0_.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">
&nbsp
</div>
<div id="p8_playarea_flex" style="display:flex; position:relative">
<canvas class="emscripten" id="canvas" oncontextmenu="event.preventDefault();">
</canvas>
</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_layout(); 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 = ["/bbs/cposts/bad_lid_0_.png"];</script></div>
<div style="width:320px; display:table; top:0px; position:absolute">
<img id="zoot" onclick="bunny_hide_or_say(help_index);" src="/gfx/zoot_idle.gif" style="cursor:pointer; display:table; width:48px; height:48px;
max-width:6vw; max-height:6vw; margin:4px">
<div id="speech_bubble" style="display:none; min-width:200px; max-width:80vw; background-color:#fff0e8; float:left; padding:16px; color:#222; border-radius: 10px; margin-left:10px;">
</div>
</div><img src="/gfx/zoot_talking.gif" style="width:1px; height:1px; display:none; position:absolute"><script>
// give a second to load talking animation
bunny_timeout_id = setTimeout(function(){
// annoying
// bunny_say(help_about, 'MORE TIPS');
addEventListener("touchstart", function(event)
{
if (!showed_touch_message)
bunny_say('Sorry, touchscreen typing is not available ~ please plug in a keyboard!');
showed_touch_message = true;
});
}, 1000);
</script>
<script defer src="https://static.cloudflareinsights.com/beacon.min.js/v8c78df7c7c0f484497ecbca7046644da1771523124516" integrity="sha512-8DS7rgIrAmghBFwoOTujcf6D9rXvH8xm8JQ1Ja01h9QX8EzXldiszufYa4IFfKdLUKTTrnSFXLDkUEOTrZQ8Qg==" data-cf-beacon='{"version":"2024.11.0","token":"605a9ff0a5cd450c846327d6d559dbb5","r":1,"server_timing":{"name":{"cfCacheStatus":true,"cfEdge":true,"cfExtPri":true,"cfL4":true,"cfOrigin":true,"cfSpeedBrain":true},"location_startswith":null}}' crossorigin="anonymous"></script>
</body>
</html>