moved over files [from xrfragment-haxe repo]
This commit is contained in:
		
						commit
						b9c9cc6ea7
					
				
					 49 changed files with 905401 additions and 0 deletions
				
			
		
							
								
								
									
										1
									
								
								.domains
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								.domains
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1 @@
 | 
				
			||||||
 | 
					xrfragment.org
 | 
				
			||||||
							
								
								
									
										32
									
								
								.env
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								.env
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,32 @@
 | 
				
			||||||
 | 
					which nix && test -z "$NIX_SHELL_XRF" && {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  # automatically mirror main between forgejo<->codeberg
 | 
				
			||||||
 | 
					  #git(){
 | 
				
			||||||
 | 
					  #  set -x
 | 
				
			||||||
 | 
					  #  test $1 = "push" && test $3 = main && mirror=1 
 | 
				
			||||||
 | 
					  #  $(which git) "$@"
 | 
				
			||||||
 | 
					  #  test -n "$mirror" && {
 | 
				
			||||||
 | 
					  #    set -x
 | 
				
			||||||
 | 
					  #    shift ; shift # remove first to args
 | 
				
			||||||
 | 
					  #    $(which git) push codeberg "$@" 
 | 
				
			||||||
 | 
					  #  }
 | 
				
			||||||
 | 
					  #  set +x
 | 
				
			||||||
 | 
					  #}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  echo '[i] installing nix-shell' && nix-shell 
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					test "$GITEA_ROOT_URL" = "https://forgejo.isvery.ninja/" && {
 | 
				
			||||||
 | 
					  # on the website xrfragment.org copy examples to root-dir 
 | 
				
			||||||
 | 
					  # (so https://xrfragment.org/index.glb can be requested remotely)
 | 
				
			||||||
 | 
					  # because that really emphasizes a nice WebXR experience-at-website-root paradigm
 | 
				
			||||||
 | 
					  wget "https://raw.codeberg.page/coderofsalvation/xrfragment-haxe/example/assets/index.glb"
 | 
				
			||||||
 | 
					  test -d dist && rm -rf dist 
 | 
				
			||||||
 | 
					  mkdir dist
 | 
				
			||||||
 | 
					  wget "https://raw.codeberg.page/coderofsalvation/xrfragment-haxe/dist/xrfragment.aframe.all.js"
 | 
				
			||||||
 | 
					  wget "https://raw.codeberg.page/coderofsalvation/xrfragment-haxe/dist/xrfragment.aframe.js"
 | 
				
			||||||
 | 
					  wget "https://raw.codeberg.page/coderofsalvation/xrfragment-haxe/dist/xrfragment.js"
 | 
				
			||||||
 | 
					  wget "https://raw.codeberg.page/coderofsalvation/xrfragment-haxe/dist/xrfragment.module.js"
 | 
				
			||||||
 | 
					  wget "https://raw.codeberg.page/coderofsalvation/xrfragment-haxe/dist/xrfragment.three.js"
 | 
				
			||||||
 | 
					  wget "https://raw.codeberg.page/coderofsalvation/xrfragment-haxe/dist/xrfragment.three.module.js"
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										
											BIN
										
									
								
								assets/library/assets.webp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								assets/library/assets.webp
									
										
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| 
		 After Width: | Height: | Size: 88 KiB  | 
							
								
								
									
										61
									
								
								assets/library/datapackage.json
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										61
									
								
								assets/library/datapackage.json
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,61 @@
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					  "$schema": "https://manyfold.app/profiles/0.0/datapackage.json",
 | 
				
			||||||
 | 
					  "name": "xr-fragments-assets",
 | 
				
			||||||
 | 
					  "title": "XR Fragments assets",
 | 
				
			||||||
 | 
					  "homepage": "http://xrfragment.org",
 | 
				
			||||||
 | 
					  "image": "assets.webp",
 | 
				
			||||||
 | 
					  "keywords": [
 | 
				
			||||||
 | 
					    "!new",
 | 
				
			||||||
 | 
					    "fontmap"
 | 
				
			||||||
 | 
					  ],
 | 
				
			||||||
 | 
					  "licenses": [
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					      "name": "CC-BY-SA-4.0",
 | 
				
			||||||
 | 
					      "path": "https://spdx.org/licenses/CC-BY-SA-4.0.html"
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  ],
 | 
				
			||||||
 | 
					  "resources": [
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					      "name": "assets",
 | 
				
			||||||
 | 
					      "path": "assets.webp",
 | 
				
			||||||
 | 
					      "mediatype": "image/webp",
 | 
				
			||||||
 | 
					      "up": "+z",
 | 
				
			||||||
 | 
					      "presupported": false
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					      "name": "fontmap",
 | 
				
			||||||
 | 
					      "path": "fontmap.glb",
 | 
				
			||||||
 | 
					      "mediatype": "model/gltf",
 | 
				
			||||||
 | 
					      "up": "+z",
 | 
				
			||||||
 | 
					      "presupported": false
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					      "name": "tree",
 | 
				
			||||||
 | 
					      "path": "tree.glb",
 | 
				
			||||||
 | 
					      "mediatype": "model/gltf",
 | 
				
			||||||
 | 
					      "up": "+z",
 | 
				
			||||||
 | 
					      "presupported": false
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  ],
 | 
				
			||||||
 | 
					  "sensitive": false,
 | 
				
			||||||
 | 
					  "contributors": [
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					      "title": "xrfragments",
 | 
				
			||||||
 | 
					      "path": "http://localhost:3214/creators/xrfragments",
 | 
				
			||||||
 | 
					      "roles": [
 | 
				
			||||||
 | 
					        "creator"
 | 
				
			||||||
 | 
					      ],
 | 
				
			||||||
 | 
					      "caption": "",
 | 
				
			||||||
 | 
					      "description": "XR Fragments is an open specification for hyperlinking & deeplinking 3D fileformats .\nTurn 3D files into linkable AR/VR websites .\n3D files with XR Fragments enable interoperable, networkable and interactions via so-called extras and promote URL standards .",
 | 
				
			||||||
 | 
					      "links": [
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					          "path": "https://xrfragment.org"
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      ]
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  ],
 | 
				
			||||||
 | 
					  "collections": [
 | 
				
			||||||
 | 
					  ],
 | 
				
			||||||
 | 
					  "links": [
 | 
				
			||||||
 | 
					  ]
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										
											BIN
										
									
								
								assets/library/fontmap.glb
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								assets/library/fontmap.glb
									
										
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								assets/library/glass.glb
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								assets/library/glass.glb
									
										
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								assets/library/lakes.glb
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								assets/library/lakes.glb
									
										
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								assets/library/tree.glb
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								assets/library/tree.glb
									
										
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										54
									
								
								assets/template/datapackage.json
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										54
									
								
								assets/template/datapackage.json
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,54 @@
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					  "$schema": "https://manyfold.app/profiles/0.0/datapackage.json",
 | 
				
			||||||
 | 
					  "name": "xr-fragments-template-website",
 | 
				
			||||||
 | 
					  "title": "Spatial website template",
 | 
				
			||||||
 | 
					  "homepage": "http://xrfragment.org",
 | 
				
			||||||
 | 
					  "image": "website.jpg",
 | 
				
			||||||
 | 
					  "keywords": [
 | 
				
			||||||
 | 
					    "!new",
 | 
				
			||||||
 | 
					    "template"
 | 
				
			||||||
 | 
					  ],
 | 
				
			||||||
 | 
					  "licenses": [
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					      "name": "CC-BY-SA-4.0",
 | 
				
			||||||
 | 
					      "path": "https://spdx.org/licenses/CC-BY-SA-4.0.html"
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  ],
 | 
				
			||||||
 | 
					  "resources": [
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					      "name": "website",
 | 
				
			||||||
 | 
					      "path": "website.jpg",
 | 
				
			||||||
 | 
					      "mediatype": "image/jpeg",
 | 
				
			||||||
 | 
					      "up": "+z",
 | 
				
			||||||
 | 
					      "presupported": false
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					      "name": "website",
 | 
				
			||||||
 | 
					      "path": "website.glb",
 | 
				
			||||||
 | 
					      "mediatype": "model/gltf",
 | 
				
			||||||
 | 
					      "up": "+z",
 | 
				
			||||||
 | 
					      "presupported": false
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  ],
 | 
				
			||||||
 | 
					  "sensitive": false,
 | 
				
			||||||
 | 
					  "contributors": [
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					      "title": "xrfragments",
 | 
				
			||||||
 | 
					      "path": "http://localhost:3214/creators/xrfragments",
 | 
				
			||||||
 | 
					      "roles": [
 | 
				
			||||||
 | 
					        "creator"
 | 
				
			||||||
 | 
					      ],
 | 
				
			||||||
 | 
					      "caption": "",
 | 
				
			||||||
 | 
					      "description": "XR Fragments is an open specification for hyperlinking & deeplinking 3D fileformats .\nTurn 3D files into linkable AR/VR websites .\n3D files with XR Fragments enable interoperable, networkable and interactions via so-called extras and promote URL standards .",
 | 
				
			||||||
 | 
					      "links": [
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					          "path": "https://xrfragment.org"
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      ]
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  ],
 | 
				
			||||||
 | 
					  "collections": [
 | 
				
			||||||
 | 
					  ],
 | 
				
			||||||
 | 
					  "links": [
 | 
				
			||||||
 | 
					  ]
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										
											BIN
										
									
								
								assets/template/explainer.xcf
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								assets/template/explainer.xcf
									
										
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								assets/template/website.glb
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								assets/template/website.glb
									
										
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								assets/template/website.jpg
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								assets/template/website.jpg
									
										
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| 
		 After Width: | Height: | Size: 56 KiB  | 
							
								
								
									
										7703
									
								
								dist/xrfragment.aframe.all.js
									
										
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										7703
									
								
								dist/xrfragment.aframe.all.js
									
										
									
									
										vendored
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
							
								
								
									
										7701
									
								
								dist/xrfragment.aframe.js
									
										
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										7701
									
								
								dist/xrfragment.aframe.js
									
										
									
									
										vendored
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
							
								
								
									
										3241
									
								
								dist/xrfragment.js
									
										
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										3241
									
								
								dist/xrfragment.js
									
										
									
									
										vendored
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load diff
											
										
									
								
							
							
								
								
									
										3161
									
								
								dist/xrfragment.lua
									
										
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										3161
									
								
								dist/xrfragment.lua
									
										
									
									
										vendored
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load diff
											
										
									
								
							
							
								
								
									
										7798
									
								
								dist/xrfragment.model-viewer.js
									
										
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										7798
									
								
								dist/xrfragment.model-viewer.js
									
										
									
									
										vendored
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
							
								
								
									
										807912
									
								
								dist/xrfragment.module.js
									
										
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										807912
									
								
								dist/xrfragment.module.js
									
										
									
									
										vendored
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load diff
											
										
									
								
							
							
								
								
									
										268
									
								
								dist/xrfragment.plugin.editor.js
									
										
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										268
									
								
								dist/xrfragment.plugin.editor.js
									
										
									
									
										vendored
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,268 @@
 | 
				
			||||||
 | 
					/*
 | 
				
			||||||
 | 
					 * v0.5.1 generated at Thu May 22 07:30:49 PM CEST 2025
 | 
				
			||||||
 | 
					 * https://xrfragment.org
 | 
				
			||||||
 | 
					 * SPDX-License-Identifier: AGPL-3.0-or-later
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					// reactive component for displaying the menu 
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					$editorPopup = (el) => new Proxy({
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  html: (opts) => `
 | 
				
			||||||
 | 
					    <div>
 | 
				
			||||||
 | 
					      <b>#${$editor.selected.name}</b>
 | 
				
			||||||
 | 
					      <table class="editorPopup">
 | 
				
			||||||
 | 
					        <tbody>
 | 
				
			||||||
 | 
					          <tr>
 | 
				
			||||||
 | 
					            <td><b class="badge">href</a></td>
 | 
				
			||||||
 | 
					            <td>
 | 
				
			||||||
 | 
					              <input type="text" id="href" placeholder="https://foo.com" maxlength="255" list="objects" 
 | 
				
			||||||
 | 
					                     onkeydown="document.querySelector('#editActions').classList.add('show')" 
 | 
				
			||||||
 | 
					                     onkeyup="$editor.selected.edited = $editor.selected.userData.href = this.value" 
 | 
				
			||||||
 | 
					                     value="${$editor.selected.userData.href||''}" />
 | 
				
			||||||
 | 
					            </td>
 | 
				
			||||||
 | 
					          </tr>
 | 
				
			||||||
 | 
					          <tr>
 | 
				
			||||||
 | 
					            <td><b class="badge">src</a></td>
 | 
				
			||||||
 | 
					            <td>
 | 
				
			||||||
 | 
					              <input type="text" id="src" placeholder="https://foo.com" maxlength="255" list="objects"
 | 
				
			||||||
 | 
					                     onkeydown="document.querySelector('#editActions').classList.add('show')" 
 | 
				
			||||||
 | 
					                     onkeyup="$editor.selected.edited = $editor.selected.userData.src = this.value" 
 | 
				
			||||||
 | 
					                     value="${$editor.selected.userData.src||''}" />
 | 
				
			||||||
 | 
					            </td>
 | 
				
			||||||
 | 
					          </tr>
 | 
				
			||||||
 | 
					          <tr>
 | 
				
			||||||
 | 
					            <td><b class="badge">tag</a></td>
 | 
				
			||||||
 | 
					            <td>
 | 
				
			||||||
 | 
					              <input type="text" id="tag" placeholder="foo bar" maxlength="255" 
 | 
				
			||||||
 | 
					                     onkeydown="document.querySelector('#editActions').classList.add('show')" 
 | 
				
			||||||
 | 
					                     onkeyup="$editor.selected.edited = $editor.selected.userData.tag = this.value" 
 | 
				
			||||||
 | 
					                     value="${$editor.selected.userData.tag||''}" />
 | 
				
			||||||
 | 
					            </td>
 | 
				
			||||||
 | 
					          </tr>
 | 
				
			||||||
 | 
					        </tbody>
 | 
				
			||||||
 | 
					      </table>
 | 
				
			||||||
 | 
					      <datalist id="objects">
 | 
				
			||||||
 | 
					        <option>https://xrfragment.org/index.glb#pos=start</option>
 | 
				
			||||||
 | 
					        <option>
 | 
				
			||||||
 | 
					        ${opts.objectNames.join('</option><option>')}
 | 
				
			||||||
 | 
					        </option>
 | 
				
			||||||
 | 
					      </datalist>
 | 
				
			||||||
 | 
					      <br>
 | 
				
			||||||
 | 
					      <div id="editActions">
 | 
				
			||||||
 | 
					        <button class="download" onclick="$editor.export()"><i class="gg-software-download"></i>    download scene file</button> 
 | 
				
			||||||
 | 
					        <br>
 | 
				
			||||||
 | 
					        NOTE: updates to src-values will require reloading the scene 
 | 
				
			||||||
 | 
					      </div>
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					    <style type="text/css">
 | 
				
			||||||
 | 
					      table.editorPopup input{
 | 
				
			||||||
 | 
					        min-width:200px;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      table.editorPopup tr td:nth-child(1){
 | 
				
			||||||
 | 
					        text-align:left;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      #editActions{
 | 
				
			||||||
 | 
					        visibility:hidden;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      #editActions.show{
 | 
				
			||||||
 | 
					        visibility:visible;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    </style>
 | 
				
			||||||
 | 
					  `,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  init(opts){
 | 
				
			||||||
 | 
					    el.innerHTML = this.html(opts)
 | 
				
			||||||
 | 
					    return (this.el = el)
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					},{
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  get(me,k,v){ return me[k] },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  set(me,k,v){ 
 | 
				
			||||||
 | 
					    me[k] = v    
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					$editor = (el,opts) => new Proxy({
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  html: `
 | 
				
			||||||
 | 
					    <div style="position:absolute; width:100%; text-align:right; right:166px;">
 | 
				
			||||||
 | 
					      <button class="btn edit-btn">
 | 
				
			||||||
 | 
					        <i class="gg-pen"></i>
 | 
				
			||||||
 | 
					      </button>
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					    <style type="text/css">
 | 
				
			||||||
 | 
					      .xrf button.edit-btn{
 | 
				
			||||||
 | 
					        height: 32px;
 | 
				
			||||||
 | 
					        width: 30px;
 | 
				
			||||||
 | 
					        margin-top: 7px;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      .edit-btn.enabled,
 | 
				
			||||||
 | 
					      .edit-btn.enabled:hover{
 | 
				
			||||||
 | 
					        background:black;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      .edit-btn i.gg-pen{
 | 
				
			||||||
 | 
					         margin-top: -26px;
 | 
				
			||||||
 | 
					         margin-left: 4px;
 | 
				
			||||||
 | 
					         width: 10px;
 | 
				
			||||||
 | 
					         color: var(--xrf-white); 
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    </style>
 | 
				
			||||||
 | 
					  `,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  selecting: false,
 | 
				
			||||||
 | 
					  editing: false,
 | 
				
			||||||
 | 
					  helper: null,
 | 
				
			||||||
 | 
					  selected: null,
 | 
				
			||||||
 | 
					  objectNames: [],
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  init(opts){
 | 
				
			||||||
 | 
					    el.innerHTML = this.html
 | 
				
			||||||
 | 
					    window.frontend.el.querySelector('#topbar').appendChild(el);
 | 
				
			||||||
 | 
					    el.querySelector('.edit-btn').addEventListener('click', () =>  {
 | 
				
			||||||
 | 
					      if( $editor.selecting || $editor.editing ) this.reset()
 | 
				
			||||||
 | 
					      else{
 | 
				
			||||||
 | 
					        $editor.selecting = true 
 | 
				
			||||||
 | 
					        $editor.editing   = false 
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    xrf.addEventListener('export', (e) => this.updateOriginalScene(e) )
 | 
				
			||||||
 | 
					    xrf.addEventListener('href', (opts) => {
 | 
				
			||||||
 | 
					      if( $editor.selecting || $editor.editing ) return opts.promise().reject("$editor should block hrefs while editing") // never resolve (block hrefs from interfering)
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					    return this
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  reset(){
 | 
				
			||||||
 | 
					    if( this.helper) xrf.scene.remove(this.helper)
 | 
				
			||||||
 | 
					    $editor.selecting = false
 | 
				
			||||||
 | 
					    $editor.editing = false
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  export(){
 | 
				
			||||||
 | 
					    window.frontend.download()
 | 
				
			||||||
 | 
					    this.reset()
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  editNode(){
 | 
				
			||||||
 | 
					    if( !this.selecting ) return console.log("not editing")
 | 
				
			||||||
 | 
					    this.reset()
 | 
				
			||||||
 | 
					    $editor.editing = true
 | 
				
			||||||
 | 
					    this.collectObjects()
 | 
				
			||||||
 | 
					      //`<b>XR Fragment:</b> #${this.selected.name}<br><br>${this.getMetaData(this.selected)}`),{
 | 
				
			||||||
 | 
					    notify( $editorPopup( document.createElement('div') ).init(this) , {
 | 
				
			||||||
 | 
					      timeout:false,
 | 
				
			||||||
 | 
					      onclose: () => this.reset()
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  collectObjects(){
 | 
				
			||||||
 | 
					    this.objectNames = []
 | 
				
			||||||
 | 
					    const escape = (str) => {
 | 
				
			||||||
 | 
					      let d = document.createElement('div')
 | 
				
			||||||
 | 
					      d.innerText = str
 | 
				
			||||||
 | 
					      return d.innerHTML
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    xrf.scene.traverse( (n) => {
 | 
				
			||||||
 | 
					      if( n.userData && n.userData.href ){
 | 
				
			||||||
 | 
					        this.objectNames.push( escape(n.userData.href) )
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					    xrf.scene.traverse( (n) => {
 | 
				
			||||||
 | 
					      if( n.name ) this.objectNames.push( escape('#'+n.name) )
 | 
				
			||||||
 | 
					    }) 
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  initEdit(scene){
 | 
				
			||||||
 | 
					    if( !this.listenersInstalled ){
 | 
				
			||||||
 | 
					      AFRAME.scenes[0].addEventListener('click', () => this.editNode() )
 | 
				
			||||||
 | 
					      this.listenersInstalled = true
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    scene.traverse( (n) => {
 | 
				
			||||||
 | 
					      let highlight = (n) => (e) => {
 | 
				
			||||||
 | 
					        if( !this.selecting || this.editing ) return // do nothing
 | 
				
			||||||
 | 
					        if( this.helper){
 | 
				
			||||||
 | 
					          if( this.helper.selected == n.uuid ) return // already selected
 | 
				
			||||||
 | 
					          xrf.scene.remove(this.helper)
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        this.selected = n
 | 
				
			||||||
 | 
					        this.helper = new THREE.BoxHelper( n, 0xFF00FF )
 | 
				
			||||||
 | 
					        this.helper.material.linewidth = 4
 | 
				
			||||||
 | 
					        this.helper.material.color     = xrf.focusLine.material.color
 | 
				
			||||||
 | 
					        this.helper.material.dashSize  = xrf.focusLine.material.dashSize
 | 
				
			||||||
 | 
					        this.helper.material.gapSize   = xrf.focusLine.material.gapSize  
 | 
				
			||||||
 | 
					        this.helper.selected = n.uuid
 | 
				
			||||||
 | 
					        xrf.scene.add(this.helper)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        let div = document.createElement('div')
 | 
				
			||||||
 | 
					        notify(`<b>#${n.name}</b><br>${this.getMetaData(this.selected)}`)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      if( n.material ) n.addEventListener('mousemove', n.highlightOnMouseMove = highlight(n) )
 | 
				
			||||||
 | 
					    }) 
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  getMetaData(n){
 | 
				
			||||||
 | 
					    let html = `${n.userData.href ? `<b class="badge">href</b>${n.userData.href}<br>`:''}`
 | 
				
			||||||
 | 
					    html    += `${n.userData.src  ? `<b class="badge">src</b>${n.userData.src}<br>`  :''}` 
 | 
				
			||||||
 | 
					    html    += `${n.userData.tag  ? `<b class="badge">tag</b>${n.userData.tag}<br>`  :''}` 
 | 
				
			||||||
 | 
					    return html
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  updateOriginalScene(e){
 | 
				
			||||||
 | 
					    const {scene,ext} = e
 | 
				
			||||||
 | 
					    scene.traverse( (n) => {
 | 
				
			||||||
 | 
					      if( !n.name ) return
 | 
				
			||||||
 | 
					      // overwrite node with modified userData from scene
 | 
				
			||||||
 | 
					      let o = xrf.scene.getObjectByName(n.name)
 | 
				
			||||||
 | 
					      if( o && o.edited ){
 | 
				
			||||||
 | 
					        for( let i in o.userData ) n.userData[i] = o.userData[i]
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					},
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  get(me,k,v){ return me[k] },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  set(me,k,v){ 
 | 
				
			||||||
 | 
					    me[k] = v    
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    switch( k ){
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      case "selecting":{ 
 | 
				
			||||||
 | 
					          lookctl = $('[look-controls]').components['look-controls']
 | 
				
			||||||
 | 
					          if( v ){
 | 
				
			||||||
 | 
					            lookctl.pause() // prevent click-conflict
 | 
				
			||||||
 | 
					            notify("click an object to reveal XR Fragment metadata")
 | 
				
			||||||
 | 
					            xrf.interactive.raycastAll = true
 | 
				
			||||||
 | 
					            me.initEdit(xrf.scene)
 | 
				
			||||||
 | 
					            lookctl.pause() // prevent click-conflict
 | 
				
			||||||
 | 
					            el.querySelector('.edit-btn').classList.add(['enabled'])
 | 
				
			||||||
 | 
					          }else{
 | 
				
			||||||
 | 
					            lookctl.pause() // prevent click-conflict
 | 
				
			||||||
 | 
					            xrf.scene.traverse( (n) => {
 | 
				
			||||||
 | 
					              if( n.highlightOnMouseMove ){
 | 
				
			||||||
 | 
					               n.removeEventListener( 'mousemove', n.highlightOnMouseMove )    
 | 
				
			||||||
 | 
					              }
 | 
				
			||||||
 | 
					            })
 | 
				
			||||||
 | 
					            lookctl.play() // prevent click-conflict (resume)
 | 
				
			||||||
 | 
					            el.querySelector('.edit-btn').classList.remove(['enabled'])
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					          break;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// reactify component!
 | 
				
			||||||
 | 
					document.addEventListener('frontend:ready', (e) => {
 | 
				
			||||||
 | 
					  window.$editor = $editor( document.createElement('div') ).init(e.detail)
 | 
				
			||||||
 | 
					})
 | 
				
			||||||
							
								
								
									
										932
									
								
								dist/xrfragment.plugin.frontend.css.js
									
										
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										932
									
								
								dist/xrfragment.plugin.frontend.css.js
									
										
									
									
										vendored
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,932 @@
 | 
				
			||||||
 | 
					/*
 | 
				
			||||||
 | 
					 * v0.5.1 generated at Thu May 22 07:30:49 PM CEST 2025
 | 
				
			||||||
 | 
					 * https://xrfragment.org
 | 
				
			||||||
 | 
					 * SPDX-License-Identifier: AGPL-3.0-or-later
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					document.head.innerHTML += `
 | 
				
			||||||
 | 
					  <style type="text/css">
 | 
				
			||||||
 | 
					    :root {
 | 
				
			||||||
 | 
					        --xrf-primary: #6839dc;
 | 
				
			||||||
 | 
					        --xrf-primary-fg: #FFF;
 | 
				
			||||||
 | 
					        --xrf-light-primary: #ea23cf;
 | 
				
			||||||
 | 
					        --xrf-secondary: #872eff;
 | 
				
			||||||
 | 
					        --xrf-light-xrf-secondary: #ce7df2;
 | 
				
			||||||
 | 
					        --xrf-topbar-bg: #fffb;
 | 
				
			||||||
 | 
					        --xrf-box-shadow: #0005;
 | 
				
			||||||
 | 
					        --xrf-red: red;
 | 
				
			||||||
 | 
					        --xrf-dark-gray: #343334;
 | 
				
			||||||
 | 
					        --xrf-gray: #424280;
 | 
				
			||||||
 | 
					        --xrf-white: #fdfdfd;
 | 
				
			||||||
 | 
					        --xrf-light-gray: #efefef;
 | 
				
			||||||
 | 
					        --xrf-lighter-gray: #e4e2fb96;
 | 
				
			||||||
 | 
					        --xrf-font-sans-serif: system-ui, -apple-system, segoe ui, roboto, ubuntu, helvetica, cantarell, noto sans, sans-serif;
 | 
				
			||||||
 | 
					        --xrf-font-monospace: menlo, monaco, lucida console, liberation mono, dejavu sans mono, bitstream vera sans mono, courier new, monospace, serif;
 | 
				
			||||||
 | 
					        --xrf-font-size-0: 12px;
 | 
				
			||||||
 | 
					        --xrf-font-size-1: 14px;
 | 
				
			||||||
 | 
					        --xrf-font-size-2: 17px;
 | 
				
			||||||
 | 
					        --xrf-font-size-3: 21px;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /* CSS reset */
 | 
				
			||||||
 | 
					    html{line-height:1.15;-webkit-text-size-adjust:100%}body{margin:0}main{display:block}h1{font-size:2em;margin:0.67em 0}hr{box-sizing:content-box;height:0;overflow:visible}pre{font-family:monospace, monospace;font-size:1em}a{background-color:transparent}abbr[title]{border-bottom:none;text-decoration:underline;text-decoration:underline dotted}b,strong{font-weight:bolder}code,kbd,samp{font-family:monospace, monospace;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-0.25em}sup{top:-0.5em}img{border-style:none}button,input,optgroup,select,textarea{font-family:inherit;font-size:100%;line-height:1.15;margin:0}button,input{overflow:visible}button,select{text-transform:none}[type="button"],[type="reset"],[type="submit"],button{-webkit-appearance:button}[type="button"]::-moz-focus-inner,[type="reset"]::-moz-focus-inner,[type="submit"]::-moz-focus-inner,button::-moz-focus-inner{border-style:none;padding:0}[type="button"]:-moz-focusring,[type="reset"]:-moz-focusring,[type="submit"]:-moz-focusring,button:-moz-focusring{outline:1px dotted ButtonText}fieldset{padding:0.35em 0.75em 0.625em}legend{box-sizing:border-box;color:inherit;display:table;max-width:100%;padding:0;white-space:normal}progress{vertical-align:baseline}textarea{overflow:auto}[type="checkbox"],[type="radio"]{box-sizing:border-box;padding:0}[type="number"]::-webkit-inner-spin-button,[type="number"]::-webkit-outer-spin-button{height:auto}[type="search"]{-webkit-appearance:textfield;outline-offset:-2px}[type="search"]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}details{display:block}summary{display:list-item}template{display:none}[hidden]{display:none}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    .xrf table tr td{
 | 
				
			||||||
 | 
					      vertical-align:top;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    .xrf button,
 | 
				
			||||||
 | 
					    .xrf input[type="submit"],
 | 
				
			||||||
 | 
					    .xrf .btn {
 | 
				
			||||||
 | 
					      text-decoration:none;
 | 
				
			||||||
 | 
					      background: var(--xrf-primary);
 | 
				
			||||||
 | 
					      border: 0;
 | 
				
			||||||
 | 
					      border-radius: 25px;
 | 
				
			||||||
 | 
					      padding: 11px 15px;
 | 
				
			||||||
 | 
					      font-weight: bold;  
 | 
				
			||||||
 | 
					      transition: 0.3s;
 | 
				
			||||||
 | 
					      height: 40px;
 | 
				
			||||||
 | 
					      font-size: var(--xrf-font-size-1);
 | 
				
			||||||
 | 
					      color: var(--xrf-primary-fg);
 | 
				
			||||||
 | 
					      line-height: var(--xrf-font-size-1);
 | 
				
			||||||
 | 
					      cursor:pointer;
 | 
				
			||||||
 | 
					      white-space:pre;
 | 
				
			||||||
 | 
					      min-width: 45px;
 | 
				
			||||||
 | 
					      box-shadow: 0px 0px 10px var(--xrf-box-shadow);
 | 
				
			||||||
 | 
					      display:inline-block;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    .xrf button:hover,
 | 
				
			||||||
 | 
					    .xrf input[type="submit"]:hover,
 | 
				
			||||||
 | 
					    .xrf .btn:hover {
 | 
				
			||||||
 | 
					      background: var(--xrf-secondary);
 | 
				
			||||||
 | 
					      text-decoration:none;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    .xrf, .xrf *{
 | 
				
			||||||
 | 
					      font-family: var(--xrf-font-sans-serif);
 | 
				
			||||||
 | 
					      font-size: var(--xrf-font-size-1);
 | 
				
			||||||
 | 
					      line-height:27px;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    textarea, select, input[type="text"] {
 | 
				
			||||||
 | 
					      background: transparent; /* linear-gradient( var(--xrf-lighter-gray), var(--xrf-gray) ) !important; */
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    input[type="submit"] {
 | 
				
			||||||
 | 
					      color: var(--xrf-light-gray);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    input[type=text]{
 | 
				
			||||||
 | 
					      padding:7px 15px;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    input{
 | 
				
			||||||
 | 
					      border-radius:7px;
 | 
				
			||||||
 | 
					      margin:5px 0px;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    .title {
 | 
				
			||||||
 | 
					      border-bottom: 2px solid var(--xrf-secondary);
 | 
				
			||||||
 | 
					      padding-bottom: 20px;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    #topbar{
 | 
				
			||||||
 | 
					      background: var(--xrf-topbar-bg);
 | 
				
			||||||
 | 
					      position: fixed;
 | 
				
			||||||
 | 
					      top: 0;
 | 
				
			||||||
 | 
					      left: 0;
 | 
				
			||||||
 | 
					      width: 100%;
 | 
				
			||||||
 | 
					      height: 48px;
 | 
				
			||||||
 | 
					      box-shadow: 0px 0px 10px var(--xrf-box-shadow);
 | 
				
			||||||
 | 
					      opacity: 0.9;
 | 
				
			||||||
 | 
					      z-index:2000;
 | 
				
			||||||
 | 
					      display:none;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    #topbar .logo{
 | 
				
			||||||
 | 
					      width: 92px;
 | 
				
			||||||
 | 
					      position: absolute;
 | 
				
			||||||
 | 
					      top: 9px;
 | 
				
			||||||
 | 
					      left: 93px;
 | 
				
			||||||
 | 
					      height: 30px;
 | 
				
			||||||
 | 
					      background-size: contain;
 | 
				
			||||||
 | 
					      background-repeat: no-repeat;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    #topbar > input[type="submit"] {
 | 
				
			||||||
 | 
					      height: 32px;
 | 
				
			||||||
 | 
					      position: absolute;
 | 
				
			||||||
 | 
					      right: 20px;
 | 
				
			||||||
 | 
					      top: 2px;
 | 
				
			||||||
 | 
					      min-width:135px;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    #topbar > button#navback,
 | 
				
			||||||
 | 
					    #topbar > button#navforward {
 | 
				
			||||||
 | 
					      height: 32px;
 | 
				
			||||||
 | 
					      font-size: 24px;
 | 
				
			||||||
 | 
					      position: absolute;
 | 
				
			||||||
 | 
					      left: 9px;
 | 
				
			||||||
 | 
					      padding: 2px 13px;
 | 
				
			||||||
 | 
					      border-radius:6px;
 | 
				
			||||||
 | 
					      top: 8px;
 | 
				
			||||||
 | 
					      color: var(--xrf-light-gray);
 | 
				
			||||||
 | 
					      width: 36px;
 | 
				
			||||||
 | 
					      min-width: unset;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    #topbar > button#navforward {
 | 
				
			||||||
 | 
					      left:49px;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    #topbar > #uri {
 | 
				
			||||||
 | 
					        height: 18px;
 | 
				
			||||||
 | 
					        font-size: var(--xrf-font-size-3);
 | 
				
			||||||
 | 
					        position: absolute;
 | 
				
			||||||
 | 
					        left: 200px;
 | 
				
			||||||
 | 
					        top: 9px;
 | 
				
			||||||
 | 
					        max-width: 550px;
 | 
				
			||||||
 | 
					        padding: 5px 0px 5px 5px;
 | 
				
			||||||
 | 
					        width: calc( 63% - 200px);
 | 
				
			||||||
 | 
					        background: #f0f0f0;
 | 
				
			||||||
 | 
					        border-color: #Ccc;
 | 
				
			||||||
 | 
					        border: 2px solid #CCC;
 | 
				
			||||||
 | 
					        border-radius: 7px;
 | 
				
			||||||
 | 
					        color: #555;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    .footer > .menu .btn{
 | 
				
			||||||
 | 
					      display:inline-block;
 | 
				
			||||||
 | 
					      background: var(--xrf-primary);
 | 
				
			||||||
 | 
					      border-radius: 25px;
 | 
				
			||||||
 | 
					      border: 0;
 | 
				
			||||||
 | 
					      padding: 5px 19px;
 | 
				
			||||||
 | 
					      font-weight: 1000;
 | 
				
			||||||
 | 
					      font-family: sans-serif;
 | 
				
			||||||
 | 
					      font-size: var(--xrf-font-size-2);
 | 
				
			||||||
 | 
					      color:var(--xrf-primary-fg);
 | 
				
			||||||
 | 
					      height:33px;
 | 
				
			||||||
 | 
					      z-index:2000;
 | 
				
			||||||
 | 
					      cursor:pointer;
 | 
				
			||||||
 | 
					      min-width:145px;
 | 
				
			||||||
 | 
					      text-decoration:none;
 | 
				
			||||||
 | 
					      margin-top: 15px;
 | 
				
			||||||
 | 
					      line-height:36px;
 | 
				
			||||||
 | 
					      margin-right:10px;
 | 
				
			||||||
 | 
					      text-align:left;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    .xrf a.btn#more{
 | 
				
			||||||
 | 
					      z-index:3000;
 | 
				
			||||||
 | 
					      width: 19px;
 | 
				
			||||||
 | 
					      min-width: 19px;
 | 
				
			||||||
 | 
					      font-size:16px;
 | 
				
			||||||
 | 
					      text-align: center;
 | 
				
			||||||
 | 
					      background:white;
 | 
				
			||||||
 | 
					      color: var(--xrf-primary);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    .xrf a.btn#more i.gg-menu{
 | 
				
			||||||
 | 
					      margin-top:15px;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    .xrf a.btn#more i.gg-close,
 | 
				
			||||||
 | 
					    .xrf a.btn#more i.gg-menu{
 | 
				
			||||||
 | 
					      color:#888;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    .xrf a.btn#meeting i.gg-user-add{
 | 
				
			||||||
 | 
					      margin-right: 12px;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    .xrf a.btn#share i.gg-link{
 | 
				
			||||||
 | 
					      margin-right:24px;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    .xrf a.btn#accessibility i.gg-yinyang{
 | 
				
			||||||
 | 
					      margin-right:13px;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    html{
 | 
				
			||||||
 | 
					      max-width:unset;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    .render {
 | 
				
			||||||
 | 
					      position:absolute;
 | 
				
			||||||
 | 
					      top:0;
 | 
				
			||||||
 | 
					      left:0;
 | 
				
			||||||
 | 
					      right:0;
 | 
				
			||||||
 | 
					      bottom:0;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    .lil-gui.autoPlace{
 | 
				
			||||||
 | 
					      right:0px !important;
 | 
				
			||||||
 | 
					      top:48px !important;
 | 
				
			||||||
 | 
					      height:33vh;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    #VRButton {
 | 
				
			||||||
 | 
					      margin-bottom:20vh;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @media (max-width: 450px) {
 | 
				
			||||||
 | 
					      #uri{ display:none; }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @media (max-width: 640px) {
 | 
				
			||||||
 | 
					      .lil-gui.root{
 | 
				
			||||||
 | 
					        top:auto !important;
 | 
				
			||||||
 | 
					        left:auto !important;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      .js-snackbar__message{
 | 
				
			||||||
 | 
					        overflow-y:auto;
 | 
				
			||||||
 | 
					        max-height:600px;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      .js-snackbar__message h1,h2,h3{
 | 
				
			||||||
 | 
					        font-size:22px;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      .xrf table tr td {
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      :root{
 | 
				
			||||||
 | 
					        --xrf-font-size-1: 13px;
 | 
				
			||||||
 | 
					        --xrf-font-size-2: 17px;
 | 
				
			||||||
 | 
					        --xrf-font-size-3: 20px;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    .a-enter-vr-button, .a-enter-ar-button{
 | 
				
			||||||
 | 
					      height:41px;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    #qrcode{
 | 
				
			||||||
 | 
					      background: transparent;
 | 
				
			||||||
 | 
					      overflow: hidden;
 | 
				
			||||||
 | 
					      height: 121px;
 | 
				
			||||||
 | 
					      display: inline-block;
 | 
				
			||||||
 | 
					      position: relative;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    input#share{
 | 
				
			||||||
 | 
					      font-size: var(--xrf-font-size-1);
 | 
				
			||||||
 | 
					      font-family: var(--xrf-font-monospace);
 | 
				
			||||||
 | 
					      border:2px solid #AAA;
 | 
				
			||||||
 | 
					      width:50vw;
 | 
				
			||||||
 | 
					      max-width:400px;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    .footer {
 | 
				
			||||||
 | 
					      z-index:1000;
 | 
				
			||||||
 | 
					      display: flex;
 | 
				
			||||||
 | 
					      flex-direction: column-reverse; /* This reverses the stacking order of the flex container */
 | 
				
			||||||
 | 
					      align-items: flex-end;
 | 
				
			||||||
 | 
					      height: 100%;
 | 
				
			||||||
 | 
					      position: fixed;
 | 
				
			||||||
 | 
					      top: 71px;
 | 
				
			||||||
 | 
					      right: 11px;
 | 
				
			||||||
 | 
					      bottom: 0;
 | 
				
			||||||
 | 
					      padding-bottom:140px;
 | 
				
			||||||
 | 
					      box-sizing:border-box;
 | 
				
			||||||
 | 
					      pointer-events:none;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    .footer *{
 | 
				
			||||||
 | 
					      pointer-events:all;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    .footer .menu{
 | 
				
			||||||
 | 
					      text-align:right;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    .badge,
 | 
				
			||||||
 | 
					    #messages .msg .badge{
 | 
				
			||||||
 | 
					      box-sizing:border-box;
 | 
				
			||||||
 | 
					      display:inline-block;
 | 
				
			||||||
 | 
					      color: var(--xrf-white);
 | 
				
			||||||
 | 
					      font-weight: bold;
 | 
				
			||||||
 | 
					      background: var(--xrf-dark-gray);
 | 
				
			||||||
 | 
					      border-radius:7px;
 | 
				
			||||||
 | 
					      padding:3px 12px;
 | 
				
			||||||
 | 
					      font-size: var(--xrf-font-size-0);
 | 
				
			||||||
 | 
					      margin-right:10px;
 | 
				
			||||||
 | 
					      text-decoration:none !important;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    #messages .msg.ui div.badge a{
 | 
				
			||||||
 | 
					      color:#FFF;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    #messages .msg .badge{
 | 
				
			||||||
 | 
					      display:inline;
 | 
				
			||||||
 | 
					      color: var(--xrf-primary-fg);
 | 
				
			||||||
 | 
					      background: var(--xrf-dark-gray);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    .ruler{
 | 
				
			||||||
 | 
					      width:97%; 
 | 
				
			||||||
 | 
					      margin:7px 0px;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    a.badge {
 | 
				
			||||||
 | 
					      text-decoration:none;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    .xrf select{
 | 
				
			||||||
 | 
					      border-inline: none;
 | 
				
			||||||
 | 
					      border-inline: none;
 | 
				
			||||||
 | 
					      border-block: none;
 | 
				
			||||||
 | 
					      border: 3px solid var(--xrf-primary);
 | 
				
			||||||
 | 
					      border-radius: 5px;
 | 
				
			||||||
 | 
					      background: none;
 | 
				
			||||||
 | 
					      border-radius:30px;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    .xrf select,
 | 
				
			||||||
 | 
					    .xrf option{
 | 
				
			||||||
 | 
					      padding: 0px 16px;
 | 
				
			||||||
 | 
					      min-width: 150px;
 | 
				
			||||||
 | 
					      max-width: 150px;
 | 
				
			||||||
 | 
					      height: 35px;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    .xrf input{
 | 
				
			||||||
 | 
					      border-radius:30px;
 | 
				
			||||||
 | 
					      padding: 7px 15px;
 | 
				
			||||||
 | 
					      border-block: none;
 | 
				
			||||||
 | 
					      border-inline: none;
 | 
				
			||||||
 | 
					      border: 1px solid #888;
 | 
				
			||||||
 | 
					      background: transparent;
 | 
				
			||||||
 | 
					      max-width:105px;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    .xrf table tr td {
 | 
				
			||||||
 | 
					      vertical-align:middle;
 | 
				
			||||||
 | 
					      text-align:right;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    .xrf table tr td:nth-child(1){
 | 
				
			||||||
 | 
					      min-width:82px;
 | 
				
			||||||
 | 
					      height:40px;
 | 
				
			||||||
 | 
					      padding-right:15px;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    .xrf small{
 | 
				
			||||||
 | 
					      font-size: var(--xrf-font-size-0);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    .disabled{
 | 
				
			||||||
 | 
					      opacity:0.5
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  body.menu .js-snackbar__wrapper, 
 | 
				
			||||||
 | 
					  body.topbar .js-snackbar__wrapper {
 | 
				
			||||||
 | 
					    transform: translateY(40px);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  .transcript{
 | 
				
			||||||
 | 
					    max-height:132px;
 | 
				
			||||||
 | 
					    width:100%;
 | 
				
			||||||
 | 
					    overflow-y:auto;
 | 
				
			||||||
 | 
					    border: 1px solid var(--xrf-gray);
 | 
				
			||||||
 | 
					    border-radius: 5px;
 | 
				
			||||||
 | 
					    padding: 10px;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  .right { float:right }
 | 
				
			||||||
 | 
					  .left  { float:left  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /*
 | 
				
			||||||
 | 
					   * tabs 
 | 
				
			||||||
 | 
					   */ 
 | 
				
			||||||
 | 
					  div.tab-frame > input{ display:none;}
 | 
				
			||||||
 | 
					  div.tab-frame > label{ display:block; float:left;padding:5px 10px; cursor:pointer;  }
 | 
				
			||||||
 | 
					  div.tab-frame > input:checked + label{ cursor:default; border-bottom:1px solid #888; font-weight:bold; }
 | 
				
			||||||
 | 
					  div.tab-frame > div.tab{ display:none; padding:15px 10px 5px 10px;clear:left}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  div.tab-frame > input:nth-of-type(1):checked ~ .tab:nth-of-type(1),
 | 
				
			||||||
 | 
					  div.tab-frame > input:nth-of-type(2):checked ~ .tab:nth-of-type(2),
 | 
				
			||||||
 | 
					  div.tab-frame > input:nth-of-type(3):checked ~ .tab:nth-of-type(3){ display:block;}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /*
 | 
				
			||||||
 | 
					   * joystick.js controller 
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					  .controller {
 | 
				
			||||||
 | 
					      position: fixed;
 | 
				
			||||||
 | 
					      display: block;
 | 
				
			||||||
 | 
					      width: 100px;
 | 
				
			||||||
 | 
					      height: 100px;
 | 
				
			||||||
 | 
					      left: 25px;
 | 
				
			||||||
 | 
					      bottom: 20px;
 | 
				
			||||||
 | 
					      cursor:pointer;
 | 
				
			||||||
 | 
					      z-index: 999;
 | 
				
			||||||
 | 
					      border-radius: 50%;
 | 
				
			||||||
 | 
					      border: 5px solid #333;
 | 
				
			||||||
 | 
					      filter: alpha(opacity=50);
 | 
				
			||||||
 | 
					      -khtml-opacity: 0.3;
 | 
				
			||||||
 | 
					      -moz-opacity: 0.3;
 | 
				
			||||||
 | 
					      opacity:0.3;
 | 
				
			||||||
 | 
					      transition: opacity 1s ease-out;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /*
 | 
				
			||||||
 | 
					   * css icons from https://css.gg
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  .gg-close-o {
 | 
				
			||||||
 | 
					      box-sizing: border-box;
 | 
				
			||||||
 | 
					      position: relative;
 | 
				
			||||||
 | 
					      display: block;
 | 
				
			||||||
 | 
					      transform: scale(var(--ggs,1));
 | 
				
			||||||
 | 
					      width: 22px;
 | 
				
			||||||
 | 
					      height: 22px;
 | 
				
			||||||
 | 
					      border: 2px solid;
 | 
				
			||||||
 | 
					      border-radius: 40px
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  .gg-close-o::after,
 | 
				
			||||||
 | 
					  .gg-close-o::before {
 | 
				
			||||||
 | 
					      content: "";
 | 
				
			||||||
 | 
					      display: block;
 | 
				
			||||||
 | 
					      box-sizing: border-box;
 | 
				
			||||||
 | 
					      position: absolute;
 | 
				
			||||||
 | 
					      width: 12px;
 | 
				
			||||||
 | 
					      height: 2px;
 | 
				
			||||||
 | 
					      background: currentColor;
 | 
				
			||||||
 | 
					      transform: rotate(45deg);
 | 
				
			||||||
 | 
					      border-radius: 5px;
 | 
				
			||||||
 | 
					      top: 8px;
 | 
				
			||||||
 | 
					      left: 3px
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  .gg-close-o::after {
 | 
				
			||||||
 | 
					      transform: rotate(-45deg)
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  .gg-user-add {
 | 
				
			||||||
 | 
					      display: inline-block;
 | 
				
			||||||
 | 
					      transform: scale(var(--ggs,1));
 | 
				
			||||||
 | 
					      box-sizing: border-box;
 | 
				
			||||||
 | 
					      width: 20px;
 | 
				
			||||||
 | 
					      height: 18px;
 | 
				
			||||||
 | 
					      background:
 | 
				
			||||||
 | 
					          linear-gradient(
 | 
				
			||||||
 | 
					              to left,
 | 
				
			||||||
 | 
					              currentColor 8px,
 | 
				
			||||||
 | 
					              transparent 0)
 | 
				
			||||||
 | 
					              no-repeat 14px 6px/6px 2px,
 | 
				
			||||||
 | 
					          linear-gradient(
 | 
				
			||||||
 | 
					              to left,
 | 
				
			||||||
 | 
					              currentColor 8px,
 | 
				
			||||||
 | 
					              transparent 0)
 | 
				
			||||||
 | 
					              no-repeat 16px 4px/2px 6px
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  .gg-user-add::after,.gg-user-add::before {
 | 
				
			||||||
 | 
					      content: "";
 | 
				
			||||||
 | 
					      display: block;
 | 
				
			||||||
 | 
					      box-sizing: border-box;
 | 
				
			||||||
 | 
					      position: absolute;
 | 
				
			||||||
 | 
					      border: 2px solid
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  .gg-user-add::before {
 | 
				
			||||||
 | 
					      width: 8px;
 | 
				
			||||||
 | 
					      height: 8px;
 | 
				
			||||||
 | 
					      border-radius: 30px;
 | 
				
			||||||
 | 
					      top: 0;
 | 
				
			||||||
 | 
					      left: 2px
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  .gg-user-add::after {
 | 
				
			||||||
 | 
					      width: 12px;
 | 
				
			||||||
 | 
					      height: 9px;
 | 
				
			||||||
 | 
					      border-bottom: 0;
 | 
				
			||||||
 | 
					      border-top-left-radius: 3px;
 | 
				
			||||||
 | 
					      border-top-right-radius: 3px;
 | 
				
			||||||
 | 
					      top: 9px
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  .gg-user {
 | 
				
			||||||
 | 
					      display: inline-block;
 | 
				
			||||||
 | 
					      transform: scale(var(--ggs,1));
 | 
				
			||||||
 | 
					      box-sizing: border-box;
 | 
				
			||||||
 | 
					      width: 12px;
 | 
				
			||||||
 | 
					      height: 18px
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  .gg-user::after,
 | 
				
			||||||
 | 
					  .gg-user::before {
 | 
				
			||||||
 | 
					      content: "";
 | 
				
			||||||
 | 
					      display: block;
 | 
				
			||||||
 | 
					      box-sizing: border-box;
 | 
				
			||||||
 | 
					      position: absolute;
 | 
				
			||||||
 | 
					      border: 2px solid
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  .gg-user::before {
 | 
				
			||||||
 | 
					      width: 8px;
 | 
				
			||||||
 | 
					      height: 8px;
 | 
				
			||||||
 | 
					      border-radius: 30px;
 | 
				
			||||||
 | 
					      top: 0;
 | 
				
			||||||
 | 
					      left: 2px
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  .gg-user::after {
 | 
				
			||||||
 | 
					      width: 12px;
 | 
				
			||||||
 | 
					      height: 9px;
 | 
				
			||||||
 | 
					      border-bottom: 0;
 | 
				
			||||||
 | 
					      border-top-left-radius: 3px;
 | 
				
			||||||
 | 
					      border-top-right-radius: 3px;
 | 
				
			||||||
 | 
					      top: 9px
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  .gg-menu {
 | 
				
			||||||
 | 
					      transform: scale(var(--ggs,1))
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  .gg-menu,
 | 
				
			||||||
 | 
					  .gg-menu::after,
 | 
				
			||||||
 | 
					  .gg-menu::before {
 | 
				
			||||||
 | 
					      box-sizing: border-box;
 | 
				
			||||||
 | 
					      position: relative;
 | 
				
			||||||
 | 
					      display: block;
 | 
				
			||||||
 | 
					      width: 20px;
 | 
				
			||||||
 | 
					      height: 2px;
 | 
				
			||||||
 | 
					      border-radius: 3px;
 | 
				
			||||||
 | 
					      background: currentColor
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  .gg-menu::after,
 | 
				
			||||||
 | 
					  .gg-menu::before {
 | 
				
			||||||
 | 
					      content: "";
 | 
				
			||||||
 | 
					      position: absolute;
 | 
				
			||||||
 | 
					      top: -6px
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  .gg-menu::after {
 | 
				
			||||||
 | 
					      top: 6px
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  .gg-close {
 | 
				
			||||||
 | 
					      box-sizing: border-box;
 | 
				
			||||||
 | 
					      position: relative;
 | 
				
			||||||
 | 
					      display: block;
 | 
				
			||||||
 | 
					      transform: scale(var(--ggs,1)) scale(var(--ggs,1)) translate(-2px,5px);
 | 
				
			||||||
 | 
					      width: 22px;
 | 
				
			||||||
 | 
					      height: 22px;
 | 
				
			||||||
 | 
					      border: 2px solid transparent;
 | 
				
			||||||
 | 
					      border-radius: 40px
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  .gg-close::after,
 | 
				
			||||||
 | 
					  .gg-close::before {
 | 
				
			||||||
 | 
					      content: "";
 | 
				
			||||||
 | 
					      display: block;
 | 
				
			||||||
 | 
					      box-sizing: border-box;
 | 
				
			||||||
 | 
					      position: absolute;
 | 
				
			||||||
 | 
					      width: 16px;
 | 
				
			||||||
 | 
					      height: 2px;
 | 
				
			||||||
 | 
					      background: currentColor;
 | 
				
			||||||
 | 
					      transform: rotate(45deg);
 | 
				
			||||||
 | 
					      border-radius: 5px;
 | 
				
			||||||
 | 
					      top: 8px;
 | 
				
			||||||
 | 
					      left: 1px
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  .gg-close::after {
 | 
				
			||||||
 | 
					      transform: rotate(-45deg)
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  .gg-link {
 | 
				
			||||||
 | 
					      box-sizing: border-box;
 | 
				
			||||||
 | 
					      position: relative;
 | 
				
			||||||
 | 
					      display: inline-block;
 | 
				
			||||||
 | 
					      -moz-transform: rotate(-45deg) scale(var(--ggs,1));
 | 
				
			||||||
 | 
					      transform: translate(4px,-5px) rotate(-45deg) scale(var(--ggs,1));
 | 
				
			||||||
 | 
					      width: 8px;
 | 
				
			||||||
 | 
					      height: 2px;
 | 
				
			||||||
 | 
					      background: currentColor;
 | 
				
			||||||
 | 
					      line-height:11px;
 | 
				
			||||||
 | 
					      border-radius: 4px
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  .gg-link::after,
 | 
				
			||||||
 | 
					  .gg-link::before {
 | 
				
			||||||
 | 
					      content: "";
 | 
				
			||||||
 | 
					      display: block;
 | 
				
			||||||
 | 
					      box-sizing: border-box;
 | 
				
			||||||
 | 
					      position: absolute;
 | 
				
			||||||
 | 
					      border-radius: 3px;
 | 
				
			||||||
 | 
					      width: 8px;
 | 
				
			||||||
 | 
					      height: 10px;
 | 
				
			||||||
 | 
					      border: 2px solid;
 | 
				
			||||||
 | 
					      top: -4px
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  .gg-link::before {
 | 
				
			||||||
 | 
					      border-right: 0;
 | 
				
			||||||
 | 
					      border-top-left-radius: 40px;
 | 
				
			||||||
 | 
					      border-bottom-left-radius: 40px;
 | 
				
			||||||
 | 
					      left: -6px
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  .gg-link::after {
 | 
				
			||||||
 | 
					      border-left: 0;
 | 
				
			||||||
 | 
					      border-top-right-radius: 40px;
 | 
				
			||||||
 | 
					      border-bottom-right-radius: 40px;
 | 
				
			||||||
 | 
					      right: -6px
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  .gg-info {
 | 
				
			||||||
 | 
					      box-sizing: border-box;
 | 
				
			||||||
 | 
					      position: relative;
 | 
				
			||||||
 | 
					      display: inline-block;
 | 
				
			||||||
 | 
					      transform: scale(var(--ggs,1)) translate(-3px, 3px);
 | 
				
			||||||
 | 
					      width: 20px;
 | 
				
			||||||
 | 
					      height: 20px;
 | 
				
			||||||
 | 
					      border: 2px solid;
 | 
				
			||||||
 | 
					      border-radius: 40px
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  .gg-info::after,
 | 
				
			||||||
 | 
					  .gg-info::before {
 | 
				
			||||||
 | 
					      content: "";
 | 
				
			||||||
 | 
					      display: block;
 | 
				
			||||||
 | 
					      box-sizing: border-box;
 | 
				
			||||||
 | 
					      position: absolute;
 | 
				
			||||||
 | 
					      border-radius: 3px;
 | 
				
			||||||
 | 
					      width: 2px;
 | 
				
			||||||
 | 
					      background: currentColor;
 | 
				
			||||||
 | 
					      left: 7px
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  .gg-info::after {
 | 
				
			||||||
 | 
					      bottom: 2px;
 | 
				
			||||||
 | 
					      height: 8px
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  .gg-info::before {
 | 
				
			||||||
 | 
					      height: 2px;
 | 
				
			||||||
 | 
					      top: 2px
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  .gg-yinyang {
 | 
				
			||||||
 | 
					    box-sizing: border-box;
 | 
				
			||||||
 | 
					    position: relative;
 | 
				
			||||||
 | 
					    display: inline-block;
 | 
				
			||||||
 | 
					    transform: rotate(95deg) scale(var(--ggs,1)) translate(4px,4px); 
 | 
				
			||||||
 | 
					    width: 20px;
 | 
				
			||||||
 | 
					    height: 20px;
 | 
				
			||||||
 | 
					    border: 2px solid;
 | 
				
			||||||
 | 
					    border-radius: 22px
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  .gg-yinyang::after,
 | 
				
			||||||
 | 
					  .gg-yinyang::before {
 | 
				
			||||||
 | 
					    content: "";
 | 
				
			||||||
 | 
					    display: block;
 | 
				
			||||||
 | 
					    box-sizing: border-box;
 | 
				
			||||||
 | 
					    position: absolute;
 | 
				
			||||||
 | 
					    width: 8px;
 | 
				
			||||||
 | 
					    height: 8px;
 | 
				
			||||||
 | 
					    border-radius: 10px;
 | 
				
			||||||
 | 
					    top: 4px
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  .gg-yinyang::before {
 | 
				
			||||||
 | 
					    border: 2px solid;
 | 
				
			||||||
 | 
					    left: 0
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  .gg-yinyang::after {
 | 
				
			||||||
 | 
					    border: 2px solid transparent;
 | 
				
			||||||
 | 
					    right: 0;
 | 
				
			||||||
 | 
					    box-shadow:
 | 
				
			||||||
 | 
					      inset 0 0 0 4px,
 | 
				
			||||||
 | 
					      0 -3px 0 1px,
 | 
				
			||||||
 | 
					      -2px -4px 0 1px,
 | 
				
			||||||
 | 
					      -8px -5px 0 -1px,
 | 
				
			||||||
 | 
					      -11px -3px 0 -2px,
 | 
				
			||||||
 | 
					      -12px -1px 0 -3px,
 | 
				
			||||||
 | 
					      -6px -6px 0 -1px
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  .gg-image {
 | 
				
			||||||
 | 
					      box-sizing: border-box;
 | 
				
			||||||
 | 
					      position: relative;
 | 
				
			||||||
 | 
					      display: inline-block;
 | 
				
			||||||
 | 
					      transform: scale(var(--ggs,1)) translate(1px,2px); 
 | 
				
			||||||
 | 
					      width: 20px;
 | 
				
			||||||
 | 
					      height: 16px;
 | 
				
			||||||
 | 
					      overflow: hidden;
 | 
				
			||||||
 | 
					      box-shadow: 0 0 0 2px;
 | 
				
			||||||
 | 
					      border-radius: 2px
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  .gg-image::after,
 | 
				
			||||||
 | 
					  .gg-image::before {
 | 
				
			||||||
 | 
					      content: "";
 | 
				
			||||||
 | 
					      display: block;
 | 
				
			||||||
 | 
					      box-sizing: border-box;
 | 
				
			||||||
 | 
					      position: absolute;
 | 
				
			||||||
 | 
					      border: 2px solid
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  .gg-image::after {
 | 
				
			||||||
 | 
					      transform: rotate(45deg);
 | 
				
			||||||
 | 
					      border-radius: 3px;
 | 
				
			||||||
 | 
					      width: 16px;
 | 
				
			||||||
 | 
					      height: 16px;
 | 
				
			||||||
 | 
					      top: 9px;
 | 
				
			||||||
 | 
					      left: 6px
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  .gg-image::before {
 | 
				
			||||||
 | 
					      width: 6px;
 | 
				
			||||||
 | 
					      height: 6px;
 | 
				
			||||||
 | 
					      border-radius: 100%;
 | 
				
			||||||
 | 
					      top: 2px;
 | 
				
			||||||
 | 
					      left: 2px
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  .gg-serverless {
 | 
				
			||||||
 | 
					      box-sizing: border-box;
 | 
				
			||||||
 | 
					      position: relative;
 | 
				
			||||||
 | 
					      display: inline-block;
 | 
				
			||||||
 | 
					      transform: scale(var(--ggs,1)) translate(2px,1px); 
 | 
				
			||||||
 | 
					      width: 15px;
 | 
				
			||||||
 | 
					      height: 13px;
 | 
				
			||||||
 | 
					      overflow: hidden
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  .gg-serverless::after,
 | 
				
			||||||
 | 
					  .gg-serverless::before {
 | 
				
			||||||
 | 
					      background: currentColor;
 | 
				
			||||||
 | 
					      content: "";
 | 
				
			||||||
 | 
					      box-sizing: border-box;
 | 
				
			||||||
 | 
					      position: absolute;
 | 
				
			||||||
 | 
					      display: block;
 | 
				
			||||||
 | 
					      height: 3px;
 | 
				
			||||||
 | 
					      box-shadow: 0 5px 0,0 10px 0;
 | 
				
			||||||
 | 
					      transform: skew(-20deg)
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  .gg-serverless::before {
 | 
				
			||||||
 | 
					      width: 8px;
 | 
				
			||||||
 | 
					      left: -2px
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  .gg-serverless::after {
 | 
				
			||||||
 | 
					      width: 12px;
 | 
				
			||||||
 | 
					      right: -5px
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  .gg-software-download {
 | 
				
			||||||
 | 
					      box-sizing: border-box;
 | 
				
			||||||
 | 
					      position: relative;
 | 
				
			||||||
 | 
					      display: inline-block;
 | 
				
			||||||
 | 
					      margin-right:5px;
 | 
				
			||||||
 | 
					      transform: scale(var(--ggs,1)) translate(3px,3px); 
 | 
				
			||||||
 | 
					      width: 16px;
 | 
				
			||||||
 | 
					      height: 6px;
 | 
				
			||||||
 | 
					      border: 2px solid;
 | 
				
			||||||
 | 
					      border-top: 0;
 | 
				
			||||||
 | 
					      border-bottom-left-radius: 2px;
 | 
				
			||||||
 | 
					      border-bottom-right-radius: 2px;
 | 
				
			||||||
 | 
					      line-height:15px;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  .gg-software-download::after {
 | 
				
			||||||
 | 
					      content: "";
 | 
				
			||||||
 | 
					      display: block;
 | 
				
			||||||
 | 
					      box-sizing: border-box;
 | 
				
			||||||
 | 
					      position: absolute;
 | 
				
			||||||
 | 
					      width: 8px;
 | 
				
			||||||
 | 
					      height: 8px;
 | 
				
			||||||
 | 
					      border-left: 2px solid;
 | 
				
			||||||
 | 
					      border-bottom: 2px solid;
 | 
				
			||||||
 | 
					      transform: rotate(-45deg);
 | 
				
			||||||
 | 
					      left: 2px;
 | 
				
			||||||
 | 
					      bottom: 4px
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  .gg-software-download::before {
 | 
				
			||||||
 | 
					      content: "";
 | 
				
			||||||
 | 
					      display: block;
 | 
				
			||||||
 | 
					      box-sizing: border-box;
 | 
				
			||||||
 | 
					      position: absolute;
 | 
				
			||||||
 | 
					      border-radius: 3px;
 | 
				
			||||||
 | 
					      width: 2px;
 | 
				
			||||||
 | 
					      height: 10px;
 | 
				
			||||||
 | 
					      background: currentColor;
 | 
				
			||||||
 | 
					      left: 5px;
 | 
				
			||||||
 | 
					      bottom: 5px
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  .gg-arrow-left-r {
 | 
				
			||||||
 | 
					      box-sizing: border-box;
 | 
				
			||||||
 | 
					      position: relative;
 | 
				
			||||||
 | 
					      display: inline-block;
 | 
				
			||||||
 | 
					      width: 22px;
 | 
				
			||||||
 | 
					      height: 22px;
 | 
				
			||||||
 | 
					      border: 2px solid;
 | 
				
			||||||
 | 
					      transform: scale(var(--ggs,1));
 | 
				
			||||||
 | 
					      border-radius: 4px
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  .gg-arrow-left-r::after,
 | 
				
			||||||
 | 
					  .gg-arrow-left-r::before {
 | 
				
			||||||
 | 
					      content: "";
 | 
				
			||||||
 | 
					      display: block;
 | 
				
			||||||
 | 
					      box-sizing: border-box;
 | 
				
			||||||
 | 
					      position: absolute;
 | 
				
			||||||
 | 
					      left: 4px
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  .gg-arrow-left-r::after {
 | 
				
			||||||
 | 
					      width: 6px;
 | 
				
			||||||
 | 
					      height: 6px;
 | 
				
			||||||
 | 
					      border-bottom: 2px solid;
 | 
				
			||||||
 | 
					      border-left: 2px solid;
 | 
				
			||||||
 | 
					      transform: rotate(45deg);
 | 
				
			||||||
 | 
					      bottom: 6px
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  .gg-arrow-left-r::before {
 | 
				
			||||||
 | 
					      width: 10px;
 | 
				
			||||||
 | 
					      height: 2px;
 | 
				
			||||||
 | 
					      bottom: 8px;
 | 
				
			||||||
 | 
					      background: currentColor
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  .gg-pen {
 | 
				
			||||||
 | 
					      box-sizing: border-box;
 | 
				
			||||||
 | 
					      position: relative;
 | 
				
			||||||
 | 
					      display: block;
 | 
				
			||||||
 | 
					      transform: rotate(-45deg) scale(var(--ggs,1));
 | 
				
			||||||
 | 
					      width: 14px;
 | 
				
			||||||
 | 
					      height: 4px;
 | 
				
			||||||
 | 
					      border-right: 2px solid transparent;
 | 
				
			||||||
 | 
					      box-shadow:
 | 
				
			||||||
 | 
					          0 0 0 2px,
 | 
				
			||||||
 | 
					          inset -2px 0 0;
 | 
				
			||||||
 | 
					      border-top-right-radius: 1px;
 | 
				
			||||||
 | 
					      border-bottom-right-radius: 1px;
 | 
				
			||||||
 | 
					      margin-right: -2px
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  .gg-pen::after,
 | 
				
			||||||
 | 
					  .gg-pen::before {
 | 
				
			||||||
 | 
					      content: "";
 | 
				
			||||||
 | 
					      display: block;
 | 
				
			||||||
 | 
					      box-sizing: border-box;
 | 
				
			||||||
 | 
					      position: absolute
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  .gg-pen::before {
 | 
				
			||||||
 | 
					      background: currentColor;
 | 
				
			||||||
 | 
					      border-left: 0;
 | 
				
			||||||
 | 
					      right: -6px;
 | 
				
			||||||
 | 
					      width: 3px;
 | 
				
			||||||
 | 
					      height: 4px;
 | 
				
			||||||
 | 
					      border-radius: 1px;
 | 
				
			||||||
 | 
					      top: 0
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  .gg-pen::after {
 | 
				
			||||||
 | 
					      width: 8px;
 | 
				
			||||||
 | 
					      height: 7px;
 | 
				
			||||||
 | 
					      border-top: 4px solid transparent;
 | 
				
			||||||
 | 
					      border-bottom: 4px solid transparent;
 | 
				
			||||||
 | 
					      border-right: 7px solid;
 | 
				
			||||||
 | 
					      left: -11px;
 | 
				
			||||||
 | 
					      top: -2px
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  .gg-software-upload {
 | 
				
			||||||
 | 
					    box-sizing: border-box;
 | 
				
			||||||
 | 
					    position: relative;
 | 
				
			||||||
 | 
					    display: inline-block;
 | 
				
			||||||
 | 
					    margin-right:5px;
 | 
				
			||||||
 | 
					    transform: scale(var(--ggs, 1));
 | 
				
			||||||
 | 
					    width: 16px;
 | 
				
			||||||
 | 
					    height: 6px;
 | 
				
			||||||
 | 
					    border: 2px solid;
 | 
				
			||||||
 | 
					    border-top: 0;
 | 
				
			||||||
 | 
					    border-bottom-left-radius: 2px;
 | 
				
			||||||
 | 
					    border-bottom-right-radius: 2px;
 | 
				
			||||||
 | 
					    margin-top: 8px;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  .gg-software-upload::after {
 | 
				
			||||||
 | 
					    content: "";
 | 
				
			||||||
 | 
					    display: block;
 | 
				
			||||||
 | 
					    box-sizing: border-box;
 | 
				
			||||||
 | 
					    position: absolute;
 | 
				
			||||||
 | 
					    width: 8px;
 | 
				
			||||||
 | 
					    height: 8px;
 | 
				
			||||||
 | 
					    border-left: 2px solid;
 | 
				
			||||||
 | 
					    border-top: 2px solid;
 | 
				
			||||||
 | 
					    transform: rotate(45deg);
 | 
				
			||||||
 | 
					    left: 2px;
 | 
				
			||||||
 | 
					    bottom: 4px;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  .gg-software-upload::before {
 | 
				
			||||||
 | 
					    content: "";
 | 
				
			||||||
 | 
					    display: block;
 | 
				
			||||||
 | 
					    box-sizing: border-box;
 | 
				
			||||||
 | 
					    position: absolute;
 | 
				
			||||||
 | 
					    border-radius: 3px;
 | 
				
			||||||
 | 
					    width: 2px;
 | 
				
			||||||
 | 
					    height: 10px;
 | 
				
			||||||
 | 
					    background: currentColor;
 | 
				
			||||||
 | 
					    left: 5px;
 | 
				
			||||||
 | 
					    bottom: 3px;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  .gg-globe-alt,
 | 
				
			||||||
 | 
					  .gg-globe-alt::after,
 | 
				
			||||||
 | 
					  .gg-globe-alt::before {
 | 
				
			||||||
 | 
					    display: inline-block;
 | 
				
			||||||
 | 
					    box-sizing: border-box;
 | 
				
			||||||
 | 
					    height: 18px;
 | 
				
			||||||
 | 
					    border: 2px solid;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  .gg-globe-alt {
 | 
				
			||||||
 | 
					    position: relative;
 | 
				
			||||||
 | 
					    transform: scale(var(--ggs, 1));
 | 
				
			||||||
 | 
					    width: 18px;
 | 
				
			||||||
 | 
					    border-radius: 22px;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  .gg-globe-alt::after,
 | 
				
			||||||
 | 
					  .gg-globe-alt::before {
 | 
				
			||||||
 | 
					    content: "";
 | 
				
			||||||
 | 
					    position: absolute;
 | 
				
			||||||
 | 
					    width: 8px;
 | 
				
			||||||
 | 
					    border-radius: 100%;
 | 
				
			||||||
 | 
					    top: -2px;
 | 
				
			||||||
 | 
					    left: 3px;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  .gg-globe-alt::after {
 | 
				
			||||||
 | 
					    width: 24px;
 | 
				
			||||||
 | 
					    height: 20px;
 | 
				
			||||||
 | 
					    border: 2px solid transparent;
 | 
				
			||||||
 | 
					    border-bottom: 2px solid;
 | 
				
			||||||
 | 
					    top: -11px;
 | 
				
			||||||
 | 
					    left: -5px;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  </style>
 | 
				
			||||||
 | 
					`
 | 
				
			||||||
							
								
								
									
										1480
									
								
								dist/xrfragment.plugin.frontend.js
									
										
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										1480
									
								
								dist/xrfragment.plugin.frontend.js
									
										
									
									
										vendored
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
							
								
								
									
										343
									
								
								dist/xrfragment.plugin.matrix.js
									
										
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										343
									
								
								dist/xrfragment.plugin.matrix.js
									
										
									
									
										vendored
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
							
								
								
									
										998
									
								
								dist/xrfragment.plugin.network.js
									
										
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										998
									
								
								dist/xrfragment.plugin.network.js
									
										
									
									
										vendored
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,998 @@
 | 
				
			||||||
 | 
					/*
 | 
				
			||||||
 | 
					 * v0.5.1 generated at Thu May 22 07:30:49 PM CEST 2025
 | 
				
			||||||
 | 
					 * https://xrfragment.org
 | 
				
			||||||
 | 
					 * SPDX-License-Identifier: AGPL-3.0-or-later
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					(function(){
 | 
				
			||||||
 | 
					// this orchestrates multiplayer events from the scene graph
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					window.network = (opts) => new Proxy({
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  connected: false,
 | 
				
			||||||
 | 
					  pos: '',
 | 
				
			||||||
 | 
					  posName: '',
 | 
				
			||||||
 | 
					  meetinglink: "",
 | 
				
			||||||
 | 
					  peers: {},
 | 
				
			||||||
 | 
					  plugin: {},
 | 
				
			||||||
 | 
					  opts,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  init(){
 | 
				
			||||||
 | 
					    document.addEventListener('network.disconnect', () => this.connected = false )
 | 
				
			||||||
 | 
					    document.addEventListener('network.connected',  () => this.connected = true  )
 | 
				
			||||||
 | 
					    setTimeout( () => window.frontend.emit('network.init'), 100 )
 | 
				
			||||||
 | 
					    return this
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  connect(opts){
 | 
				
			||||||
 | 
					    window.frontend.emit(`network.${this.connected?'disconnect':'connect'}`,opts)
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  add(peerid,data){
 | 
				
			||||||
 | 
					    data = {lastUpdated: new Date().getTime(), id: peerid, ...data }
 | 
				
			||||||
 | 
					    this.peers[peerid] = data 
 | 
				
			||||||
 | 
					    window.frontend.emit(`network.peer.add`,{peer})
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  remove(peerid,data){
 | 
				
			||||||
 | 
					    delete this.peers[peerid]
 | 
				
			||||||
 | 
					    window.frontend.emit(`network.peer.remove`,{peer})
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  send(opts){
 | 
				
			||||||
 | 
					    window.frontend.emit('network.send',opts)
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  receive(opts){
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  getMeetingFromUrl(url){
 | 
				
			||||||
 | 
					    let hash = url.replace(/.*#/,'')
 | 
				
			||||||
 | 
					    let parts = hash.split("&")
 | 
				
			||||||
 | 
					    let meeting = ''
 | 
				
			||||||
 | 
					    parts.map( (p) => {
 | 
				
			||||||
 | 
					      if( p.split("=")[0] == 'meet' ) meeting = p.split("=")[1]
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					    return meeting
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  randomRoom(){
 | 
				
			||||||
 | 
					    var names = []
 | 
				
			||||||
 | 
					    let add = (s) => s.length < 6 && !s.match(/[0-9$]/) && !s.match(/_/) ? names.push(s) : false
 | 
				
			||||||
 | 
					    for ( var i in window             ) add(i)
 | 
				
			||||||
 | 
					    for ( var i in Object.prototype   ) add(i)
 | 
				
			||||||
 | 
					    for ( var i in Function.prototype ) add(i)
 | 
				
			||||||
 | 
					    for ( var i in Array.prototype    ) add(i)
 | 
				
			||||||
 | 
					    for ( var i in String.prototype   ) add(i) 
 | 
				
			||||||
 | 
					    var a = names[Math.floor(Math.random() * names.length)];
 | 
				
			||||||
 | 
					    var b = names[Math.floor(Math.random() * names.length)];
 | 
				
			||||||
 | 
					    return String(`${a}-${b}-${String(Math.random()).substr(13)}`).toLowerCase()
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					},
 | 
				
			||||||
 | 
					{ 
 | 
				
			||||||
 | 
					  // auto-trigger events on changes 
 | 
				
			||||||
 | 
					  get(data,k,receiver){ return data[k] },
 | 
				
			||||||
 | 
					  set(data,k,v){
 | 
				
			||||||
 | 
					    let from   = data[k]
 | 
				
			||||||
 | 
					    data[k] = v
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					})
 | 
				
			||||||
 | 
					      
 | 
				
			||||||
 | 
					document.addEventListener('frontend:ready', (e) => {
 | 
				
			||||||
 | 
					  window.network = network(e.detail).init()
 | 
				
			||||||
 | 
					  document.dispatchEvent( new CustomEvent("network:ready", e ) )
 | 
				
			||||||
 | 
					})
 | 
				
			||||||
 | 
					connectionsComponent = {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  html: `
 | 
				
			||||||
 | 
					   <div id="connections">
 | 
				
			||||||
 | 
					      <i class="gg-close-o" id="close" onclick="$connections.visible = false"></i>
 | 
				
			||||||
 | 
					      <br>
 | 
				
			||||||
 | 
					      <div class="tab-frame">
 | 
				
			||||||
 | 
					        <input type="radio" name="tab" id="login" checked>
 | 
				
			||||||
 | 
					        <label for="login">login</label>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        <input type="radio" name="tab" id="io">
 | 
				
			||||||
 | 
					        <label for="io">devices</label>
 | 
				
			||||||
 | 
					       
 | 
				
			||||||
 | 
					        <input type="radio" name="tab" id="networks">
 | 
				
			||||||
 | 
					        <label for="networks">advanced</label>
 | 
				
			||||||
 | 
					          
 | 
				
			||||||
 | 
					        <div class="tab">
 | 
				
			||||||
 | 
					          <div id="settings"></div>
 | 
				
			||||||
 | 
					          <table>
 | 
				
			||||||
 | 
					            <tr>
 | 
				
			||||||
 | 
					              <td></td>
 | 
				
			||||||
 | 
					              <td>
 | 
				
			||||||
 | 
					                <button id="connect" onclick="network.connect( $connections )">📡 Connect!</button>
 | 
				
			||||||
 | 
					              </td>
 | 
				
			||||||
 | 
					            </tr>
 | 
				
			||||||
 | 
					        </table>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        <div class="tab">
 | 
				
			||||||
 | 
					          <div id="devices">
 | 
				
			||||||
 | 
					            <a class="badge ruler">Webcam and/or Audio</a>
 | 
				
			||||||
 | 
					            <table>
 | 
				
			||||||
 | 
					              <tr>
 | 
				
			||||||
 | 
					                <td>Video</td>
 | 
				
			||||||
 | 
					                <td>
 | 
				
			||||||
 | 
					                  <select id="videoInput"></select> 
 | 
				
			||||||
 | 
					                </td>
 | 
				
			||||||
 | 
					              </tr>
 | 
				
			||||||
 | 
					              <tr>
 | 
				
			||||||
 | 
					                <td>Mic</td>
 | 
				
			||||||
 | 
					                <td>
 | 
				
			||||||
 | 
					                  <select id="audioInput"></select> 
 | 
				
			||||||
 | 
					                </td>
 | 
				
			||||||
 | 
					              </tr>
 | 
				
			||||||
 | 
					              <tr style="display:none"> <!-- not used (for now) -->
 | 
				
			||||||
 | 
					                <td>Audio</td>
 | 
				
			||||||
 | 
					                <td>
 | 
				
			||||||
 | 
					                  <select id="audioOutput"></select> 
 | 
				
			||||||
 | 
					                </td>
 | 
				
			||||||
 | 
					              </tr>
 | 
				
			||||||
 | 
					            </table>
 | 
				
			||||||
 | 
					          </div>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        <div class="tab">
 | 
				
			||||||
 | 
					          <div id="networking">
 | 
				
			||||||
 | 
					            Networking a la carte:<br>
 | 
				
			||||||
 | 
					            <table>
 | 
				
			||||||
 | 
					              <tr>
 | 
				
			||||||
 | 
					                <td>Webcam</td>
 | 
				
			||||||
 | 
					                <td>
 | 
				
			||||||
 | 
					                  <select id="webcam"></select>
 | 
				
			||||||
 | 
					                </td>
 | 
				
			||||||
 | 
					              </tr>
 | 
				
			||||||
 | 
					              <tr>
 | 
				
			||||||
 | 
					                <td>Chat</td>
 | 
				
			||||||
 | 
					                <td>
 | 
				
			||||||
 | 
					                  <select id="chatnetwork"></select>
 | 
				
			||||||
 | 
					                </td>
 | 
				
			||||||
 | 
					              </tr>
 | 
				
			||||||
 | 
					              <tr>
 | 
				
			||||||
 | 
					                <td>World sync</td>
 | 
				
			||||||
 | 
					                <td>
 | 
				
			||||||
 | 
					                  <select id="scene"></select>
 | 
				
			||||||
 | 
					                </td>
 | 
				
			||||||
 | 
					              </tr>
 | 
				
			||||||
 | 
					            </table>
 | 
				
			||||||
 | 
					          </div>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					      </div>
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					  `,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  init: (el) => new Proxy({
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    visible: true,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    webcam:       [{profile:{name:"No thanks"},config: () => document.createElement('div')}],
 | 
				
			||||||
 | 
					    chatnetwork:  [{profile:{name:"No thanks"},config: () => document.createElement('div')}],
 | 
				
			||||||
 | 
					    scene:        [{profile:{name:"No thanks"},config: () => document.createElement('div')}],
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    selectedWebcam:     '',
 | 
				
			||||||
 | 
					    selectedChatnetwork:'',
 | 
				
			||||||
 | 
					    selectedScene:      '',
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    $webcam:       $webcam      = el.querySelector("#webcam"),
 | 
				
			||||||
 | 
					    $chatnetwork:  $chatnetwork = el.querySelector("#chatnetwork"),
 | 
				
			||||||
 | 
					    $scene:        $scene       = el.querySelector("#scene"),
 | 
				
			||||||
 | 
					    $settings:     $settings    = el.querySelector("#settings"),
 | 
				
			||||||
 | 
					    $devices:      $devices     = el.querySelector("#devices"),
 | 
				
			||||||
 | 
					    $connect:      $connect     = el.querySelector("#connect"),
 | 
				
			||||||
 | 
					    $networking:   $networking  = el.querySelector("#networking"),
 | 
				
			||||||
 | 
					 
 | 
				
			||||||
 | 
					    $audioInput:   el.querySelector('select#audioInput'),
 | 
				
			||||||
 | 
					    $audioOutput:  el.querySelector('select#audioOutput'),
 | 
				
			||||||
 | 
					    $videoInput:   el.querySelector('select#videoInput'),
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    install(opts){
 | 
				
			||||||
 | 
					      this.opts  = opts;
 | 
				
			||||||
 | 
					      (['change']).map( (e) => el.addEventListener(e, (ev) => this[e] && this[e](ev.target.id,ev) ) )
 | 
				
			||||||
 | 
					      this.reactToNetwork()
 | 
				
			||||||
 | 
					      $menu.buttons = ([
 | 
				
			||||||
 | 
					        `<a class="btn" aria-label="button" aria-title="connect button" aria-description="use this to talk or chat with other people" id="meeting" onclick="$connections.show()"><i class="gg-user-add"></i> connect</a><br>`
 | 
				
			||||||
 | 
					      ]).concat($menu.buttons)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      if( document.location.href.match(/meet=/) ) this.show()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      setTimeout( () => document.dispatchEvent( new CustomEvent("$connections:ready", {detail: opts}) ), 1 )
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    toggle(){
 | 
				
			||||||
 | 
					      $chat.visible = !$chat.visible 
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    change(id,e){
 | 
				
			||||||
 | 
					      if( id.match(/^(webcam|chatnetwork|scene)$/) ){
 | 
				
			||||||
 | 
					        this.renderSettings() // trigger this when 'change' event fires on children dom elements
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    show(opts){
 | 
				
			||||||
 | 
					      opts = opts || {}
 | 
				
			||||||
 | 
					      if( opts.hide ){
 | 
				
			||||||
 | 
					        if( el.parentElement ) el.parentElement.parentElement.style.display = 'none' // hide along with wrapper elements
 | 
				
			||||||
 | 
					        if( !opts.showChat ) $chat.visible = false
 | 
				
			||||||
 | 
					      }else{
 | 
				
			||||||
 | 
					        $chat.visible = true
 | 
				
			||||||
 | 
					        this.visible  = true
 | 
				
			||||||
 | 
					        // hide networking settings if entering thru meetinglink
 | 
				
			||||||
 | 
					        $networking.style.display = document.location.href.match(/meet=/) ? 'none' : 'block'
 | 
				
			||||||
 | 
					        if( !network.connected ){
 | 
				
			||||||
 | 
					            document.querySelector('body > .xrf').appendChild(el)
 | 
				
			||||||
 | 
					            $chat.send({message:"", el, class:['ui']})
 | 
				
			||||||
 | 
					            if( !network.meetinglink ){ // set default
 | 
				
			||||||
 | 
					              $webcam.value      = opts.webcam      || 'Peer2Peer'
 | 
				
			||||||
 | 
					              $chatnetwork.value = opts.chatnetwork || 'Peer2Peer'
 | 
				
			||||||
 | 
					              $scene.value       = opts.scene       || 'Peer2Peer'
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            this.renderSettings()
 | 
				
			||||||
 | 
					        }else{
 | 
				
			||||||
 | 
					          $chat.send({message:"you are already connected, refresh page to create new connection",class:['info']})
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    update(){
 | 
				
			||||||
 | 
					      this.selectedWebcam      = $webcam.value
 | 
				
			||||||
 | 
					      this.selectedChatnetwork = $chatnetwork.value
 | 
				
			||||||
 | 
					      this.selectedScene       = $scene.value
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    forSelectedPluginsDo(cb){
 | 
				
			||||||
 | 
					      // this function looks weird but it's handy to prevent the same plugins rendering duplicate configurations
 | 
				
			||||||
 | 
					      let plugins = {}
 | 
				
			||||||
 | 
					      let select = (name) => (o) => o.profile.name == name ? plugins[ o.profile.name ] = o : ''
 | 
				
			||||||
 | 
					      this.webcam.find( select(this.selectedWebcam) )
 | 
				
			||||||
 | 
					      this.chatnetwork.find( select(this.selectedChatnetwork) )
 | 
				
			||||||
 | 
					      this.scene.find( select(this.selectedScene) )
 | 
				
			||||||
 | 
					      for( let i in plugins ){
 | 
				
			||||||
 | 
					        try{ cb(plugins[i]) }catch(e){ console.error(e) }
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    renderSettings(){
 | 
				
			||||||
 | 
					      let opts = {webcam: $webcam.value, chatnetwork: $chatnetwork.value, scene: $scene.value }
 | 
				
			||||||
 | 
					      this.update()
 | 
				
			||||||
 | 
					      $settings.innerHTML = ''
 | 
				
			||||||
 | 
					      this.forSelectedPluginsDo( (plugin) => $settings.appendChild( plugin.config({...opts,plugin}) ) )
 | 
				
			||||||
 | 
					      this.renderInputs()
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    renderInputs(){
 | 
				
			||||||
 | 
					      if( !this.selectedWebcam || this.selectedWebcam == 'No thanks' ){
 | 
				
			||||||
 | 
					        return this.$devices.style.display = 'none' 
 | 
				
			||||||
 | 
					      }else this.$devices.style.display = ''
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      navigator.mediaDevices.getUserMedia({
 | 
				
			||||||
 | 
					        audio: true,
 | 
				
			||||||
 | 
					        video: true
 | 
				
			||||||
 | 
					      })
 | 
				
			||||||
 | 
					      .then( () => {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const selectors = [this.$audioInput, this.$audioOutput, this.$videoInput];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const gotDevices = (deviceInfos) => {
 | 
				
			||||||
 | 
					          // Handles being called several times to update labels. Preserve values.
 | 
				
			||||||
 | 
					          const values = selectors.map(select => select.value);
 | 
				
			||||||
 | 
					          selectors.forEach(select => {
 | 
				
			||||||
 | 
					            while (select.firstChild) {
 | 
				
			||||||
 | 
					              select.removeChild(select.firstChild);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					          });
 | 
				
			||||||
 | 
					          for (let i = 0; i !== deviceInfos.length; ++i) {
 | 
				
			||||||
 | 
					            const deviceInfo = deviceInfos[i];
 | 
				
			||||||
 | 
					            const option = document.createElement('option');
 | 
				
			||||||
 | 
					            option.value = deviceInfo.deviceId;
 | 
				
			||||||
 | 
					            if (deviceInfo.kind === 'audioinput') {
 | 
				
			||||||
 | 
					              option.text = deviceInfo.label || `microphone ${this.$audioInput.length + 1}`;
 | 
				
			||||||
 | 
					              this.$audioInput.appendChild(option);
 | 
				
			||||||
 | 
					            } else if (deviceInfo.kind === 'audiooutput') {
 | 
				
			||||||
 | 
					              option.text = deviceInfo.label || `speaker ${this.$audioOutput.length + 1}`;
 | 
				
			||||||
 | 
					              this.$audioOutput.appendChild(option);
 | 
				
			||||||
 | 
					            } else if (deviceInfo.kind === 'videoinput') {
 | 
				
			||||||
 | 
					              option.text = deviceInfo.label || `camera this.${this.$videoInput.length + 1}`;
 | 
				
			||||||
 | 
					              this.$videoInput.appendChild(option);
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					              console.log('Some other kind of source/device: ', deviceInfo);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					          // hide if there's nothing to choose
 | 
				
			||||||
 | 
					          let totalDevices = this.$audioInput.options.length + this.$audioOutput.options.length + this.$videoInput.options.length
 | 
				
			||||||
 | 
					          this.$devices.style.display = totalDevices > 3 ? 'block' : 'none'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					          selectors.forEach((select, selectorIndex) => {
 | 
				
			||||||
 | 
					            if (Array.prototype.slice.call(select.childNodes).some(n => n.value === values[selectorIndex])) {
 | 
				
			||||||
 | 
					              select.value = values[selectorIndex];
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					          });
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        // after getUserMedia we can enumerate
 | 
				
			||||||
 | 
					        navigator.mediaDevices.enumerateDevices().then(gotDevices).catch(console.warn);
 | 
				
			||||||
 | 
					      })
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    reactToNetwork(){ // *TODO* move to network?
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      document.addEventListener('network.connect',    () => {
 | 
				
			||||||
 | 
					        this.show({hide:true, showChat: true})
 | 
				
			||||||
 | 
					      })
 | 
				
			||||||
 | 
					      document.addEventListener('network.disconnect',    () => {
 | 
				
			||||||
 | 
					        this.connected = false 
 | 
				
			||||||
 | 
					      })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  },{
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    get(data,k,v){ return data[k] },
 | 
				
			||||||
 | 
					    set(data,k,v){ 
 | 
				
			||||||
 | 
					      data[k] = v 
 | 
				
			||||||
 | 
					      switch( k ){
 | 
				
			||||||
 | 
					        case "visible":             el.style.display = v ? '' : 'none'; 
 | 
				
			||||||
 | 
					                                    if( !v && el.parentNode && el.parentNode.parentNode ) el.parentNode.parentNode.remove() 
 | 
				
			||||||
 | 
					                                    break;
 | 
				
			||||||
 | 
					        case "webcam":              $webcam.innerHTML       = `<option>${data[k].map((p)=>p.profile.name).join('</option><option>')}</option>`; break;
 | 
				
			||||||
 | 
					        case "chatnetwork":         $chatnetwork.innerHTML  = `<option>${data[k].map((p)=>p.profile.name).join('</option><option>')}</option>`; break;
 | 
				
			||||||
 | 
					        case "scene":               $scene.innerHTML        = `<option>${data[k].map((p)=>p.profile.name).join('</option><option>')}</option>`; break;
 | 
				
			||||||
 | 
					        case "selectedScene":       $scene.value       = v; data.renderSettings(); break;
 | 
				
			||||||
 | 
					        case "selectedChatnetwork": $chatnetwork.value = v; data.renderSettings(); break;
 | 
				
			||||||
 | 
					        case "selectedWebcam":      {
 | 
				
			||||||
 | 
					                                        $webcam.value      = v; 
 | 
				
			||||||
 | 
					                                        data.renderSettings(); 
 | 
				
			||||||
 | 
					                                        $devices.style.display = v ? 'block' : 'none'
 | 
				
			||||||
 | 
					                                        break;
 | 
				
			||||||
 | 
					                                    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      } 
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  })
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// reactify component!
 | 
				
			||||||
 | 
					document.addEventListener('$chat:ready', (opts) => {
 | 
				
			||||||
 | 
					  opts = opts.detail
 | 
				
			||||||
 | 
					  document.head.innerHTML += connectionsComponent.css 
 | 
				
			||||||
 | 
					  window.$connections = document.createElement('div')
 | 
				
			||||||
 | 
					  $connections.innerHTML = connectionsComponent.html
 | 
				
			||||||
 | 
					  $connections = connectionsComponent.init($connections)
 | 
				
			||||||
 | 
					  $connections.install(opts)
 | 
				
			||||||
 | 
					})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// alpine component for displaying meetings
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					connectionsComponent.css = `
 | 
				
			||||||
 | 
					    <style type="text/css">
 | 
				
			||||||
 | 
					      button#connect{
 | 
				
			||||||
 | 
					        height: 43px;
 | 
				
			||||||
 | 
					        width:100%;
 | 
				
			||||||
 | 
					        margin: 0px;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      #messages .msg #connections{
 | 
				
			||||||
 | 
					        position:relative;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      .connecthide {
 | 
				
			||||||
 | 
					        transform:translateY(-1000px);
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      #close{
 | 
				
			||||||
 | 
					        display: block;
 | 
				
			||||||
 | 
					        position: relative;
 | 
				
			||||||
 | 
					        float: right;
 | 
				
			||||||
 | 
					        top: 16px;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      #messages .msg.ui  div.tab-frame > div.tab{ padding:25px 10px 5px 10px;}
 | 
				
			||||||
 | 
					   </style>`
 | 
				
			||||||
 | 
					chatComponent = {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  html: `
 | 
				
			||||||
 | 
					    <div id="chat">
 | 
				
			||||||
 | 
					     <div id="videos" style="pointer-events:none"></div>
 | 
				
			||||||
 | 
					     <div id="messages" aria-live="assertive" role="log" aria-relevant="additions"></div>
 | 
				
			||||||
 | 
					     <div id="chatfooter">
 | 
				
			||||||
 | 
					       <div id="chatbar">
 | 
				
			||||||
 | 
					           <input id="chatline" type="text" placeholder="chat here"></input>
 | 
				
			||||||
 | 
					       </div>
 | 
				
			||||||
 | 
					       <button id="chatsend" class="btn" aria-label="send message">
 | 
				
			||||||
 | 
					          <i class="gg-chevron-right-o"></i>
 | 
				
			||||||
 | 
					       </button>
 | 
				
			||||||
 | 
					     </div>
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					  `,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  init: (el) => new Proxy({
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    scene:             null,
 | 
				
			||||||
 | 
					    visible:           true,
 | 
				
			||||||
 | 
					    messages:          [],
 | 
				
			||||||
 | 
					    oneMessagePerUser: false,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    username: '',    // configured by 'network.connected' event
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    $videos:         el.querySelector("#videos"),
 | 
				
			||||||
 | 
					    $messages:       el.querySelector("#messages"),
 | 
				
			||||||
 | 
					    $chatline:       el.querySelector("#chatline"),
 | 
				
			||||||
 | 
					    $chatbar:        el.querySelector("#chatbar"),
 | 
				
			||||||
 | 
					    $chatsend:       el.querySelector("#chatsend"),
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    install(opts){
 | 
				
			||||||
 | 
					      this.opts  = opts
 | 
				
			||||||
 | 
					      this.scene = opts.scene 
 | 
				
			||||||
 | 
					      this.$chatbar.style.display = 'none'
 | 
				
			||||||
 | 
					      el.className = "xrf"
 | 
				
			||||||
 | 
					      el.style.display = 'none' // start hidden 
 | 
				
			||||||
 | 
					      document.body.appendChild( el )
 | 
				
			||||||
 | 
					      document.dispatchEvent( new CustomEvent("$chat:ready", {detail: opts}) )
 | 
				
			||||||
 | 
					      this.send({message:`Welcome to <b>${document.location.search.substr(1)}</b>, a 3D scene(file) which simply links to other ones.<br>You can start a solo offline exploration in XR right away.<br>Type /help below, or use the arrow- or WASD-keys on your keyboard, and mouse-drag to rotate.<br>`, class: ["info","guide","multiline"] })
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    sendInput(value){
 | 
				
			||||||
 | 
					      if( value[0] == '#' ) return xrf.navigator.to(value)
 | 
				
			||||||
 | 
					      let event   = value.match(/^[!\/]/) ? "chat.command" : "network.send"
 | 
				
			||||||
 | 
					      let message = value.replace(/^[!\/]/,'')
 | 
				
			||||||
 | 
					      let raw     = {detail:{message:value, halt:false}}
 | 
				
			||||||
 | 
					      document.dispatchEvent( new CustomEvent( event, {detail: {message}} ) )
 | 
				
			||||||
 | 
					      document.dispatchEvent( new CustomEvent( "chat.input", raw ) )
 | 
				
			||||||
 | 
					      if( event == "network.send" && !raw.detail.halt ) this.send({message: value })
 | 
				
			||||||
 | 
					      this.$chatline.lastValue = value
 | 
				
			||||||
 | 
					      this.$chatline.value = ''
 | 
				
			||||||
 | 
					      if( window.innerHeight < 600 ) this.$chatline.blur()
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    initListeners(){
 | 
				
			||||||
 | 
					      let {$chatline} = this
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      $chatline.addEventListener('click', (e) => this.inform() )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      $chatline.addEventListener('keydown', (e) => {
 | 
				
			||||||
 | 
					        if (e.key == 'Enter' ){
 | 
				
			||||||
 | 
					          this.sendInput($chatline.value)
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        if (e.key == 'ArrowUp' ){
 | 
				
			||||||
 | 
					          $chatline.value = $chatline.lastValue || ''
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      document.addEventListener('network.connect', (e) => {
 | 
				
			||||||
 | 
					        this.visible = true
 | 
				
			||||||
 | 
					        this.$chatbar.style.display = '' // show
 | 
				
			||||||
 | 
					      })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      document.addEventListener('network.connected', (e) => {
 | 
				
			||||||
 | 
					        if( e.detail.username ) this.username = e.detail.username
 | 
				
			||||||
 | 
					      })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      document.addEventListener('chat.command', (e) => {
 | 
				
			||||||
 | 
					        if( String(e.detail.message).trim() == 'help' ){
 | 
				
			||||||
 | 
					          let detail = {message:`The following commands are available
 | 
				
			||||||
 | 
					          <br><br>
 | 
				
			||||||
 | 
					          <b class="badge">/help</b> shows this help screen
 | 
				
			||||||
 | 
					          `}
 | 
				
			||||||
 | 
					          document.dispatchEvent( new CustomEvent( 'chat.command.help', {detail}))
 | 
				
			||||||
 | 
					          this.send({message: detail.message})
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      this.$chatsend.addEventListener('click', (e) => {
 | 
				
			||||||
 | 
					        this.sendInput($chatline.value)
 | 
				
			||||||
 | 
					      })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    inform(){
 | 
				
			||||||
 | 
					      if( !this.inform.informed && (this.inform.informed = true) ){
 | 
				
			||||||
 | 
					        window.notify("You can now type messages in the textfield below.")
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    toggle(){
 | 
				
			||||||
 | 
					      this.visible = !this.visible
 | 
				
			||||||
 | 
					      if( this.visible && window.meeting.status == 'offline' ) window.meeting.start(this.opts)
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    hyphenate(str){
 | 
				
			||||||
 | 
					      return String(str).replace(/[^a-zA-Z0-9]/g,'-')
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // sending messages to the #messages div 
 | 
				
			||||||
 | 
					    // every user can post maximum one msg at a time 
 | 
				
			||||||
 | 
					    // it's more like a 'status' which is more friendly 
 | 
				
			||||||
 | 
					    // for accessibility reasons 
 | 
				
			||||||
 | 
					    // for a fullfledged chat/transcript see matrix clients
 | 
				
			||||||
 | 
					    send(opts){
 | 
				
			||||||
 | 
					      let {$messages} = this
 | 
				
			||||||
 | 
					      opts = { linebreak:true, message:"", class:[], ...opts }
 | 
				
			||||||
 | 
					      if( window.frontend && window.frontend.emit ) window.frontend.emit('$chat.send', opts )
 | 
				
			||||||
 | 
					      opts.pos  = opts.pos || network.posName || network.pos
 | 
				
			||||||
 | 
					      let div  = document.createElement('div')
 | 
				
			||||||
 | 
					      let msg  = document.createElement('div')
 | 
				
			||||||
 | 
					      let br   = document.createElement('br')
 | 
				
			||||||
 | 
					      let nick = document.createElement('div')
 | 
				
			||||||
 | 
					      msg.className = "msg"
 | 
				
			||||||
 | 
					      let html = `${ opts.message || ''}${ opts.html ? opts.html(opts) : ''}`
 | 
				
			||||||
 | 
					      if( $messages.last == html ) return
 | 
				
			||||||
 | 
					      msg.innerHTML = html 
 | 
				
			||||||
 | 
					      if( opts.el ) msg.appendChild(opts.el)
 | 
				
			||||||
 | 
					      opts.id       = Math.random()
 | 
				
			||||||
 | 
					      if( opts.class ){
 | 
				
			||||||
 | 
					        msg.classList.add.apply(msg.classList, opts.class)
 | 
				
			||||||
 | 
					        br.classList.add.apply(br.classList, opts.class)
 | 
				
			||||||
 | 
					        div.classList.add.apply(div.classList, opts.class.concat(["envelope"]))
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      if( !msg.className.match(/(info|guide|ui)/) && !opts.from ){
 | 
				
			||||||
 | 
					        let frag = xrf.URI.parse(document.location.hash).XRF
 | 
				
			||||||
 | 
					        opts.from = 'you'
 | 
				
			||||||
 | 
					        if( frag.pos ) opts.pos = frag.pos.string
 | 
				
			||||||
 | 
					        msg.classList.add('self')
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      if( opts.from ){
 | 
				
			||||||
 | 
					        nick.className = "user"
 | 
				
			||||||
 | 
					        nick.innerText = opts.from+' '
 | 
				
			||||||
 | 
					        div.appendChild(nick)
 | 
				
			||||||
 | 
					        if( opts.pos ){
 | 
				
			||||||
 | 
					          let a = document.createElement("a")
 | 
				
			||||||
 | 
					          a.href = a.innerText = `#pos=${opts.pos}`
 | 
				
			||||||
 | 
					          nick.appendChild(a)
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      div.appendChild(msg)
 | 
				
			||||||
 | 
					      // force one message per user 
 | 
				
			||||||
 | 
					      if( this.oneMessagePerUser && opts.from ){
 | 
				
			||||||
 | 
					        div.id = this.hyphenate(opts.from)
 | 
				
			||||||
 | 
					        let oldMsg = $messages.querySelector(`#${div.id}`)
 | 
				
			||||||
 | 
					        if( oldMsg ) oldMsg.remove()
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      // remove after timeout
 | 
				
			||||||
 | 
					      if( opts.timeout ) setTimeout( (div) => div.remove(), opts.timeout, div )
 | 
				
			||||||
 | 
					      // finally add the message on top
 | 
				
			||||||
 | 
					      $messages.appendChild(div)
 | 
				
			||||||
 | 
					      if( opts.linebreak ) div.appendChild(br)
 | 
				
			||||||
 | 
					      $messages.scrollTop = $messages.scrollHeight // scroll down
 | 
				
			||||||
 | 
					      $messages.last = msg.innerHTML
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    getChatLog(){
 | 
				
			||||||
 | 
					        return ([...this.$messages.querySelectorAll('.envelope')])
 | 
				
			||||||
 | 
					                .filter( (d) => !d.className.match(/(info|ui)/) ) 
 | 
				
			||||||
 | 
					                .map( (d) => d.innerHTML )
 | 
				
			||||||
 | 
					                .join('\n')
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  },{
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    get(me,k,v){ return me[k] },
 | 
				
			||||||
 | 
					    set(me,k,v){ 
 | 
				
			||||||
 | 
					      me[k] = v    
 | 
				
			||||||
 | 
					      switch( k ){
 | 
				
			||||||
 | 
					        case "visible":         { 
 | 
				
			||||||
 | 
					                                  el.style.display = me.visible ? 'block' : 'none'
 | 
				
			||||||
 | 
					                                  if( !el.inited && (el.inited = true) ) me.initListeners()
 | 
				
			||||||
 | 
					                                  break;
 | 
				
			||||||
 | 
					                                }
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  })
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// reactify component!
 | 
				
			||||||
 | 
					document.addEventListener('$menu:ready', (opts) => {
 | 
				
			||||||
 | 
					  opts = opts.detail
 | 
				
			||||||
 | 
					  document.head.innerHTML += chatComponent.css 
 | 
				
			||||||
 | 
					  window.$chat = document.createElement('div')
 | 
				
			||||||
 | 
					  $chat.innerHTML = chatComponent.html
 | 
				
			||||||
 | 
					  $chat = chatComponent.init($chat)
 | 
				
			||||||
 | 
					  $chat.install(opts)
 | 
				
			||||||
 | 
					  //$menu.buttons = ([`<a class="btn" aria-label="button" aria-description="toggle text" id="meeting" onclick="$chat.toggle()">📜 toggle text</a><br>`])
 | 
				
			||||||
 | 
					  //                  .concat($menu.buttons)
 | 
				
			||||||
 | 
					})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// alpine component for displaying meetings
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					chatComponent.css = `
 | 
				
			||||||
 | 
					    <style type="text/css">
 | 
				
			||||||
 | 
					     #videos{
 | 
				
			||||||
 | 
					       display:grid-auto-columns;
 | 
				
			||||||
 | 
					       grid-column-gap:5px;
 | 
				
			||||||
 | 
					       margin-bottom:15px;
 | 
				
			||||||
 | 
					       position: fixed;
 | 
				
			||||||
 | 
					       top: 0;
 | 
				
			||||||
 | 
					       left: 0;
 | 
				
			||||||
 | 
					       bottom: 0;
 | 
				
			||||||
 | 
					       right: 0;
 | 
				
			||||||
 | 
					       margin: 15px;
 | 
				
			||||||
 | 
					       z-index:1000;
 | 
				
			||||||
 | 
					     }
 | 
				
			||||||
 | 
					     #videos > video{
 | 
				
			||||||
 | 
					       border-radius:7px;
 | 
				
			||||||
 | 
					       display:inline-block;
 | 
				
			||||||
 | 
					       background:black;
 | 
				
			||||||
 | 
					       width:80px;
 | 
				
			||||||
 | 
					       height:60px;
 | 
				
			||||||
 | 
					       margin-right:5px;
 | 
				
			||||||
 | 
					       margin-bottom:5px;
 | 
				
			||||||
 | 
					       vertical-align:top;
 | 
				
			||||||
 | 
					       pointer-events:all;
 | 
				
			||||||
 | 
					     }
 | 
				
			||||||
 | 
					     #videos > video:hover{
 | 
				
			||||||
 | 
					       filter: brightness(1.8);
 | 
				
			||||||
 | 
					       cursor:pointer;
 | 
				
			||||||
 | 
					     }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					     #chatbar,
 | 
				
			||||||
 | 
					     button#chatsend{
 | 
				
			||||||
 | 
					       z-index: 1500;
 | 
				
			||||||
 | 
					       position: fixed;
 | 
				
			||||||
 | 
					       bottom: 24px;
 | 
				
			||||||
 | 
					       height: 34px;
 | 
				
			||||||
 | 
					       left: 20px;
 | 
				
			||||||
 | 
					       width: 48%;
 | 
				
			||||||
 | 
					       background: white;
 | 
				
			||||||
 | 
					       padding: 0px 0px 0px 15px; 
 | 
				
			||||||
 | 
					       border-radius: 30px;
 | 
				
			||||||
 | 
					       max-width: 500px;
 | 
				
			||||||
 | 
					       box-sizing: border-box;
 | 
				
			||||||
 | 
					       box-shadow: 0px 0px 5px 5px #0002;
 | 
				
			||||||
 | 
					     }
 | 
				
			||||||
 | 
					     button#chatsend{
 | 
				
			||||||
 | 
					      line-height:0px;
 | 
				
			||||||
 | 
					      display:none;
 | 
				
			||||||
 | 
					      z-index: 1550;
 | 
				
			||||||
 | 
					      color: white;
 | 
				
			||||||
 | 
					      border: 0;
 | 
				
			||||||
 | 
					      height: 35px;
 | 
				
			||||||
 | 
					      background: var(--xrf-dark-gray);
 | 
				
			||||||
 | 
					      font-weight: bold;
 | 
				
			||||||
 | 
					      width: 20px;
 | 
				
			||||||
 | 
					      max-width: 20px;
 | 
				
			||||||
 | 
					      border-radius: 20px 0px 0px 20px;
 | 
				
			||||||
 | 
					      overflow: hidden;
 | 
				
			||||||
 | 
					      margin:0;
 | 
				
			||||||
 | 
					     }
 | 
				
			||||||
 | 
					     #chatbar input{
 | 
				
			||||||
 | 
					       border:none;
 | 
				
			||||||
 | 
					       width:90%;
 | 
				
			||||||
 | 
					       box-sizing:border-box;
 | 
				
			||||||
 | 
					       height: 24px;
 | 
				
			||||||
 | 
					       font-size: var(--xrf-font-size-2);
 | 
				
			||||||
 | 
					       max-width:unset;
 | 
				
			||||||
 | 
					     }
 | 
				
			||||||
 | 
					     #messages{
 | 
				
			||||||
 | 
					       /*
 | 
				
			||||||
 | 
					       display: flex;
 | 
				
			||||||
 | 
					       flex-direction: column;
 | 
				
			||||||
 | 
					       width: 100%;
 | 
				
			||||||
 | 
					       max-width: 40%;
 | 
				
			||||||
 | 
					       */
 | 
				
			||||||
 | 
					       width:100%;
 | 
				
			||||||
 | 
					       box-sizing:border-box;
 | 
				
			||||||
 | 
					       align-items: flex-start;
 | 
				
			||||||
 | 
					       position: absolute;
 | 
				
			||||||
 | 
					       transition:1s;
 | 
				
			||||||
 | 
					       top: 77px;
 | 
				
			||||||
 | 
					       left: 0;
 | 
				
			||||||
 | 
					       bottom: 49px;
 | 
				
			||||||
 | 
					       padding: 20px;
 | 
				
			||||||
 | 
					       overflow:hidden;
 | 
				
			||||||
 | 
					       overflow-y: auto;
 | 
				
			||||||
 | 
					       pointer-events:none;
 | 
				
			||||||
 | 
					       transition:1s;
 | 
				
			||||||
 | 
					       z-index: 100;
 | 
				
			||||||
 | 
					     }
 | 
				
			||||||
 | 
					     body.menu #messages{
 | 
				
			||||||
 | 
					       top:50px;
 | 
				
			||||||
 | 
					     }
 | 
				
			||||||
 | 
					     #messages:hover {
 | 
				
			||||||
 | 
					       pointer-events:all;
 | 
				
			||||||
 | 
					     }
 | 
				
			||||||
 | 
					     #messages *{
 | 
				
			||||||
 | 
					       box-sizing:border-box;
 | 
				
			||||||
 | 
					/*
 | 
				
			||||||
 | 
					       pointer-events:none;
 | 
				
			||||||
 | 
					       -webkit-user-select:none;
 | 
				
			||||||
 | 
					       -moz-user-select:-moz-none;
 | 
				
			||||||
 | 
					       -ms-user-select:none;
 | 
				
			||||||
 | 
					       user-select:none;
 | 
				
			||||||
 | 
					*/
 | 
				
			||||||
 | 
					     }
 | 
				
			||||||
 | 
					     #messages .msg{
 | 
				
			||||||
 | 
					       transition:all 1s ease;
 | 
				
			||||||
 | 
					       background: #fff;
 | 
				
			||||||
 | 
					       display: inline-block;
 | 
				
			||||||
 | 
					       padding: 1px 17px;
 | 
				
			||||||
 | 
					       border-radius: 20px;
 | 
				
			||||||
 | 
					       color: #000c;
 | 
				
			||||||
 | 
					       margin-bottom: 10px;
 | 
				
			||||||
 | 
					       line-height:23px;
 | 
				
			||||||
 | 
					       line-height:33px;
 | 
				
			||||||
 | 
					       cursor:grabbing;
 | 
				
			||||||
 | 
					       border: 1px solid #0002;
 | 
				
			||||||
 | 
					     }
 | 
				
			||||||
 | 
					     #messages .msg *,
 | 
				
			||||||
 | 
					     #messages .user *{
 | 
				
			||||||
 | 
					       pointer-events:all;
 | 
				
			||||||
 | 
					       -webkit-user-select:text;
 | 
				
			||||||
 | 
					       -moz-user-select:-moz-text;
 | 
				
			||||||
 | 
					       -ms-user-select:text;
 | 
				
			||||||
 | 
					       user-select:text;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					     #messages .msg.self{
 | 
				
			||||||
 | 
					       border-radius: 20px;
 | 
				
			||||||
 | 
					       background:var(--xrf-dark-gray);
 | 
				
			||||||
 | 
					     }
 | 
				
			||||||
 | 
					     #messages .msg.self,
 | 
				
			||||||
 | 
					     #messages .msg.self div{
 | 
				
			||||||
 | 
					       color:#FFF;
 | 
				
			||||||
 | 
					     }
 | 
				
			||||||
 | 
					     #messages .msg.info{
 | 
				
			||||||
 | 
					       background: var(--xrf-white);
 | 
				
			||||||
 | 
					       border-radius: 20px;
 | 
				
			||||||
 | 
					       color: var(--xrf-dark-gray);
 | 
				
			||||||
 | 
					       text-align: left;
 | 
				
			||||||
 | 
					       line-height: 19px;
 | 
				
			||||||
 | 
					     }
 | 
				
			||||||
 | 
					     #messages .msg.info,
 | 
				
			||||||
 | 
					     #messages .msg.info *{
 | 
				
			||||||
 | 
					       font-size: var(--xrf-font-size-0);
 | 
				
			||||||
 | 
					     }
 | 
				
			||||||
 | 
					     #messages .msg a {
 | 
				
			||||||
 | 
					       text-decoration:underline;
 | 
				
			||||||
 | 
					       color: var(--xrf-light-xrf-secondary);
 | 
				
			||||||
 | 
					       font-weight:bold;
 | 
				
			||||||
 | 
					       transition:0.3s;
 | 
				
			||||||
 | 
					     }
 | 
				
			||||||
 | 
					     #messages .msg.info a,
 | 
				
			||||||
 | 
					     #messages a.ruler{
 | 
				
			||||||
 | 
					       color:#FFF;
 | 
				
			||||||
 | 
					     }
 | 
				
			||||||
 | 
					     #messages .msg a:hover{
 | 
				
			||||||
 | 
					        color:#000;
 | 
				
			||||||
 | 
					     }
 | 
				
			||||||
 | 
					     #messages .msg.ui, 
 | 
				
			||||||
 | 
					     #messages .msg.ui div{ 
 | 
				
			||||||
 | 
					       background: white;
 | 
				
			||||||
 | 
					       border:none;
 | 
				
			||||||
 | 
					       color: #333;
 | 
				
			||||||
 | 
					       border-radius: 20px;
 | 
				
			||||||
 | 
					       margin:0;
 | 
				
			||||||
 | 
					       padding:0px 5px 5px 5px;
 | 
				
			||||||
 | 
					     }
 | 
				
			||||||
 | 
					     #messages.guide, .guide{
 | 
				
			||||||
 | 
					      display:unset;
 | 
				
			||||||
 | 
					     }
 | 
				
			||||||
 | 
					     #messages .guide, .guide{
 | 
				
			||||||
 | 
					       display:none;
 | 
				
			||||||
 | 
					     }
 | 
				
			||||||
 | 
					     br.guide{
 | 
				
			||||||
 | 
					       display:inline-block;
 | 
				
			||||||
 | 
					     }
 | 
				
			||||||
 | 
					     #messages .msg.info a:hover,
 | 
				
			||||||
 | 
					     #messages button:hover{
 | 
				
			||||||
 | 
					       filter: brightness(1.4);
 | 
				
			||||||
 | 
					     }
 | 
				
			||||||
 | 
					     #messages .msg.multiline {
 | 
				
			||||||
 | 
					       padding: 2px 14px;
 | 
				
			||||||
 | 
					     }
 | 
				
			||||||
 | 
					     #messages button {
 | 
				
			||||||
 | 
					       text-decoration:none;
 | 
				
			||||||
 | 
					       margin: 0px 15px 10px 0px;
 | 
				
			||||||
 | 
					       background: var(--xrf-primary);
 | 
				
			||||||
 | 
					       font-family: var(--xrf-font-sans-serif);
 | 
				
			||||||
 | 
					       color: #FFF;
 | 
				
			||||||
 | 
					       border-radius: 7px;
 | 
				
			||||||
 | 
					       padding: 11px 15px;
 | 
				
			||||||
 | 
					       border: 0;
 | 
				
			||||||
 | 
					       font-weight: bold;
 | 
				
			||||||
 | 
					       box-shadow: 0px 0px 5px 5px #0002;
 | 
				
			||||||
 | 
					       pointer-events:all;
 | 
				
			||||||
 | 
					     }
 | 
				
			||||||
 | 
					     #messages,#chatbar,#chatbar *, #messages *{
 | 
				
			||||||
 | 
					     }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    #messages button.emoticon,
 | 
				
			||||||
 | 
					    #messages .btn.emoticon {
 | 
				
			||||||
 | 
					      line-height:2px;
 | 
				
			||||||
 | 
					      width: 20px;
 | 
				
			||||||
 | 
					      display: inline-block;
 | 
				
			||||||
 | 
					      padding: 0px 0px;
 | 
				
			||||||
 | 
					      margin: 0;
 | 
				
			||||||
 | 
					      vertical-align: middle;
 | 
				
			||||||
 | 
					      background: none;
 | 
				
			||||||
 | 
					      border: none;
 | 
				
			||||||
 | 
					      min-width: 31px;
 | 
				
			||||||
 | 
					      box-shadow:none;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    #messages button.emoticon:hover,
 | 
				
			||||||
 | 
					    #messages .btn.emoticon:hover {
 | 
				
			||||||
 | 
					      border: 1px solid #ccc !important;
 | 
				
			||||||
 | 
					      background:#EEE;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    .nomargin{
 | 
				
			||||||
 | 
					      margin:0;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    .envelope{
 | 
				
			||||||
 | 
					      margin-right:15px;
 | 
				
			||||||
 | 
					      width:100%;
 | 
				
			||||||
 | 
					      max-width:40%;
 | 
				
			||||||
 | 
					      margin-bottom:7px;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    .envelope,
 | 
				
			||||||
 | 
					    .envelope * {
 | 
				
			||||||
 | 
					      transition:1s;
 | 
				
			||||||
 | 
					      pointer-events:none;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    .envelope a,
 | 
				
			||||||
 | 
					    .envelope button,
 | 
				
			||||||
 | 
					    .envelope input,
 | 
				
			||||||
 | 
					    .envelope textarea,
 | 
				
			||||||
 | 
					    .envelope msg,
 | 
				
			||||||
 | 
					    .envelope msg * {
 | 
				
			||||||
 | 
					      pointer-events:all;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    .user{
 | 
				
			||||||
 | 
					      margin-left:13px;
 | 
				
			||||||
 | 
					      font-weight: bold;
 | 
				
			||||||
 | 
					      color: var(--xrf-dark-gray);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    .user, .user *{ 
 | 
				
			||||||
 | 
					      font-size: var(--xrf-font-size-0);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    .gg-chevron-right-o {
 | 
				
			||||||
 | 
					      color:#FFF;
 | 
				
			||||||
 | 
					      box-sizing: border-box;
 | 
				
			||||||
 | 
					      position: relative;
 | 
				
			||||||
 | 
					      display: block;
 | 
				
			||||||
 | 
					      transform: scale(var(--ggs,1));
 | 
				
			||||||
 | 
					      width: 22px;
 | 
				
			||||||
 | 
					      height: 22px;
 | 
				
			||||||
 | 
					      border: 2px solid;
 | 
				
			||||||
 | 
					      border-radius: 100px
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    .gg-chevron-right-o::after {
 | 
				
			||||||
 | 
					      color:#FFF;
 | 
				
			||||||
 | 
					      content: "";
 | 
				
			||||||
 | 
					      display: block;
 | 
				
			||||||
 | 
					      box-sizing: border-box;
 | 
				
			||||||
 | 
					      position: absolute;
 | 
				
			||||||
 | 
					      width: 6px;
 | 
				
			||||||
 | 
					      height: 6px;
 | 
				
			||||||
 | 
					      border-bottom: 2px solid;
 | 
				
			||||||
 | 
					      border-right: 2px solid;
 | 
				
			||||||
 | 
					      transform: rotate(-45deg);
 | 
				
			||||||
 | 
					      left: 5px;
 | 
				
			||||||
 | 
					      top: 6px
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					   </style>`
 | 
				
			||||||
 | 
					window.debug = (opts) => new Proxy({
 | 
				
			||||||
 | 
					  opts,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  enabled: false,
 | 
				
			||||||
 | 
					  $console: false,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  toggle(){ this.enabled = !this.enabled },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  settings(){
 | 
				
			||||||
 | 
					    this.toggle()
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  init(){
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  setupConsole(){
 | 
				
			||||||
 | 
					    // add onscreen console
 | 
				
			||||||
 | 
					    let $console = this.$console = document.createElement('pre')
 | 
				
			||||||
 | 
					    $console.style.position = 'fixed'
 | 
				
			||||||
 | 
					    $console.style.overflow = 'auto'
 | 
				
			||||||
 | 
					    $console.style.top = $console.style.left = $console.style.bottom = $console.style.right = '0px'
 | 
				
			||||||
 | 
					    $console.style.height = '98.5vh';
 | 
				
			||||||
 | 
					    $console.style.width = '100%'
 | 
				
			||||||
 | 
					    $console.style.pointerEvents = 'none'
 | 
				
			||||||
 | 
					    $console.id = 'console'
 | 
				
			||||||
 | 
					    document.body.appendChild($console)
 | 
				
			||||||
 | 
					    const wrapper = (scope, fn, name) => {
 | 
				
			||||||
 | 
					      return function(msg) {
 | 
				
			||||||
 | 
					        $console.innerHTML += `[${name}] ${msg}<br>`;
 | 
				
			||||||
 | 
					        if( name == 'err'){
 | 
				
			||||||
 | 
					          let err = new Error()
 | 
				
			||||||
 | 
					          String(err.stack).split("\n").slice(2).map( (l) => $console.innerHTML += ` └☑ ${l}\n` )
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        $console.scrollTop = $console.scrollHeight;
 | 
				
			||||||
 | 
					        fn.call(scope,msg);
 | 
				
			||||||
 | 
					      };
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    window.console.log   = wrapper(console, console.log, "log");
 | 
				
			||||||
 | 
					    window.console.warn  = wrapper(console, console.warn, "wrn");
 | 
				
			||||||
 | 
					    window.console.error = wrapper(console, console.error, "err");
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					},
 | 
				
			||||||
 | 
					{ 
 | 
				
			||||||
 | 
					  // auto-trigger events on changes 
 | 
				
			||||||
 | 
					  get(data,k,receiver){ return data[k] },
 | 
				
			||||||
 | 
					  set(data,k,v){ 
 | 
				
			||||||
 | 
					    data[k] = v 
 | 
				
			||||||
 | 
					    switch( k ){
 | 
				
			||||||
 | 
					      case "enabled": {
 | 
				
			||||||
 | 
					                        if( !data.$console ) data.setupConsole()
 | 
				
			||||||
 | 
					                        $('#debug.btn').style.filter= v ? 'brightness(1.0)' : 'brightness(0.5)'
 | 
				
			||||||
 | 
					                        data.$console.style.display = v ? 'block' : 'none'
 | 
				
			||||||
 | 
					                        data.enabled = v
 | 
				
			||||||
 | 
					                      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					document.addEventListener('$menu:ready', (e) => {
 | 
				
			||||||
 | 
					  try{
 | 
				
			||||||
 | 
					    debug = debug(e.detail) 
 | 
				
			||||||
 | 
					    debug.init()
 | 
				
			||||||
 | 
					    document.dispatchEvent( new CustomEvent("debug:ready", e ) )
 | 
				
			||||||
 | 
					    $menu.buttons = $menu.buttons.concat([`<a class="btn" style="background:var(--xrf-dark-gray);filter: brightness(0.5);" aria-label="button" aria-description="enable all debug features" id="debug" onclick="debug.enabled = !debug.enabled"><i class="gg-debug"></i>debug</a><br>`])
 | 
				
			||||||
 | 
					  }catch(e){console.error(e)}
 | 
				
			||||||
 | 
					})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					document.querySelector('head').innerHTML += `
 | 
				
			||||||
 | 
					  <style type="text/css"> 
 | 
				
			||||||
 | 
					    .gg-debug {
 | 
				
			||||||
 | 
					       box-sizing: border-box;
 | 
				
			||||||
 | 
					       position: relative;
 | 
				
			||||||
 | 
					       display: block;
 | 
				
			||||||
 | 
					       transform: scale(var(--ggs,1));
 | 
				
			||||||
 | 
					       width: 12px;
 | 
				
			||||||
 | 
					       height: 18px;
 | 
				
			||||||
 | 
					       border: 2px solid;
 | 
				
			||||||
 | 
					       border-radius: 22px;
 | 
				
			||||||
 | 
					       display: inline-block;
 | 
				
			||||||
 | 
					       transform: translate(0px,4px);
 | 
				
			||||||
 | 
					       margin-right: 21px;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    .gg-debug::after,
 | 
				
			||||||
 | 
					    .gg-debug::before {
 | 
				
			||||||
 | 
					       content: "";
 | 
				
			||||||
 | 
					       display: block;
 | 
				
			||||||
 | 
					       box-sizing: border-box;
 | 
				
			||||||
 | 
					       position: absolute
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    .gg-debug::before {
 | 
				
			||||||
 | 
					       width: 8px;
 | 
				
			||||||
 | 
					       height: 4px;
 | 
				
			||||||
 | 
					       border: 2px solid;
 | 
				
			||||||
 | 
					       top: -4px;
 | 
				
			||||||
 | 
					       border-bottom-left-radius: 10px;
 | 
				
			||||||
 | 
					       border-bottom-right-radius: 10px;
 | 
				
			||||||
 | 
					       border-top: 0
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    .gg-debug::after {
 | 
				
			||||||
 | 
					       background: currentColor;
 | 
				
			||||||
 | 
					       width: 4px;
 | 
				
			||||||
 | 
					       height: 2px;
 | 
				
			||||||
 | 
					       border-radius: 5px;
 | 
				
			||||||
 | 
					       top: 4px;
 | 
				
			||||||
 | 
					       left: 2px;
 | 
				
			||||||
 | 
					       box-shadow:
 | 
				
			||||||
 | 
					          0 4px 0,
 | 
				
			||||||
 | 
					          -6px -2px 0,
 | 
				
			||||||
 | 
					          -6px 2px 0,
 | 
				
			||||||
 | 
					          -6px 6px 0,
 | 
				
			||||||
 | 
					          6px -2px 0,
 | 
				
			||||||
 | 
					          6px 2px 0,
 | 
				
			||||||
 | 
					          6px 6px 0
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  </style>
 | 
				
			||||||
 | 
					`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}).apply({})
 | 
				
			||||||
							
								
								
									
										392
									
								
								dist/xrfragment.plugin.p2p.js
									
										
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										392
									
								
								dist/xrfragment.plugin.p2p.js
									
										
									
									
										vendored
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
							
								
								
									
										293
									
								
								dist/xrfragment.plugin.remotestorage.js
									
										
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										293
									
								
								dist/xrfragment.plugin.remotestorage.js
									
										
									
									
										vendored
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,293 @@
 | 
				
			||||||
 | 
					/*
 | 
				
			||||||
 | 
					 * v0.5.1 generated at Thu May 22 07:30:49 PM CEST 2025
 | 
				
			||||||
 | 
					 * https://xrfragment.org
 | 
				
			||||||
 | 
					 * SPDX-License-Identifier: AGPL-3.0-or-later
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					(function(){
 | 
				
			||||||
 | 
					// this demonstrates the remotestorage aframe component 
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// reactive component for displaying the menu 
 | 
				
			||||||
 | 
					remoteStorageComponent = (el) => new Proxy({
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  html: (data) => (`
 | 
				
			||||||
 | 
					    <style type="text/css">
 | 
				
			||||||
 | 
					      body #files .rs-button-big{
 | 
				
			||||||
 | 
					        background: #FFF;
 | 
				
			||||||
 | 
					        box-shadow: none;
 | 
				
			||||||
 | 
					        border: 1px solid #CCC;
 | 
				
			||||||
 | 
					        padding: 10px 0px 49px 10px;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      #files .rs-button{
 | 
				
			||||||
 | 
					        background:#CCC;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      #files input {
 | 
				
			||||||
 | 
					        min-width:345px;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      #files #buttons select,
 | 
				
			||||||
 | 
					      #files #buttons button{
 | 
				
			||||||
 | 
					        width:255px;
 | 
				
			||||||
 | 
					        max-width:unset;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      #files #listing{
 | 
				
			||||||
 | 
					        margin-bottom:15px;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      #files #delete{
 | 
				
			||||||
 | 
					        display: none;
 | 
				
			||||||
 | 
					        transform: translate(10px, 5px);
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      #links{
 | 
				
			||||||
 | 
					        display:none
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    </style> 
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    <div id="remoteFilesTab">
 | 
				
			||||||
 | 
					      <div id="rswidget"></div>
 | 
				
			||||||
 | 
					      <br>
 | 
				
			||||||
 | 
					      <div id="buttons" style="display:none">
 | 
				
			||||||
 | 
					        <select id="listing" alt="your files"></select> 
 | 
				
			||||||
 | 
					        <i class="gg-close-o" id="delete" alt="delete" onclick="$remotestorage.remove()"></i>
 | 
				
			||||||
 | 
					        <a href="https://inspektor.5apps.com/?path=%2Fwebxr%2F" target="_blank" style="margin-left:15px">manage files</a>
 | 
				
			||||||
 | 
					        <br>
 | 
				
			||||||
 | 
					        <button onclick="$remotestorage.savePrivate()"><i class="gg-software-download"></i> save experience</button>
 | 
				
			||||||
 | 
					        <br>
 | 
				
			||||||
 | 
					        <button onclick="$remotestorage.savePublic()" ><i class="gg-globe-alt"></i> publish online</button>
 | 
				
			||||||
 | 
					        <br>
 | 
				
			||||||
 | 
					        <div id="links">
 | 
				
			||||||
 | 
					          <br>
 | 
				
			||||||
 | 
					          <div><i class="gg-link"></i>   3D file</div>
 | 
				
			||||||
 | 
					          <input type="text" value='' id="file">
 | 
				
			||||||
 | 
					          <br>
 | 
				
			||||||
 | 
					          <div><i class="gg-link"></i>   3D file in webviewer</div>
 | 
				
			||||||
 | 
					          <input type="text" value='' id="webviewer">
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					      </div>
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					  `),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  connected: false,
 | 
				
			||||||
 | 
					  $listing: false,
 | 
				
			||||||
 | 
					  $links: false,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  init(opts){
 | 
				
			||||||
 | 
					    // create HTML element
 | 
				
			||||||
 | 
					    $files.tabs = $files.tabs.concat({id:"remoteFiles", name: "remote storage"})
 | 
				
			||||||
 | 
					    el.innerHTML = this.html(this)
 | 
				
			||||||
 | 
					    el.className = "tab"
 | 
				
			||||||
 | 
					    document.querySelector("#files .tab-frame").appendChild(el);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // setup references
 | 
				
			||||||
 | 
					    this.$listing = document.querySelector('#files .tab-frame select#listing');
 | 
				
			||||||
 | 
					    this.$links   = document.querySelector('#files .tab-frame #links');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // setup input listeners
 | 
				
			||||||
 | 
					    (['click']).map( (e) => el.addEventListener(e, (ev) => typeof this[e] == 'function' && this[e](ev.target.id,ev) ) )
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    // signal ready 
 | 
				
			||||||
 | 
					    setTimeout( () => {
 | 
				
			||||||
 | 
					      document.dispatchEvent( new CustomEvent("remotestorage:ready", {detail: {$files:this,xrf}}) )
 | 
				
			||||||
 | 
					    },100)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    this.loadScript( () => this.initRemoteStorage() )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return this
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  loadScript(cb){
 | 
				
			||||||
 | 
					    let el = document.createElement("script")
 | 
				
			||||||
 | 
					    el.setAttribute("defer","")
 | 
				
			||||||
 | 
					    el.src = "https://unpkg.com/remotestoragejs@2.0.0-beta.7/release/remotestorage.js"
 | 
				
			||||||
 | 
					    document.head.appendChild(el)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    el = document.createElement("script")
 | 
				
			||||||
 | 
					    el.src = "https://unpkg.com/remotestorage-widget@1.6.0/build/widget.js"
 | 
				
			||||||
 | 
					    el.addEventListener('load', () => setTimeout(cb,2000) )
 | 
				
			||||||
 | 
					    document.head.appendChild(el)
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  initRemoteStorage(){
 | 
				
			||||||
 | 
					    let apis = {
 | 
				
			||||||
 | 
					      dropbox: "4jc8nx1lbarp472"
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    const modules = []
 | 
				
			||||||
 | 
					    if( typeof WebXR != undefined ){
 | 
				
			||||||
 | 
					      modules.push(WebXR) // defined in remotestorage-module-webXRF.js
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    window.remoteStorage = new RemoteStorage({logging: true, modules })
 | 
				
			||||||
 | 
					    if( Object.keys(apis).length ) remoteStorage.setApiKeys(apis)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    remoteStorage.on('not-connected',   (e) => { this.connected = false })
 | 
				
			||||||
 | 
					    remoteStorage.on('ready',           (e) => { })
 | 
				
			||||||
 | 
					    remoteStorage.on('connected',       (e) => { 
 | 
				
			||||||
 | 
					      this.connected = true 
 | 
				
			||||||
 | 
					      // force open dialog and click remote-tab
 | 
				
			||||||
 | 
					      frontend.$topbar.toggle(true)
 | 
				
			||||||
 | 
					      $files.toggle(true)
 | 
				
			||||||
 | 
					      document.querySelector("#files input#remoteFiles").click()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      this.updateFiles()
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    remoteStorage.access.claim( `webxr`, 'rw');           // our data dir
 | 
				
			||||||
 | 
					    remoteStorage.caching.enable( `/webxr/` )             // local-first, remotestorage-second
 | 
				
			||||||
 | 
					    remoteStorage.caching.enable( `/public/webxr/` )      // local-first, remotestorage-second
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // create widget
 | 
				
			||||||
 | 
					    let opts = {}
 | 
				
			||||||
 | 
					    opts.modalBackdrop = false
 | 
				
			||||||
 | 
					    opts.leaveOpen     = true
 | 
				
			||||||
 | 
					    widget = new window.Widget(window.remoteStorage, opts)
 | 
				
			||||||
 | 
					    widget.attach( "rswidget" );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  savePrivate(){
 | 
				
			||||||
 | 
					    frontend.download( (data,filename) => {
 | 
				
			||||||
 | 
					      filename = prompt('save-as filename', filename)
 | 
				
			||||||
 | 
					      remoteStorage.webxr.add(data,{public:false,filename,mimetype: 'model/glb-binary'})    
 | 
				
			||||||
 | 
					      .then( () => window.notify(`saved webxr/${filename} to remote storage`) )
 | 
				
			||||||
 | 
					      .catch( (e) => {
 | 
				
			||||||
 | 
					        console.error(e)
 | 
				
			||||||
 | 
					        window.notify(`failed to save webxr/${filename} to remote storage`,{status:'error'})
 | 
				
			||||||
 | 
					      })
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  savePublic(){
 | 
				
			||||||
 | 
					    frontend.download( (data,filename) => {
 | 
				
			||||||
 | 
					      filename = prompt('save-as filename', filename)
 | 
				
			||||||
 | 
					      opts = {public:true,filename,mimetype: 'model/glb-binary'}
 | 
				
			||||||
 | 
					      remoteStorage.webxr.add(data,opts)
 | 
				
			||||||
 | 
					      .then( (res) => {
 | 
				
			||||||
 | 
					        window.notify(`saved webxr/${filename} to remote storage`) 
 | 
				
			||||||
 | 
					        const link = opts.client.storage.remote.href + opts.client.base + filename 
 | 
				
			||||||
 | 
					        const linkWebView = document.location.href.replace(/(\?|#).*/,'') + `?${link}` 
 | 
				
			||||||
 | 
					        this.$links.querySelector("#file").value = link
 | 
				
			||||||
 | 
					        this.$links.querySelector("#webviewer").value = linkWebView 
 | 
				
			||||||
 | 
					        this.$links.style.display = 'block'
 | 
				
			||||||
 | 
					      })
 | 
				
			||||||
 | 
					      .catch( (e) => {
 | 
				
			||||||
 | 
					        console.error(e)
 | 
				
			||||||
 | 
					        window.notify(`failed to save webxr/${filename} to remote storage`,{status:'error'})
 | 
				
			||||||
 | 
					      })
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  openPrivate(file){
 | 
				
			||||||
 | 
					    if( !confirm(`teleport to ${file} on your remotestorage?`) ) return
 | 
				
			||||||
 | 
					    remoteStorage.webxr.getFile(file)
 | 
				
			||||||
 | 
					    .then( (res) => {
 | 
				
			||||||
 | 
					      for( var i in xrf.loaders ){
 | 
				
			||||||
 | 
					        if( file.replace(/.*\./).match(i) ){
 | 
				
			||||||
 | 
					          xrf.navigator.URI.file = '' // bypass cached file (easy refresh same file for testing)
 | 
				
			||||||
 | 
					          xrf.navigator.to(file,null, (new xrf.loaders[i]()), res.data)
 | 
				
			||||||
 | 
					          return
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      throw 'unknown filetype: '+file
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					    .catch( (e) => {
 | 
				
			||||||
 | 
					      console.error(e)
 | 
				
			||||||
 | 
					      window.notify("could not load webxr/"+file)
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  remove(){
 | 
				
			||||||
 | 
					    const currentFile = el.querySelector('select#listing').value
 | 
				
			||||||
 | 
					    if( confirm("remove "+currentFile+" from remote storage?") ){
 | 
				
			||||||
 | 
					      remoteStorage.webxr.remove(currentFile)
 | 
				
			||||||
 | 
					      .then( () => {
 | 
				
			||||||
 | 
					        window.notify(`removed webxr/${filename} from remote storage`) 
 | 
				
			||||||
 | 
					        this.updateFiles()
 | 
				
			||||||
 | 
					      })
 | 
				
			||||||
 | 
					      .catch( (e) => {
 | 
				
			||||||
 | 
					        console.error(e)
 | 
				
			||||||
 | 
					        window.notify(`could not webxr/${filename} from remote storage`,{status:'error'}) 
 | 
				
			||||||
 | 
					      })
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  updateFiles(){
 | 
				
			||||||
 | 
					    remoteStorage.webxr.getListing()
 | 
				
			||||||
 | 
					    .then( (listing) => {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      this.$listing.innerHTML = '' // empty
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      const addOption = (value,text) => {
 | 
				
			||||||
 | 
					        let opt = document.createElement("option")
 | 
				
			||||||
 | 
					        opt.text = text
 | 
				
			||||||
 | 
					        opt.value = value
 | 
				
			||||||
 | 
					        this.$listing.appendChild(opt)
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      addOption("","--- your experiences ---")
 | 
				
			||||||
 | 
					      for( let file in listing ){
 | 
				
			||||||
 | 
					        if( file.match(/\.(glb|gltf|usd|obj|col|fbx)$/) ) addOption(file,file)
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      // autoload selection 
 | 
				
			||||||
 | 
					      if( !this.updateFiles.autoload ){ // run once
 | 
				
			||||||
 | 
					        this.$listing.addEventListener('change', () => {
 | 
				
			||||||
 | 
					          if( this.$listing.options.selectedIndex > 0 ) this.openPrivate(this.$listing.value) 
 | 
				
			||||||
 | 
					          document.querySelector("#delete").style.display = this.$listing.options.selectedIndex == 0 ? "none" : "inline-block"
 | 
				
			||||||
 | 
					          this.$links.style.display = 'none'
 | 
				
			||||||
 | 
					        })
 | 
				
			||||||
 | 
					        this.updateFiles.autoload = true
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  click(id,e){
 | 
				
			||||||
 | 
					    //switch(id){
 | 
				
			||||||
 | 
					    //  case "more": return this.toggle(); break;
 | 
				
			||||||
 | 
					    //}
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					},
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  get(me,k,v){ return me[k] },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  set(me,k,v){ 
 | 
				
			||||||
 | 
					    me[k] = v    
 | 
				
			||||||
 | 
					    switch( k ){
 | 
				
			||||||
 | 
					        case 'connected': el.querySelector("#buttons").style.display = v ? 'block' : 'none'; break;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// reactify component!
 | 
				
			||||||
 | 
					document.addEventListener('$files:ready', (e) => {
 | 
				
			||||||
 | 
					  window.$remotestorage = remoteStorageComponent( document.createElement('div') ).init(e.detail)
 | 
				
			||||||
 | 
					})
 | 
				
			||||||
 | 
					const WebXR = { name: 'webxr', builder: function(privateClient, publicClient) {
 | 
				
			||||||
 | 
					  return {
 | 
				
			||||||
 | 
					    exports: {
 | 
				
			||||||
 | 
					      add: function(data,opts) {
 | 
				
			||||||
 | 
					        if( !data || !opts.filename || !opts.mimetype) throw 'webxr.add() needs filedata + filename + mimetype'
 | 
				
			||||||
 | 
					        const client = opts.client = opts.public ? publicClient : privateClient;
 | 
				
			||||||
 | 
					        return client.storeFile(opts.mimetype, opts.filename,data)
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      getListing: function(a,opts){
 | 
				
			||||||
 | 
					        opts = opts || {}
 | 
				
			||||||
 | 
					        const client = opts.public ? publicClient : privateClient;
 | 
				
			||||||
 | 
					        return client.getListing(a)
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      getFile: function(file,opts){
 | 
				
			||||||
 | 
					        opts = opts || {}
 | 
				
			||||||
 | 
					        const client = opts.public ? publicClient : privateClient;
 | 
				
			||||||
 | 
					        return client.getFile(file)
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      remove: function(file,opts){
 | 
				
			||||||
 | 
					        opts = opts || {}
 | 
				
			||||||
 | 
					        const client = opts.public ? publicClient : privateClient;
 | 
				
			||||||
 | 
					        return client.remove(file)
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}};
 | 
				
			||||||
 | 
					}).apply({})
 | 
				
			||||||
							
								
								
									
										2722
									
								
								dist/xrfragment.py
									
										
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										2722
									
								
								dist/xrfragment.py
									
										
									
									
										vendored
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load diff
											
										
									
								
							
							
								
								
									
										5882
									
								
								dist/xrfragment.three.js
									
										
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										5882
									
								
								dist/xrfragment.three.js
									
										
									
									
										vendored
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
							
								
								
									
										5883
									
								
								dist/xrfragment.three.module.js
									
										
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										5883
									
								
								dist/xrfragment.three.module.js
									
										
									
									
										vendored
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								doc/.RFC_XR_Fragments.md.swp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								doc/.RFC_XR_Fragments.md.swp
									
										
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								doc/RF6_XR_Fragments.png
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								doc/RF6_XR_Fragments.png
									
										
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| 
		 After Width: | Height: | Size: 31 KiB  | 
							
								
								
									
										1640
									
								
								doc/RFC_XR_Fragments.html
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										1640
									
								
								doc/RFC_XR_Fragments.html
									
										
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load diff
											
										
									
								
							
							
								
								
									
										1007
									
								
								doc/RFC_XR_Fragments.md
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										1007
									
								
								doc/RFC_XR_Fragments.md
									
										
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load diff
											
										
									
								
							
							
								
								
									
										614
									
								
								doc/RFC_XR_Fragments.md.bak
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										614
									
								
								doc/RFC_XR_Fragments.md.bak
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,614 @@
 | 
				
			||||||
 | 
					%%%
 | 
				
			||||||
 | 
					Title = "XR Fragments"
 | 
				
			||||||
 | 
					area = "Internet"
 | 
				
			||||||
 | 
					workgroup = "Internet Engineering Task Force"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[seriesInfo]
 | 
				
			||||||
 | 
					name = "XR-Fragments"
 | 
				
			||||||
 | 
					value = "draft-XRFRAGMENTS-leonvankammen-00"
 | 
				
			||||||
 | 
					stream = "IETF"
 | 
				
			||||||
 | 
					status = "informational"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					date = 2023-04-12T00:00:00Z
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[[author]]
 | 
				
			||||||
 | 
					initials="L.R."
 | 
				
			||||||
 | 
					surname="van Kammen"
 | 
				
			||||||
 | 
					fullname="L.R. van Kammen"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					%%%
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<!-- for annotated version see: https://raw.githubusercontent.com/ietf-tools/rfcxml-templates-and-schemas/main/draft-rfcxml-general-template-annotated-00.xml -->
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<!--{
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  <style type="text/css">
 | 
				
			||||||
 | 
					  body{
 | 
				
			||||||
 | 
					    font-family: monospace;
 | 
				
			||||||
 | 
					    max-width: 1000px;
 | 
				
			||||||
 | 
					    font-size: 15px;
 | 
				
			||||||
 | 
					    padding: 0% 20%;
 | 
				
			||||||
 | 
					    line-height: 30px;
 | 
				
			||||||
 | 
					    color:#555;
 | 
				
			||||||
 | 
					    background:#F0F0F3
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  h1 { margin-top:40px; }
 | 
				
			||||||
 | 
					  pre{ line-height:18px; }
 | 
				
			||||||
 | 
					  a,a:visited,a:active{ color: #70f; }
 | 
				
			||||||
 | 
					  code{
 | 
				
			||||||
 | 
					    border: 1px solid #AAA;
 | 
				
			||||||
 | 
					    border-radius: 3px;
 | 
				
			||||||
 | 
					    padding: 0px 5px 2px 5px;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  pre{
 | 
				
			||||||
 | 
					    line-height: 18px;
 | 
				
			||||||
 | 
					    overflow: auto;
 | 
				
			||||||
 | 
					    padding: 12px;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  pre + code {
 | 
				
			||||||
 | 
					    background:#DDD;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  pre>code{
 | 
				
			||||||
 | 
					    border:none;
 | 
				
			||||||
 | 
					    border-radius:0px;
 | 
				
			||||||
 | 
					    padding:0;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  blockquote{
 | 
				
			||||||
 | 
					    padding-left: 30px;
 | 
				
			||||||
 | 
					    margin: 0;
 | 
				
			||||||
 | 
					    border-left: 5px solid #CCC;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  th {
 | 
				
			||||||
 | 
					      border-bottom: 1px solid #000;
 | 
				
			||||||
 | 
					      text-align: left;
 | 
				
			||||||
 | 
					      padding-right:45px;
 | 
				
			||||||
 | 
					      padding-left:7px;
 | 
				
			||||||
 | 
					      background: #DDD;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  td {
 | 
				
			||||||
 | 
					      border-bottom: 1px solid #CCC;
 | 
				
			||||||
 | 
					      font-size:13px; 
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  </style>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<br>
 | 
				
			||||||
 | 
					<h1>XR Fragments</h1>
 | 
				
			||||||
 | 
					<br>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<pre>
 | 
				
			||||||
 | 
					stream:    IETF
 | 
				
			||||||
 | 
					area:      Internet
 | 
				
			||||||
 | 
					status:    informational
 | 
				
			||||||
 | 
					author:    Leon van Kammen
 | 
				
			||||||
 | 
					date:      2023-04-12T00:00:00Z
 | 
				
			||||||
 | 
					workgroup: Internet Engineering Task Force
 | 
				
			||||||
 | 
					value:     draft-XRFRAGMENTS-leonvankammen-00
 | 
				
			||||||
 | 
					</pre>  
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}-->
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.# Abstract
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					This draft offers a specification for 4D URLs & navigation, to link 3D scenes and text together with- or without a network-connection.<br>
 | 
				
			||||||
 | 
					The specification promotes spatial addressibility, sharing, navigation, query-ing and tagging interactive (text)objects across for (XR) Browsers.<br>
 | 
				
			||||||
 | 
					XR Fragments allows us to enrich existing dataformats, by recursive use of existing proven technologies like [URI Fragments](https://en.wikipedia.org/wiki/URI_fragment) and BibTags notation.<br>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					> Almost every idea in this document is demonstrated at [https://xrfragment.org](https://xrfragment.org)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					{mainmatter}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Introduction
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					How can we add more features to existing text & 3D scenes, without introducing new dataformats?<br>
 | 
				
			||||||
 | 
					Historically, there's many attempts to create the ultimate markuplanguage or 3D fileformat.<br>
 | 
				
			||||||
 | 
					However, thru the lens of authoring, their lowest common denominator is still: plain text.<br>
 | 
				
			||||||
 | 
					XR Fragments allows us to enrich/connect existing dataformats, by recursive use of existing technologies:<br>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					1. addressibility and navigation of 3D scenes/objects: [URI Fragments](https://en.wikipedia.org/wiki/URI_fragment) + src/href spatial metadata 
 | 
				
			||||||
 | 
					1. hasslefree tagging across text and spatial objects using [BibTags](https://en.wikipedia.org/wiki/BibTeX) as appendix (see [visual-meta](https://visual-meta.info) e.g.)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					> NOTE: The chapters in this document are ordered from highlevel to lowlevel (technical) as much as possible
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Core principle
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					XR Fragments strives to serve (nontechnical/fuzzy) humans first, and machine(implementations) later, by ensuring hasslefree text-vs-thought feedback loops.<br>
 | 
				
			||||||
 | 
					This also means that the repair-ability of machine-matters should be human friendly too (not too complex).<br>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					> "When a car breaks down, the ones **without** turbosupercharger are easier to fix"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Let's always focus on average humans: the 'fuzzy symbolical mind' must be served first, before serving the greater ['categorized typesafe RDF hive mind'](https://en.wikipedia.org/wiki/Borg)).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					> Humans first, machines (AI) later.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Conventions and Definitions
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					|definition            | explanation                                                                                                                   |
 | 
				
			||||||
 | 
					|----------------------|-------------------------------------------------------------------------------------------------------------------------------|
 | 
				
			||||||
 | 
					|human                 | a sentient being who thinks fuzzy, absorbs, and shares thought (by plain text, not markuplanguage)                            |
 | 
				
			||||||
 | 
					|scene                 | a (local/remote) 3D scene or 3D file (index.gltf e.g.)                                                                        |
 | 
				
			||||||
 | 
					|3D object             | an object inside a scene characterized by vertex-, face- and customproperty data.                                             |
 | 
				
			||||||
 | 
					|metadata              | custom properties of text, 3D Scene or Object(nodes), relevant to machines and a human minority (academics/developers)        |
 | 
				
			||||||
 | 
					|XR fragment           | URI Fragment with spatial hints like `#pos=0,0,0&t=1,100` e.g.                                                                |
 | 
				
			||||||
 | 
					|src                   | (HTML-piggybacked) metadata of a 3D object which instances content                                                            |
 | 
				
			||||||
 | 
					|href                  | (HTML-piggybacked) metadata of a 3D object which links to content                                                             |
 | 
				
			||||||
 | 
					|query                 | an URI Fragment-operator which queries object(s) from a scene like `#q=cube`                                                  |
 | 
				
			||||||
 | 
					|visual-meta           | [visual-meta](https://visual.meta.info) data appended to text/books/papers which is indirectly visible/editable in XR.        |
 | 
				
			||||||
 | 
					|requestless metadata  | opposite of networked metadata (RDF/HTML requests can easily fan out into framerate-dropping, hence not used a lot in games). |
 | 
				
			||||||
 | 
					|FPS                   | frames per second in spatial experiences (games,VR,AR e.g.), should be as high as possible                                    |
 | 
				
			||||||
 | 
					|introspective         | inward sensemaking ("I feel this belongs to that")                                                                            |
 | 
				
			||||||
 | 
					|extrospective         | outward sensemaking ("I'm fairly sure John is a person who lives in oklahoma")                                                |
 | 
				
			||||||
 | 
					|`◻`                   | ascii representation of an 3D object/mesh                                                                                     |
 | 
				
			||||||
 | 
					|(un)obtrusive         | obtrusive: wrapping human text/thought in XML/HTML/JSON obfuscates human text into a salad of machine-symbols and words       |
 | 
				
			||||||
 | 
					|BibTeX                | simple tagging/citing/referencing standard for plaintext                                                                      |
 | 
				
			||||||
 | 
					|BibTag                | a BibTeX tag                                                                                                                  |
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# List of URI Fragments
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					| fragment     | type     | example           | info                                                              |
 | 
				
			||||||
 | 
					|--------------|----------|-------------------|-------------------------------------------------------------------|
 | 
				
			||||||
 | 
					| `#pos`       | vector3  | `#pos=0.5,0,0`    | positions camera to xyz-coord 0.5,0,0                             |
 | 
				
			||||||
 | 
					| `#rot`       | vector3  | `#rot=0,90,0`     | rotates camera to xyz-coord 0.5,0,0                               |
 | 
				
			||||||
 | 
					| `#t`         | vector2  | `#t=500,1000`     | sets animation-loop range between frame 500 and 1000              |
 | 
				
			||||||
 | 
					| `#......`    | string   | `#.cubes` `#cube` | object(s) of interest (fragment to object name or class mapping)  |
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					> xyz coordinates are similar to ones found in SVG Media Fragments
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# List of metadata for 3D nodes 
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					| key          | type     | example (JSON)     | info                                                   |
 | 
				
			||||||
 | 
					|--------------|----------|--------------------|--------------------------------------------------------|
 | 
				
			||||||
 | 
					| `name`       | string   | `"name": "cube"`   | available in all 3D fileformats & scenes               |
 | 
				
			||||||
 | 
					| `class`      | string   | `"class": "cubes"` | available through custom property in 3D fileformats    |
 | 
				
			||||||
 | 
					| `href`       | string   | `"href": "b.gltf"` | available through custom property in 3D fileformats    |
 | 
				
			||||||
 | 
					| `src`        | string   | `"src": "#q=cube"` | available through custom property in 3D fileformats    |
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Popular compatible 3D fileformats: `.gltf`, `.obj`, `.fbx`, `.usdz`, `.json` (THREEjs), `COLLADA` and so on.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					> NOTE: XR Fragments are file-agnostic, which means that the metadata exist in programmatic 3D scene(nodes) too.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Navigating 3D
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Here's an ascii representation of a 3D scene-graph which contains 3D objects `◻` and their metadata:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					  +--------------------------------------------------------+ 
 | 
				
			||||||
 | 
					  |                                                        |
 | 
				
			||||||
 | 
					  |  index.gltf                                            |
 | 
				
			||||||
 | 
					  |    │                                                   |
 | 
				
			||||||
 | 
					  |    ├── ◻ buttonA                                       |
 | 
				
			||||||
 | 
					  |    │      └ href: #pos=1,0,1&t=100,200                 |
 | 
				
			||||||
 | 
					  |    │                                                   |
 | 
				
			||||||
 | 
					  |    └── ◻ buttonB                                       |
 | 
				
			||||||
 | 
					  |           └ href: other.fbx                            |   <-- file-agnostic (can be .gltf .obj etc)
 | 
				
			||||||
 | 
					  |                                                        |
 | 
				
			||||||
 | 
					  +--------------------------------------------------------+
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					An XR Fragment-compatible browser viewing this scene, allows the end-user to interact with the `buttonA` and `buttonB`.<br>
 | 
				
			||||||
 | 
					In case of `buttonA` the end-user will be teleported to another location and time in the **current loaded scene**, but `buttonB` will
 | 
				
			||||||
 | 
					 **replace the current scene** with a new one, like `other.fbx`.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Embedding 3D content 
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Here's an ascii representation of a 3D scene-graph with 3D objects `◻` which embeds remote & local 3D objects `◻` (without) using queries:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					  +--------------------------------------------------------+  +-------------------------+ 
 | 
				
			||||||
 | 
					  |                                                        |  |                         |
 | 
				
			||||||
 | 
					  |  index.gltf                                            |  | ocean.com/aquarium.fbx  |
 | 
				
			||||||
 | 
					  |    │                                                   |  |   │                     |
 | 
				
			||||||
 | 
					  |    ├── ◻ canvas                                        |  |   └── ◻ fishbowl        |
 | 
				
			||||||
 | 
					  |    │      └ src: painting.png                          |  |         ├─ ◻ bass       |
 | 
				
			||||||
 | 
					  |    │                                                   |  |         └─ ◻ tuna       |
 | 
				
			||||||
 | 
					  |    ├── ◻ aquariumcube                                  |  |                         |       
 | 
				
			||||||
 | 
					  |    │      └ src: ://rescue.com/fish.gltf#q=bass%20tuna |  +-------------------------+
 | 
				
			||||||
 | 
					  |    │                                                   |    
 | 
				
			||||||
 | 
					  |    ├── ◻ bedroom                                       |   
 | 
				
			||||||
 | 
					  |    │      └ src: #q=canvas                             |
 | 
				
			||||||
 | 
					  |    │                                                   |   
 | 
				
			||||||
 | 
					  |    └── ◻ livingroom                                    |      
 | 
				
			||||||
 | 
					  |           └ src: #q=canvas                             |
 | 
				
			||||||
 | 
					  |                                                        |
 | 
				
			||||||
 | 
					  +--------------------------------------------------------+
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					An XR Fragment-compatible browser viewing this scene, lazy-loads and projects `painting.png` onto the (plane) object called `canvas` (which is copy-instanced in the bed and livingroom).<br>
 | 
				
			||||||
 | 
					Also, after lazy-loading `ocean.com/aquarium.gltf`, only the queried objects `bass` and `tuna` will be instanced inside `aquariumcube`.<br>
 | 
				
			||||||
 | 
					Resizing will be happen accordingly to its placeholder object `aquariumcube`, see chapter Scaling.<br>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# XR Fragment queries
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Include, exclude, hide/shows objects using space-separated strings:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					* `#q=cube`
 | 
				
			||||||
 | 
					* `#q=cube -ball_inside_cube`
 | 
				
			||||||
 | 
					* `#q=* -sky`
 | 
				
			||||||
 | 
					* `#q=-.language .english`
 | 
				
			||||||
 | 
					* `#q=cube&rot=0,90,0`
 | 
				
			||||||
 | 
					* `#q=price:>2 price:<5`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					It's simple but powerful syntax which allows <b>css</b>-like class/id-selectors with a searchengine prompt-style feeling:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					1. queries are showing/hiding objects **only** when defined as `src` value (prevents sharing of scene-tampered URL's).
 | 
				
			||||||
 | 
					1. queries are highlighting objects when defined in the top-Level (browser) URL (bar).
 | 
				
			||||||
 | 
					1. search words like `cube` and `foo` in `#q=cube foo` are matched against 3D object names or custom metadata-key(values)
 | 
				
			||||||
 | 
					1. search words like `cube` and `foo` in `#q=cube foo` are matched against tags (BibTeX) inside plaintext `src` values like `@cube{redcube, ...` e.g.
 | 
				
			||||||
 | 
					1. `#` equals `#q=*`
 | 
				
			||||||
 | 
					1. words starting with `.` like `.german` match class-metadata of 3D objects like `"class":"german"`
 | 
				
			||||||
 | 
					1. words starting with `.` like `.german` match class-metadata of (BibTeX) tags in XR Text objects like `@german{KarlHeinz, ...` e.g. 
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					> **For example**: `#q=.foo` is a shorthand for `#q=class:foo`, which will select objects with custom property `class`:`foo`. Just a simple `#q=cube` will simply select an object named `cube`.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					* see [an example video here](https://coderofsalvation.github.io/xrfragment.media/queries.mp4)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## including/excluding
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					| operator | info                                                                                                                          |
 | 
				
			||||||
 | 
					|----------|-------------------------------------------------------------------------------------------------------------------------------|
 | 
				
			||||||
 | 
					| `*`      | select all objects (only useful in `src` custom property)                                                                     |
 | 
				
			||||||
 | 
					| `-`      | removes/hides object(s)                                                                                                       |
 | 
				
			||||||
 | 
					| `:`      | indicates an object-embedded custom property key/value                                                                        |
 | 
				
			||||||
 | 
					| `.`      | alias for `"class" :".foo"` equals `class:foo`                                                                                 |
 | 
				
			||||||
 | 
					| `>` `<`  | compare float or int number                                                                                                   |
 | 
				
			||||||
 | 
					| `/`      | reference to root-scene.<br>Useful in case of (preventing) showing/hiding objects in nested scenes (instanced by `src`) (*) |
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					> \* = `#q=-/cube` hides object `cube` only in the root-scene (not nested `cube` objects)<br> `#q=-cube` hides both object `cube` in the root-scene <b>AND</b> nested `skybox` objects |
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[» example implementation](https://github.com/coderofsalvation/xrfragment/blob/main/src/3rd/js/three/xrf/q.js)
 | 
				
			||||||
 | 
					[» example 3D asset](https://github.com/coderofsalvation/xrfragment/blob/main/example/assets/query.gltf#L192)
 | 
				
			||||||
 | 
					[» discussion](https://github.com/coderofsalvation/xrfragment/issues/3)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## Query Parser 
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Here's how to write a query parser:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					1. create an associative array/object to store query-arguments as objects
 | 
				
			||||||
 | 
					1. detect object id's & properties `foo:1` and `foo` (reference regex: `/^.*:[><=!]?/`  )
 | 
				
			||||||
 | 
					1. detect excluders like `-foo`,`-foo:1`,`-.foo`,`-/foo` (reference regex: `/^-/` )
 | 
				
			||||||
 | 
					1. detect root selectors like `/foo` (reference regex: `/^[-]?\//` )
 | 
				
			||||||
 | 
					1. detect class selectors like `.foo` (reference regex: `/^[-]?class$/` )
 | 
				
			||||||
 | 
					1. detect number values like `foo:1` (reference regex: `/^[0-9\.]+$/` )
 | 
				
			||||||
 | 
					1. expand aliases like `.foo` into `class:foo`
 | 
				
			||||||
 | 
					1. for every query token split string on `:`
 | 
				
			||||||
 | 
					1. create an empty array `rules`
 | 
				
			||||||
 | 
					1. then strip key-operator: convert "-foo" into "foo" 
 | 
				
			||||||
 | 
					1. add operator and value to rule-array
 | 
				
			||||||
 | 
					1. therefore we we set `id` to `true` or `false` (false=excluder `-`)
 | 
				
			||||||
 | 
					1. and we set `root` to `true` or `false` (true=`/` root selector is present)
 | 
				
			||||||
 | 
					1. we convert key '/foo' into 'foo'
 | 
				
			||||||
 | 
					1. finally we add the key/value to the store like `store.foo = {id:false,root:true}` e.g.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					> An example query-parser (which compiles to many languages) can be [found here](https://github.com/coderofsalvation/xrfragment/blob/main/src/xrfragment/Query.hx)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## XR Fragment URI Grammar 
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					reserved    = gen-delims / sub-delims
 | 
				
			||||||
 | 
					gen-delims  = "#" / "&"
 | 
				
			||||||
 | 
					sub-delims  = "," / "="
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					> Example: `://foo.com/my3d.gltf#pos=1,0,0&prio=-5&t=0,100`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					| Demo                          | Explanation                     |
 | 
				
			||||||
 | 
					|-------------------------------|---------------------------------|
 | 
				
			||||||
 | 
					| `pos=1,2,3`                   | vector/coordinate argument e.g. |
 | 
				
			||||||
 | 
					| `pos=1,2,3&rot=0,90,0&q=.foo` | combinators                     |
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Text in XR (tagging,linking to spatial objects)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					We still think and speak in simple text, not in HTML or RDF.<br>
 | 
				
			||||||
 | 
					The most advanced human will probably not shout `<h1>FIRE!</h1>` in case of emergency.<br>
 | 
				
			||||||
 | 
					Given the new dawn of (non-keyboard) XR interfaces, keeping text as is (not obscuring with markup) is preferred.<br>
 | 
				
			||||||
 | 
					Ideally metadata must come **later with** text, but not **obfuscate** the text, or **in another** file.<br>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					> Humans first, machines (AI) later ([core principle](#core-principle)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					This way:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					1. XR Fragments allows <b id="tagging-text">hasslefree XR text tagging</b>, using BibTeX metadata **at the end of content** (like [visual-meta](https://visual.meta.info)).
 | 
				
			||||||
 | 
					1. XR Fragments allows hasslefree <a href="#textual-tag">textual tagging</a>, <a href="#spatial-tag">spatial tagging</a>, and <a href="#supra-tagging">supra tagging</a>, by mapping 3D/text object (class)names using BibTeX 'tags'
 | 
				
			||||||
 | 
					1. Bibs/BibTeX-appendices is first-choice **requestless metadata**-layer for XR text, HTML/RDF/JSON is great (but fits better in the application-layer)
 | 
				
			||||||
 | 
					1. Default font (unless specified otherwise) is a modern monospace font, for maximized tabular expressiveness (see [the core principle](#core-principle)).
 | 
				
			||||||
 | 
					1. anti-pattern: hardcoupling a mandatory **obtrusive markuplanguage** or framework with an XR browsers (HTML/VRML/Javascript) (see [the core principle](#core-principle))
 | 
				
			||||||
 | 
					1. anti-pattern: limiting human introspection, by immediately funneling human thought into typesafe, precise, pre-categorized metadata like RDF (see [the core principle](#core-principle))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					This allows recursive connections between text itself, as well as 3D objects and vice versa, using **BibTags** :
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					  +---------------------------------------------+         +------------------+
 | 
				
			||||||
 | 
					  | My Notes                                    |         |        / \       |
 | 
				
			||||||
 | 
					  |                                             |         |       /   \      |
 | 
				
			||||||
 | 
					  | The houses here are built in baroque style. |         |      /house\     |
 | 
				
			||||||
 | 
					  |                                             |         |      |_____|     |
 | 
				
			||||||
 | 
					  |                                             |         +---------|--------+ 
 | 
				
			||||||
 | 
					  | @house{houses,                              >----'house'--------|    class/name match?
 | 
				
			||||||
 | 
					  |   url  = {#.house}                          >----'houses'-------`    class/name match? 
 | 
				
			||||||
 | 
					  | }                                           |
 | 
				
			||||||
 | 
					  +---------------------------------------------+
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					> The enduser can add connections by speaking/typing/scanning [hashtagbibs](https://github.com/coderofsalvation/hashtagbibs) which the XR Browser can expand to BibTags.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					This allows instant realtime tagging of objects at various scopes:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					| scope                                 | matching algo                                                                                                                                                          |
 | 
				
			||||||
 | 
					|---------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
 | 
				
			||||||
 | 
					| <b id="textual-tagging">textual</b>   | text containing 'houses' is now automatically tagged with 'house' (incl. plaintext `src` child nodes)                                                                  |
 | 
				
			||||||
 | 
					| <b id="spatial-tagging">spatial</b>   | spatial object(s) with `"class":"house"` (because of `{#.house}`) are now automatically tagged with 'house' (incl. child nodes)                                        |
 | 
				
			||||||
 | 
					| <b id="supra-tagging">supra</b>       | text- or spatial-object(s) (non-descendant nodes) elsewhere, named 'house', are automatically tagged with 'house' (current node to root node)                          |
 | 
				
			||||||
 | 
					| <b id="omni-tagging">omni</b>         | text- or spatial-object(s) (non-descendant nodes) elsewhere, containing class/name 'house', are automatically tagged with 'house' (too node to all nodes)              |
 | 
				
			||||||
 | 
					| <b id="infinite-tagging">infinite</b> | text- or spatial-object(s) (non-descendant nodes) elsewhere, containing class/name 'house' or 'houses', are automatically tagged with 'house' (too node to all nodes)  |
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					This empowers the enduser spatial expressiveness (see [the core principle](#core-principle)): spatial wires can be rendered, words can be highlighted, spatial objects can be highlighted/moved/scaled, links can be manipulated by the user.<br>
 | 
				
			||||||
 | 
					The simplicity of appending BibTeX 'tags' (humans first, machines later) is also demonstrated by [visual-meta](https://visual-meta.info) in greater detail.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					1. The XR Browser needs to adjust tag-scope based on the endusers needs/focus (infinite tagging only makes sense when environment is scaled down significantly)
 | 
				
			||||||
 | 
					1. The XR Browser should always allow the human to view/edit the metadata, by clicking 'toggle metadata' on the 'back' (contextmenu e.g.) of any XR text, anywhere anytime.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					> NOTE: infinite matches both 'house' and 'houses' in text, as well as spatial objects with `"class":"house"` or name "house". This multiplexing of id/category is deliberate because of [the core principle](#core-principle).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## Default Data URI mimetype 
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					The `src`-values work as expected (respecting mime-types), however:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					The XR Fragment specification bumps the traditional default browser-mimetype 
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					`text/plain;charset=US-ASCII` 
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					to a hashtagbib(tex)-friendly one:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					`text/plain;charset=utf-8;bib=^@`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					This indicates that:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					* utf-8 is supported by default
 | 
				
			||||||
 | 
					* [hashtagbibs](https://github.com/coderofsalvation/hashtagbibs) are expanded to [bibtags](https://en.wikipedia.org/wiki/BibTeX)
 | 
				
			||||||
 | 
					* lines matching regex `^@` will automatically get filtered out, in order to:
 | 
				
			||||||
 | 
					* links between textual/spatial objects can automatically be detected 
 | 
				
			||||||
 | 
					* bibtag appendices ([visual-meta](https://visual-meta.info) can be interpreted e.g.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					> for more info on this mimetype see [bibs](https://github.com/coderofsalvation/hashtagbibs)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Advantages: 
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					* out-of-the-box (de)multiplex human text and metadata in one go (see [the core principle](#core-principle))
 | 
				
			||||||
 | 
					* no network-overhead for metadata (see [the core principle](#core-principle)) 
 | 
				
			||||||
 | 
					* ensuring high FPS: HTML/RDF historically is too 'requesty'/'parsy' for game studios
 | 
				
			||||||
 | 
					* rich send/receive/copy-paste everywhere by default, metadata being retained (see [the core principle](#core-principle))
 | 
				
			||||||
 | 
					* netto result: less webservices, therefore less servers, and overall better FPS in XR 
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					> This significantly expands expressiveness and portability of human tagged text, by **postponing machine-concerns to the end of the human text** in contrast to literal interweaving of content and markupsymbols (or extra network requests, webservices e.g.).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					For all other purposes, regular mimetypes can be used (but are not required by the spec).<br>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## URL and Data URI
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					  +--------------------------------------------------------------+  +------------------------+
 | 
				
			||||||
 | 
					  |                                                              |  | author.com/article.txt |
 | 
				
			||||||
 | 
					  |  index.gltf                                                  |  +------------------------+
 | 
				
			||||||
 | 
					  |    │                                                         |  |                        |
 | 
				
			||||||
 | 
					  |    ├── ◻ article_canvas                                      |  | Hello friends.         |
 | 
				
			||||||
 | 
					  |    │    └ src: ://author.com/article.txt                     |  |                        |
 | 
				
			||||||
 | 
					  |    │                                                         |  | @friend{friends        |
 | 
				
			||||||
 | 
					  |    └── ◻ note_canvas                                         |  |   ...                  |
 | 
				
			||||||
 | 
					  |           └ src:`data:welcome human\n@...`                   |  | }                      | 
 | 
				
			||||||
 | 
					  |                                                              |  +------------------------+
 | 
				
			||||||
 | 
					  |                                                              |
 | 
				
			||||||
 | 
					  +--------------------------------------------------------------+
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					The enduser will only see `welcome human` and `Hello friends` rendered spatially.
 | 
				
			||||||
 | 
					The beauty is that text (AND visual-meta) in Data URI promotes rich copy-paste.
 | 
				
			||||||
 | 
					In both cases, the text gets rendered immediately (onto a plane geometry, hence the name '_canvas').
 | 
				
			||||||
 | 
					The XR Fragment-compatible browser can let the enduser access visual-meta(data)-fields after interacting with the object (contextmenu e.g.).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					> additional tagging using [bibs](https://github.com/coderofsalvation/hashtagbibs): to tag spatial object `note_canvas` with 'todo', the enduser can type or speak `@note_canvas@todo`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					The mapping between 3D objects and text (src-data) is simple (the :
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Example:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					  +------------------------------------------------+ 
 | 
				
			||||||
 | 
					  |                                                | 
 | 
				
			||||||
 | 
					  |  index.gltf                                    | 
 | 
				
			||||||
 | 
					  |    │                                           | 
 | 
				
			||||||
 | 
					  |    └── ◻ rentalhouse                           | 
 | 
				
			||||||
 | 
					  |           └ class: house              <----------------- matches -------+
 | 
				
			||||||
 | 
					  |           └ ◻ note                             |                        |
 | 
				
			||||||
 | 
					  |                 └ src:`data: todo: call owner  |       hashtagbib       |
 | 
				
			||||||
 | 
					  |                              #owner@house@todo | ----> expands to     @house{owner,
 | 
				
			||||||
 | 
					  |                                                |          bibtex:     }
 | 
				
			||||||
 | 
					  |                        `                       |                      @contact{
 | 
				
			||||||
 | 
					  +------------------------------------------------+                      }
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Bi-directional mapping between 3D object names and/or classnames and text using bibs,BibTags & XR Fragments, allows for rich interlinking between text and 3D objects:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					1. When the user surfs to https://.../index.gltf#rentalhouse the XR Fragments-parser points the enduser to the rentalhouse object, and can show contextual info about it.
 | 
				
			||||||
 | 
					2. When (partial) remote content is embedded thru XR Fragment queries (see XR Fragment queries), indirectly related metadata can be embedded along.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## Bibs & BibTeX: lowest common denominator for linking data 
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					> "When a car breaks down, the ones **without** turbosupercharger are easier to fix"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Unlike XML or JSON, BibTex is typeless, unnested, and uncomplicated, hence a great advantage for introspection.<br>
 | 
				
			||||||
 | 
					It's a missing sensemaking precursor to extrospective RDF.<br>
 | 
				
			||||||
 | 
					BibTeX-appendices are already used in the digital AND physical world (academic books, [visual-meta](https://visual-meta.info)), perhaps due to its terseness & simplicity.<br>
 | 
				
			||||||
 | 
					In that sense, it's one step up from the `.ini` fileformat (which has never leaked into the physical world like BibTex):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					1. <b id="frictionless-copy-paste">frictionless copy/pasting</b> (by humans) of (unobtrusive) content AND metadata
 | 
				
			||||||
 | 
					1. an introspective 'sketchpad' for metadata, which can (optionally) mature into RDF later 
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					| characteristic                     | UTF8 Plain Text (with BibTeX) | RDF                       |
 | 
				
			||||||
 | 
					|------------------------------------|-------------------------------|---------------------------|
 | 
				
			||||||
 | 
					| perspective                        | introspective                 | extrospective             |
 | 
				
			||||||
 | 
					| structure                          | fuzzy (sensemaking)           | precise                   |
 | 
				
			||||||
 | 
					| space/scope                        | local                         | world                     |
 | 
				
			||||||
 | 
					| everything is text (string)        | yes                           | no                        |
 | 
				
			||||||
 | 
					| voice/paper-friendly               | [bibs](https://github.com/coderofsalvation/hashtagbibs) | no  |
 | 
				
			||||||
 | 
					| leaves (dictated) text intact      | yes                           | no                        |
 | 
				
			||||||
 | 
					| markup language                    | just an appendix              | ~4 different              |                 
 | 
				
			||||||
 | 
					| polyglot format                    | no                            | yes                       |
 | 
				
			||||||
 | 
					| easy to copy/paste content+metadata| yes                           | up to application         |
 | 
				
			||||||
 | 
					| easy to write/repair for layman    | yes                           | depends                   |
 | 
				
			||||||
 | 
					| easy to (de)serialize              | yes (fits on A4 paper)        | depends                   |
 | 
				
			||||||
 | 
					| infrastructure                     | selfcontained (plain text)    | (semi)networked           |
 | 
				
			||||||
 | 
					| freeform tagging/annotation        | yes, terse                    | yes, verbose              |
 | 
				
			||||||
 | 
					| can be appended to text-content    | yes                           | up to application         |
 | 
				
			||||||
 | 
					| copy-paste text preserves metadata | yes                           | up to application         |
 | 
				
			||||||
 | 
					| emoji                              | yes                           | depends on encoding       |
 | 
				
			||||||
 | 
					| predicates                         | free                          | semi pre-determined       |
 | 
				
			||||||
 | 
					| implementation/network overhead    | no                            | depends                   |
 | 
				
			||||||
 | 
					| used in (physical) books/PDF       | yes (visual-meta)             | no                        |
 | 
				
			||||||
 | 
					| terse non-verb predicates          | yes                           | no                        |
 | 
				
			||||||
 | 
					| nested structures                  | no (but: BibTex rulers)       | yes                       |
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					> To keep XR Fragments a lightweight spec, BibTeX is used for rudimentary text/spatial tagging (not JSON, RDF or a scripting language because they're harder to write/speak/repair.). 
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Applications are also free to attach any JSON(LD / RDF) to spatial objects using custom properties (but is not interpreted by this spec).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## XR Text example parser
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					1. The XR Fragments spec does not aim to harden the BiBTeX format
 | 
				
			||||||
 | 
					2. respect multi-line BibTex values because of [the core principle](#core-principle)
 | 
				
			||||||
 | 
					3. Expand hashtag(bibs) and rulers (like `${visual-meta-start}`) according to the [hashtagbibs spec](https://github.com/coderofsalvation/hashtagbibs)
 | 
				
			||||||
 | 
					4. BibTeX snippets should always start in the beginning of a line (regex: ^@), hence mimetype `text/plain;charset=utf-8;bib=^@`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Here's an XR Text (de)multiplexer in javascript, which ticks all the above boxes:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					xrtext = {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  expandBibs: (text) => { 
 | 
				
			||||||
 | 
					    let bibs   = { regex: /(#[a-zA-Z0-9_+@\-]+(#)?)/g, tags: {}}
 | 
				
			||||||
 | 
					    text.replace( bibs.regex , (m,k,v) => {
 | 
				
			||||||
 | 
					       tok   = m.substr(1).split("@")
 | 
				
			||||||
 | 
					       match = tok.shift()
 | 
				
			||||||
 | 
					       if( tok.length ) tok.map( (t) => bibs.tags[t] = `@${t}{${match},\n}` )
 | 
				
			||||||
 | 
					       else if( match.substr(-1) == '#' ) 
 | 
				
			||||||
 | 
					          bibs.tags[match] = `@{${match.replace(/#/,'')}}`
 | 
				
			||||||
 | 
					       else bibs.tags[match] = `@${match}{${match},\n}`
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					    return text.replace( bibs.regex, '') + Object.values(bibs.tags).join('\n')
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					  decode: (str) => {
 | 
				
			||||||
 | 
					    // bibtex:     ↓@   ↓<tag|tag{phrase,|{ruler}>  ↓property  ↓end
 | 
				
			||||||
 | 
					    let pat    = [ /@/, /^\S+[,{}]/,                /},/,      /}/ ]
 | 
				
			||||||
 | 
					    let tags   = [], text='', i=0, prop=''
 | 
				
			||||||
 | 
					    let lines  = xrtext.expandBibs(str).replace(/\r?\n/g,'\n').split(/\n/)
 | 
				
			||||||
 | 
					    for( let i = 0; i < lines.length && !String(lines[i]).match( /^@/ ); i++ ) 
 | 
				
			||||||
 | 
					        text += lines[i]+'\n'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    bibtex = lines.join('\n').substr( text.length )
 | 
				
			||||||
 | 
					    bibtex.split( pat[0] ).map( (t) => {
 | 
				
			||||||
 | 
					        try{
 | 
				
			||||||
 | 
					           let v = {}
 | 
				
			||||||
 | 
					           if( !(t = t.trim())         ) return
 | 
				
			||||||
 | 
					           if( tag = t.match( pat[1] ) ) tag = tag[0]
 | 
				
			||||||
 | 
					           if( tag.match( /^{.*}$/ )   ) return tags.push({ruler:tag})
 | 
				
			||||||
 | 
					           t = t.substr( tag.length )
 | 
				
			||||||
 | 
					           t.split( pat[2] )
 | 
				
			||||||
 | 
					           .map( kv => {
 | 
				
			||||||
 | 
					             if( !(kv = kv.trim()) || kv == "}" ) return
 | 
				
			||||||
 | 
					             v[ kv.match(/\s?(\S+)\s?=/)[1] ] = kv.substr( kv.indexOf("{")+1 )
 | 
				
			||||||
 | 
					           })
 | 
				
			||||||
 | 
					           tags.push( { k:tag, v } )
 | 
				
			||||||
 | 
					        }catch(e){ console.error(e) }
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					    return {text, tags}
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  encode: (text,tags) => {
 | 
				
			||||||
 | 
					    let str = text+"\n"
 | 
				
			||||||
 | 
					    for( let i in tags ){
 | 
				
			||||||
 | 
					      let item = tags[i]
 | 
				
			||||||
 | 
					      if( item.ruler ){
 | 
				
			||||||
 | 
					          str += `@${item.ruler}\n`
 | 
				
			||||||
 | 
					          continue;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      str += `@${item.k}\n`
 | 
				
			||||||
 | 
					      for( let j in item.v ) str += `  ${j} = {${item.v[j]}}\n`
 | 
				
			||||||
 | 
					      str += `}\n`
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    return str
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					The above functions (de)multiplexe text/metadata, expands bibs, (de)serialize bibtex (and all fits more or less on one A4 paper)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					> above can be used as a startingpoint for LLVM's to translate/steelman to a more formal form/language.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					str = `
 | 
				
			||||||
 | 
					hello world
 | 
				
			||||||
 | 
					here are some hashtagbibs followed by bibtex:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#world
 | 
				
			||||||
 | 
					#hello@greeting
 | 
				
			||||||
 | 
					#another-section#
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@{some-section}
 | 
				
			||||||
 | 
					@flap{
 | 
				
			||||||
 | 
					  asdf = {23423}
 | 
				
			||||||
 | 
					}`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					var {tags,text} = xrtext.decode(str)          // demultiplex text & bibtex
 | 
				
			||||||
 | 
					tags.find( (t) => t.k == 'flap{' ).v.asdf = 1 // edit tag
 | 
				
			||||||
 | 
					tags.push({ k:'bar{', v:{abc:123} })          // add tag
 | 
				
			||||||
 | 
					console.log( xrtext.encode(text,tags) )       // multiplex text & bibtex back together 
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					This expands to the following (hidden by default) BibTex appendix:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					hello world
 | 
				
			||||||
 | 
					here are some hashtagbibs followed by bibtex:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@{some-section}
 | 
				
			||||||
 | 
					@flap{
 | 
				
			||||||
 | 
					  asdf = {1}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					@world{world,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					@greeting{hello,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					@{another-section}
 | 
				
			||||||
 | 
					@bar{
 | 
				
			||||||
 | 
					  abc = {123}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# HYPER copy/paste 
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					The previous example, offers something exciting compared to simple copy/paste of 3D objects or text.
 | 
				
			||||||
 | 
					XR Text according to the XR Fragment spec, allows HYPER-copy/paste: time, space and text interlinked.
 | 
				
			||||||
 | 
					Therefore, the enduser in an XR Fragment-compatible browser can copy/paste/share data in these ways:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					1. time/space: 3D object (current animation-loop)
 | 
				
			||||||
 | 
					1. text: TeXt object (including BibTeX/visual-meta if any)
 | 
				
			||||||
 | 
					1. interlinked: Collected objects by visual-meta tag
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Security Considerations
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Since XR Text contains metadata too, the user should be able to set up tagging-rules, so the copy-paste feature can :
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					* filter out sensitive data when copy/pasting (XR text with `class:secret` e.g.)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# IANA Considerations
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					This document has no IANA actions.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Acknowledgments
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					TODO acknowledge.
 | 
				
			||||||
							
								
								
									
										
											BIN
										
									
								
								doc/RFC_XR_Fragments.png
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								doc/RFC_XR_Fragments.png
									
										
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| 
		 After Width: | Height: | Size: 12 KiB  | 
							
								
								
									
										35994
									
								
								doc/RFC_XR_Fragments.svg
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										35994
									
								
								doc/RFC_XR_Fragments.svg
									
										
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load diff
											
										
									
								
							| 
		 After Width: | Height: | Size: 1,002 KiB  | 
							
								
								
									
										1736
									
								
								doc/RFC_XR_Fragments.txt
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										1736
									
								
								doc/RFC_XR_Fragments.txt
									
										
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load diff
											
										
									
								
							
							
								
								
									
										1259
									
								
								doc/RFC_XR_Fragments.xml
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										1259
									
								
								doc/RFC_XR_Fragments.xml
									
										
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load diff
											
										
									
								
							
							
								
								
									
										465
									
								
								doc/RFC_XR_Macros.html
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										465
									
								
								doc/RFC_XR_Macros.html
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,465 @@
 | 
				
			||||||
 | 
					<!DOCTYPE html>
 | 
				
			||||||
 | 
					<html>
 | 
				
			||||||
 | 
					<head>
 | 
				
			||||||
 | 
					  <title>XR Macros</title>
 | 
				
			||||||
 | 
					  <meta name="GENERATOR" content="github.com/mmarkdown/mmark Mmark Markdown Processor - mmark.miek.nl">
 | 
				
			||||||
 | 
					  <meta charset="utf-8">
 | 
				
			||||||
 | 
					</head>
 | 
				
			||||||
 | 
					<body>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<!-- for annotated version see: https://raw.githubusercontent.com/ietf-tools/rfcxml-templates-and-schemas/main/draft-rfcxml-general-template-annotated-00.xml -->
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  <style type="text/css">
 | 
				
			||||||
 | 
					  body{
 | 
				
			||||||
 | 
					    font-family: monospace;
 | 
				
			||||||
 | 
					    max-width: 1000px;
 | 
				
			||||||
 | 
					    font-size: 15px;
 | 
				
			||||||
 | 
					    padding: 0% 20%;
 | 
				
			||||||
 | 
					    line-height: 30px;
 | 
				
			||||||
 | 
					    color:#555;
 | 
				
			||||||
 | 
					    background:#F0F0F3
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  h1 { margin-top:40px; }
 | 
				
			||||||
 | 
					  pre{ line-height:18px; }
 | 
				
			||||||
 | 
					  a,a:visited,a:active{ color: #70f; }
 | 
				
			||||||
 | 
					  code{
 | 
				
			||||||
 | 
					    border: 1px solid #AAA;
 | 
				
			||||||
 | 
					    border-radius: 3px;
 | 
				
			||||||
 | 
					    padding: 0px 5px 2px 5px;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  pre{
 | 
				
			||||||
 | 
					    line-height: 18px;
 | 
				
			||||||
 | 
					    overflow: auto;
 | 
				
			||||||
 | 
					    padding: 12px;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  pre + code {
 | 
				
			||||||
 | 
					    background:#DDD;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  pre>code{
 | 
				
			||||||
 | 
					    border:none;
 | 
				
			||||||
 | 
					    border-radius:0px;
 | 
				
			||||||
 | 
					    padding:0;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  blockquote{
 | 
				
			||||||
 | 
					    padding-left: 30px;
 | 
				
			||||||
 | 
					    margin: 0;
 | 
				
			||||||
 | 
					    border-left: 5px solid #CCC;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  th {
 | 
				
			||||||
 | 
					      border-bottom: 1px solid #000;
 | 
				
			||||||
 | 
					      text-align: left;
 | 
				
			||||||
 | 
					      padding-right:45px;
 | 
				
			||||||
 | 
					      padding-left:7px;
 | 
				
			||||||
 | 
					      background: #DDD;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  td {
 | 
				
			||||||
 | 
					      border-bottom: 1px solid #CCC;
 | 
				
			||||||
 | 
					      font-size:13px; 
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  </style>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<br>
 | 
				
			||||||
 | 
					<h1>XR Macros</h1>
 | 
				
			||||||
 | 
					<br>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<pre>
 | 
				
			||||||
 | 
					stream:    IETF
 | 
				
			||||||
 | 
					area:      Internet
 | 
				
			||||||
 | 
					status:    informational
 | 
				
			||||||
 | 
					author:    Leon van Kammen
 | 
				
			||||||
 | 
					date:      2023-04-12T00:00:00Z
 | 
				
			||||||
 | 
					workgroup: Internet Engineering Task Force
 | 
				
			||||||
 | 
					value:     draft-XRMACROS-leonvankammen-00
 | 
				
			||||||
 | 
					</pre>  
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<h1 class="special" id="abstract">Abstract</h1>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<p>This draft offers a specification for embedding macros in existing 3D scenes/assets, to offer simple interactions and configure the renderer further.<br>
 | 
				
			||||||
 | 
					Together with URI Fragments, it allows for rich immersive experiences without the need of a complicated sandboxed scripting languages.</p>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<blockquote>
 | 
				
			||||||
 | 
					<p>Almost every idea in this document is demonstrated at <a href="https://xrfragment.org">https://xrfragment.org</a>, as this spec was created during the <a href="https://xrfragment.org">XR Fragments</a> spec.</p>
 | 
				
			||||||
 | 
					</blockquote>
 | 
				
			||||||
 | 
					<section data-matter="main">
 | 
				
			||||||
 | 
					<h1 id="introduction">Introduction</h1>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<p>How can we add more features to existing text & 3D scenes, without introducing new dataformats?<br>
 | 
				
			||||||
 | 
					Historically, there’s many attempts to create the ultimate markuplanguage or 3D fileformat.<br>
 | 
				
			||||||
 | 
					Their lowest common denominator is: (co)authoring using plain text.<br>
 | 
				
			||||||
 | 
					Therefore, XR Macros allows us to enrich/connect existing dataformats, by offering a polyglot notation based on existing notations:<br></p>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<ol>
 | 
				
			||||||
 | 
					<li>getting/setting common used 3D properties using querystring- or JSON-notation</li>
 | 
				
			||||||
 | 
					<li>targeting 3D properties using the lightweight query notation present in <a href="https://xrfragment.org">XR Fragments</a></li>
 | 
				
			||||||
 | 
					</ol>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<blockquote>
 | 
				
			||||||
 | 
					<p>NOTE: The chapters in this document are ordered from highlevel to lowlevel (technical) as much as possible</p>
 | 
				
			||||||
 | 
					</blockquote>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<h1 id="core-principle">Core principle</h1>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<ol>
 | 
				
			||||||
 | 
					<li>XR Macros use querystrings, but are HTML-agnostic (though pseudo-XR Fragment browsers <strong>can</strong> be implemented on top of HTML/Javascript).</li>
 | 
				
			||||||
 | 
					<li>An XR Macro is 3D metadata which starts with ‘!’ (<code>!clickme: fog=0,10</code> e.g.)</li>
 | 
				
			||||||
 | 
					<li>Metadata-values can contain the <code>|</code> symbol to 🎲 roundrobin variable values (<code>!toggleme: fog=0,10|fog=0,1000</code> e.g.)</li>
 | 
				
			||||||
 | 
					<li>XR Macros acts as simple eventhandlers for URI Fragments: they are automatically published on the (<a href="https://xrfragment.org">XR Fragments</a>) hashbus, to act as events (so more serious scripting languages can react to them as well).</li>
 | 
				
			||||||
 | 
					<li>XR Macros can assign object metadata (<code>!setlocal: foo=1</code> writes <code>foo:1</code> metadata to the object containing the <code>!setlocal</code> metadata)</li>
 | 
				
			||||||
 | 
					<li>XR Macros can assign global metadata  (<code>!setfoo: #foo=1</code> writes <code>foo:1</code> metadata to the root scene-node)</li>
 | 
				
			||||||
 | 
					</ol>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<blockquote>
 | 
				
			||||||
 | 
					<p>These very simple principles allow for rich interactions and dynamic querying</p>
 | 
				
			||||||
 | 
					</blockquote>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<h1 id="conventions-and-definitions">Conventions and Definitions</h1>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<p>See appendix below in case certain terms are not clear.</p>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<h1 id="list-of-xr-macros">List of XR Macros</h1>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<p>(XR) Macros can be embedded in 3D assets/scenes.<br>
 | 
				
			||||||
 | 
					Macros enrich existing spatial content with a lowcode, limited logic-layer, by recursive (economic) use of the querystring syntax (which search engines and <a href="https://xrfragment.org">XR Fragments</a> already uses.<br>
 | 
				
			||||||
 | 
					This is done by allowing string/integer variables, and the <code>|</code> symbol to roundrobin variable values.<br>
 | 
				
			||||||
 | 
					Macros also act as events, so more serious scripting languages can react to them as well.<br></p>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<table>
 | 
				
			||||||
 | 
					<thead>
 | 
				
			||||||
 | 
					<tr>
 | 
				
			||||||
 | 
					<th>key</th>
 | 
				
			||||||
 | 
					<th>type</th>
 | 
				
			||||||
 | 
					<th>example (JSON)</th>
 | 
				
			||||||
 | 
					<th>function</th>
 | 
				
			||||||
 | 
					<th>existing compatibility</th>
 | 
				
			||||||
 | 
					</tr>
 | 
				
			||||||
 | 
					</thead>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<tbody>
 | 
				
			||||||
 | 
					<tr>
 | 
				
			||||||
 | 
					<td><code>@bg</code></td>
 | 
				
			||||||
 | 
					<td>string</td>
 | 
				
			||||||
 | 
					<td><code>"@bg":"#cube"</code></td>
 | 
				
			||||||
 | 
					<td>bg: binds fog near/far based to cube x/y/z (anim) values</td>
 | 
				
			||||||
 | 
					<td>custom property in 3D fileformats</td>
 | 
				
			||||||
 | 
					</tr>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<tr>
 | 
				
			||||||
 | 
					<td><code>@fog</code></td>
 | 
				
			||||||
 | 
					<td>string</td>
 | 
				
			||||||
 | 
					<td><code>"@fog":"#cube"</code></td>
 | 
				
			||||||
 | 
					<td>fog: binds fog near/far based to cube x/y (anim) values</td>
 | 
				
			||||||
 | 
					<td>custom property in 3D fileformats</td>
 | 
				
			||||||
 | 
					</tr>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<tr>
 | 
				
			||||||
 | 
					<td><code>@scroll</code></td>
 | 
				
			||||||
 | 
					<td>string</td>
 | 
				
			||||||
 | 
					<td><code>"@scroll":"#cube"</code></td>
 | 
				
			||||||
 | 
					<td>texturescrolling: binds texture x/y/rot based to cube x/y/z (anim) values</td>
 | 
				
			||||||
 | 
					<td>custom property in 3D fileformats</td>
 | 
				
			||||||
 | 
					</tr>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<tr>
 | 
				
			||||||
 | 
					<td><code>@emissive</code></td>
 | 
				
			||||||
 | 
					<td>string</td>
 | 
				
			||||||
 | 
					<td><code>"@emissive":"#cube"</code></td>
 | 
				
			||||||
 | 
					<td>day/night/mood: binds material’s emissive value to cube x/y/z (anim) values</td>
 | 
				
			||||||
 | 
					<td>custom property in 3D fileformats</td>
 | 
				
			||||||
 | 
					</tr>
 | 
				
			||||||
 | 
					</tbody>
 | 
				
			||||||
 | 
					</table>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<h2 id="usecase-click-object">Usecase: click object</h2>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<table>
 | 
				
			||||||
 | 
					<thead>
 | 
				
			||||||
 | 
					<tr>
 | 
				
			||||||
 | 
					<th>custom property</th>
 | 
				
			||||||
 | 
					<th>value</th>
 | 
				
			||||||
 | 
					<th>trigger when</th>
 | 
				
			||||||
 | 
					</tr>
 | 
				
			||||||
 | 
					</thead>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<tbody>
 | 
				
			||||||
 | 
					<tr>
 | 
				
			||||||
 | 
					<td>!clickme</td>
 | 
				
			||||||
 | 
					<td>bg=1,1,1&foo=2</td>
 | 
				
			||||||
 | 
					<td>object clicked</td>
 | 
				
			||||||
 | 
					</tr>
 | 
				
			||||||
 | 
					</tbody>
 | 
				
			||||||
 | 
					</table>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<h2 id="usecase-conditional-click-object">Usecase: conditional click object</h2>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<table>
 | 
				
			||||||
 | 
					<thead>
 | 
				
			||||||
 | 
					<tr>
 | 
				
			||||||
 | 
					<th>custom property</th>
 | 
				
			||||||
 | 
					<th>value</th>
 | 
				
			||||||
 | 
					<th>trigger when</th>
 | 
				
			||||||
 | 
					</tr>
 | 
				
			||||||
 | 
					</thead>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<tbody>
 | 
				
			||||||
 | 
					<tr>
 | 
				
			||||||
 | 
					<td>#</td>
 | 
				
			||||||
 | 
					<td>foo=1</td>
 | 
				
			||||||
 | 
					<td>scene</td>
 | 
				
			||||||
 | 
					</tr>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<tr>
 | 
				
			||||||
 | 
					<td>!clickme</td>
 | 
				
			||||||
 | 
					<td>q=foo>2&bg=1,1,1</td>
 | 
				
			||||||
 | 
					<td>object clicked and foo > 2</td>
 | 
				
			||||||
 | 
					</tr>
 | 
				
			||||||
 | 
					</tbody>
 | 
				
			||||||
 | 
					</table>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<blockquote>
 | 
				
			||||||
 | 
					<p>when a user clicks an object with the custom properties above, it should set the backgroundcolor to <code>1,1,1</code> when <code>foo</code> is greater than <code>2</code> (see previous example)</p>
 | 
				
			||||||
 | 
					</blockquote>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<h2 id="usecase-click-object-roundrobin">Usecase: click object (roundrobin)</h2>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<table>
 | 
				
			||||||
 | 
					<thead>
 | 
				
			||||||
 | 
					<tr>
 | 
				
			||||||
 | 
					<th>custom property</th>
 | 
				
			||||||
 | 
					<th>value</th>
 | 
				
			||||||
 | 
					<th>trigger when</th>
 | 
				
			||||||
 | 
					</tr>
 | 
				
			||||||
 | 
					</thead>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<tbody>
 | 
				
			||||||
 | 
					<tr>
 | 
				
			||||||
 | 
					<td>!cycleme</td>
 | 
				
			||||||
 | 
					<td>day|noon|night</td>
 | 
				
			||||||
 | 
					<td>object clicked</td>
 | 
				
			||||||
 | 
					</tr>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<tr>
 | 
				
			||||||
 | 
					<td>day</td>
 | 
				
			||||||
 | 
					<td>bg=1,1,1</td>
 | 
				
			||||||
 | 
					<td>roundrobin</td>
 | 
				
			||||||
 | 
					</tr>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<tr>
 | 
				
			||||||
 | 
					<td>noon</td>
 | 
				
			||||||
 | 
					<td>bg=0.5,0.5,0.5</td>
 | 
				
			||||||
 | 
					<td>roundrobin</td>
 | 
				
			||||||
 | 
					</tr>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<tr>
 | 
				
			||||||
 | 
					<td>night</td>
 | 
				
			||||||
 | 
					<td>bg=0,0,0&foo=2</td>
 | 
				
			||||||
 | 
					<td>roundrobin</td>
 | 
				
			||||||
 | 
					</tr>
 | 
				
			||||||
 | 
					</tbody>
 | 
				
			||||||
 | 
					</table>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<blockquote>
 | 
				
			||||||
 | 
					<p>when a user clicks an object with the custom properties above, it should trigger either <code>day</code> <code>noon</code> or <code>night</code> in roundrobin fashion.</p>
 | 
				
			||||||
 | 
					</blockquote>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<h2 id="usecase-click-object-or-uri-fragment-and-scene-load-trigger">Usecase: click object or URI fragment, and scene load trigger</h2>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<table>
 | 
				
			||||||
 | 
					<thead>
 | 
				
			||||||
 | 
					<tr>
 | 
				
			||||||
 | 
					<th>custom property</th>
 | 
				
			||||||
 | 
					<th>value</th>
 | 
				
			||||||
 | 
					<th>trigger when</th>
 | 
				
			||||||
 | 
					</tr>
 | 
				
			||||||
 | 
					</thead>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<tbody>
 | 
				
			||||||
 | 
					<tr>
 | 
				
			||||||
 | 
					<td>#</td>
 | 
				
			||||||
 | 
					<td>random</td>
 | 
				
			||||||
 | 
					<td>scene loaded</td>
 | 
				
			||||||
 | 
					</tr>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<tr>
 | 
				
			||||||
 | 
					<td>#random</td>
 | 
				
			||||||
 | 
					<td>random</td>
 | 
				
			||||||
 | 
					<td>URL contains #random</td>
 | 
				
			||||||
 | 
					</tr>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<tr>
 | 
				
			||||||
 | 
					<td>!random</td>
 | 
				
			||||||
 | 
					<td>day|noon|night</td>
 | 
				
			||||||
 | 
					<td>#random, # or click</td>
 | 
				
			||||||
 | 
					</tr>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<tr>
 | 
				
			||||||
 | 
					<td>day</td>
 | 
				
			||||||
 | 
					<td>bg=1,1,1</td>
 | 
				
			||||||
 | 
					<td>roundrobin</td>
 | 
				
			||||||
 | 
					</tr>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<tr>
 | 
				
			||||||
 | 
					<td>noon</td>
 | 
				
			||||||
 | 
					<td>bg=0.5,0.5,0.5</td>
 | 
				
			||||||
 | 
					<td>roundrobin</td>
 | 
				
			||||||
 | 
					</tr>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<tr>
 | 
				
			||||||
 | 
					<td>night</td>
 | 
				
			||||||
 | 
					<td>bg=0,0,0&foo=2</td>
 | 
				
			||||||
 | 
					<td>roundrobin</td>
 | 
				
			||||||
 | 
					</tr>
 | 
				
			||||||
 | 
					</tbody>
 | 
				
			||||||
 | 
					</table>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<h2 id="usecase-present-context-menu-with-options">Usecase: present context menu with options</h2>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<table>
 | 
				
			||||||
 | 
					<thead>
 | 
				
			||||||
 | 
					<tr>
 | 
				
			||||||
 | 
					<th>custom property</th>
 | 
				
			||||||
 | 
					<th>value</th>
 | 
				
			||||||
 | 
					<th>trigger when</th>
 | 
				
			||||||
 | 
					</tr>
 | 
				
			||||||
 | 
					</thead>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<tbody>
 | 
				
			||||||
 | 
					<tr>
 | 
				
			||||||
 | 
					<td>!random</td>
 | 
				
			||||||
 | 
					<td>!day</td>
 | 
				
			||||||
 | 
					<td>!noon</td>
 | 
				
			||||||
 | 
					</tr>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<tr>
 | 
				
			||||||
 | 
					<td>!day</td>
 | 
				
			||||||
 | 
					<td>bg=1,1,1</td>
 | 
				
			||||||
 | 
					<td>clicked in contextmenu</td>
 | 
				
			||||||
 | 
					</tr>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<tr>
 | 
				
			||||||
 | 
					<td>!noon</td>
 | 
				
			||||||
 | 
					<td>bg=0.5,0.5,0.5</td>
 | 
				
			||||||
 | 
					<td>clicked in contextmenu</td>
 | 
				
			||||||
 | 
					</tr>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<tr>
 | 
				
			||||||
 | 
					<td>!night</td>
 | 
				
			||||||
 | 
					<td>bg=0,0,0&foo=2</td>
 | 
				
			||||||
 | 
					<td>clicked in contextmenu</td>
 | 
				
			||||||
 | 
					</tr>
 | 
				
			||||||
 | 
					</tbody>
 | 
				
			||||||
 | 
					</table>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<blockquote>
 | 
				
			||||||
 | 
					<p>When interacting with an object with more than one <code>!</code>-macro, the XR Browser should offer a contextmenu to execute a macro.</p>
 | 
				
			||||||
 | 
					</blockquote>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<p>In a similar way, when <strong>any</strong> <code>!</code>-macro is present on the sceneroot, the XR Browser should offer a context-menu to execute those macro’s.</p>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<h2 id="event-bubble-flow">Event Bubble-flow</h2>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<p>click object with (<code>!clickme</code>:<code>AR</code> or <code>!clickme</code>: <code>!reset</code> e.g.)</p>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<pre><code>  ◻
 | 
				
			||||||
 | 
					  │  
 | 
				
			||||||
 | 
					  └── does current object contain this property-key (`AR` or `!reset` e.g.)?
 | 
				
			||||||
 | 
					         └── no: is there any (root)object containing property `AR`
 | 
				
			||||||
 | 
					           └── yes: evaluate its (roundrobin) XR macro-value(s) (and exit)
 | 
				
			||||||
 | 
					           └── no: trigger URL: #AR 
 | 
				
			||||||
 | 
					</code></pre>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<p>click object with (<code>!clickme</code>:<code>#AR|#VR</code> e.g.)</p>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<pre><code>  ◻
 | 
				
			||||||
 | 
					  │  
 | 
				
			||||||
 | 
					  └── apply the roundrobin (rotate the options, value `#AR` becomes `#VR` upon next click)
 | 
				
			||||||
 | 
					      └── is there any object with property-key (`#AR` e.g.)?
 | 
				
			||||||
 | 
					         └── no: just update the URL to `#AR`
 | 
				
			||||||
 | 
					           └── yes: apply its value to the scene, and update the URL to `#AR`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					click object with (`!clickme`:`!foo|!bar|!flop` e.g.)
 | 
				
			||||||
 | 
					</code></pre>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<p>◻
 | 
				
			||||||
 | 
					  │<br>
 | 
				
			||||||
 | 
					  └── apply the roundrobin (rotate the options, value <code>!foo</code> becomes <code>!bar</code> upon next click)
 | 
				
			||||||
 | 
					      └── is there any object with property-key (<code>!foo</code> e.g.)?
 | 
				
			||||||
 | 
					         └── no: do nothing
 | 
				
			||||||
 | 
					           └── yes: apply its value to the scene
 | 
				
			||||||
 | 
					“`</p>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<blockquote>
 | 
				
			||||||
 | 
					<p>Note that only macro’s can trigger roundrobin values or contextmenu’s, as well as roundrobin values never ending up in the toplevel URL.</p>
 | 
				
			||||||
 | 
					</blockquote>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<h1 id="security-considerations">Security Considerations</h1>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<h1 id="iana-considerations">IANA Considerations</h1>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<p>This document has no IANA actions.</p>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<h1 id="acknowledgments">Acknowledgments</h1>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<ul>
 | 
				
			||||||
 | 
					<li><a href="https://nlnet.nl">NLNET</a></li>
 | 
				
			||||||
 | 
					<li><a href="https://futureoftext.org">Future of Text</a></li>
 | 
				
			||||||
 | 
					<li><a href="https://visual-meta.info">visual-meta.info</a></li>
 | 
				
			||||||
 | 
					</ul>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<h1 id="appendix-definitions">Appendix: Definitions</h1>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<table>
 | 
				
			||||||
 | 
					<thead>
 | 
				
			||||||
 | 
					<tr>
 | 
				
			||||||
 | 
					<th>definition</th>
 | 
				
			||||||
 | 
					<th>explanation</th>
 | 
				
			||||||
 | 
					</tr>
 | 
				
			||||||
 | 
					</thead>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<tbody>
 | 
				
			||||||
 | 
					<tr>
 | 
				
			||||||
 | 
					<td>scene</td>
 | 
				
			||||||
 | 
					<td>a (local/remote) 3D scene or 3D file (index.gltf e.g.)</td>
 | 
				
			||||||
 | 
					</tr>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<tr>
 | 
				
			||||||
 | 
					<td>3D object</td>
 | 
				
			||||||
 | 
					<td>an object inside a scene characterized by vertex-, face- and customproperty data.</td>
 | 
				
			||||||
 | 
					</tr>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<tr>
 | 
				
			||||||
 | 
					<td>XR fragments</td>
 | 
				
			||||||
 | 
					<td>URI Fragment with spatial hints like <code>#pos=0,0,0&t=1,100</code> e.g.</td>
 | 
				
			||||||
 | 
					</tr>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<tr>
 | 
				
			||||||
 | 
					<td>query</td>
 | 
				
			||||||
 | 
					<td>an URI Fragment-operator which queries object(s) from a scene like <code>#q=cube</code></td>
 | 
				
			||||||
 | 
					</tr>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<tr>
 | 
				
			||||||
 | 
					<td>FPS</td>
 | 
				
			||||||
 | 
					<td>frames per second in spatial experiences (games,VR,AR e.g.), should be as high as possible</td>
 | 
				
			||||||
 | 
					</tr>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<tr>
 | 
				
			||||||
 | 
					<td><code>◻</code></td>
 | 
				
			||||||
 | 
					<td>ascii representation of an 3D object/mesh</td>
 | 
				
			||||||
 | 
					</tr>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<tr>
 | 
				
			||||||
 | 
					<td>(un)obtrusive</td>
 | 
				
			||||||
 | 
					<td>obtrusive: wrapping human text/thought in XML/HTML/JSON obfuscates human text into a salad of machine-symbols and words</td>
 | 
				
			||||||
 | 
					</tr>
 | 
				
			||||||
 | 
					</tbody>
 | 
				
			||||||
 | 
					</table>
 | 
				
			||||||
 | 
					</section>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					</body>
 | 
				
			||||||
 | 
					</html>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
							
								
								
									
										251
									
								
								doc/RFC_XR_Macros.md
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										251
									
								
								doc/RFC_XR_Macros.md
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,251 @@
 | 
				
			||||||
 | 
					%%%
 | 
				
			||||||
 | 
					Title = "XR Macros"
 | 
				
			||||||
 | 
					area = "Internet"
 | 
				
			||||||
 | 
					workgroup = "Internet Engineering Task Force"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[seriesInfo]
 | 
				
			||||||
 | 
					name = "XR-Macros"
 | 
				
			||||||
 | 
					value = "draft-XRMACROS-leonvankammen-00"
 | 
				
			||||||
 | 
					stream = "IETF"
 | 
				
			||||||
 | 
					status = "informational"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					date = 2023-04-12T00:00:00Z
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[[author]]
 | 
				
			||||||
 | 
					initials="L.R."
 | 
				
			||||||
 | 
					surname="van Kammen"
 | 
				
			||||||
 | 
					fullname="L.R. van Kammen"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					%%%
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<!-- for annotated version see: https://raw.githubusercontent.com/ietf-tools/rfcxml-templates-and-schemas/main/draft-rfcxml-general-template-annotated-00.xml -->
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<!--{
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  <style type="text/css">
 | 
				
			||||||
 | 
					  body{
 | 
				
			||||||
 | 
					    font-family: monospace;
 | 
				
			||||||
 | 
					    max-width: 1000px;
 | 
				
			||||||
 | 
					    font-size: 15px;
 | 
				
			||||||
 | 
					    padding: 0% 20%;
 | 
				
			||||||
 | 
					    line-height: 30px;
 | 
				
			||||||
 | 
					    color:#555;
 | 
				
			||||||
 | 
					    background:#F0F0F3
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  h1 { margin-top:40px; }
 | 
				
			||||||
 | 
					  pre{ line-height:18px; }
 | 
				
			||||||
 | 
					  a,a:visited,a:active{ color: #70f; }
 | 
				
			||||||
 | 
					  code{
 | 
				
			||||||
 | 
					    border: 1px solid #AAA;
 | 
				
			||||||
 | 
					    border-radius: 3px;
 | 
				
			||||||
 | 
					    padding: 0px 5px 2px 5px;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  pre{
 | 
				
			||||||
 | 
					    line-height: 18px;
 | 
				
			||||||
 | 
					    overflow: auto;
 | 
				
			||||||
 | 
					    padding: 12px;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  pre + code {
 | 
				
			||||||
 | 
					    background:#DDD;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  pre>code{
 | 
				
			||||||
 | 
					    border:none;
 | 
				
			||||||
 | 
					    border-radius:0px;
 | 
				
			||||||
 | 
					    padding:0;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  blockquote{
 | 
				
			||||||
 | 
					    padding-left: 30px;
 | 
				
			||||||
 | 
					    margin: 0;
 | 
				
			||||||
 | 
					    border-left: 5px solid #CCC;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  th {
 | 
				
			||||||
 | 
					      border-bottom: 1px solid #000;
 | 
				
			||||||
 | 
					      text-align: left;
 | 
				
			||||||
 | 
					      padding-right:45px;
 | 
				
			||||||
 | 
					      padding-left:7px;
 | 
				
			||||||
 | 
					      background: #DDD;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  td {
 | 
				
			||||||
 | 
					      border-bottom: 1px solid #CCC;
 | 
				
			||||||
 | 
					      font-size:13px; 
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  </style>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<br>
 | 
				
			||||||
 | 
					<h1>XR Macros</h1>
 | 
				
			||||||
 | 
					<br>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<pre>
 | 
				
			||||||
 | 
					stream:    IETF
 | 
				
			||||||
 | 
					area:      Internet
 | 
				
			||||||
 | 
					status:    informational
 | 
				
			||||||
 | 
					author:    Leon van Kammen
 | 
				
			||||||
 | 
					date:      2023-04-12T00:00:00Z
 | 
				
			||||||
 | 
					workgroup: Internet Engineering Task Force
 | 
				
			||||||
 | 
					value:     draft-XRMACROS-leonvankammen-00
 | 
				
			||||||
 | 
					</pre>  
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}-->
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.# Abstract
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					This draft offers a specification for embedding macros in existing 3D scenes/assets, to offer simple interactions and configure the renderer further.<br>
 | 
				
			||||||
 | 
					Together with URI Fragments, it allows for rich immersive experiences without the need of a complicated sandboxed scripting languages.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					> Almost every idea in this document is demonstrated at [https://xrfragment.org](https://xrfragment.org), as this spec was created during the [XR Fragments](https://xrfragment.org) spec.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					{mainmatter}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Introduction
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					How can we add more features to existing text & 3D scenes, without introducing new dataformats?<br>
 | 
				
			||||||
 | 
					Historically, there's many attempts to create the ultimate markuplanguage or 3D fileformat.<br>
 | 
				
			||||||
 | 
					Their lowest common denominator is: (co)authoring using plain text.<br>
 | 
				
			||||||
 | 
					Therefore, XR Macros allows us to enrich/connect existing dataformats, by offering a polyglot notation based on existing notations:<br>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					1. getting/setting common used 3D properties using querystring- or JSON-notation 
 | 
				
			||||||
 | 
					1. targeting 3D properties using the lightweight query notation present in [XR Fragments](https://xrfragment.org)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					> NOTE: The chapters in this document are ordered from highlevel to lowlevel (technical) as much as possible
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Core principle
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					1. XR Macros use querystrings, but are HTML-agnostic (though pseudo-XR Fragment browsers **can** be implemented on top of HTML/Javascript). 
 | 
				
			||||||
 | 
					1. An XR Macro is 3D metadata which starts with '!' (`!clickme: fog=0,10` e.g.)
 | 
				
			||||||
 | 
					1. Metadata-values can contain the `|` symbol to 🎲 roundrobin variable values (`!toggleme: fog=0,10|fog=0,1000` e.g.)
 | 
				
			||||||
 | 
					1. XR Macros acts as simple eventhandlers for URI Fragments: they are automatically published on the ([XR Fragments](https://xrfragment.org)) hashbus, to act as events (so more serious scripting languages can react to them as well).
 | 
				
			||||||
 | 
					1. XR Macros can assign object metadata (`!setlocal: foo=1` writes `foo:1` metadata to the object containing the `!setlocal` metadata)
 | 
				
			||||||
 | 
					1. XR Macros can assign global metadata  (`!setfoo: #foo=1` writes `foo:1` metadata to the root scene-node)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					> These very simple principles allow for rich interactions and dynamic querying
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Conventions and Definitions
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					See appendix below in case certain terms are not clear.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# List of XR Macros 
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					(XR) Macros can be embedded in 3D assets/scenes.<br>
 | 
				
			||||||
 | 
					Macros enrich existing spatial content with a lowcode, limited logic-layer, by recursive (economic) use of the querystring syntax (which search engines and [XR Fragments](https://xrfragment.org) already uses.<br>
 | 
				
			||||||
 | 
					This is done by allowing string/integer variables, and the `|` symbol to roundrobin variable values.<br>
 | 
				
			||||||
 | 
					Macros also act as events, so more serious scripting languages can react to them as well.<br>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					| key          | type     | example (JSON)         | function            | existing compatibility                 |
 | 
				
			||||||
 | 
					|--------------|----------|------------------------|---------------------|----------------------------------------|
 | 
				
			||||||
 | 
					| `@bg`        | string   | `"@bg":"#cube"`        | bg: binds fog near/far based to cube x/y/z (anim) values | custom property in 3D fileformats      |
 | 
				
			||||||
 | 
					| `@fog`       | string   | `"@fog":"#cube"`       | fog: binds fog near/far based to cube x/y (anim) values | custom property in 3D fileformats      |
 | 
				
			||||||
 | 
					| `@scroll`    | string   | `"@scroll":"#cube"`    | texturescrolling: binds texture x/y/rot based to cube x/y/z (anim) values | custom property in 3D fileformats      |
 | 
				
			||||||
 | 
					| `@emissive`  | string   | `"@emissive":"#cube"`  | day/night/mood: binds material's emissive value to cube x/y/z (anim) values | custom property in 3D fileformats      |
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## Usecase: click object
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					| custom property | value                    | trigger when           |
 | 
				
			||||||
 | 
					|-----------------|--------------------------|------------------------|
 | 
				
			||||||
 | 
					| !clickme        | bg=1,1,1&foo=2           | object clicked         |
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## Usecase: conditional click object
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					| custom property | value                    | trigger when                |
 | 
				
			||||||
 | 
					|-----------------|--------------------------|-----------------------------|
 | 
				
			||||||
 | 
					| #               | foo=1                    | scene                       |
 | 
				
			||||||
 | 
					| !clickme        | q=foo>2&bg=1,1,1         | object clicked and foo > 2  |
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					> when a user clicks an object with the custom properties above, it should set the backgroundcolor to `1,1,1` when `foo` is greater than `2` (see previous example)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## Usecase: click object (roundrobin)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					| custom property | value                    | trigger when           |
 | 
				
			||||||
 | 
					|-----------------|--------------------------|------------------------|
 | 
				
			||||||
 | 
					| !cycleme        | day|noon|night | object clicked         |
 | 
				
			||||||
 | 
					| day             | bg=1,1,1                 | roundrobin             |
 | 
				
			||||||
 | 
					| noon            | bg=0.5,0.5,0.5           | roundrobin             |
 | 
				
			||||||
 | 
					| night           | bg=0,0,0&foo=2           | roundrobin             |
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					> when a user clicks an object with the custom properties above, it should trigger either `day` `noon` or `night` in roundrobin fashion.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## Usecase: click object or URI fragment, and scene load trigger
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					| custom property | value                    | trigger when           |
 | 
				
			||||||
 | 
					|-----------------|--------------------------|------------------------|
 | 
				
			||||||
 | 
					| #               | random                   | scene loaded           |
 | 
				
			||||||
 | 
					| #random         | random                   | URL contains #random   |
 | 
				
			||||||
 | 
					| !random         | day|noon|night | #random, # or click    |
 | 
				
			||||||
 | 
					| day             | bg=1,1,1                 | roundrobin             |
 | 
				
			||||||
 | 
					| noon            | bg=0.5,0.5,0.5           | roundrobin             |
 | 
				
			||||||
 | 
					| night           | bg=0,0,0&foo=2           | roundrobin             |
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## Usecase: present context menu with options
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					| custom property | value                    | trigger when           |
 | 
				
			||||||
 | 
					|-----------------|--------------------------|------------------------|
 | 
				
			||||||
 | 
					| !random         | !day|!noon|!night        | clicked in contextmenu |
 | 
				
			||||||
 | 
					| !day            | bg=1,1,1                 | clicked in contextmenu |
 | 
				
			||||||
 | 
					| !noon           | bg=0.5,0.5,0.5           | clicked in contextmenu |
 | 
				
			||||||
 | 
					| !night          | bg=0,0,0&foo=2           | clicked in contextmenu |
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					> When interacting with an object with more than one `!`-macro, the XR Browser should offer a contextmenu to execute a macro.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					In a similar way, when **any** `!`-macro is present on the sceneroot, the XR Browser should offer a context-menu to execute those macro's.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## Event Bubble-flow
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					click object with (`!clickme`:`AR` or `!clickme`: `!reset` e.g.)
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					  ◻
 | 
				
			||||||
 | 
					  │  
 | 
				
			||||||
 | 
					  └── does current object contain this property-key (`AR` or `!reset` e.g.)?
 | 
				
			||||||
 | 
					         └── no: is there any (root)object containing property `AR`
 | 
				
			||||||
 | 
					           └── yes: evaluate its (roundrobin) XR macro-value(s) (and exit)
 | 
				
			||||||
 | 
					           └── no: trigger URL: #AR 
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					click object with (`!clickme`:`#AR|#VR` e.g.)
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					  ◻
 | 
				
			||||||
 | 
					  │  
 | 
				
			||||||
 | 
					  └── apply the roundrobin (rotate the options, value `#AR` becomes `#VR` upon next click)
 | 
				
			||||||
 | 
					      └── is there any object with property-key (`#AR` e.g.)?
 | 
				
			||||||
 | 
					         └── no: just update the URL to `#AR`
 | 
				
			||||||
 | 
					           └── yes: apply its value to the scene, and update the URL to `#AR`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					click object with (`!clickme`:`!foo|!bar|!flop` e.g.)
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					  ◻
 | 
				
			||||||
 | 
					  │  
 | 
				
			||||||
 | 
					  └── apply the roundrobin (rotate the options, value `!foo` becomes `!bar` upon next click)
 | 
				
			||||||
 | 
					      └── is there any object with property-key (`!foo` e.g.)?
 | 
				
			||||||
 | 
					         └── no: do nothing 
 | 
				
			||||||
 | 
					           └── yes: apply its value to the scene
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					> Note that only macro's can trigger roundrobin values or contextmenu's, as well as roundrobin values never ending up in the toplevel URL.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Security Considerations
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# IANA Considerations
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					This document has no IANA actions.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Acknowledgments
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					* [NLNET](https://nlnet.nl)
 | 
				
			||||||
 | 
					* [Future of Text](https://futureoftext.org)
 | 
				
			||||||
 | 
					* [visual-meta.info](https://visual-meta.info)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Appendix: Definitions 
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					|definition            | explanation                                                                                                                   |
 | 
				
			||||||
 | 
					|----------------------|-------------------------------------------------------------------------------------------------------------------------------|
 | 
				
			||||||
 | 
					|scene                 | a (local/remote) 3D scene or 3D file (index.gltf e.g.)                                                                        |
 | 
				
			||||||
 | 
					|3D object             | an object inside a scene characterized by vertex-, face- and customproperty data.                                             |
 | 
				
			||||||
 | 
					|XR fragments          | URI Fragment with spatial hints like `#pos=0,0,0&t=1,100` e.g.                                                                |
 | 
				
			||||||
 | 
					|query                 | an URI Fragment-operator which queries object(s) from a scene like `#q=cube`                                                  |
 | 
				
			||||||
 | 
					|FPS                   | frames per second in spatial experiences (games,VR,AR e.g.), should be as high as possible                                    |
 | 
				
			||||||
 | 
					|`◻`                   | ascii representation of an 3D object/mesh                                                                                     |
 | 
				
			||||||
 | 
					|(un)obtrusive         | obtrusive: wrapping human text/thought in XML/HTML/JSON obfuscates human text into a salad of machine-symbols and words       |
 | 
				
			||||||
 | 
					
 | 
				
			||||||
							
								
								
									
										392
									
								
								doc/RFC_XR_Macros.txt
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										392
									
								
								doc/RFC_XR_Macros.txt
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,392 @@
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Internet Engineering Task Force                          L.R. van Kammen
 | 
				
			||||||
 | 
					Internet-Draft                                         24 September 2025
 | 
				
			||||||
 | 
					Intended status: Informational                                          
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                               XR Macros
 | 
				
			||||||
 | 
					                    draft-XRMACROS-leonvankammen-00
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Abstract
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					   This draft offers a specification for embedding macros in existing 3D
 | 
				
			||||||
 | 
					   scenes/assets, to offer simple interactions and configure the
 | 
				
			||||||
 | 
					   renderer further.
 | 
				
			||||||
 | 
					   Together with URI Fragments, it allows for rich immersive experiences
 | 
				
			||||||
 | 
					   without the need of a complicated sandboxed scripting languages.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					   Almost every idea in this document is demonstrated at
 | 
				
			||||||
 | 
					   https://xrfragment.org (https://xrfragment.org), as this spec was
 | 
				
			||||||
 | 
					   created during the XR Fragments (https://xrfragment.org) spec.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Status of This Memo
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					   This Internet-Draft is submitted in full conformance with the
 | 
				
			||||||
 | 
					   provisions of BCP 78 and BCP 79.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					   Internet-Drafts are working documents of the Internet Engineering
 | 
				
			||||||
 | 
					   Task Force (IETF).  Note that other groups may also distribute
 | 
				
			||||||
 | 
					   working documents as Internet-Drafts.  The list of current Internet-
 | 
				
			||||||
 | 
					   Drafts is at https://datatracker.ietf.org/drafts/current/.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					   Internet-Drafts are draft documents valid for a maximum of six months
 | 
				
			||||||
 | 
					   and may be updated, replaced, or obsoleted by other documents at any
 | 
				
			||||||
 | 
					   time.  It is inappropriate to use Internet-Drafts as reference
 | 
				
			||||||
 | 
					   material or to cite them other than as "work in progress."
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					   This Internet-Draft will expire on 28 March 2026.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Copyright Notice
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					   Copyright (c) 2025 IETF Trust and the persons identified as the
 | 
				
			||||||
 | 
					   document authors.  All rights reserved.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					   This document is subject to BCP 78 and the IETF Trust's Legal
 | 
				
			||||||
 | 
					   Provisions Relating to IETF Documents (https://trustee.ietf.org/
 | 
				
			||||||
 | 
					   license-info) in effect on the date of publication of this document.
 | 
				
			||||||
 | 
					   Please review these documents carefully, as they describe your rights
 | 
				
			||||||
 | 
					   and restrictions with respect to this document.  Code Components
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					van Kammen                Expires 28 March 2026                 [Page 1]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Internet-Draft                  XR Macros                 September 2025
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					   extracted from this document must include Revised BSD License text as
 | 
				
			||||||
 | 
					   described in Section 4.e of the Trust Legal Provisions and are
 | 
				
			||||||
 | 
					   provided without warranty as described in the Revised BSD License.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Table of Contents
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					   1.  Introduction  . . . . . . . . . . . . . . . . . . . . . . . .   2
 | 
				
			||||||
 | 
					   2.  Core principle  . . . . . . . . . . . . . . . . . . . . . . .   2
 | 
				
			||||||
 | 
					   3.  Conventions and Definitions . . . . . . . . . . . . . . . . .   3
 | 
				
			||||||
 | 
					   4.  List of XR Macros . . . . . . . . . . . . . . . . . . . . . .   3
 | 
				
			||||||
 | 
					     4.1.  Usecase: click object . . . . . . . . . . . . . . . . . .   4
 | 
				
			||||||
 | 
					     4.2.  Usecase: conditional click object . . . . . . . . . . . .   4
 | 
				
			||||||
 | 
					     4.3.  Usecase: click object (roundrobin)  . . . . . . . . . . .   5
 | 
				
			||||||
 | 
					     4.4.  Usecase: click object or URI fragment, and scene load
 | 
				
			||||||
 | 
					           trigger . . . . . . . . . . . . . . . . . . . . . . . . .   5
 | 
				
			||||||
 | 
					     4.5.  Usecase: present context menu with options  . . . . . . .   6
 | 
				
			||||||
 | 
					     4.6.  Event Bubble-flow . . . . . . . . . . . . . . . . . . . .   6
 | 
				
			||||||
 | 
					   5.  Security Considerations . . . . . . . . . . . . . . . . . . .   7
 | 
				
			||||||
 | 
					   6.  IANA Considerations . . . . . . . . . . . . . . . . . . . . .   7
 | 
				
			||||||
 | 
					   7.  Acknowledgments . . . . . . . . . . . . . . . . . . . . . . .   7
 | 
				
			||||||
 | 
					   8.  Appendix: Definitions . . . . . . . . . . . . . . . . . . . .   7
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					1.  Introduction
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					   How can we add more features to existing text & 3D scenes, without
 | 
				
			||||||
 | 
					   introducing new dataformats?
 | 
				
			||||||
 | 
					   Historically, there's many attempts to create the ultimate
 | 
				
			||||||
 | 
					   markuplanguage or 3D fileformat.
 | 
				
			||||||
 | 
					   Their lowest common denominator is: (co)authoring using plain text.
 | 
				
			||||||
 | 
					   Therefore, XR Macros allows us to enrich/connect existing
 | 
				
			||||||
 | 
					   dataformats, by offering a polyglot notation based on existing
 | 
				
			||||||
 | 
					   notations:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					   1.  getting/setting common used 3D properties using querystring- or
 | 
				
			||||||
 | 
					       JSON-notation
 | 
				
			||||||
 | 
					   2.  targeting 3D properties using the lightweight query notation
 | 
				
			||||||
 | 
					       present in XR Fragments (https://xrfragment.org)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					   |  NOTE: The chapters in this document are ordered from highlevel to
 | 
				
			||||||
 | 
					   |  lowlevel (technical) as much as possible
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					2.  Core principle
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					   1.  XR Macros use querystrings, but are HTML-agnostic (though pseudo-
 | 
				
			||||||
 | 
					       XR Fragment browsers *can* be implemented on top of HTML/
 | 
				
			||||||
 | 
					       Javascript).
 | 
				
			||||||
 | 
					   2.  An XR Macro is 3D metadata which starts with '!' (!clickme:
 | 
				
			||||||
 | 
					       fog=0,10 e.g.)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					van Kammen                Expires 28 March 2026                 [Page 2]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Internet-Draft                  XR Macros                 September 2025
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					   3.  Metadata-values can contain the | symbol to 🎲 roundrobin variable
 | 
				
			||||||
 | 
					       values (!toggleme: fog=0,10|fog=0,1000 e.g.)
 | 
				
			||||||
 | 
					   4.  XR Macros acts as simple eventhandlers for URI Fragments: they
 | 
				
			||||||
 | 
					       are automatically published on the (XR Fragments
 | 
				
			||||||
 | 
					       (https://xrfragment.org)) hashbus, to act as events (so more
 | 
				
			||||||
 | 
					       serious scripting languages can react to them as well).
 | 
				
			||||||
 | 
					   5.  XR Macros can assign object metadata (!setlocal: foo=1 writes
 | 
				
			||||||
 | 
					       foo:1 metadata to the object containing the !setlocal metadata)
 | 
				
			||||||
 | 
					   6.  XR Macros can assign global metadata (!setfoo: #foo=1 writes
 | 
				
			||||||
 | 
					       foo:1 metadata to the root scene-node)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					   |  These very simple principles allow for rich interactions and
 | 
				
			||||||
 | 
					   |  dynamic querying
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					3.  Conventions and Definitions
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					   See appendix below in case certain terms are not clear.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					4.  List of XR Macros
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					   (XR) Macros can be embedded in 3D assets/scenes.
 | 
				
			||||||
 | 
					   Macros enrich existing spatial content with a lowcode, limited logic-
 | 
				
			||||||
 | 
					   layer, by recursive (economic) use of the querystring syntax (which
 | 
				
			||||||
 | 
					   search engines and XR Fragments (https://xrfragment.org) already
 | 
				
			||||||
 | 
					   uses.
 | 
				
			||||||
 | 
					   This is done by allowing string/integer variables, and the | symbol
 | 
				
			||||||
 | 
					   to roundrobin variable values.
 | 
				
			||||||
 | 
					   Macros also act as events, so more serious scripting languages can
 | 
				
			||||||
 | 
					   react to them as well.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					van Kammen                Expires 28 March 2026                 [Page 3]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Internet-Draft                  XR Macros                 September 2025
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					   +=========+======+===================+=================+=============+
 | 
				
			||||||
 | 
					   |key      |type  |example (JSON)     |function         |existing     |
 | 
				
			||||||
 | 
					   |         |      |                   |                 |compatibility|
 | 
				
			||||||
 | 
					   +=========+======+===================+=================+=============+
 | 
				
			||||||
 | 
					   |@bg      |string|"@bg":"#cube"      |bg: binds fog    |custom       |
 | 
				
			||||||
 | 
					   |         |      |                   |near/far based to|property in  |
 | 
				
			||||||
 | 
					   |         |      |                   |cube x/y/z (anim)|3D           |
 | 
				
			||||||
 | 
					   |         |      |                   |values           |fileformats  |
 | 
				
			||||||
 | 
					   +---------+------+-------------------+-----------------+-------------+
 | 
				
			||||||
 | 
					   |@fog     |string|"@fog":"#cube"     |fog: binds fog   |custom       |
 | 
				
			||||||
 | 
					   |         |      |                   |near/far based to|property in  |
 | 
				
			||||||
 | 
					   |         |      |                   |cube x/y (anim)  |3D           |
 | 
				
			||||||
 | 
					   |         |      |                   |values           |fileformats  |
 | 
				
			||||||
 | 
					   +---------+------+-------------------+-----------------+-------------+
 | 
				
			||||||
 | 
					   |@scroll  |string|"@scroll":"#cube"  |texturescrolling:|custom       |
 | 
				
			||||||
 | 
					   |         |      |                   |binds texture    |property in  |
 | 
				
			||||||
 | 
					   |         |      |                   |x/y/rot based to |3D           |
 | 
				
			||||||
 | 
					   |         |      |                   |cube x/y/z (anim)|fileformats  |
 | 
				
			||||||
 | 
					   |         |      |                   |values           |             |
 | 
				
			||||||
 | 
					   +---------+------+-------------------+-----------------+-------------+
 | 
				
			||||||
 | 
					   |@emissive|string|"@emissive":"#cube"|day/night/mood:  |custom       |
 | 
				
			||||||
 | 
					   |         |      |                   |binds material's |property in  |
 | 
				
			||||||
 | 
					   |         |      |                   |emissive value to|3D           |
 | 
				
			||||||
 | 
					   |         |      |                   |cube x/y/z (anim)|fileformats  |
 | 
				
			||||||
 | 
					   |         |      |                   |values           |             |
 | 
				
			||||||
 | 
					   +---------+------+-------------------+-----------------+-------------+
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                                  Table 1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					4.1.  Usecase: click object
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					           +=================+================+================+
 | 
				
			||||||
 | 
					           | custom property | value          | trigger when   |
 | 
				
			||||||
 | 
					           +=================+================+================+
 | 
				
			||||||
 | 
					           | !clickme        | bg=1,1,1&foo=2 | object clicked |
 | 
				
			||||||
 | 
					           +-----------------+----------------+----------------+
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                                  Table 2
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					4.2.  Usecase: conditional click object
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    +=================+==================+============================+
 | 
				
			||||||
 | 
					    | custom property | value            | trigger when               |
 | 
				
			||||||
 | 
					    +=================+==================+============================+
 | 
				
			||||||
 | 
					    | #               | foo=1            | scene                      |
 | 
				
			||||||
 | 
					    +-----------------+------------------+----------------------------+
 | 
				
			||||||
 | 
					    | !clickme        | q=foo>2&bg=1,1,1 | object clicked and foo > 2 |
 | 
				
			||||||
 | 
					    +-----------------+------------------+----------------------------+
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					van Kammen                Expires 28 March 2026                 [Page 4]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Internet-Draft                  XR Macros                 September 2025
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                                  Table 3
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					   |  when a user clicks an object with the custom properties above, it
 | 
				
			||||||
 | 
					   |  should set the backgroundcolor to 1,1,1 when foo is greater than 2
 | 
				
			||||||
 | 
					   |  (see previous example)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					4.3.  Usecase: click object (roundrobin)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					           +=================+================+================+
 | 
				
			||||||
 | 
					           | custom property | value          | trigger when   |
 | 
				
			||||||
 | 
					           +=================+================+================+
 | 
				
			||||||
 | 
					           | !cycleme        | day|noon|night | object clicked |
 | 
				
			||||||
 | 
					           +-----------------+----------------+----------------+
 | 
				
			||||||
 | 
					           | day             | bg=1,1,1       | roundrobin     |
 | 
				
			||||||
 | 
					           +-----------------+----------------+----------------+
 | 
				
			||||||
 | 
					           | noon            | bg=0.5,0.5,0.5 | roundrobin     |
 | 
				
			||||||
 | 
					           +-----------------+----------------+----------------+
 | 
				
			||||||
 | 
					           | night           | bg=0,0,0&foo=2 | roundrobin     |
 | 
				
			||||||
 | 
					           +-----------------+----------------+----------------+
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                                  Table 4
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					   |  when a user clicks an object with the custom properties above, it
 | 
				
			||||||
 | 
					   |  should trigger either day noon or night in roundrobin fashion.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					4.4.  Usecase: click object or URI fragment, and scene load trigger
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        +=================+================+======================+
 | 
				
			||||||
 | 
					        | custom property | value          | trigger when         |
 | 
				
			||||||
 | 
					        +=================+================+======================+
 | 
				
			||||||
 | 
					        | #               | random         | scene loaded         |
 | 
				
			||||||
 | 
					        +-----------------+----------------+----------------------+
 | 
				
			||||||
 | 
					        | #random         | random         | URL contains #random |
 | 
				
			||||||
 | 
					        +-----------------+----------------+----------------------+
 | 
				
			||||||
 | 
					        | !random         | day|noon|night | #random, # or click  |
 | 
				
			||||||
 | 
					        +-----------------+----------------+----------------------+
 | 
				
			||||||
 | 
					        | day             | bg=1,1,1       | roundrobin           |
 | 
				
			||||||
 | 
					        +-----------------+----------------+----------------------+
 | 
				
			||||||
 | 
					        | noon            | bg=0.5,0.5,0.5 | roundrobin           |
 | 
				
			||||||
 | 
					        +-----------------+----------------+----------------------+
 | 
				
			||||||
 | 
					        | night           | bg=0,0,0&foo=2 | roundrobin           |
 | 
				
			||||||
 | 
					        +-----------------+----------------+----------------------+
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                                  Table 5
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					van Kammen                Expires 28 March 2026                 [Page 5]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Internet-Draft                  XR Macros                 September 2025
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					4.5.  Usecase: present context menu with options
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					       +=================+================+========================+
 | 
				
			||||||
 | 
					       | custom property | value          | trigger when           |
 | 
				
			||||||
 | 
					       +=================+================+========================+
 | 
				
			||||||
 | 
					       | !random         | !day           | !noon                  |
 | 
				
			||||||
 | 
					       +-----------------+----------------+------------------------+
 | 
				
			||||||
 | 
					       | !day            | bg=1,1,1       | clicked in contextmenu |
 | 
				
			||||||
 | 
					       +-----------------+----------------+------------------------+
 | 
				
			||||||
 | 
					       | !noon           | bg=0.5,0.5,0.5 | clicked in contextmenu |
 | 
				
			||||||
 | 
					       +-----------------+----------------+------------------------+
 | 
				
			||||||
 | 
					       | !night          | bg=0,0,0&foo=2 | clicked in contextmenu |
 | 
				
			||||||
 | 
					       +-----------------+----------------+------------------------+
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                                  Table 6
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					   |  When interacting with an object with more than one !-macro, the XR
 | 
				
			||||||
 | 
					   |  Browser should offer a contextmenu to execute a macro.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					   In a similar way, when *any* !-macro is present on the sceneroot, the
 | 
				
			||||||
 | 
					   XR Browser should offer a context-menu to execute those macro's.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					4.6.  Event Bubble-flow
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					   click object with (!clickme:AR or !clickme: !reset e.g.)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  ◻
 | 
				
			||||||
 | 
					  │
 | 
				
			||||||
 | 
					  └── does current object contain this property-key (`AR` or `!reset` e.g.)?
 | 
				
			||||||
 | 
					         └── no: is there any (root)object containing property `AR`
 | 
				
			||||||
 | 
					           └── yes: evaluate its (roundrobin) XR macro-value(s) (and exit)
 | 
				
			||||||
 | 
					           └── no: trigger URL: #AR
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					   click object with (!clickme:#AR|#VR e.g.)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  ◻
 | 
				
			||||||
 | 
					  │
 | 
				
			||||||
 | 
					  └── apply the roundrobin (rotate the options, value `#AR` becomes `#VR` upon next click)
 | 
				
			||||||
 | 
					      └── is there any object with property-key (`#AR` e.g.)?
 | 
				
			||||||
 | 
					         └── no: just update the URL to `#AR`
 | 
				
			||||||
 | 
					           └── yes: apply its value to the scene, and update the URL to `#AR`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					click object with (`!clickme`:`!foo|!bar|!flop` e.g.)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					   ◻ │
 | 
				
			||||||
 | 
					   └── apply the roundrobin (rotate the options, value !foo becomes !bar
 | 
				
			||||||
 | 
					   upon next click) └── is there any object with property-key (!foo
 | 
				
			||||||
 | 
					   e.g.)?  └── no: do nothing └── yes: apply its value to the scene ```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					van Kammen                Expires 28 March 2026                 [Page 6]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Internet-Draft                  XR Macros                 September 2025
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					   |  Note that only macro's can trigger roundrobin values or
 | 
				
			||||||
 | 
					   |  contextmenu's, as well as roundrobin values never ending up in the
 | 
				
			||||||
 | 
					   |  toplevel URL.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					5.  Security Considerations
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					6.  IANA Considerations
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					   This document has no IANA actions.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					7.  Acknowledgments
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					   *  NLNET (https://nlnet.nl)
 | 
				
			||||||
 | 
					   *  Future of Text (https://futureoftext.org)
 | 
				
			||||||
 | 
					   *  visual-meta.info (https://visual-meta.info)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					8.  Appendix: Definitions
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					   +===============+===================================================+
 | 
				
			||||||
 | 
					   | definition    | explanation                                       |
 | 
				
			||||||
 | 
					   +===============+===================================================+
 | 
				
			||||||
 | 
					   | scene         | a (local/remote) 3D scene or 3D file              |
 | 
				
			||||||
 | 
					   |               | (index.gltf e.g.)                                 |
 | 
				
			||||||
 | 
					   +---------------+---------------------------------------------------+
 | 
				
			||||||
 | 
					   | 3D object     | an object inside a scene characterized by         |
 | 
				
			||||||
 | 
					   |               | vertex-, face- and customproperty data.           |
 | 
				
			||||||
 | 
					   +---------------+---------------------------------------------------+
 | 
				
			||||||
 | 
					   | XR fragments  | URI Fragment with spatial hints like              |
 | 
				
			||||||
 | 
					   |               | #pos=0,0,0&t=1,100 e.g.                           |
 | 
				
			||||||
 | 
					   +---------------+---------------------------------------------------+
 | 
				
			||||||
 | 
					   | query         | an URI Fragment-operator which queries            |
 | 
				
			||||||
 | 
					   |               | object(s) from a scene like #q=cube               |
 | 
				
			||||||
 | 
					   +---------------+---------------------------------------------------+
 | 
				
			||||||
 | 
					   | FPS           | frames per second in spatial experiences          |
 | 
				
			||||||
 | 
					   |               | (games,VR,AR e.g.), should be as high as          |
 | 
				
			||||||
 | 
					   |               | possible                                          |
 | 
				
			||||||
 | 
					   +---------------+---------------------------------------------------+
 | 
				
			||||||
 | 
					   | ◻             | ascii representation of an 3D object/mesh         |
 | 
				
			||||||
 | 
					   +---------------+---------------------------------------------------+
 | 
				
			||||||
 | 
					   | (un)obtrusive | obtrusive: wrapping human text/thought in         |
 | 
				
			||||||
 | 
					   |               | XML/HTML/JSON obfuscates human text into          |
 | 
				
			||||||
 | 
					   |               | a salad of machine-symbols and words              |
 | 
				
			||||||
 | 
					   +---------------+---------------------------------------------------+
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                                  Table 7
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					van Kammen                Expires 28 March 2026                 [Page 7]
 | 
				
			||||||
							
								
								
									
										381
									
								
								doc/RFC_XR_Macros.xml
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										381
									
								
								doc/RFC_XR_Macros.xml
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,381 @@
 | 
				
			||||||
 | 
					<?xml version="1.0" encoding="utf-8"?>
 | 
				
			||||||
 | 
					<!-- name="GENERATOR" content="github.com/mmarkdown/mmark Mmark Markdown Processor - mmark.miek.nl" -->
 | 
				
			||||||
 | 
					<rfc version="3" ipr="trust200902" docName="draft-XRMACROS-leonvankammen-00" submissionType="IETF" category="info" xml:lang="en" xmlns:xi="http://www.w3.org/2001/XInclude" indexInclude="true" consensus="true">
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<front>
 | 
				
			||||||
 | 
					<title>XR Macros</title><seriesInfo value="draft-XRMACROS-leonvankammen-00" stream="IETF" status="informational" name="XR-Macros"></seriesInfo>
 | 
				
			||||||
 | 
					<author initials="L.R." surname="van Kammen" fullname="L.R. van Kammen"><organization></organization><address><postal><street></street>
 | 
				
			||||||
 | 
					</postal></address></author><date/>
 | 
				
			||||||
 | 
					<area>Internet</area>
 | 
				
			||||||
 | 
					<workgroup>Internet Engineering Task Force</workgroup>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<abstract>
 | 
				
			||||||
 | 
					<t>This draft offers a specification for embedding macros in existing 3D scenes/assets, to offer simple interactions and configure the renderer further.<br />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Together with URI Fragments, it allows for rich immersive experiences without the need of a complicated sandboxed scripting languages.</t>
 | 
				
			||||||
 | 
					<t>Almost every idea in this document is demonstrated at <eref target="https://xrfragment.org">https://xrfragment.org</eref>, as this spec was created during the <eref target="https://xrfragment.org">XR Fragments</eref> spec.</t>
 | 
				
			||||||
 | 
					</abstract>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					</front>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<middle>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<section anchor="introduction"><name>Introduction</name>
 | 
				
			||||||
 | 
					<t>How can we add more features to existing text & 3D scenes, without introducing new dataformats?<br />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Historically, there's many attempts to create the ultimate markuplanguage or 3D fileformat.<br />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Their lowest common denominator is: (co)authoring using plain text.<br />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Therefore, XR Macros allows us to enrich/connect existing dataformats, by offering a polyglot notation based on existing notations:<br />
 | 
				
			||||||
 | 
					</t>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<ol spacing="compact">
 | 
				
			||||||
 | 
					<li>getting/setting common used 3D properties using querystring- or JSON-notation</li>
 | 
				
			||||||
 | 
					<li>targeting 3D properties using the lightweight query notation present in <eref target="https://xrfragment.org">XR Fragments</eref></li>
 | 
				
			||||||
 | 
					</ol>
 | 
				
			||||||
 | 
					<blockquote><t>NOTE: The chapters in this document are ordered from highlevel to lowlevel (technical) as much as possible</t>
 | 
				
			||||||
 | 
					</blockquote></section>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<section anchor="core-principle"><name>Core principle</name>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<ol spacing="compact">
 | 
				
			||||||
 | 
					<li>XR Macros use querystrings, but are HTML-agnostic (though pseudo-XR Fragment browsers <strong>can</strong> be implemented on top of HTML/Javascript).</li>
 | 
				
			||||||
 | 
					<li>An XR Macro is 3D metadata which starts with '!' (<tt>!clickme: fog=0,10</tt> e.g.)</li>
 | 
				
			||||||
 | 
					<li>Metadata-values can contain the <tt>|</tt> symbol to 🎲 roundrobin variable values (<tt>!toggleme: fog=0,10|fog=0,1000</tt> e.g.)</li>
 | 
				
			||||||
 | 
					<li>XR Macros acts as simple eventhandlers for URI Fragments: they are automatically published on the (<eref target="https://xrfragment.org">XR Fragments</eref>) hashbus, to act as events (so more serious scripting languages can react to them as well).</li>
 | 
				
			||||||
 | 
					<li>XR Macros can assign object metadata (<tt>!setlocal: foo=1</tt> writes <tt>foo:1</tt> metadata to the object containing the <tt>!setlocal</tt> metadata)</li>
 | 
				
			||||||
 | 
					<li>XR Macros can assign global metadata  (<tt>!setfoo: #foo=1</tt> writes <tt>foo:1</tt> metadata to the root scene-node)</li>
 | 
				
			||||||
 | 
					</ol>
 | 
				
			||||||
 | 
					<blockquote><t>These very simple principles allow for rich interactions and dynamic querying</t>
 | 
				
			||||||
 | 
					</blockquote></section>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<section anchor="conventions-and-definitions"><name>Conventions and Definitions</name>
 | 
				
			||||||
 | 
					<t>See appendix below in case certain terms are not clear.</t>
 | 
				
			||||||
 | 
					</section>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<section anchor="list-of-xr-macros"><name>List of XR Macros</name>
 | 
				
			||||||
 | 
					<t>(XR) Macros can be embedded in 3D assets/scenes.<br />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Macros enrich existing spatial content with a lowcode, limited logic-layer, by recursive (economic) use of the querystring syntax (which search engines and <eref target="https://xrfragment.org">XR Fragments</eref> already uses.<br />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					This is done by allowing string/integer variables, and the <tt>|</tt> symbol to roundrobin variable values.<br />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Macros also act as events, so more serious scripting languages can react to them as well.<br />
 | 
				
			||||||
 | 
					</t>
 | 
				
			||||||
 | 
					<table>
 | 
				
			||||||
 | 
					<thead>
 | 
				
			||||||
 | 
					<tr>
 | 
				
			||||||
 | 
					<th>key</th>
 | 
				
			||||||
 | 
					<th>type</th>
 | 
				
			||||||
 | 
					<th>example (JSON)</th>
 | 
				
			||||||
 | 
					<th>function</th>
 | 
				
			||||||
 | 
					<th>existing compatibility</th>
 | 
				
			||||||
 | 
					</tr>
 | 
				
			||||||
 | 
					</thead>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<tbody>
 | 
				
			||||||
 | 
					<tr>
 | 
				
			||||||
 | 
					<td><tt>@bg</tt></td>
 | 
				
			||||||
 | 
					<td>string</td>
 | 
				
			||||||
 | 
					<td><tt>"@bg":"#cube"</tt></td>
 | 
				
			||||||
 | 
					<td>bg: binds fog near/far based to cube x/y/z (anim) values</td>
 | 
				
			||||||
 | 
					<td>custom property in 3D fileformats</td>
 | 
				
			||||||
 | 
					</tr>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<tr>
 | 
				
			||||||
 | 
					<td><tt>@fog</tt></td>
 | 
				
			||||||
 | 
					<td>string</td>
 | 
				
			||||||
 | 
					<td><tt>"@fog":"#cube"</tt></td>
 | 
				
			||||||
 | 
					<td>fog: binds fog near/far based to cube x/y (anim) values</td>
 | 
				
			||||||
 | 
					<td>custom property in 3D fileformats</td>
 | 
				
			||||||
 | 
					</tr>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<tr>
 | 
				
			||||||
 | 
					<td><tt>@scroll</tt></td>
 | 
				
			||||||
 | 
					<td>string</td>
 | 
				
			||||||
 | 
					<td><tt>"@scroll":"#cube"</tt></td>
 | 
				
			||||||
 | 
					<td>texturescrolling: binds texture x/y/rot based to cube x/y/z (anim) values</td>
 | 
				
			||||||
 | 
					<td>custom property in 3D fileformats</td>
 | 
				
			||||||
 | 
					</tr>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<tr>
 | 
				
			||||||
 | 
					<td><tt>@emissive</tt></td>
 | 
				
			||||||
 | 
					<td>string</td>
 | 
				
			||||||
 | 
					<td><tt>"@emissive":"#cube"</tt></td>
 | 
				
			||||||
 | 
					<td>day/night/mood: binds material's emissive value to cube x/y/z (anim) values</td>
 | 
				
			||||||
 | 
					<td>custom property in 3D fileformats</td>
 | 
				
			||||||
 | 
					</tr>
 | 
				
			||||||
 | 
					</tbody>
 | 
				
			||||||
 | 
					</table>
 | 
				
			||||||
 | 
					<section anchor="usecase-click-object"><name>Usecase: click object</name>
 | 
				
			||||||
 | 
					<table>
 | 
				
			||||||
 | 
					<thead>
 | 
				
			||||||
 | 
					<tr>
 | 
				
			||||||
 | 
					<th>custom property</th>
 | 
				
			||||||
 | 
					<th>value</th>
 | 
				
			||||||
 | 
					<th>trigger when</th>
 | 
				
			||||||
 | 
					</tr>
 | 
				
			||||||
 | 
					</thead>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<tbody>
 | 
				
			||||||
 | 
					<tr>
 | 
				
			||||||
 | 
					<td>!clickme</td>
 | 
				
			||||||
 | 
					<td>bg=1,1,1&foo=2</td>
 | 
				
			||||||
 | 
					<td>object clicked</td>
 | 
				
			||||||
 | 
					</tr>
 | 
				
			||||||
 | 
					</tbody>
 | 
				
			||||||
 | 
					</table></section>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<section anchor="usecase-conditional-click-object"><name>Usecase: conditional click object</name>
 | 
				
			||||||
 | 
					<table>
 | 
				
			||||||
 | 
					<thead>
 | 
				
			||||||
 | 
					<tr>
 | 
				
			||||||
 | 
					<th>custom property</th>
 | 
				
			||||||
 | 
					<th>value</th>
 | 
				
			||||||
 | 
					<th>trigger when</th>
 | 
				
			||||||
 | 
					</tr>
 | 
				
			||||||
 | 
					</thead>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<tbody>
 | 
				
			||||||
 | 
					<tr>
 | 
				
			||||||
 | 
					<td>#</td>
 | 
				
			||||||
 | 
					<td>foo=1</td>
 | 
				
			||||||
 | 
					<td>scene</td>
 | 
				
			||||||
 | 
					</tr>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<tr>
 | 
				
			||||||
 | 
					<td>!clickme</td>
 | 
				
			||||||
 | 
					<td>q=foo>2&bg=1,1,1</td>
 | 
				
			||||||
 | 
					<td>object clicked and foo > 2</td>
 | 
				
			||||||
 | 
					</tr>
 | 
				
			||||||
 | 
					</tbody>
 | 
				
			||||||
 | 
					</table><blockquote><t>when a user clicks an object with the custom properties above, it should set the backgroundcolor to <tt>1,1,1</tt> when <tt>foo</tt> is greater than <tt>2</tt> (see previous example)</t>
 | 
				
			||||||
 | 
					</blockquote></section>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<section anchor="usecase-click-object-roundrobin"><name>Usecase: click object (roundrobin)</name>
 | 
				
			||||||
 | 
					<table>
 | 
				
			||||||
 | 
					<thead>
 | 
				
			||||||
 | 
					<tr>
 | 
				
			||||||
 | 
					<th>custom property</th>
 | 
				
			||||||
 | 
					<th>value</th>
 | 
				
			||||||
 | 
					<th>trigger when</th>
 | 
				
			||||||
 | 
					</tr>
 | 
				
			||||||
 | 
					</thead>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<tbody>
 | 
				
			||||||
 | 
					<tr>
 | 
				
			||||||
 | 
					<td>!cycleme</td>
 | 
				
			||||||
 | 
					<td>day|noon|night</td>
 | 
				
			||||||
 | 
					<td>object clicked</td>
 | 
				
			||||||
 | 
					</tr>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<tr>
 | 
				
			||||||
 | 
					<td>day</td>
 | 
				
			||||||
 | 
					<td>bg=1,1,1</td>
 | 
				
			||||||
 | 
					<td>roundrobin</td>
 | 
				
			||||||
 | 
					</tr>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<tr>
 | 
				
			||||||
 | 
					<td>noon</td>
 | 
				
			||||||
 | 
					<td>bg=0.5,0.5,0.5</td>
 | 
				
			||||||
 | 
					<td>roundrobin</td>
 | 
				
			||||||
 | 
					</tr>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<tr>
 | 
				
			||||||
 | 
					<td>night</td>
 | 
				
			||||||
 | 
					<td>bg=0,0,0&foo=2</td>
 | 
				
			||||||
 | 
					<td>roundrobin</td>
 | 
				
			||||||
 | 
					</tr>
 | 
				
			||||||
 | 
					</tbody>
 | 
				
			||||||
 | 
					</table><blockquote><t>when a user clicks an object with the custom properties above, it should trigger either <tt>day</tt> <tt>noon</tt> or <tt>night</tt> in roundrobin fashion.</t>
 | 
				
			||||||
 | 
					</blockquote></section>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<section anchor="usecase-click-object-or-uri-fragment-and-scene-load-trigger"><name>Usecase: click object or URI fragment, and scene load trigger</name>
 | 
				
			||||||
 | 
					<table>
 | 
				
			||||||
 | 
					<thead>
 | 
				
			||||||
 | 
					<tr>
 | 
				
			||||||
 | 
					<th>custom property</th>
 | 
				
			||||||
 | 
					<th>value</th>
 | 
				
			||||||
 | 
					<th>trigger when</th>
 | 
				
			||||||
 | 
					</tr>
 | 
				
			||||||
 | 
					</thead>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<tbody>
 | 
				
			||||||
 | 
					<tr>
 | 
				
			||||||
 | 
					<td>#</td>
 | 
				
			||||||
 | 
					<td>random</td>
 | 
				
			||||||
 | 
					<td>scene loaded</td>
 | 
				
			||||||
 | 
					</tr>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<tr>
 | 
				
			||||||
 | 
					<td>#random</td>
 | 
				
			||||||
 | 
					<td>random</td>
 | 
				
			||||||
 | 
					<td>URL contains #random</td>
 | 
				
			||||||
 | 
					</tr>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<tr>
 | 
				
			||||||
 | 
					<td>!random</td>
 | 
				
			||||||
 | 
					<td>day|noon|night</td>
 | 
				
			||||||
 | 
					<td>#random, # or click</td>
 | 
				
			||||||
 | 
					</tr>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<tr>
 | 
				
			||||||
 | 
					<td>day</td>
 | 
				
			||||||
 | 
					<td>bg=1,1,1</td>
 | 
				
			||||||
 | 
					<td>roundrobin</td>
 | 
				
			||||||
 | 
					</tr>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<tr>
 | 
				
			||||||
 | 
					<td>noon</td>
 | 
				
			||||||
 | 
					<td>bg=0.5,0.5,0.5</td>
 | 
				
			||||||
 | 
					<td>roundrobin</td>
 | 
				
			||||||
 | 
					</tr>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<tr>
 | 
				
			||||||
 | 
					<td>night</td>
 | 
				
			||||||
 | 
					<td>bg=0,0,0&foo=2</td>
 | 
				
			||||||
 | 
					<td>roundrobin</td>
 | 
				
			||||||
 | 
					</tr>
 | 
				
			||||||
 | 
					</tbody>
 | 
				
			||||||
 | 
					</table></section>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<section anchor="usecase-present-context-menu-with-options"><name>Usecase: present context menu with options</name>
 | 
				
			||||||
 | 
					<table>
 | 
				
			||||||
 | 
					<thead>
 | 
				
			||||||
 | 
					<tr>
 | 
				
			||||||
 | 
					<th>custom property</th>
 | 
				
			||||||
 | 
					<th>value</th>
 | 
				
			||||||
 | 
					<th>trigger when</th>
 | 
				
			||||||
 | 
					</tr>
 | 
				
			||||||
 | 
					</thead>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<tbody>
 | 
				
			||||||
 | 
					<tr>
 | 
				
			||||||
 | 
					<td>!random</td>
 | 
				
			||||||
 | 
					<td>!day</td>
 | 
				
			||||||
 | 
					<td>!noon</td>
 | 
				
			||||||
 | 
					</tr>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<tr>
 | 
				
			||||||
 | 
					<td>!day</td>
 | 
				
			||||||
 | 
					<td>bg=1,1,1</td>
 | 
				
			||||||
 | 
					<td>clicked in contextmenu</td>
 | 
				
			||||||
 | 
					</tr>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<tr>
 | 
				
			||||||
 | 
					<td>!noon</td>
 | 
				
			||||||
 | 
					<td>bg=0.5,0.5,0.5</td>
 | 
				
			||||||
 | 
					<td>clicked in contextmenu</td>
 | 
				
			||||||
 | 
					</tr>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<tr>
 | 
				
			||||||
 | 
					<td>!night</td>
 | 
				
			||||||
 | 
					<td>bg=0,0,0&foo=2</td>
 | 
				
			||||||
 | 
					<td>clicked in contextmenu</td>
 | 
				
			||||||
 | 
					</tr>
 | 
				
			||||||
 | 
					</tbody>
 | 
				
			||||||
 | 
					</table><blockquote><t>When interacting with an object with more than one <tt>!</tt>-macro, the XR Browser should offer a contextmenu to execute a macro.</t>
 | 
				
			||||||
 | 
					</blockquote><t>In a similar way, when <strong>any</strong> <tt>!</tt>-macro is present on the sceneroot, the XR Browser should offer a context-menu to execute those macro's.</t>
 | 
				
			||||||
 | 
					</section>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<section anchor="event-bubble-flow"><name>Event Bubble-flow</name>
 | 
				
			||||||
 | 
					<t>click object with (<tt>!clickme</tt>:<tt>AR</tt> or <tt>!clickme</tt>: <tt>!reset</tt> e.g.)</t>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<artwork><![CDATA[  ◻
 | 
				
			||||||
 | 
					  │  
 | 
				
			||||||
 | 
					  └── does current object contain this property-key (`AR` or `!reset` e.g.)?
 | 
				
			||||||
 | 
					         └── no: is there any (root)object containing property `AR`
 | 
				
			||||||
 | 
					           └── yes: evaluate its (roundrobin) XR macro-value(s) (and exit)
 | 
				
			||||||
 | 
					           └── no: trigger URL: #AR 
 | 
				
			||||||
 | 
					]]>
 | 
				
			||||||
 | 
					</artwork>
 | 
				
			||||||
 | 
					<t>click object with (<tt>!clickme</tt>:<tt>#AR|#VR</tt> e.g.)</t>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<artwork><![CDATA[  ◻
 | 
				
			||||||
 | 
					  │  
 | 
				
			||||||
 | 
					  └── apply the roundrobin (rotate the options, value `#AR` becomes `#VR` upon next click)
 | 
				
			||||||
 | 
					      └── is there any object with property-key (`#AR` e.g.)?
 | 
				
			||||||
 | 
					         └── no: just update the URL to `#AR`
 | 
				
			||||||
 | 
					           └── yes: apply its value to the scene, and update the URL to `#AR`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					click object with (`!clickme`:`!foo|!bar|!flop` e.g.)
 | 
				
			||||||
 | 
					]]>
 | 
				
			||||||
 | 
					</artwork>
 | 
				
			||||||
 | 
					<t>◻
 | 
				
			||||||
 | 
					  │<br />
 | 
				
			||||||
 | 
					  └── apply the roundrobin (rotate the options, value <tt>!foo</tt> becomes <tt>!bar</tt> upon next click)
 | 
				
			||||||
 | 
					      └── is there any object with property-key (<tt>!foo</tt> e.g.)?
 | 
				
			||||||
 | 
					         └── no: do nothing
 | 
				
			||||||
 | 
					           └── yes: apply its value to the scene
 | 
				
			||||||
 | 
					```</t>
 | 
				
			||||||
 | 
					<blockquote><t>Note that only macro's can trigger roundrobin values or contextmenu's, as well as roundrobin values never ending up in the toplevel URL.</t>
 | 
				
			||||||
 | 
					</blockquote></section>
 | 
				
			||||||
 | 
					</section>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<section anchor="security-considerations"><name>Security Considerations</name>
 | 
				
			||||||
 | 
					</section>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<section anchor="iana-considerations"><name>IANA Considerations</name>
 | 
				
			||||||
 | 
					<t>This document has no IANA actions.</t>
 | 
				
			||||||
 | 
					</section>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<section anchor="acknowledgments"><name>Acknowledgments</name>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<ul spacing="compact">
 | 
				
			||||||
 | 
					<li><eref target="https://nlnet.nl">NLNET</eref></li>
 | 
				
			||||||
 | 
					<li><eref target="https://futureoftext.org">Future of Text</eref></li>
 | 
				
			||||||
 | 
					<li><eref target="https://visual-meta.info">visual-meta.info</eref></li>
 | 
				
			||||||
 | 
					</ul>
 | 
				
			||||||
 | 
					</section>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<section anchor="appendix-definitions"><name>Appendix: Definitions</name>
 | 
				
			||||||
 | 
					<table>
 | 
				
			||||||
 | 
					<thead>
 | 
				
			||||||
 | 
					<tr>
 | 
				
			||||||
 | 
					<th>definition</th>
 | 
				
			||||||
 | 
					<th>explanation</th>
 | 
				
			||||||
 | 
					</tr>
 | 
				
			||||||
 | 
					</thead>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<tbody>
 | 
				
			||||||
 | 
					<tr>
 | 
				
			||||||
 | 
					<td>scene</td>
 | 
				
			||||||
 | 
					<td>a (local/remote) 3D scene or 3D file (index.gltf e.g.)</td>
 | 
				
			||||||
 | 
					</tr>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<tr>
 | 
				
			||||||
 | 
					<td>3D object</td>
 | 
				
			||||||
 | 
					<td>an object inside a scene characterized by vertex-, face- and customproperty data.</td>
 | 
				
			||||||
 | 
					</tr>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<tr>
 | 
				
			||||||
 | 
					<td>XR fragments</td>
 | 
				
			||||||
 | 
					<td>URI Fragment with spatial hints like <tt>#pos=0,0,0&t=1,100</tt> e.g.</td>
 | 
				
			||||||
 | 
					</tr>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<tr>
 | 
				
			||||||
 | 
					<td>query</td>
 | 
				
			||||||
 | 
					<td>an URI Fragment-operator which queries object(s) from a scene like <tt>#q=cube</tt></td>
 | 
				
			||||||
 | 
					</tr>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<tr>
 | 
				
			||||||
 | 
					<td>FPS</td>
 | 
				
			||||||
 | 
					<td>frames per second in spatial experiences (games,VR,AR e.g.), should be as high as possible</td>
 | 
				
			||||||
 | 
					</tr>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<tr>
 | 
				
			||||||
 | 
					<td><tt>◻</tt></td>
 | 
				
			||||||
 | 
					<td>ascii representation of an 3D object/mesh</td>
 | 
				
			||||||
 | 
					</tr>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<tr>
 | 
				
			||||||
 | 
					<td>(un)obtrusive</td>
 | 
				
			||||||
 | 
					<td>obtrusive: wrapping human text/thought in XML/HTML/JSON obfuscates human text into a salad of machine-symbols and words</td>
 | 
				
			||||||
 | 
					</tr>
 | 
				
			||||||
 | 
					</tbody>
 | 
				
			||||||
 | 
					</table></section>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					</middle>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					</rfc>
 | 
				
			||||||
							
								
								
									
										53
									
								
								doc/fragments.awk
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										53
									
								
								doc/fragments.awk
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,53 @@
 | 
				
			||||||
 | 
					BEGIN{
 | 
				
			||||||
 | 
					  ROUNDROBIN="🎲"
 | 
				
			||||||
 | 
					  ASSET="🔒"
 | 
				
			||||||
 | 
					  OVERRIDE="🔓"
 | 
				
			||||||
 | 
					  PV_OVERRIDE="💥"
 | 
				
			||||||
 | 
					  NAVIGATOR="👩"
 | 
				
			||||||
 | 
					  PROMPT="✋?"
 | 
				
			||||||
 | 
					  EMBEDDED="🔗"
 | 
				
			||||||
 | 
					  print "| fragment | type | access       | scope |"
 | 
				
			||||||
 | 
					  print "|----------|------|--------------|-------|"
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					END{
 | 
				
			||||||
 | 
					  print ""
 | 
				
			||||||
 | 
					  print ASSET" = value(s) can only defined in 3D asset (immutable)<br>"
 | 
				
			||||||
 | 
						print OVERRIDE" = value(s) can be overwritten in certain context<br>"
 | 
				
			||||||
 | 
					  print ROUNDROBIN" = multiple values will be roundrobin'ed (`#pos=0,0,0|1,0,0` e.g.)<br>"
 | 
				
			||||||
 | 
					  print PV_OVERRIDE" = value(s) can be overwritten by [predefined_view](#predefined_view)<br>"
 | 
				
			||||||
 | 
					  print NAVIGATOR" = value(s) can be overwritten when user clicks `href` (value) or top-level URL change(see [How it works](#How%20it%20works))<br>"
 | 
				
			||||||
 | 
					  print EMBEDDED" = value(s) can be overwritten when 3D asset is embedded/linked as `src` value<br>"
 | 
				
			||||||
 | 
					  print PROMPT" = value(s) can be overwritten by offering confirmation/undo to user<br><br>"
 | 
				
			||||||
 | 
					  print ""
 | 
				
			||||||
 | 
						print "for more info see [How it works](#How%20it%20works)"
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/category:/    {
 | 
				
			||||||
 | 
					  $1=$2=""
 | 
				
			||||||
 | 
					  sub(/^[[:space:]]+/, "", $0 )    # remove leading spaces
 | 
				
			||||||
 | 
					  sub(/[[:space:]]+$/, "", $0 )    # remove trailing spaces
 | 
				
			||||||
 | 
					  scope=$0
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/Frag.*XRF\.*/ {
 | 
				
			||||||
 | 
					  gsub(/.*\("/,"",$1)
 | 
				
			||||||
 | 
					  gsub(/".*/,"",$1)
 | 
				
			||||||
 | 
					  type="string"
 | 
				
			||||||
 | 
					  perms = $0 ~ /OVERRIDE/ ? OVERRIDE : ASSET
 | 
				
			||||||
 | 
					  frag=$1
 | 
				
			||||||
 | 
					  $1=""
 | 
				
			||||||
 | 
					  if( $0 ~ /T_INT/             ) type="int"
 | 
				
			||||||
 | 
					  if( $0 ~ /T_STRING_OBJ/      ) type="[string object](string object      ) "
 | 
				
			||||||
 | 
					  if( $0 ~ /T_VECTOR2/         ) type="[vector2](#vector                  ) "
 | 
				
			||||||
 | 
					  if( $0 ~ /T_VECTOR3/         ) type="[vector3](#vector                  ) "
 | 
				
			||||||
 | 
					  if( $0 ~ /T_URL/             ) type="[url](#url                         ) "
 | 
				
			||||||
 | 
					  if( $0 ~ /T_PREDEFINED_VIEW/ ) type="[predefined view](#predefined_view ) "
 | 
				
			||||||
 | 
					  if( $0 ~ /ROUNDROBIN/        ) perms=perms" "ROUNDROBIN
 | 
				
			||||||
 | 
					  if( $0 ~ /PV_OVERRIDE/       ) perms=perms" "PV_OVERRIDE
 | 
				
			||||||
 | 
					  if( $0 ~ /NAVIGATOR/  ) perms=perms" "NAVIGATOR
 | 
				
			||||||
 | 
					  if( $0 ~ /EMBEDDED/    ) perms=perms" "EMBEDDED
 | 
				
			||||||
 | 
					  if( $0 ~ /PROMPT/            ) perms=perms" "PROMPT
 | 
				
			||||||
 | 
					  print "| **"frag"** |" type "|" perms "|" scope "|"
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										33
									
								
								doc/generate.awk
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								doc/generate.awk
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,33 @@
 | 
				
			||||||
 | 
					# a no-nonsense source-to-markdown generator which scans for:
 | 
				
			||||||
 | 
					#
 | 
				
			||||||
 | 
					# /**
 | 
				
			||||||
 | 
					#  * # foo
 | 
				
			||||||
 | 
					#  *
 | 
				
			||||||
 | 
					#  * this is markdown $(cat bar.md)
 | 
				
			||||||
 | 
					#  */
 | 
				
			||||||
 | 
					#
 | 
				
			||||||
 | 
					#  var foo; //  comment with 2 leading spaces is markdown too $(date)
 | 
				
			||||||
 | 
					#
 | 
				
			||||||
 | 
					# easily refactorable to hash-based languages (py/bash/perl/lua e.g.) 
 | 
				
			||||||
 | 
					# by changing the regexes
 | 
				
			||||||
 | 
					#
 | 
				
			||||||
 | 
					/\$\(/                   { cmd=$0; 
 | 
				
			||||||
 | 
					                           gsub(/^.*\$\(/,"",cmd); 
 | 
				
			||||||
 | 
					                           gsub(/\).*/,"",cmd);
 | 
				
			||||||
 | 
					                           cmd | getline stdout; close(cmd);
 | 
				
			||||||
 | 
					                           sub(/\$\(.*\)/,stdout);
 | 
				
			||||||
 | 
					                         } 
 | 
				
			||||||
 | 
					/\/\*\*/                 { doc=1; sub(/^.*\/\*/,""); }
 | 
				
			||||||
 | 
					doc && /\*\//            { doc=0;
 | 
				
			||||||
 | 
					                           sub(/[[:space:]]*\*\/.*/,"");
 | 
				
			||||||
 | 
					                           sub(/^[[:space:]]*\*[[:space:]]?/,"");
 | 
				
			||||||
 | 
					                           print
 | 
				
			||||||
 | 
					                         }
 | 
				
			||||||
 | 
					doc && /^[[:space:]]*\*/ { sub(/^[[:space:]]*\*[[:space:]]?/,""); 
 | 
				
			||||||
 | 
					                           print 
 | 
				
			||||||
 | 
					                         }
 | 
				
			||||||
 | 
					!doc && /\/\/  /         { sub(".*//  ",""); 
 | 
				
			||||||
 | 
					                           sub("# ","\n# ");
 | 
				
			||||||
 | 
					                           sub("> ","\n> ");
 | 
				
			||||||
 | 
					                           print
 | 
				
			||||||
 | 
					                         }
 | 
				
			||||||
							
								
								
									
										10
									
								
								doc/generate.sh
									
										
									
									
									
										Executable file
									
								
							
							
						
						
									
										10
									
								
								doc/generate.sh
									
										
									
									
									
										Executable file
									
								
							| 
						 | 
					@ -0,0 +1,10 @@
 | 
				
			||||||
 | 
					#!/bin/sh 
 | 
				
			||||||
 | 
					set -e
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					for topic in Fragments Macros; do
 | 
				
			||||||
 | 
					  mmark RFC_XR_$topic.md         > RFC_XR_$topic.xml
 | 
				
			||||||
 | 
					  mmark --html RFC_XR_$topic.md | grep -vE '(<!--{|}-->)' > RFC_XR_$topic.html
 | 
				
			||||||
 | 
					  xml2rfc --v3 RFC_XR_$topic.xml # RFC_XR_$topic.txt
 | 
				
			||||||
 | 
					  sed -i 's/Expires: .*//g' RFC_XR_$topic.txt
 | 
				
			||||||
 | 
					  convert -size 700x2400 xc:white -font "FreeMono" +antialias -pointsize 12 -fill black -annotate +15+15 "@RFC_XR_Fragments.txt" -colorspace gray +dither -posterize 6 RF6_XR_Fragments.png
 | 
				
			||||||
 | 
					done
 | 
				
			||||||
							
								
								
									
										3
									
								
								doc/notes/prio.md
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								doc/notes/prio.md
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,3 @@
 | 
				
			||||||
 | 
					\#static allow client to ignore lower-prio objects in the renderloop, to compensate frame-drop/cpu/gpu-overload scenario’s
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Q: should `-` be used to indicate 'lowering' priority (css's `z-index` 0-??? range is a bit confusing in that sense)
 | 
				
			||||||
							
								
								
									
										25
									
								
								doc/shell.nix
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								doc/shell.nix
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,25 @@
 | 
				
			||||||
 | 
					{ pkgs ? import <nixos-unstable> {} } :
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  pkgs = import (builtins.fetchGit {
 | 
				
			||||||
 | 
					      name = "nixos-23.05";
 | 
				
			||||||
 | 
					    url = "https://github.com/nixos/nixpkgs/";
 | 
				
			||||||
 | 
					    ref = "refs/heads/nixos-unstable";
 | 
				
			||||||
 | 
					    rev = "ef99fa5c5ed624460217c31ac4271cfb5cb2502c";
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  foo = pkgs.mkShell {
 | 
				
			||||||
 | 
					    # nativeBuildInputs is usually what you want -- tools you need to run
 | 
				
			||||||
 | 
					    nativeBuildInputs = with pkgs.buildPackages; [ 
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      mmark
 | 
				
			||||||
 | 
					      xml2rfc
 | 
				
			||||||
 | 
					      python312Packages.lxml
 | 
				
			||||||
 | 
					      wkhtmltopdf-bin
 | 
				
			||||||
 | 
					      imagemagick
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    ];
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  # to create [markdown] table of contents use LLM with this input: awk '/id="/ { print $0 }' RFC_XR_Fragments.html | grep -v idx
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										489
									
								
								doc/style.css
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										489
									
								
								doc/style.css
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,489 @@
 | 
				
			||||||
 | 
					:root {
 | 
				
			||||||
 | 
					    --color-a: black;
 | 
				
			||||||
 | 
					    --color-at: #000000CC;
 | 
				
			||||||
 | 
					    --color-b: #222;
 | 
				
			||||||
 | 
					    --color-c: #444;
 | 
				
			||||||
 | 
					    --color-d: #888;
 | 
				
			||||||
 | 
					    --color-e: #FF0;
 | 
				
			||||||
 | 
					    --color-f: #CCC;
 | 
				
			||||||
 | 
					    --color-g: #FFF;
 | 
				
			||||||
 | 
					    --color-gt: #FFF5;
 | 
				
			||||||
 | 
					    --params-width: 320px;
 | 
				
			||||||
 | 
							--font-1: 'Montserrat', sans-serif;
 | 
				
			||||||
 | 
							--font-2: monospace,  courier;
 | 
				
			||||||
 | 
							--font-size-1: 57px;
 | 
				
			||||||
 | 
							--font-size-2: 26px;
 | 
				
			||||||
 | 
					    --font-size-3: 18px;
 | 
				
			||||||
 | 
					    --font-size-4: 15px;
 | 
				
			||||||
 | 
					    --gutter-1: 10%;
 | 
				
			||||||
 | 
					    --gradient-1: linear-gradient(45deg, #dc2454 0%, #944a9d 51%, #4824dc 100%);
 | 
				
			||||||
 | 
					    --gradient-2: linear-gradient(45deg, #dc2454 0%, #944a9d 21%, #4824dc 50%,#dc2454 70%, #944a9d 88%, #4824dc 100%);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					body>h1:nth-child(1) {
 | 
				
			||||||
 | 
					    display:none;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					body{
 | 
				
			||||||
 | 
						position:relative;
 | 
				
			||||||
 | 
						width:100%;
 | 
				
			||||||
 | 
						height:100vh;
 | 
				
			||||||
 | 
						color: var(--color-d);
 | 
				
			||||||
 | 
					  line-height: 38px;
 | 
				
			||||||
 | 
						margin:0;
 | 
				
			||||||
 | 
						padding:0;
 | 
				
			||||||
 | 
					  font-size: var(--font-size-3);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					* {
 | 
				
			||||||
 | 
						font-family: var(--font-1);
 | 
				
			||||||
 | 
					  font-size: var(--font-size-3);
 | 
				
			||||||
 | 
					  font-weight:500;
 | 
				
			||||||
 | 
						color: var(--color-c);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					img,
 | 
				
			||||||
 | 
					.img{
 | 
				
			||||||
 | 
					  image-rendering: -webkit-optimize-contrast;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					#jumbotron{
 | 
				
			||||||
 | 
					  margin-top:50px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					b{
 | 
				
			||||||
 | 
					  font-weight: 700;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					h1 {
 | 
				
			||||||
 | 
						color: var(--color-c);
 | 
				
			||||||
 | 
					  font-size: var(--font-size-1);
 | 
				
			||||||
 | 
					  font-weight:700;
 | 
				
			||||||
 | 
					  margin: 0;
 | 
				
			||||||
 | 
					  line-height: 11vh;
 | 
				
			||||||
 | 
					  padding-right:40px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					h2 {
 | 
				
			||||||
 | 
					  font-size: var(--font-size-2);
 | 
				
			||||||
 | 
					  font-weight:400;
 | 
				
			||||||
 | 
					  padding-right:40px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					h3 {
 | 
				
			||||||
 | 
					  background-image: var(--gradient-1);
 | 
				
			||||||
 | 
					  color: var(--color-g);
 | 
				
			||||||
 | 
					  display:inline;
 | 
				
			||||||
 | 
					  border-radius: 5px;
 | 
				
			||||||
 | 
					  padding: 2px 10px 2px 10px;
 | 
				
			||||||
 | 
					  font-weight: 600;
 | 
				
			||||||
 | 
					  font-size: var(--font-size-4);
 | 
				
			||||||
 | 
					  text-transform:uppercase;
 | 
				
			||||||
 | 
					  white-space:nowrap;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@media only screen and (max-width: 700px) {
 | 
				
			||||||
 | 
					  body {
 | 
				
			||||||
 | 
					    font-size: var(--font-size-3);
 | 
				
			||||||
 | 
					    line-height: 40px;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  svg.img{
 | 
				
			||||||
 | 
					    transform: scale(0.8);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  .content{
 | 
				
			||||||
 | 
						padding-top: 60px;
 | 
				
			||||||
 | 
						margin-left: 27px !important;
 | 
				
			||||||
 | 
						padding-right: 54px !important;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  .heading{
 | 
				
			||||||
 | 
					    transform:scale(0.7);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					table {
 | 
				
			||||||
 | 
						border:none;
 | 
				
			||||||
 | 
						box-shadow: 0px 0px 10px #0005;
 | 
				
			||||||
 | 
						padding:20px;
 | 
				
			||||||
 | 
					  background: transparent !important;
 | 
				
			||||||
 | 
					  border-radius:4px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					table tr td,
 | 
				
			||||||
 | 
					table tr th {
 | 
				
			||||||
 | 
						vertical-align:top;
 | 
				
			||||||
 | 
						text-align:left;
 | 
				
			||||||
 | 
						border-bottom:1px solid #555;
 | 
				
			||||||
 | 
						color: var(--color-d);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					table tr td a:visited,
 | 
				
			||||||
 | 
					table tr td a:active,
 | 
				
			||||||
 | 
					table tr td a{
 | 
				
			||||||
 | 
						color: var(--color-e);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					table tr:last-child td{
 | 
				
			||||||
 | 
						border:none;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					table tr th {
 | 
				
			||||||
 | 
						border-bottom:2px solid var(--color-d);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					code{
 | 
				
			||||||
 | 
						display: inline-block;
 | 
				
			||||||
 | 
						unicode-bidi: embed;
 | 
				
			||||||
 | 
						font-family: monospace;
 | 
				
			||||||
 | 
						white-space: pre;
 | 
				
			||||||
 | 
						background: var(--color-c);
 | 
				
			||||||
 | 
						margin-bottom: 5px;
 | 
				
			||||||
 | 
						padding: 0px 5px;
 | 
				
			||||||
 | 
						font-size:16px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.content {
 | 
				
			||||||
 | 
						height:99vh;
 | 
				
			||||||
 | 
						max-width: 50em;
 | 
				
			||||||
 | 
						width: 100%;
 | 
				
			||||||
 | 
						margin: 0 auto;
 | 
				
			||||||
 | 
						padding-top:25px;
 | 
				
			||||||
 | 
					  box-sizing:border-box;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					a,a:visited,a:active{
 | 
				
			||||||
 | 
						color: var(--color-a);
 | 
				
			||||||
 | 
						display:inline-block;
 | 
				
			||||||
 | 
					  text-decoration:none;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					a:hover{
 | 
				
			||||||
 | 
					  opacity:0.9;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* lato-regular - latin */
 | 
				
			||||||
 | 
					@font-face {
 | 
				
			||||||
 | 
					  font-family: 'Lato';
 | 
				
			||||||
 | 
					  font-style: normal;
 | 
				
			||||||
 | 
					  font-weight: 400;
 | 
				
			||||||
 | 
					  src: url('/assets/font/lato-v17-latin-regular.eot'); /* IE9 Compat Modes */
 | 
				
			||||||
 | 
					  src: local(''),
 | 
				
			||||||
 | 
					       url('/assets/font/lato-v17-latin-regular.eot?#iefix') format('embedded-opentype'), /* IE6-IE8 */
 | 
				
			||||||
 | 
					       url('/assets/font/lato-v17-latin-regular.woff2') format('woff2'), /* Super Modern Browsers */
 | 
				
			||||||
 | 
					       url('/assets/font/lato-v17-latin-regular.woff') format('woff'), /* Modern Browsers */
 | 
				
			||||||
 | 
					       url('/assets/font/lato-v17-latin-regular.ttf') format('truetype'), /* Safari, Android, iOS */
 | 
				
			||||||
 | 
					       url('/assets/font/lato-v17-latin-regular.svg#Lato') format('svg'); /* Legacy iOS */
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#sidemenu {
 | 
				
			||||||
 | 
						position:absolute;
 | 
				
			||||||
 | 
						top:0;
 | 
				
			||||||
 | 
						left:0;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#sidemenu nav {
 | 
				
			||||||
 | 
						width: 300px;
 | 
				
			||||||
 | 
						box-shadow: 0px 0px 8px #0005;
 | 
				
			||||||
 | 
						background:#FFF;
 | 
				
			||||||
 | 
						z-index:10;
 | 
				
			||||||
 | 
						position: fixed;
 | 
				
			||||||
 | 
						top: 0;
 | 
				
			||||||
 | 
						left: 0;
 | 
				
			||||||
 | 
						z-index: 99;
 | 
				
			||||||
 | 
						height:100vh;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					#sidemenu .sidemenu__btn {
 | 
				
			||||||
 | 
					  display: block;
 | 
				
			||||||
 | 
					  width: 50px;
 | 
				
			||||||
 | 
					  height: 50px;
 | 
				
			||||||
 | 
					  top:20px;
 | 
				
			||||||
 | 
					  left:20px;
 | 
				
			||||||
 | 
					  background: transparent;
 | 
				
			||||||
 | 
					  border: none;
 | 
				
			||||||
 | 
					  position: fixed;
 | 
				
			||||||
 | 
					  z-index: 100;
 | 
				
			||||||
 | 
					  -webkit-appearance: none;
 | 
				
			||||||
 | 
					     -moz-appearance: none;
 | 
				
			||||||
 | 
					  		appearance: none;
 | 
				
			||||||
 | 
					  cursor: pointer;
 | 
				
			||||||
 | 
					  outline: none;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					#sidemenu .sidemenu__btn span {
 | 
				
			||||||
 | 
					  display: block;
 | 
				
			||||||
 | 
					  width: 30px;
 | 
				
			||||||
 | 
					  height: 3px;
 | 
				
			||||||
 | 
					  margin: auto;
 | 
				
			||||||
 | 
					  background:var(--color-d);
 | 
				
			||||||
 | 
					  position: absolute;
 | 
				
			||||||
 | 
					  top: 0;
 | 
				
			||||||
 | 
					  bottom: 0;
 | 
				
			||||||
 | 
					  left: 0;
 | 
				
			||||||
 | 
					  right: 0;
 | 
				
			||||||
 | 
					  -webkit-transition: all .4s ease;
 | 
				
			||||||
 | 
					  transition: all .4s ease;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					#sidemenu .sidemenu__btn span.top {
 | 
				
			||||||
 | 
					  -webkit-transform: translateY(-8px);
 | 
				
			||||||
 | 
					  		transform: translateY(-8px);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					#sidemenu .sidemenu__btn span.bottom {
 | 
				
			||||||
 | 
					  -webkit-transform: translateY(8px);
 | 
				
			||||||
 | 
					  		transform: translateY(8px);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					#sidemenu .sidemenu__btn.active .top {
 | 
				
			||||||
 | 
					  -webkit-transform: rotate(-45deg);
 | 
				
			||||||
 | 
					  		transform: rotate(-45deg);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					#sidemenu .sidemenu__btn.active .mid {
 | 
				
			||||||
 | 
					  -webkit-transform: translateX(-20px) rotate(360deg);
 | 
				
			||||||
 | 
					  		transform: translateX(-20px) rotate(360deg);
 | 
				
			||||||
 | 
					  opacity: 0;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					#sidemenu .sidemenu__btn.active .bottom {
 | 
				
			||||||
 | 
					  -webkit-transform: rotate(45deg);
 | 
				
			||||||
 | 
					  		transform: rotate(45deg);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					#sidemenu .sidemenu__wrapper {
 | 
				
			||||||
 | 
					  padding-top: 70px;
 | 
				
			||||||
 | 
					  margin-left:20px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					#sidemenu .sidemenu__list {
 | 
				
			||||||
 | 
					  overflow-y: scroll;
 | 
				
			||||||
 | 
					  max-height: 73vh;
 | 
				
			||||||
 | 
					  padding-top: 50px;
 | 
				
			||||||
 | 
					  list-style: none;
 | 
				
			||||||
 | 
					  padding: 0;
 | 
				
			||||||
 | 
					  margin: 0;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					  #sidemenu .sidemenu__item {
 | 
				
			||||||
 | 
					  	padding: 0;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					#sidemenu li > a {
 | 
				
			||||||
 | 
					  text-decoration: none;
 | 
				
			||||||
 | 
					  display: inline-block;
 | 
				
			||||||
 | 
					  -webkit-transition: .4s ease;
 | 
				
			||||||
 | 
					  transition: .4s ease;
 | 
				
			||||||
 | 
						cursor: pointer;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					nav {
 | 
				
			||||||
 | 
					  -webkit-transition: .2s ease;
 | 
				
			||||||
 | 
					  transition: .2s ease;
 | 
				
			||||||
 | 
					  -webkit-transform: translateX(0px);
 | 
				
			||||||
 | 
					  		transform: translateX(0px);
 | 
				
			||||||
 | 
					  opacity:1;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					nav.hide {
 | 
				
			||||||
 | 
					  -webkit-transform: translateX(-300px);
 | 
				
			||||||
 | 
					  		transform: translateX(-300px);
 | 
				
			||||||
 | 
					  opacity:0;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					ul, ol {
 | 
				
			||||||
 | 
						margin-left:30px !important;
 | 
				
			||||||
 | 
					    margin-top: 35px !important;
 | 
				
			||||||
 | 
					    margin-bottom: 35px !important;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					ul:nth-child(1){
 | 
				
			||||||
 | 
						margin:0;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					ul,li{
 | 
				
			||||||
 | 
						text-decoration:none !important;
 | 
				
			||||||
 | 
						/*list-style:none;*/
 | 
				
			||||||
 | 
						padding-left:10px;
 | 
				
			||||||
 | 
						padding-right:10px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					li > a,
 | 
				
			||||||
 | 
					li > a.visited,
 | 
				
			||||||
 | 
					li > a.active{
 | 
				
			||||||
 | 
						color:#555;
 | 
				
			||||||
 | 
						padding:5px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					li > b {
 | 
				
			||||||
 | 
						background:#FFF;
 | 
				
			||||||
 | 
						padding:5px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.sidemenu__list > li,
 | 
				
			||||||
 | 
					.sidemenu__list > ul > li{
 | 
				
			||||||
 | 
						display:inline-block;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					ul > li {
 | 
				
			||||||
 | 
					  display:block;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.content ul > li::before {content: "•"; padding-right:10px;color: #54F;}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					li > b {
 | 
				
			||||||
 | 
						font-weight:100;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.touchscreen {
 | 
				
			||||||
 | 
						border:30px solid #aab5bb;
 | 
				
			||||||
 | 
						border-radius:20px;
 | 
				
			||||||
 | 
						position:relative;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					.touchscreen:after {
 | 
				
			||||||
 | 
						content:"";
 | 
				
			||||||
 | 
						display:block;
 | 
				
			||||||
 | 
						position:absolute;
 | 
				
			||||||
 | 
						border-radius:50%;
 | 
				
			||||||
 | 
						width:15px;
 | 
				
			||||||
 | 
						height:15px;
 | 
				
			||||||
 | 
						background:#FFF;
 | 
				
			||||||
 | 
						top:136px;
 | 
				
			||||||
 | 
						right:-22px;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					div.pretty{
 | 
				
			||||||
 | 
						border-radius: 4px;
 | 
				
			||||||
 | 
						display: block;
 | 
				
			||||||
 | 
						background: #372e42;
 | 
				
			||||||
 | 
						overflow-x: scroll;
 | 
				
			||||||
 | 
					  white-space:pre-wrap;
 | 
				
			||||||
 | 
					  color:#65e;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					code.pretty,
 | 
				
			||||||
 | 
					div.pretty > pre > code{
 | 
				
			||||||
 | 
						background: -webkit-linear-gradient(-70deg,#98a7f0,#0d4e50);
 | 
				
			||||||
 | 
					    background-clip: border-box;
 | 
				
			||||||
 | 
						-webkit-background-clip: text;
 | 
				
			||||||
 | 
						-webkit-text-fill-color: transparent;
 | 
				
			||||||
 | 
						border: none;
 | 
				
			||||||
 | 
						text-shadow: 0px 0px 5px #fff9;
 | 
				
			||||||
 | 
						filter: brightness(1.8);
 | 
				
			||||||
 | 
						font-size:14px;
 | 
				
			||||||
 | 
						width:98%;
 | 
				
			||||||
 | 
						padding:0px 15px;
 | 
				
			||||||
 | 
						margin:0;
 | 
				
			||||||
 | 
						box-sizing:border-box;
 | 
				
			||||||
 | 
						line-height:20px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					blockquote{
 | 
				
			||||||
 | 
					  box-sizing: border-box;
 | 
				
			||||||
 | 
					  background: transparent !important;
 | 
				
			||||||
 | 
					  border-radius: 7px;
 | 
				
			||||||
 | 
					  padding: 20px;
 | 
				
			||||||
 | 
					  width: 100%;
 | 
				
			||||||
 | 
					  margin: 30px 0px 20px 0px;
 | 
				
			||||||
 | 
					  color: var(--color-f) !important;
 | 
				
			||||||
 | 
					  font-weight: bold;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					blockquote a {
 | 
				
			||||||
 | 
						color:#000 !important;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					blockquote a:hover {
 | 
				
			||||||
 | 
						color:#FFF !important;
 | 
				
			||||||
 | 
					    background:#000;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					img {
 | 
				
			||||||
 | 
					  width:100%;
 | 
				
			||||||
 | 
					  filter: brightness(0.97)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* GRID SYSTEM */
 | 
				
			||||||
 | 
					.block{
 | 
				
			||||||
 | 
					  display: block;
 | 
				
			||||||
 | 
					  text-align: left;
 | 
				
			||||||
 | 
					  margin-bottom: var(--gutter-1);
 | 
				
			||||||
 | 
					  min-height:55vh;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.block .img{
 | 
				
			||||||
 | 
						border-radius:15px;
 | 
				
			||||||
 | 
					  width:100%;
 | 
				
			||||||
 | 
					  height:35vh;
 | 
				
			||||||
 | 
						margin-bottom:15px;
 | 
				
			||||||
 | 
						background-size: cover;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					p{ margin:0; padding:0; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					//the grid:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.grid{
 | 
				
			||||||
 | 
					  width: 90%;
 | 
				
			||||||
 | 
					  max-width: 900px;
 | 
				
			||||||
 | 
					  margin: 0 auto;
 | 
				
			||||||
 | 
					  background-color: $grid-color;
 | 
				
			||||||
 | 
					  //just for styling:
 | 
				
			||||||
 | 
					  padding-top: var(--gutter-1);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.gutter{
 | 
				
			||||||
 | 
					/*
 | 
				
			||||||
 | 
					  margin-left: calc( var(--gutter-1) /2 );
 | 
				
			||||||
 | 
					  margin-right: calc( var(--gutter-1) /2 );
 | 
				
			||||||
 | 
					*/
 | 
				
			||||||
 | 
					  margin-right: var(--gutter-1);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.col-33{
 | 
				
			||||||
 | 
					  width: 33.3%;
 | 
				
			||||||
 | 
					  float: left;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					.col-50{
 | 
				
			||||||
 | 
					  width: 50%;
 | 
				
			||||||
 | 
					  float: left;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.clear{
 | 
				
			||||||
 | 
					  clear: both;
 | 
				
			||||||
 | 
					  display: block;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					//responsive
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@media all and (max-width:800px) {
 | 
				
			||||||
 | 
					  .col-33{
 | 
				
			||||||
 | 
					    width: 50%;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@media all and (max-width:600px) {
 | 
				
			||||||
 | 
					  .col-50, .col-33{
 | 
				
			||||||
 | 
					    width: 100%;
 | 
				
			||||||
 | 
					    float: none;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  h1 {
 | 
				
			||||||
 | 
							font-size: 50px;
 | 
				
			||||||
 | 
					    line-height:70px;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					  h2{
 | 
				
			||||||
 | 
					    font-size: var(--font-size-3);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  .block.gutter{
 | 
				
			||||||
 | 
					    margin-right:0;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.darken{
 | 
				
			||||||
 | 
						filter: brightness(0.8);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					button{
 | 
				
			||||||
 | 
					    cursor:pointer;
 | 
				
			||||||
 | 
					    padding: 10px;
 | 
				
			||||||
 | 
					    border-radius: 6px;
 | 
				
			||||||
 | 
					    background-image: var(--gradient-1);
 | 
				
			||||||
 | 
					    border: none;
 | 
				
			||||||
 | 
					    border-radius: 20px;
 | 
				
			||||||
 | 
							display:inline-block;
 | 
				
			||||||
 | 
					    color: #FFF;
 | 
				
			||||||
 | 
					    border: none;
 | 
				
			||||||
 | 
							opacity:1.0;
 | 
				
			||||||
 | 
							font-size: var(--font-size-4);
 | 
				
			||||||
 | 
							font-weight:600;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					button:hover{
 | 
				
			||||||
 | 
							opacity:0.8;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.gradient{
 | 
				
			||||||
 | 
					  background-image: var(--gradient-2);
 | 
				
			||||||
 | 
					  -webkit-background-clip: text;
 | 
				
			||||||
 | 
					  -webkit-text-fill-color: transparent;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										1
									
								
								doc/tags
									
										
									
									
									
										Symbolic link
									
								
							
							
						
						
									
										1
									
								
								doc/tags
									
										
									
									
									
										Symbolic link
									
								
							| 
						 | 
					@ -0,0 +1 @@
 | 
				
			||||||
 | 
					/home/leon/.ctags.js
 | 
				
			||||||
							
								
								
									
										4191
									
								
								index.html
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										4191
									
								
								index.html
									
										
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
		Loading…
	
	Add table
		
		Reference in a new issue