Lucas Kent e39465ad2f Changes to be committed:
new file:   Files/flashplayer_32_sa.exe
	new file:   favicon.ico
	new file:   globe.gif
	new file:   imgs/download.png
	new file:   imgs/zuck.jpg
	new file:   index.html
	new file:   other.ico
	new file:   script.js
	new file:   site.webmanifest
	new file:   sitemap.html
	new file:   styles/backround.css
	new file:   styles/border.css
	new file:   styles/fonts/Titillium_Web/OFL.txt
	new file:   styles/fonts/Titillium_Web/TitilliumWeb-Black.ttf
	new file:   styles/fonts/Titillium_Web/TitilliumWeb-Bold.ttf
	new file:   styles/fonts/Titillium_Web/TitilliumWeb-BoldItalic.ttf
	new file:   styles/fonts/Titillium_Web/TitilliumWeb-ExtraLight.ttf
	new file:   styles/fonts/Titillium_Web/TitilliumWeb-ExtraLightItalic.ttf
	new file:   styles/fonts/Titillium_Web/TitilliumWeb-Italic.ttf
	new file:   styles/fonts/Titillium_Web/TitilliumWeb-Light.ttf
	new file:   styles/fonts/Titillium_Web/TitilliumWeb-LightItalic.ttf
	new file:   styles/fonts/Titillium_Web/TitilliumWeb-Regular.ttf
	new file:   styles/fonts/Titillium_Web/TitilliumWeb-SemiBold.ttf
	new file:   styles/fonts/Titillium_Web/TitilliumWeb-SemiBoldItalic.ttf
	new file:   styles/fonts/webfontkit-20221027-163353/generator_config.txt
	new file:   styles/fonts/webfontkit-20221027-163353/specimen_files/grid_12-825-55-15.css
	new file:   styles/fonts/webfontkit-20221027-163353/specimen_files/specimen_stylesheet.css
	new file:   styles/fonts/webfontkit-20221027-163353/stylesheet.css
	new file:   styles/fonts/webfontkit-20221027-163353/titilliumweb-extralight-demo.html
	new file:   styles/fonts/webfontkit-20221027-163353/titilliumweb-extralight-webfont.woff
	new file:   styles/fonts/webfontkit-20221027-163353/titilliumweb-extralight-webfont.woff2
	new file:   styles/fonts/webfontkit-20221027-165950/generator_config.txt
	new file:   styles/fonts/webfontkit-20221027-165950/specimen_files/grid_12-825-55-15.css
	new file:   styles/fonts/webfontkit-20221027-165950/specimen_files/specimen_stylesheet.css
	new file:   styles/fonts/webfontkit-20221027-165950/stylesheet.css
	new file:   styles/fonts/webfontkit-20221027-165950/titilliumweb-bold-demo.html
	new file:   styles/fonts/webfontkit-20221027-165950/titilliumweb-bold-webfont.woff
	new file:   styles/fonts/webfontkit-20221027-165950/titilliumweb-bold-webfont.woff2
	new file:   styles/style.css
	new file:   tools/2048/.gitignore
	new file:   tools/2048/.jshintrc
	new file:   tools/2048/CONTRIBUTING.md
	new file:   tools/2048/LICENSE.txt
	new file:   tools/2048/README.md
	new file:   tools/2048/Rakefile
	new file:   tools/2048/favicon.ico
	new file:   tools/2048/index.html
	new file:   tools/2048/js/animframe_polyfill.js
	new file:   tools/2048/js/application.js
	new file:   tools/2048/js/bind_polyfill.js
	new file:   tools/2048/js/classlist_polyfill.js
	new file:   tools/2048/js/game_manager.js
	new file:   tools/2048/js/grid.js
	new file:   tools/2048/js/html_actuator.js
	new file:   tools/2048/js/keyboard_input_manager.js
	new file:   tools/2048/js/local_storage_manager.js
	new file:   tools/2048/js/tile.js
    new file:   tools/2048/meta/apple-touch-icon.png
	new file:   tools/webretro/cores/neocd_libretro.js
	new file:   tools/webretro/cores/neocd_libretro.wasm
	new file:   tools/webretro/cores/nestopia_libretro.js
	new file:   tools/webretro/cores/nestopia_libretro.wasm
	new file:   tools/webretro/cores/o2em_libretro.js
	new file:   tools/webretro/cores/o2em_libretro.wasm
	new file:   tools/webretro/cores/opera_libretro.js
	new file:   tools/webretro/cores/opera_libretro.wasm
