

























quaternion 3d
<!doctype html><html><head><meta charset='utf-8'><meta name='viewport' content='width=device-width'><style>
body{background-color:#ffffffff;color:#000000ff}
@media(prefers-color-scheme:dark){body{background-color:#000000ff;color:#ffffffff}}
</style>
<script>{
console.log(`hello world`)//hello world
}</script></head></html>
<!doctype html><html><head><meta charset='utf-8'><meta name='viewport' content='width=device-width'><style>
body{background-color:#ffffffff;color:#000000ff}
@media(prefers-color-scheme:dark){body{background-color:#000000ff;color:#ffffffff}}
</style>
<script>{
const augend=1,addend=2,sum=3
console.log(`--addition-- augend + addend === sum`)
console.log(`${augend} + ${addend} = ${sum}`)//1 + 2 = 3
console.log(augend+addend===sum)//true
const minuend=3,subtrahend=2,difference=1
console.log(`--subtraction-- minuend - subtrahend === difference`)
console.log(`${minuend} - ${subtrahend} = ${difference}`)//3 - 2 = 1
console.log(minuend-subtrahend===difference)//true
const multiplicand=2,multiplier=3,product=6
console.log(`--multiplication-- multiplicand * multiplier === product`)
console.log(`${multiplicand} * ${multiplier} = ${product}`)//2 * 3 = 6
console.log(multiplicand*multiplier===product)//true
const dividend=6,divisor=3,quotient=2
console.log(`--division-- dividend / divisor === quotient`)
console.log(`${dividend} / ${divisor} = ${quotient}`)//6 / 3 = 2
console.log(dividend/divisor===quotient)//true
const remainder=0
console.log(`--division(modulo)-- dividend % divisor === remainder`)
console.log(`${dividend} % ${divisor} = ${remainder}`)//6 % 3 = 0
console.log(dividend%divisor===remainder)//true
const base=3,exponent=2,power=9
console.log(`--exponentiation-- base ** exponent === power`)
console.log(`${base} ** ${exponent} = ${power}`)//3 ** 2 = 9
console.log(base**exponent===power)//true
const radicand=9,root=3
console.log(`--square root-- Math.sqrt(radicand) === root`)
console.log(`√${radicand} = ${root}`)//√9 = 3
console.log(Math.sqrt(radicand)===root)//true
}</script></head></html>
<!doctype html><html><head><meta charset='utf-8'><meta name='viewport' content='width=device-width'><style>
body{background-color:#ffffffff;color:#000000ff}
@media(prefers-color-scheme:dark){body{background-color:#000000ff;color:#ffffffff}}
</style>
<script>{
console.log((0|0b00000000000000000000000000000001)===0b01)//true
console.log((0|0b11111111111111111111111111111110)===-0b10)//true
console.log((0|0b01111111111111111111111111111111)===2147483647)//true
console.log(`--not-- ~0b01 = -0b10`)
console.log(~0b01===-0b10)//true
console.log(`--and-- (0b01 & 0b11) = 0b01`)
console.log((0b01&0b11)===0b01)//true
console.log(`--xor-- (0b01 ^ 0b11) = 0b10`)
console.log((0b01^0b11)===0b10)//true
console.log(`--or-- (0b01 | 0b11) = 0b11`)
console.log((0b01|0b11)===0b11)//true
console.log(`--left shift-- 0b01<<1=0b10`)
console.log(0b01<<1===0b10)//true
console.log(`--right shift-- 0b11>>1=0b01`)
console.log(0b11>>1===0b01)//true
console.log(`--unsigned right shift-- -0b10>>>1=2147483647`)
console.log(-0b10>>>1===2147483647)//true
}</script></head></html>
<!doctype html><html><head><meta charset='utf-8'><meta name='viewport' content='width=device-width'><style>
body{background-color:#ffffffff;color:#000000ff}
@media(prefers-color-scheme:dark){body{background-color:#000000ff;color:#ffffffff}}
</style>
<script>{
console.log(`--Array--`)
console.log([])//[]
console.log([0])//[0]
console.log(Array(1))//[empty]
console.log(Array(0,1))//[0, 1]
console.log(`--push, pop, reverse--`)
const vs=[]
console.log(vs.push(0))//1
console.log(vs)//[0]
console.log(vs.push(1,2))//3
console.log(vs)//[0, 1, 2]
console.log(vs[1]=3)//3
console.log(vs)//[0, 3, 2]
console.log(vs.pop())//2
console.log(vs)//[0, 3]
console.log(vs.reverse())//[3, 0]
console.log(vs)//[3, 0]
console.log(`--isArray--`)
console.log(Array.isArray([]))//true
console.log(`--equals--`)
Array.prototype.equals=function(v){
return v.length===this.length&&v.every((v,i)=>v===this[i])
}
console.log(Object.getOwnPropertyDescriptor(Array.prototype,'equals'))//{'writable': true,'enumerable': true,'configurable': true}
console.log([].equals([]))//true
}</script></head></html>
<!doctype html><html><head><meta charset='utf-8'><meta name='viewport' content='width=device-width'><style>
body{background-color:#ffffffff;color:#000000ff}
@media(prefers-color-scheme:dark){body{background-color:#000000ff;color:#ffffffff}}
</style>
<script>{
Number.prototype.toBinarySearchIndex=function(l,c){
let v=0
for(let i;1<l-v;)this<c(i=v+l>>>1)?l=i:v=i
return v
}
const vs=[0,2,4,6,8]
console.log((0).toBinarySearchIndex(vs.length,i=>vs[i]))//0
console.log((2).toBinarySearchIndex(vs.length,i=>vs[i]))//1
console.log((6).toBinarySearchIndex(vs.length,i=>vs[i]))//3
}</script></head></html>
<!doctype html><html><head><meta charset='utf-8'><meta name='viewport' content='width=device-width'><style>
body{background-color:#ffffffff;color:#000000ff}
@media(prefers-color-scheme:dark){body{background-color:#000000ff;color:#ffffffff}}
</style>
<script>{
String.prototype.splitFrom=function(pattern){
const vs=[]
vs.length=1+this.length-this.matchAll(RegExp(pattern,`g`)).reduce((a,v)=>a+(vs[v.index-a]=v[0]).length,0)
return vs
}
console.log(`,1,,,,"5",,"""7""",,`.splitFrom(`"(?:""|[^"])*"|[^,]+`))//[empty, '1', empty × 3, '"5"', empty, '"""7"""', empty × 2]
}</script></head></html>
<!doctype html><html><head><meta charset='utf-8'><meta name='viewport' content='width=device-width'><style>
body{background-color:#ffffffff;color:#000000ff}
@media(prefers-color-scheme:dark){body{background-color:#000000ff;color:#ffffffff}}
</style>
<script>{
Number.prototype.ordinalize=function(){
return(this+`th`).replace(/(?<!1)[123]th$/,v=>`1th`===v?`1st`:`2th`===v?`2nd`:`3rd`)
}
console.log([0,1,2,3,4,10,11,12,13,14,100,111,122,133,144].map(v=>v.ordinalize()))//['0th', '1st', '2nd', '3rd', '4th', '10th', '11th', '12th', '13th', '14th', '100th', '111th', '122nd', '133rd', '144th']
}</script></head></html>
<!doctype html><html><head><meta charset='utf-8'><meta name='viewport' content='width=device-width'><style>
body{background-color:#ffffffff;color:#000000ff}
@media(prefers-color-scheme:dark){body{background-color:#000000ff;color:#ffffffff}}
</style>
<script>{
console.log(`--factorial--`)
Number.prototype.toFactorial=function(){
return 1<this?this*(this-1).toFactorial():1
}
console.log((10).toFactorial())//3628800
console.log(`--greatest common divisor--`)
Number.prototype.toGCD=function(v){
return v?v.toGCD(this%v):this
}
console.log((16).toGCD(24))//Number {8}
}</script></head></html>
<!doctype html><html><head><meta charset='utf-8'><meta name='viewport' content='width=device-width'><style>
body{background-color:#ffffffff;color:#000000ff}
@media(prefers-color-scheme:dark){body{background-color:#000000ff;color:#ffffffff}}
</style>
<script>{
if(!crypto.subtle)throw Error(`available in secure contexts.`)
const encoder=new TextEncoder(),decoder=new TextDecoder()
,aes=async(string,object,hashObject)=>{
const key=await generateKey(object.lengthParams,[`encrypt`])
,encrypted=await crypto.subtle.encrypt(object.params,key,encoder.encode(string))
,exportedKey=await crypto.subtle.exportKey(object.format,key)
,decrypted=await crypto.subtle.decrypt(object.params,await importKey(object.format,exportedKey,object.algorithm,[`decrypt`]),encrypted)
if(hashObject){
const verified=await verify(hashObject,encrypted)
return[object.algorithm.name,decoder.decode(decrypted),verified]
}else return[object.algorithm.name,decoder.decode(decrypted)]
}
,rsa=async(string,object)=>{
const keys=await generateKey(object.lengthParams,[`encrypt`,`decrypt`])
,encrypted=await crypto.subtle.encrypt(object.algorithm,keys.publicKey,encoder.encode(string))
,exportedKey=await crypto.subtle.exportKey(object.format,keys.privateKey)
,decrypted=await crypto.subtle.decrypt(object.algorithm,await importKey(object.format,exportedKey,object.params,[`decrypt`]),encrypted)
return[object.algorithm.name,decoder.decode(decrypted)]
}
,generateKey=async(params,usages)=>await crypto.subtle.generateKey(params,true,usages)//extractable=true
,importKey=async(format,key,params,usages)=>await crypto.subtle.importKey(format,key,params,false,usages)//extractable=false
,verify=async(object,encrypted)=>{
const key=await generateKey(object.params,[`sign`])
,signature=await crypto.subtle.sign(object.algorithm,key,encrypted)
,exportedKey=await crypto.subtle.exportKey(object.format,key)
return crypto.subtle.verify(object.algorithm,await importKey(object.format,exportedKey,object.params,[`verify`]),signature,encrypted)
}
(async()=>{
const string=`hello world`
,aesCTRObject={algorithm:{name:`AES-CTR`},params:{counter:crypto.getRandomValues(new Uint8Array(16)),length:64},lengthParams:{length:128},format:`raw`}
,aesCTRHashObject={algorithm:{name:`HMAC`},params:{hash:`SHA-256`},format:`raw`}
,aesCBCObject={algorithm:{name:`AES-CBC`},params:{iv:crypto.getRandomValues(new Uint8Array(16))},lengthParams:{length:128},format:`raw`}
,aesCBCHashObject={algorithm:{name:`HMAC`},params:{hash:`SHA-256`},format:`raw`}
,aesGCMObject={algorithm:{name:`AES-GCM`},params:{iv:crypto.getRandomValues(new Uint8Array(12))},lengthParams:{length:128},format:`raw`}
,rsaOAEPObject={algorithm:{name:`RSA-OAEP`},params:{hash:`SHA-256`},lengthParams:{modulusLength:2048,publicExponent:new Uint8Array([0x01,0x00,0x01])},format:`pkcs8`}
Object.assign(aesCTRObject.params,aesCTRObject.algorithm)
Object.assign(aesCTRObject.lengthParams,aesCTRObject.algorithm)
Object.assign(aesCTRHashObject.params,aesCTRHashObject.algorithm)
Object.assign(aesCBCObject.params,aesCBCObject.algorithm)
Object.assign(aesCBCObject.lengthParams,aesCBCObject.algorithm)
Object.assign(aesCBCHashObject.params,aesCBCHashObject.algorithm)
Object.assign(aesGCMObject.params,aesGCMObject.algorithm)
Object.assign(aesGCMObject.lengthParams,aesGCMObject.algorithm)
Object.assign(rsaOAEPObject.params,rsaOAEPObject.algorithm)
Object.assign(rsaOAEPObject.lengthParams,rsaOAEPObject.params)
console.log(await aes(string,aesCTRObject,aesCTRHashObject))//['AES-CTR', 'hello world', true]
console.log(await aes(string,aesCBCObject,aesCBCHashObject))//['AES-CBC', 'hello world', true]
console.log(await aes(string,aesGCMObject))//['AES-GCM', 'hello world']
console.log(await rsa(string,rsaOAEPObject))//['RSA-OAEP', 'hello world']
})()
}</script></head></html>
<!doctype html><html><head><meta charset='utf-8'><meta name='viewport' content='width=device-width'><style>
body{
*{background-color:inherit;color:inherit}
background-color:#ffffffff;color:#000000ff
}
@media(prefers-color-scheme:dark){body{background-color:#000000ff;color:#ffffffff}}
</style>
<style>
html{
*{margin:0;padding:0}
>body{
*{font-family:monospace;box-sizing:border-box;overflow-wrap:anywhere;vertical-align:bottom}
>:nth-child(1){
>dl>dd>canvas{width:100%;aspect-ratio:1}
display:grid;grid-template-columns:1fr 1fr 1fr
}
>:nth-child(2){
>dd{margin-inline-start:.5rem}
display:grid;grid-template-columns:auto 1fr
}
touch-action:none
}
}
</style>
<script>
const quaternionRotated=`p+2.*cross(q.xyz,cross(q.xyz,p)+q.w*p)`
,getRotated=(q0,q1,precision)=>`vec4${precision}(c(c(p,${q0}),${q1}),0.)+vec4${precision}(0.,0.,1.,2.)`
WebGLRenderingContext.prototype.getRenderer=function(p,is,mode,color){
const vertexShaderSrc=`precision highp float;attribute vec3 p;uniform vec4 q0;uniform vec4 q1;vec3 c(vec3 p,vec4 q){return ${quaternionRotated};}void main(){gl_Position=${getRotated(`q0`,`q1`,``)};}`
,fragmentShaderSrc=`precision highp float;void main(){gl_FragColor=vec4(${color.map(v=>v+`.`).join(`,`)});}`
,program=(()=>{
const createShader=(type,src)=>{
const shader=this.createShader(type)
this.shaderSource(shader,src)
this.compileShader(shader)
return shader
}
const program=this.createProgram()
this.attachShader(program,createShader(this.VERTEX_SHADER,vertexShaderSrc))
this.attachShader(program,createShader(this.FRAGMENT_SHADER,fragmentShaderSrc))
this.linkProgram(program)
return program
})()
,q0Location=this.getUniformLocation(program,`q0`)
,q1Location=this.getUniformLocation(program,`q1`)
this.clearColor(0,0,0,0)
this.useProgram(program)
this.enableVertexAttribArray(0)
this.bindBuffer(this.ARRAY_BUFFER,this.createBuffer())
this.bufferData(this.ARRAY_BUFFER,p,this.STATIC_DRAW)
this.bindBuffer(this.ELEMENT_ARRAY_BUFFER,this.createBuffer())
this.bufferData(this.ELEMENT_ARRAY_BUFFER,is,this.STATIC_DRAW)
this.vertexAttribPointer(0,3,this.FLOAT,false,0,0)
return(q0,q1)=>{
this.uniform4fv(q0Location,q0)
this.uniform4fv(q1Location,q1)
this.clear(this.COLOR_BUFFER_BIT|this.DEPTH_BUFFER_BIT)
this.drawElements(mode,is.length,this.UNSIGNED_SHORT,0)
}
}
WebGL2RenderingContext.prototype.getRenderer=function(p,is,mode,color){
const vertexShaderSrc=`#version 300 es\nprecision highp float;in vec3 p;layout(std140)uniform q{vec4 q0;vec4 q1;};vec3 c(vec3 p,vec4 q){return ${quaternionRotated};}void main(){gl_Position=${getRotated(`q0`,`q1`,``)};}`
,fragmentShaderSrc=`#version 300 es\nprecision highp float;out vec4 c;void main(){c=vec4(${color.map(v=>v+`.`).join(`,`)});}`
,program=(()=>{
const createShader=(type,src)=>{
const shader=this.createShader(type)
this.shaderSource(shader,src)
this.compileShader(shader)
return shader
}
const program=this.createProgram()
this.attachShader(program,createShader(this.VERTEX_SHADER,vertexShaderSrc))
this.attachShader(program,createShader(this.FRAGMENT_SHADER,fragmentShaderSrc))
this.linkProgram(program)
return program
})()
,uniformBuffer=this.createBuffer()
this.clearColor(0,0,0,0)
this.useProgram(program)
this.enableVertexAttribArray(0)
this.bindBuffer(this.ARRAY_BUFFER,this.createBuffer())
this.bufferData(this.ARRAY_BUFFER,p,this.STATIC_DRAW)
this.bindBuffer(this.ELEMENT_ARRAY_BUFFER,this.createBuffer())
this.bufferData(this.ELEMENT_ARRAY_BUFFER,is,this.STATIC_DRAW)
this.vertexAttribPointer(0,3,this.FLOAT,false,0,0)
this.uniformBlockBinding(program,0,0)
this.bindBuffer(this.UNIFORM_BUFFER,uniformBuffer)
this.bindBufferBase(this.UNIFORM_BUFFER,0,uniformBuffer)
return(q0,q1)=>{
const q=new Float32Array(q0.length+q1.length)
q.set(q0)
q.set(q1,q0.length)
this.bufferData(this.UNIFORM_BUFFER,q,this.STATIC_DRAW)
this.clear(this.COLOR_BUFFER_BIT|this.DEPTH_BUFFER_BIT)
this.drawElements(mode,is.length,this.UNSIGNED_SHORT,0)
}
}
if(navigator.gpu)GPUCanvasContext.prototype.getRenderer=function(p,is,topology,color){
const createBuffer=(usage,src)=>{
const buffer=configuration.device.createBuffer({usage:usage,size:src.byteLength,mappedAtCreation:true})
new src.constructor(buffer.getMappedRange()).set(src)
buffer.unmap()
return buffer
}
,configuration=this.getConfiguration()
,layout=configuration.device.createBindGroupLayout({entries:[{binding:0,visibility:GPUShaderStage.VERTEX,buffer:{}}]})
,vertexShaderSrc=`struct Q{q0:vec4f,q1:vec4f}@binding(0)@group(0)var<uniform>q:Q;fn c(p:vec3f,q:vec4f)->vec3f{return ${quaternionRotated};}@vertex fn main(@location(0)p:vec3f)->@builtin(position)vec4f{return ${getRotated(`q.q0`,`q.q1`,`f`)};}`
,fragmentShaderSrc=`@fragment fn main()->@location(0)vec4f{return vec4f(${color.map(v=>v+`.`).join(`,`)});}`
,pipeline=configuration.device.createRenderPipeline({
layout:configuration.device.createPipelineLayout({bindGroupLayouts:[layout]})
,primitive:{topology:topology}
,vertex:{buffers:[{attributes:[{format:`float32x3`,offset:0,shaderLocation:0}],arrayStride:12}],module:configuration.device.createShaderModule({code:vertexShaderSrc})}
,fragment:{targets:[{format:configuration.format}],module:configuration.device.createShaderModule({code:fragmentShaderSrc})}
})
,encoder=configuration.device.createRenderBundleEncoder({colorFormats:[configuration.format]})
,uniformBuffer=configuration.device.createBuffer({usage:GPUBufferUsage.UNIFORM|GPUBufferUsage.COPY_DST,size:32})
encoder.setPipeline(pipeline)
encoder.setVertexBuffer(0,createBuffer(GPUBufferUsage.VERTEX,p))
encoder.setBindGroup(0,configuration.device.createBindGroup({entries:[{binding:0,resource:{buffer:uniformBuffer}}],layout:layout}))
encoder.setIndexBuffer(createBuffer(GPUBufferUsage.INDEX,is),`uint16`)
encoder.drawIndexed(is.length)
const bundle=encoder.finish(),descriptor={colorAttachments:[{loadOp:`clear`,storeOp:`store`,clearValue:[0,0,0,0]}]}
return(q0,q1)=>{
const q=new Float32Array(q0.length+q1.length)
q.set(q0)
q.set(q1,q0.length)
configuration.device.queue.writeBuffer(uniformBuffer,0,q)
descriptor.colorAttachments[0].view=this.getCurrentTexture().createView()
const encoder=configuration.device.createCommandEncoder(),pass=encoder.beginRenderPass(descriptor)
pass.executeBundles([bundle])
pass.end()
configuration.device.queue.submit([encoder.finish()])
}
}
addEventListener(`DOMContentLoaded`,async()=>{
let size,clientX,clientY
const render=(q0,q1)=>renderers.forEach(c=>c(q0,q1))
,canvas=document.querySelector(`body>:nth-child(1)`),[webGLCanvas,webGL2Canvas,webGPUCanvas]=canvas.querySelectorAll(`dl>dd>canvas`)
,sensitivity=document.querySelector(`body>:nth-child(2)>dd>input[type=number]`),order=document.querySelector(`body>:nth-child(2)>dd>select`)
,[webGLContext,webGL2Context]=[webGLCanvas.getContext(`webgl`),webGL2Canvas.getContext(`webgl2`)]
,p=new Float32Array([
[
-1,-1,-1
, 1,-1,-1
,-1, 1,-1
,-1,-1, 1
, 1, 1,-1
, 1,-1, 1
,-1, 1, 1
, 1, 1, 1
].map(v=>v/2)
,[
1, 0, 0
, 0, 1, 0
, 0, 0, 1
, 0, 0,-1
, 0,-1, 0
,-1, 0, 0
]
].flat())
,is=new Uint16Array([
[
0,1,1,4
,7,6,6,3
,0,2,2,6
,7,5,5,1
,0,3,3,5
,7,4,4,2
]
,[
0,1,1,2,2,5
,0,4,4,3,3,5
,0,2,2,4,4,5
,0,3,3,1,1,5
].map(v=>8+v)
].flat())
,renderers=[webGLContext.getRenderer(p,is,1,[1,0,0,1]),webGL2Context.getRenderer(p,is,1,[0,1,0,1])]
,identity=new Float32Array([0,0,0,1])
if(navigator.gpu){
const adapter=await navigator.gpu.requestAdapter()
if(adapter){
const device=await adapter.requestDevice()
if(device){
const webGPUContext=webGPUCanvas.getContext(`webgpu`)
webGPUContext.configure({device:device,format:navigator.gpu.getPreferredCanvasFormat(),alphaMode:`premultiplied`})
renderers.push(webGPUContext.getRenderer(p,is,`line-list`,[0,0,1,1]))
}else webGPUCanvas.parentElement.textContent=`the device request failed.`
}else webGPUCanvas.parentElement.textContent=`the adapter request failed.`
}else webGPUCanvas.parentElement.textContent=`available in secure contexts.`
canvas.addEventListener(`pointerup`,event=>(clientX=clientY=null)||canvas.releasePointerCapture(event.pointerId)||render(new Float32Array([0,0,0,1]),new Float32Array([0,0,0,1])))
canvas.addEventListener(`pointerdown`,event=>{
canvas.setPointerCapture(event.pointerId)
clientX=event.clientX
clientY=event.clientY
})
canvas.addEventListener(`pointermove`,event=>{
if(!clientX&&!clientY)return
let x,y
const v=Number(sensitivity.value)
x=(event.clientX-clientX)*v/2
y=(event.clientY-clientY)*v/2
x=new Float32Array([0,Math.sin(x),0,Math.cos(x)])
y=new Float32Array([Math.sin(y),0,0,Math.cos(y)])
render(...Number(order.value)?[x,y]:[y,x])
})
onresize=()=>{
if(webGLCanvas.clientWidth===size)return
size=webGLCanvas.clientWidth
webGPUCanvas.height=webGPUCanvas.width=webGL2Canvas.height=webGL2Canvas.width=webGLCanvas.height=webGLCanvas.width=size
webGLContext.viewport(0,0,size,size)
webGL2Context.viewport(0,0,size,size)
render(identity,identity)
}
onresize()
},{once:true})
</script>
</head>
<body>
<dl>
<dl><dt>WebGL</dt><dd><canvas></canvas></dd></dl>
<dl><dt>WebGL2</dt><dd><canvas></canvas></dd></dl>
<dl><dt>WebGPU</dt><dd><canvas></canvas></dd></dl>
</dl>
<dl>
<dt>sensitivity</dt><dd><input type='number' step='.01' min='0' value='.05'></dd>
<dt>order</dt><dd><select><option value='0'>false</option><option value='1'>true</option></select></dd>
</dl>
</body>
</html>
<!doctype html><html><head><meta charset='utf-8'><meta name='viewport' content='width=device-width'><style>
body{
*{background-color:inherit;color:inherit}
hr{background-color:var(--color);color:var(--background-color)}
--background-color:#ffffffff;--color:#000000ff;background-color:var(--background-color);color:var(--color)
}
@media(prefers-color-scheme:dark){body{--background-color:#000000ff;--color:#ffffffff}}
</style>
<style>
html{
*{margin:0;padding:0}
>body{
*{font-family:monospace;box-sizing:border-box;overflow-wrap:anywhere;vertical-align:bottom}
textarea{resize:none}
>dl,>textarea{overflow-y:auto}
>dl{
>dl{
>dd{margin-inline-start:.5rem}
display:grid;grid-template-columns:auto 1fr;margin-bottom:.5rem
}
>dd{
>span{display:inline-block;border:1px solid;text-align:start}
&.end{text-align:end}
white-space:pre-wrap
}
}
>hr{padding:1px;border:1rem solid;cursor:row-resize}
>textarea{width:100%;border:none;outline:none}
height:100dvh;display:grid;grid-template-rows:var(--grid-template-rows,1fr auto 1fr);touch-action:none
}
}
</style>
<script>
addEventListener(`DOMContentLoaded`,()=>{
let row,clientY,v,offset=0
const clamp=v=>row<(0>v?-v:v)?0>v?-row:row:v
,send=(v,c)=>{
const dd=document.createElement(`dd`),span=document.createElement(`span`)
span.textContent=v
dd.append(span)
if(c)c(dd)
dl.append(dd)
dd.scrollIntoView({behavior:`smooth`})
}
,connection=new RTCPeerConnection(),channels=[0,1].map(v=>connection.createDataChannel(``,{negotiated:true,id:v}))
,dl=document.querySelector(`body>dl`),state=dl.querySelector(`dl>dd`),hr=document.querySelector(`body>hr`),textarea=document.querySelector(`body>textarea`)
channels[0].addEventListener(`message`,event=>send(event.data)||channels[1].send(event.data))
channels[1].addEventListener(`message`,event=>send(event.data,e=>e.setAttribute(`class`,`end`)))
connection.addEventListener(`icegatheringstatechange`,()=>{
state.textContent=connection.iceGatheringState
if(`complete`!==connection.iceGatheringState)return
textarea.value=JSON.stringify(connection.localDescription)
textarea.select()
})
connection.addEventListener(`connectionstatechange`,()=>{
state.textContent=connection.connectionState
if(`connected`===connection.connectionState)textarea.value=``
})
dl.addEventListener(`pointerdown`,async event=>{
event.preventDefault()
if(`connected`===connection.connectionState){
if(!textarea.value||`open`!==channels[0].readyState)return
channels[0].send(textarea.value)
textarea.value=``
}else if([`new`,`connecting`].includes(connection.connectionState))try{
if(!textarea.value)throw Error()
let b=0
const sdp=JSON.parse(textarea.value)
if(![`offer`,`answer`].includes(sdp.type))throw Error()
if(!connection.localDescription&&`new`===connection.connectionState&&`offer`===sdp.type)b|=0b01
if(connection.localDescription&&`connecting`===connection.connectionState&&`answer`===sdp.type)b|=0b10
if(!b)throw Error()
await connection.setRemoteDescription(sdp)
if(0b01&b)await connection.setLocalDescription(await connection.createAnswer())
}catch{
await connection.setLocalDescription(await connection.createOffer())
}
})
hr.addEventListener(`pointerup`,event=>(clientY=null)||hr.releasePointerCapture(event.pointerId)||(offset=v))
hr.addEventListener(`pointerdown`,event=>hr.setPointerCapture(event.pointerId)||(clientY=event.clientY))
hr.addEventListener(`pointermove`,event=>clientY&&((v=clamp(event.clientY-clientY+offset))?document.body.style.setProperty(`--grid-template-rows`,`${0>v?(row+v)/(row-v):1}fr auto ${0>v?1:(row-v)/(row+v)}fr`):document.body.removeAttribute(`style`)))
onresize=()=>{
row=(document.body.getBoundingClientRect().height-hr.getBoundingClientRect().height)/2
offset=clamp(offset)
}
onresize()
},{once:true})
</script>
</head>
<body>
<dl><dl><dt>state</dt><dd></dd></dl></dl>
<hr>
<textarea autofocus></textarea>
</body>
</html>
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。