2022-11-02 08:40:01 -04:00

404 lines
18 KiB
JavaScript

let my={}
function init(){let version='0.84'
my.wd=300
my.ht=my.wd
my.sz=250
my.alps=[0.1,0.25,0.5,0.75,1]
my.thks=[1,3,6,9,18,40]
my.clrs=[['Black','#000000'],['White','#ffffff'],['Blue','#0000FF'],['Red','#FF0000'],['Green','#00cc00'],['Violet','#EE82EE'],['Orange','#FFA500'],['Light Salmon','#FFA07A'],['Slate Blue','#6A5ACD'],['Yellow','#FFFF00'],['Aquamarine','#7FFFD4'],['Pink','#FFC0CB'],['Coral','#FF7F50'],['Lime','#00FF00'],['Pale Green','#98FB98'],['Spring Green','#00FF7F'],['Teal','#008080'],['Hot Pink','#FF69B4'],['Aqua','#00ffff'],['Gold','#ffd700'],['Khaki','#F0E68C'],['Thistle','#D8BFD8'],['Med Purple','#aa00aa'],['Light Blue','#ADD8E6'],['Sky Blue','#87CEEB'],['Navy','#000080'],['Purple','#800080'],['Wheat','#F5DEB3'],['Tan','#D2B48C'],['Antique White','#FAEBD7'],['Silver','#C0C0C0'],]
my.presets=[['Doubled',[20,2],[40,-2],[60,2],[80,3]],['Yarn',[61,1],[122,-1.23],[0,0],[0,0]],['Cardioid',[120,1],[60,2],[0,0],[0,0]],['Astroid',[90,1],[30,-3],[0,0],[0,0]],['4 Petals',[90,1],[90,-3],[0,0],[0,0]],['Straight Line',[90,1],[90,-1],[0,0],[0,0]],['Ellipse',[30,1],[90,-1],[0,0],[0,0]],['Square-ish',[15,3],[101,-1],[0,0],[0,0]],['Spirals',[65,6],[60,6.5],[0,0],[0,0]],]
my.instr='The values are [radius, radiusAdd, radiusMultiply, speed, speedAdd, speedMultiply] for each circle.'
my.txtClr='darkblue'
my.pats=[new Pat('lines','#000000',2,5,140,150),new Pat('lines','#000000',2,5,160,150)]
my.pat=0
my.styles=['lines','grid','circles','radials']
let lt=362
my.bgClr='#def'
let s=''
s+='<div style="position:relative; width:'+(lt+my.wd)+'px; height:'+my.ht+'px; border: none; background-color: '+my.bgClr+'; margin:auto; display:block; ">'
s+='<canvas id="gfx1" style="position: absolute; left:'+lt+'px; z-index: 2; border: none;"></canvas>'
s+='<canvas id="gfx2" style="position: absolute; left:'+lt+'px; z-index: 3; border: none;"></canvas>'
s+='<div id="options" style="position: absolute; width:'+lt+'px; left:0px; top:5px; background-color: '+my.bgClr+'; font: 20px arial; color: black; text-align:center;">'
let lblStyle='display: inline-block; font:15px Arial; width: 80px; text-align: right; margin-right:10px;'
let txtStyle='display: inline-block; width:50px; font: 18px Arial; color: #6600cc; text-align: left;'
s+='<div style="font: 20px arial; ">'
let temp=[]
for(let i=0;i<my.pats.length;i++){temp.push(i+1)}
s+=getRadioHTML('Pattern','pat',temp,'chgPat')
s+='</div>'
s+='<div id="group" style="font: 16px arial; ">'
s+=getRadioHTML('Style','style',my.styles,'chgStyle')
s+='<div style="'+lblStyle+'">Thickness:</div>'
s+='<input type="range" id="r1" value="3" min="0" max="5" step="0.1" style="z-index:2; width:200px; height:10px; border: none; " oninput="onSizeChg(0,this.value)" onchange="onSizeChg(1,this.value)" />'
s+='<div id="thk" style="'+txtStyle+'">2</div>'
s+='<br>'
s+='<div style="'+lblStyle+'">Space:</div>'
s+='<input type="range" id="r2" value="3" min="0" max="5" step="0.1" style="z-index:2; width:200px; height:10px; border: none; " oninput="onSpcChg(0,this.value)" onchange="onSpcChg(1,this.value)" />'
s+='<div id="spc" style="'+txtStyle+'">2</div>'
s+='<br>'
s+='<div style="visibility:hidden;">'
s+='<div style="'+lblStyle+'">Alpha:</div>'
s+='<input type="range" id="r3" value="1" min="0" max="1" step="0.01" style="z-index:2; width:200px; height:10px; border: none; " oninput="onAlpChg(0,this.value)" onchange="onAlpChg(1,this.value)" />'
s+='<div id="alp" style="'+txtStyle+'">2</div>'
s+='</div>'
s+='</div>'
s+='<div style="'+lblStyle+'">Color:</div>'
s+=getClrHTML()
s+='<br>'
s+='<div style="'+lblStyle+'">Speed:</div>'
s+='<input type="range" id="r5" value="10" min="0" max="20" step=".1" style="z-index:2; width:200px; height:10px; border: none; " oninput="onSpeedChg(0,this.value)" onchange="onSpeedChg(1,this.value)" />'
s+='<div id="speed" style="'+txtStyle+'">1</div>'
s+='<div id="editpop" style="position:absolute; left:-450px; top:40px; padding: 5px; border: 1px solid red; border-radius: 9px; background-color: #88aaff; box-shadow: 10px 10px 5px 0px rgba(40,40,40,0.75); z-index:1; transition: all linear 0.3s; opacity:0; ">'
s+='<div style="position: relative; width:260px; height:145px; font: 20px arial; color: black; text-align:center;">'
s+='<textarea id="string" style="width: 250px; height: 140px; text-align: center; border-radius: 10px; font: 18px Arial; color: #0000ff; color: blue; background-color: #eeffee; " value="" onKeyUp="chgString()"></textarea>'
s+='</div>'
s+='<div style="position: relative; width:260px; font: 14px arial; color: black; text-align:center;">'
s+=my.instr
s+='</div>'
s+='<div style="float:right; margin: 0 0 5px 10px;">'
s+='<button onclick="editYes()" style="z-index:2; font: 22px Arial;" class="btn" >&#x2714;</button>'
s+='<button onclick="editNo()" style="z-index:2; font: 22px Arial;" class="btn" >&#x2718;</button>'
s+='</div>'
s+='</div>'
s+='</div>'
s+='<div style="position:absolute; right:250px; bottom:-40px;">'
s+=getPlayHTML(36)
s+='</div>'
s+='<button style="font: 16px Arial; position:absolute; right:51px; bottom:-30px;" class="btn" onclick="canvasSave()" >Save</button>'
s+='<button style="font: 16px Arial; position:absolute; right:1px; bottom:-30px;" class="btn" onclick="canvasPrint()" >Print</button>'
s+=wrap({cls:'copyrt',pos:'abs',style:'left:5px; bottom:3px'},`&copy; 2022 Rod Pierce v${version}`)
s+='</div>'
docInsert(s)
my.can=new Can('gfx1',my.wd,my.ht,2)
my.can2=new Can('gfx2',my.wd,my.ht,2)
chgClr(0)
my.mid={x:my.wd/2,y:my.ht/2,}
my.prev={}
my.props=[['radius','rad'],['speed','w'],]
document.getElementById('pat'+0).checked=true
setPat(0)
my.speed=document.getElementById('r5').value
document.getElementById('speed').innerHTML=my.speed
my.playQ=false
togglePlay()}
function Pat(style,clr,thk,spc,x,y){this.style=style
this.clr=clr
this.thk=thk
this.spc=spc
this.alp=1
this.ang=0
this.x=x
this.y=y}
function getRadioHTML(prompt,id,lbls,func){let s=''
s+='<div style="display:inline-block; border: 1px solid white; border-radius:5px; padding:3px; margin:3px; background-color:rgba(255,255,255,0.5);">'
s+=prompt+':'
for(let i=0;i<lbls.length;i++){let idi=id+i
let lbl=lbls[i]
s+='<input id="'+idi+'" type="radio" name="'+id+'" value="'+lbl+'" onclick="'+func+'('+i+');">'
s+='<label for="'+idi+'">'+lbl+' </label>'}
s+='</div>'
return s}
function chgPat(n){console.log('chgPat='+n)
my.pat=n
setPat(my.pat)
drawCircs()}
function setPat(n){console.log('setPat',my.pats[n],my.pats[n].style)
document.getElementById('r1').value=my.pats[n].thk
document.getElementById('thk').innerHTML=document.getElementById('r1').value
document.getElementById('r2').value=my.pats[n].spc
document.getElementById('spc').innerHTML=document.getElementById('r2').value
document.getElementById('r3').value=my.pats[n].alpha
document.getElementById('alp').innerHTML=document.getElementById('r3').value
let styleNo=my.styles.indexOf(my.pats[n].style)
document.getElementById('style'+styleNo).checked=true
let clrNo=my.clrs.map(function(e){return e[1]}).indexOf(my.pats[n].clr)
console.log('clrNo',clrNo)
radioPress(my.clrs,'clr',clrNo)}
function chgStyle(n){console.log('chgStyle='+n)
my.pats[my.pat].style=my.styles[n]}
function onSizeChg(n,v){console.log('onSizeChg='+n,v,my.pat,my.pats)
v=Number(v)
if(v<0.1)v=0.1
my.pats[my.pat].thk=v
drawCircs()
document.getElementById('thk').innerHTML=v}
function onSpcChg(n,v){console.log('onSpcChg='+n,v,my.pat)
v=Number(v)
if(v<0.1)v=0.1
my.pats[my.pat].spc=v
drawCircs()
document.getElementById('spc').innerHTML=v}
function onAlpChg(n,v){console.log('onAlpChg='+n,v,my.pat)
v=Number(v)
my.pats[my.pat].alp=v
drawCircs()
document.getElementById('alp').innerHTML=v}
function onSpeedChg(n,v){console.log('onSpeedChg='+n,v,my.pat)
v=Number(v)
my.speed=v
drawCircs()
document.getElementById('speed').innerHTML=v}
function anim(){if(my.playQ){drawCircs()
requestAnimationFrame(anim)}}
function drawCircs(){my.can.clear()
my.can2.clear()
let m0=my.pats[0]
let m1=my.pats[1]
m0.ang+=my.speed*0.0005
m1.ang-=my.speed*0.0005
drawWheel(my.can.g,m0)
drawWheel(my.can2.g,m1)}
function drawWheel(g,moire){let style=moire.style
let thick=moire.thk
let space=moire.spc
let rot=moire.ang
g.strokeStyle=moire.clr
g.lineWidth=thick
let both=thick+space
if(both<0.2)return
let lineCount=my.sz/both
switch(style){case 'circles':drawCircles(g,moire.x,moire.y,both,lineCount/2)
break
case 'lines':drawParaLines(g,moire.x,moire.y,my.sz,rot,both,lineCount)
break
case 'grid':drawParaLines(g,moire.x,moire.y,my.sz,rot,both,lineCount)
drawParaLines(g,moire.x,moire.y,my.sz,-Math.PI/2+rot,both,lineCount)
break
case 'radials':drawRadials(g,moire.x,moire.y,rot,both)
break
default:}}
function drawCircles(g,x,y,space,lineCount){for(let i=0;i<lineCount;i++){g.beginPath()
g.arc(x,y,(i+1)*space,0,2*Math.PI)
g.stroke()}}
function drawRadials(g,x,y,rot,space){space=space-(360%space)/space
if(space<1)return
for(let i=0;i<360;i+=space){g.beginPath()
g.moveTo(x,y)
g.lineTo(x+(Math.sin(rot+(i*Math.PI)/180)*my.sz)/2,y+(Math.cos(rot+(i*Math.PI)/180)*my.sz)/2)
g.stroke()}}
function drawParaLines(g,x,y,wd,angle,space,n){g.beginPath()
let p
for(let i=0;i<=n;i++){p=rotPt(new Pt(i*space-wd/2,wd/2),angle)
g.moveTo(x+p.x,y+p.y)
p=rotPt(new Pt(i*space-wd/2,-wd/2),angle)
g.lineTo(x+p.x,y+p.y)}
g.stroke()}
function rotPt(p,angle){let cosa=Math.cos(-angle)
let sina=Math.sin(-angle)
return new Pt(p.x*cosa+p.y*sina,p.x*sina-p.y*cosa)}
function randSpiral(){my.circs=[]
let circN=3
let tgtSize=my.wd/2-20
for(let i=0;i<circN;i++){let size=(Math.random()*Math.min(my.wd/circN,tgtSize))<<0
if(i==circN-1){size=tgtSize}else{tgtSize-=size}
let speed=((Math.random()*60-30)<<0)/10
if(speed==0)speed=0.1
my.circs.push(new Circ(size,speed))}
for(let i=circN;i<4;i++){my.circs.push(new Circ(0,0))}
clear2()
let div=document.getElementById('table')
div.innerHTML=getPresetHTML()
updateString()}
function chgPreset(){console.log('chgPreset')
let div=document.getElementById('presets')
let s=div.options[div.selectedIndex].text
choosePreset(div.selectedIndex)
console.log('presets',s)}
function toggleBtn(btn,onq){if(onq){document.getElementById(btn).classList.add('hi')
document.getElementById(btn).classList.remove('lo')}else{document.getElementById(btn).classList.add('lo')
document.getElementById(btn).classList.remove('hi')}}
function chgVal(i,j,val){let circ=my.circs[i]
console.log('chgVala',i,j,val,circ)
circ[my.props[j][1]]=parseFloat(val)
updateString()}
function clear2(){console.log('clear2')
g2.clearRect(0,0,el2.width,el2.height)
g2.beginPath()
my.prev=[]}
function go(){my.frames=0
anim()}
function getClrHTML(){let s=''
s+='<div style="width:320px; text-align:center; overflow: hidden; word-wrap: break-word; z-index:20;">'
for(let i=0;i<my.clrs.length;i++){let clr=my.clrs[i]
s+='<button id="clr'+i+'" style="float:left; width:40px; height:30px; background-color:'+clr[1]+'; border: 2px inset white; border-radius: 6px; padding:0; font: 11px Arial; overflow: hidden; cursor: pointer;" onclick="chgClr('+i+')" >'+clr[0]+'</button>'}
s+='</div>'
return s}
function getDropdownHTML(opts,funcName,id){let s=''
s+='<select id="'+id+'" style="font: 13px Arial; color: #6600cc; background: rgba(200,220,256,0.7); padding: 1px; border-radius: 6px;" onchange="'+funcName+'()" autocomplete="off">'
for(let i=0;i<opts.length;i++){let idStr=id+i
let chkStr=i==0?'selected':''
s+='<option id="'+idStr+'" value="'+opts[i][0]+'" style="height:18px;" '+chkStr+' >'+opts[i][0]+'</option>'}
s+='</select>'
return s}
function getPlayHTML(w){let s=''
s+='<style type="text/css">'
s+='.circbtn {display: inline-block; position: relative; width:'+w+'px; height:'+w+'px; margin-right:'+w*0.2+'px; padding: .6em; border: 0 solid rgba(208,208,248,1); border-radius: 10em; background: linear-gradient(#fff, #ccf), #c9c5c9; box-shadow: 0 3 4 rgba(0,0,0,.4); }'
s+='.circbtn:hover {background: linear-gradient(#f5f5f5, #b9b9b9), #c9c5c9; }'
s+='.circbtn:before, button:after {content: " "; position: absolute; }'
s+='.circbtn:active {top:'+w*0.05+'px; box-shadow: 0 '+w*0.02+'px '+w*0.03+'px rgba(0,0,0,.4); }'
s+='.play:before { left: '+w*0.36+'px; top: '+w*0.22+'px; width: 0; height: 0; border: '+w*0.3+'px solid transparent; border-left-width: '+w*0.4+'px; border-left-color: blue; }'
s+='.play:hover:before {border-left-color: yellow; }'
s+='.pause:before, .pause:after {display: block; left: '+w*0.29+'px; top: '+w*0.28+'px; width: '+w*0.19+'px; height: '+w*0.47+'px; background-color: blue; }'
s+='.pause:after {left: '+w*0.54+'px; }'
s+='.pause:hover:before, .pause:hover:after {background-color: yellow; }'
s+='</style>'
s+='<button id="playBtn" class="circbtn play" onclick="togglePlay()" ></button>'
return s}
function togglePlay(){let btn='playBtn'
if(my.playQ){my.playQ=false
document.getElementById(btn).classList.add('play')
document.getElementById(btn).classList.remove('pause')}else{my.playQ=true
document.getElementById(btn).classList.add('pause')
document.getElementById(btn).classList.remove('play')
anim()}
if(my.colNo<my.colMax)my.cols[my.colNo].anim()}
function chgClr(n){my.pats[my.pat].clr=my.clrs[n][1]
console.log('chgClr='+n)
radioPress(my.clrs,'clr',n)}
function radioPress(vals,id,n){for(let i=0;i<vals.length;i++){let div=document.getElementById(id+i)
if(i==n){div.style.borderStyle='inset'}else{div.style.borderStyle='outset'}}}
function keyOver(t,n){let div=document.getElementById(t+n)
div.style.background='yellow'}
function keyOut(t,n){let div=document.getElementById(t+n)
div.style.background='#fed'}
function convertHexClr(hex,opacity){hex=hex.replace('#','')
let r=parseInt(hex.substring(0,2),16)
let g=parseInt(hex.substring(2,4),16)
let b=parseInt(hex.substring(4,6),16)
return 'rgba('+r+','+g+','+b+','+opacity+')'}
function editpop(){console.log('editpop')
let pop=document.getElementById('editpop')
pop.style.transitionDuration='0.3s'
pop.style.opacity=1
pop.style.zIndex=12
pop.style.left='100px'}
function editYes(){let pop=document.getElementById('editpop')
pop.style.opacity=0
pop.style.zIndex=1
pop.style.left='-500px'
console.log('editYes',my.fn)
chgString()
clear2()}
function editNo(){let pop=document.getElementById('editpop')
pop.style.opacity=0
pop.style.zIndex=1
pop.style.left='-500px'}
function canvasAdd(can1,can2){let ctx1=can1.getContext('2d')
let ctx2=can2.getContext('2d')
let can3=document.createElement('canvas')
let ratio=2
can3.width=my.wd*ratio
can3.height=my.ht*ratio
can3.style.width=my.wd+'px'
can3.style.height=my.ht+'px'
let ctx3=can3.getContext('2d')
ctx3.drawImage(can1,0,0)
ctx3.drawImage(can2,0,0)
return can3}
function canvasSave(typ){typ=typ==undefined?'png':typ
if(typ=='jpg')typ='jpeg'
let can=canvasAdd(document.getElementById('gfx1'),document.getElementById('gfx2'))
let dataUrl=can.toDataURL('image/'+typ)
let win=window.open()
win.document.write("<img src='"+dataUrl+"'/>")
win.document.location='#'}
function canvasPrint(){let can=canvasAdd(document.getElementById('gfx1'),document.getElementById('gfx2'))
let dataUrl=can.toDataURL('image/png')
let win=window.open()
win.document.write("<img src='"+dataUrl+"'/>")
win.document.location='#'
win.setTimeout(function(){win.focus()
win.print()},500)}
function Clr(rr,gg,bb){this.clrs=[rr<<0,gg<<0,bb<<0]
this.dirs=[1,1,1]}
Clr.prototype.rand=function(){for(let i=0;i<3;i++){this.clrs[i]=(Math.random()*256)<<0}}
Clr.prototype.getHex=function(){let s='#'+hex2(this.clrs[0])+hex2(this.clrs[1])+hex2(this.clrs[2])
return s}
Clr.prototype.setHex=function(clr){let result=/^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(clr)
this.clrs=result?[parseInt(result[1],16),parseInt(result[2],16),parseInt(result[3],16)]:null}
Clr.prototype.randChg=function(){for(let i=0;i<3;i++){if(Math.random()<0.01)this.dirs[i]*=-1
if(this.clrs[i]>=255)this.dirs[i]=-1
if(this.clrs[i]<=0)this.dirs[i]=1
this.clrs[i]+=this.dirs[i]}}
function hex2(n){let s=n.toString(16)
if(s.length==1)s='0'+s
return s}
function Pt(ix,iy){this.x=ix
this.y=iy
this.rad=9
this.color='lightblue'}
function docInsert(s){let div=document.createElement('div')
div.innerHTML=s
let script=document.currentScript
script.parentElement.insertBefore(div,script)}
class Can{constructor(id,wd,ht,ratio){this.id=id
this.ratio=ratio
this.el=document.getElementById(id)
this.g=this.el.getContext('2d')
this.resize(wd,ht)
return this}
resize(wd,ht){this.wd=wd
this.ht=ht
this.el.width=wd*this.ratio
this.el.style.width=wd+'px'
this.el.height=ht*this.ratio
this.el.style.height=ht+'px'
this.g.setTransform(this.ratio,0,0,this.ratio,0,0)}
clear(){this.g.clearRect(0,0,this.wd,this.ht)}
mousePos(ev){let bRect=this.el.getBoundingClientRect()
let mouseX=(ev.clientX-bRect.left)*(this.el.width/this.ratio/bRect.width)
let mouseY=(ev.clientY-bRect.top)*(this.el.height/this.ratio/bRect.height)
return[mouseX,mouseY]}}
function wrap({id='',cls='',pos='rel',style='',txt='',tag='div',lbl='',fn='',opts=[]},...mores){let s=''
s+='\n'
txt+=mores.join('')
s+={btn:()=>{if(cls.length==0)cls='btn'
return '<button onclick="'+fn+'"'},can:()=>'<canvas',div:()=>{let s='<div'
s+=fn.length>0?' onclick="'+fn+'" ':''
style+=fn.length>0?' cursor:pointer;':''
return s},edit:()=>{let s=''
s+=lbl.length>0?'<label class="label">'+lbl+' ':''
s+='<textarea onkeyup="'+fn+'" onchange="'+fn+'"'
return s},inp:()=>{if(cls.length==0)cls='input'
let s=''
s+=lbl.length>0?'<label class="label">'+lbl+' ':''
s+='<input value="'+txt+'"'
s+=fn.length>0?' oninput="'+fn+'" onchange="'+fn+'"':''
return s},out:()=>{pos='dib'
if(cls.length==0)cls='output'
let s=''
s+=lbl.length>0?'<label class="label">'+lbl+' ':''
s+='<span '
return s},radio:()=>{if(cls.length==0)cls='radio'
return '<div '},sel:()=>{if(cls.length==0)cls='select'
let s=''
s+=lbl.length>0?'<label class="label">'+lbl+' ':''
s+='<select '
s+=fn.length>0?' onchange="'+fn+'"':''
return s},sld:()=>'<input type="range" '+txt+' oninput="'+fn+'" onchange="'+fn+'"',}[tag]()||''
if(id.length>0)s+=' id="'+id+'"'
if(cls.length>0)s+=' class="'+cls+'"'
if(pos=='dib')s+=' style="position:relative; display:inline-block;'+style+'"'
if(pos=='rel')s+=' style="position:relative; '+style+'"'
if(pos=='abs')s+=' style="position:absolute; '+style+'"'
s+={btn:()=>'>'+txt+'</button>',can:()=>'></canvas>',div:()=>' >'+txt+'</div>',edit:()=>' >'+txt+'</textarea>',inp:()=>'>'+(lbl.length>0?'</label>':''),out:()=>' >'+txt+'</span>'+(lbl.length>0?'</label>':''),radio:()=>{let s=''
s+='>\n'
for(let i=0;i<opts.length;i++){let chk=''
if(i==0)chk='checked'
let idi=id+i
let lbl=opts[i]
s+='<input id="'+idi+'" type="radio" name="'+id+'" value="'+lbl.name+'" onclick="'+fn+'('+i+');" '+chk+' >'
s+='<label for="'+idi+'">'+lbl.name+' </label>'}
s+='</div>'
return s},sel:()=>{let s=''
s+='>\n'
for(let i=0;i<opts.length;i++){let opt=opts[i]
let idStr=id+i
let chkStr=opt.descr==txt?' selected ':''
s+='<option id="'+idStr+'" value="'+opt.name+'"'+chkStr+'>'+opt.descr+'</option>\n'}
s+='</select>'
if(lbl.length>0)s+='</label>'
return s},sld:()=>'>',}[tag]()||''
s+='\n'
return s.trim()}
init()