UNPKG

spacesvr-websessions

Version:

A standardized reality for future of the 3D Web

1,752 lines (1,547 loc) 256 kB
import React from "react"; import _extends from '@babel/runtime/helpers/esm/extends'; import React$1, { useRef, useMemo, useEffect, useLayoutEffect, useState, Suspense, useCallback, createContext, useContext, Children, cloneElement, forwardRef } from 'react'; import { useFrame, useThree, createPortal, extend } from '@react-three/fiber'; import { Color, MeshStandardMaterial, DoubleSide, Uniform, Vector3, Vector2, Quaternion, Euler, Fog as Fog$1, AudioListener, PositionalAudio, AudioAnalyser, CanvasTexture, TextureLoader, BoxBufferGeometry, MeshBasicMaterial, AudioContext, NoToneMapping, MathUtils, Raycaster, Box3, sRGBEncoding, MeshLambertMaterial, Matrix4, BufferGeometry, Float32BufferAttribute, Scene, Light, AmbientLight, WebGLCubeRenderTarget, CubeCamera, WebGLRenderTarget, UnsignedByteType, RGBAFormat, Object3D, SphereBufferGeometry, CylinderBufferGeometry, MeshNormalMaterial, NearestFilter, WebGLRenderer } from 'three'; import { useSpring, config, animated, a } from '@react-spring/three'; import * as culori from 'culori'; import { Environment as Environment$1, useGLTF, useProgress, Text as Text$2, Html, PerspectiveCamera } from '@react-three/drei'; import { usePlane, useTrimesh, useCompoundBody, Physics as Physics$1 } from '@react-three/cannon'; import { WebGLExtensions } from 'three/src/renderers/webgl/WebGLExtensions'; import { KTX2Loader } from 'three/examples/jsm/loaders/KTX2Loader'; import { suspend, preload, clear } from 'suspend-react'; import { mergeBufferGeometries } from 'three/examples/jsm/utils/BufferGeometryUtils'; import { acceleratedRaycast, computeBoundsTree, disposeBoundsTree } from 'three-mesh-bvh'; import { css, Global, keyframes } from '@emotion/react'; import nipplejs from 'nipplejs'; import styled from '@emotion/styled'; import { motion } from 'framer-motion'; import { isMobile } from 'react-device-detect'; import { XRCanvas, useXR, DefaultXRControllers, useController, Interactive } from '@react-three/xr'; import { DeviceOrientationControls, SkeletonUtils, mergeVertices, EffectComposer, RenderPass, ShaderPass } from 'three-stdlib'; import { ErrorBoundary } from 'react-error-boundary'; import { RoundedBoxGeometry } from 'three/examples/jsm/geometries/RoundedBoxGeometry'; import { getCaretAtPoint, Text as Text$3 } from 'troika-three-text'; import { SnapshotInterpolation } from '@geckos.io/snapshot-interpolation'; import { Peer } from 'peerjs'; import nodeFetch from 'node-fetch'; import { useTransition } from '@react-spring/core'; import { createPortal as createPortal$1 } from 'react-dom'; const vertHead$3 = ` // Description : Array and textureless GLSL 2D/3D/4D simplex // noise functions. // Author : Ian McEwan, Ashima Arts. // Maintainer : ijm // Lastmod : 20110822 (ijm) // License : Copyright (C) 2011 Ashima Arts. All rights reserved. // Distributed under the MIT License. See LICENSE file. // https://github.com/ashima/webgl-noise // vec3 mod289(vec3 x) { return x - floor(x * (1.0 / 289.0)) * 289.0; } vec4 mod289(vec4 x) { return x - floor(x * (1.0 / 289.0)) * 289.0; } vec4 permute(vec4 x) { return mod289(((x*34.0)+1.0)*x); } vec4 taylorInvSqrt(vec4 r) { return 1.79284291400159 - 0.85373472095314 * r; } float snoise(vec3 v) { const vec2 C = vec2(1.0/6.0, 1.0/3.0) ; const vec4 D = vec4(0.0, 0.5, 1.0, 2.0); // First corner vec3 i = floor(v + dot(v, C.yyy) ); vec3 x0 = v - i + dot(i, C.xxx) ; // Other corners vec3 g = step(x0.yzx, x0.xyz); vec3 l = 1.0 - g; vec3 i1 = min( g.xyz, l.zxy ); vec3 i2 = max( g.xyz, l.zxy ); // x0 = x0 - 0.0 + 0.0 * C.xxx; // x1 = x0 - i1 + 1.0 * C.xxx; // x2 = x0 - i2 + 2.0 * C.xxx; // x3 = x0 - 1.0 + 3.0 * C.xxx; vec3 x1 = x0 - i1 + C.xxx; vec3 x2 = x0 - i2 + C.yyy; // 2.0*C.x = 1/3 = C.y vec3 x3 = x0 - D.yyy; // -1.0+3.0*C.x = -0.5 = -D.y // Permutations i = mod289(i); vec4 p = permute( permute( permute( i.z + vec4(0.0, i1.z, i2.z, 1.0 )) + i.y + vec4(0.0, i1.y, i2.y, 1.0 )) + i.x + vec4(0.0, i1.x, i2.x, 1.0 )); // Gradients: 7x7 points over a square, mapped onto an octahedron. // The ring size 17*17 = 289 is close to a multiple of 49 (49*6 = 294) float n_ = 0.142857142857; // 1.0/7.0 vec3 ns = n_ * D.wyz - D.xzx; vec4 j = p - 49.0 * floor(p * ns.z * ns.z); // mod(p,7*7) vec4 x_ = floor(j * ns.z); vec4 y_ = floor(j - 7.0 * x_ ); // mod(j,N) vec4 x = x_ *ns.x + ns.yyyy; vec4 y = y_ *ns.x + ns.yyyy; vec4 h = 1.0 - abs(x) - abs(y); vec4 b0 = vec4( x.xy, y.xy ); vec4 b1 = vec4( x.zw, y.zw ); //vec4 s0 = vec4(lessThan(b0,0.0))*2.0 - 1.0; //vec4 s1 = vec4(lessThan(b1,0.0))*2.0 - 1.0; vec4 s0 = floor(b0)*2.0 + 1.0; vec4 s1 = floor(b1)*2.0 + 1.0; vec4 sh = -step(h, vec4(0.0)); vec4 a0 = b0.xzyw + s0.xzyw*sh.xxyy ; vec4 a1 = b1.xzyw + s1.xzyw*sh.zzww ; vec3 p0 = vec3(a0.xy,h.x); vec3 p1 = vec3(a0.zw,h.y); vec3 p2 = vec3(a1.xy,h.z); vec3 p3 = vec3(a1.zw,h.w); //Normalise gradients vec4 norm = taylorInvSqrt(vec4(dot(p0,p0), dot(p1,p1), dot(p2, p2), dot(p3,p3))); p0 *= norm.x; p1 *= norm.y; p2 *= norm.z; p3 *= norm.w; // Mix final noise value vec4 m = max(0.6 - vec4(dot(x0,x0), dot(x1,x1), dot(x2,x2), dot(x3,x3)), 0.0); m = m * m; return 42.0 * dot( m*m, vec4( dot(p0,x0), dot(p1,x1), dot(p2,x2), dot(p3,x3) ) ); } float fsnoise(float val1, float val2, float val3){ return snoise(vec3(val1,val2,val3)); } vec3 distortFunct(vec3 transformed, float factor) { float radiusVariation = -fsnoise( transformed.x * radiusNoiseFrequency + time, transformed.y * radiusNoiseFrequency + time, transformed.z * radiusNoiseFrequency + time ) * radiusVariationAmplitude * factor; return normalize(transformed) * (radiusVariation + radius); } vec3 orthogonal(vec3 v) { return normalize(abs(v.x) > abs(v.z) ? vec3(-v.y, v.x, 0.0) : vec3(0.0, -v.z, v.y)); } vec3 distortNormal(vec3 position, vec3 distortedPosition, vec3 normal){ vec3 tangent1 = orthogonal(normal); vec3 tangent2 = normalize(cross(normal, tangent1)); vec3 nearby1 = position + tangent1 * 0.1; vec3 nearby2 = position + tangent2 * 0.1; vec3 distorted1 = distortFunct(nearby1, 1.0); vec3 distorted2 = distortFunct(nearby2, 1.0); return normalize(cross(distorted1 - distortedPosition, distorted2 - distortedPosition)); } `; const vert$3 = ` #include <begin_vertex> float updateTime = time / 10.0; transformed = distortFunct(transformed, 1.0); vec3 distortedNormal = distortNormal(position, transformed, normal); vNormal = normal + distortedNormal; gl_Position = projectionMatrix * modelViewMatrix * vec4(transformed,1.); `; const frag$3 = ` #include <dithering_fragment> float angle = clamp(dot(normalize(vNormal), vec3(0., -1., 0.)), 0., 1.); gl_FragColor = vec4(gl_FragColor.rgb * color, gl_FragColor.a); gl_FragColor.rgb = mix(gl_FragColor.rgb, mix(color, vec3(0.), 0.5), angle); `; /** * Returns a function that, when used every frame, will mark itself * as ready maximum {frequency} times per second. * * @param frequency How many times per second to be marked as ready */ const useLimiter = frequency => { const lastCall = useRef(0); return { isReady: clock => { const time = clock.elapsedTime; const ready = time - lastCall.current > 1 / frequency; if (ready) { lastCall.current = time; } return ready; } }; }; /** * A 1:1 copy of useFrame, but adds a limiter * * Callback will only run {frequency} times per second */ const useLimitedFrame = (frequency, callback, renderPriority) => { const limiter = useLimiter(frequency); useFrame((state, delta) => { if (!limiter.isReady(state.clock)) return; callback(state, delta); }, renderPriority); }; function VisualIdea(props) { const { idea, ...rest } = props; const hex = useMemo(() => (idea == null ? void 0 : idea.getHex()) || "#808080", [idea]); const seed = useMemo(() => Math.random(), []); const color = useMemo(() => new Color(hex), [hex]); const RADIUS = 4; const NOISE_AMPLITUDE = 0.82; const NOISE_FREQ = 0.154; const { col } = useSpring({ col: hex }); const mat = useMemo(() => { const material = new MeshStandardMaterial({ metalness: 0.18, roughness: 0.49, envMapIntensity: 0.66, side: DoubleSide }); material.onBeforeCompile = function (shader) { shader.uniforms.radius = new Uniform(RADIUS); shader.uniforms.time = new Uniform(0); shader.uniforms.color = new Uniform(color); shader.uniforms.radiusVariationAmplitude = new Uniform(NOISE_AMPLITUDE); shader.uniforms.radiusNoiseFrequency = new Uniform(NOISE_FREQ); const uniforms = ` uniform float radius; uniform float time; uniform vec3 color; uniform float radiusVariationAmplitude; uniform float radiusNoiseFrequency; `; shader.vertexShader = uniforms + vertHead$3 + shader.vertexShader.replace("#include <begin_vertex>", vert$3); shader.fragmentShader = uniforms + shader.fragmentShader.replace("#include <dithering_fragment>", frag$3); material.userData.shader = shader; }; return material; }, [RADIUS, color, NOISE_AMPLITUDE, NOISE_FREQ, frag$3, vert$3]); const limiter = useLimiter(50); useFrame(_ref => { var _mat$userData; let { clock } = _ref; if (!(mat != null && (_mat$userData = mat.userData) != null && _mat$userData.shader) || !limiter.isReady(clock)) return; mat.userData.shader.uniforms.time.value = clock.elapsedTime / 6 + seed * 1000; mat.userData.shader.uniforms.color.value.set(col.get()); }); return /*#__PURE__*/React.createElement("group", _extends({ name: "spacesvr-basis-idea" }, rest), /*#__PURE__*/React.createElement("mesh", { material: mat, scale: 0.2 }, /*#__PURE__*/React.createElement("sphereBufferGeometry", { args: [RADIUS, 48, 32] }))); } const rgb_helper = ` vec3 rgb2hsv(vec3 c) { vec4 K = vec4(0.0, -1.0 / 3.0, 2.0 / 3.0, -1.0); vec4 p = mix(vec4(c.bg, K.wz), vec4(c.gb, K.xy), step(c.b, c.g)); vec4 q = mix(vec4(p.xyw, c.r), vec4(c.r, p.yzx), step(p.x, c.r)); float d = q.x - min(q.w, q.y); float e = 1.0e-10; return vec3(abs(q.z + (q.w - q.y) / (6.0 * d + e)), d / (q.x + e), q.x); } vec3 hsv2rgb(vec3 c) { vec4 K = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0); vec3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www); return c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y); } `; const noise4D = ` vec4 permute(vec4 x){return mod(((x*34.0)+1.0)*x, 289.0);} float permute(float x){return floor(mod(((x*34.0)+1.0)*x, 289.0));} vec4 taylorInvSqrt(vec4 r){return 1.79284291400159 - 0.85373472095314 * r;} float taylorInvSqrt(float r){return 1.79284291400159 - 0.85373472095314 * r;} vec4 grad4(float j, vec4 ip){ const vec4 ones = vec4(1.0, 1.0, 1.0, -1.0); vec4 p,s; p.xyz = floor( fract (vec3(j) * ip.xyz) * 7.0) * ip.z - 1.0; p.w = 1.5 - dot(abs(p.xyz), ones.xyz); s = vec4(lessThan(p, vec4(0.0))); p.xyz = p.xyz + (s.xyz*2.0 - 1.0) * s.www; return p; } float snoise(vec4 v){ const vec2 C = vec2( 0.138196601125010504, // (5 - sqrt(5))/20 G4 0.309016994374947451); // (sqrt(5) - 1)/4 F4 // First corner vec4 i = floor(v + dot(v, C.yyyy) ); vec4 x0 = v - i + dot(i, C.xxxx); // Other corners // Rank sorting originally contributed by Bill Licea-Kane, AMD (formerly ATI) vec4 i0; vec3 isX = step( x0.yzw, x0.xxx ); vec3 isYZ = step( x0.zww, x0.yyz ); // i0.x = dot( isX, vec3( 1.0 ) ); i0.x = isX.x + isX.y + isX.z; i0.yzw = 1.0 - isX; // i0.y += dot( isYZ.xy, vec2( 1.0 ) ); i0.y += isYZ.x + isYZ.y; i0.zw += 1.0 - isYZ.xy; i0.z += isYZ.z; i0.w += 1.0 - isYZ.z; // i0 now contains the unique values 0,1,2,3 in each channel vec4 i3 = clamp( i0, 0.0, 1.0 ); vec4 i2 = clamp( i0-1.0, 0.0, 1.0 ); vec4 i1 = clamp( i0-2.0, 0.0, 1.0 ); // x0 = x0 - 0.0 + 0.0 * C vec4 x1 = x0 - i1 + 1.0 * C.xxxx; vec4 x2 = x0 - i2 + 2.0 * C.xxxx; vec4 x3 = x0 - i3 + 3.0 * C.xxxx; vec4 x4 = x0 - 1.0 + 4.0 * C.xxxx; // Permutations i = mod(i, 289.0); float j0 = permute( permute( permute( permute(i.w) + i.z) + i.y) + i.x); vec4 j1 = permute( permute( permute( permute ( i.w + vec4(i1.w, i2.w, i3.w, 1.0 )) + i.z + vec4(i1.z, i2.z, i3.z, 1.0 )) + i.y + vec4(i1.y, i2.y, i3.y, 1.0 )) + i.x + vec4(i1.x, i2.x, i3.x, 1.0 )); // Gradients // ( 7*7*6 points uniformly over a cube, mapped onto a 4-octahedron.) // 7*7*6 = 294, which is close to the ring size 17*17 = 289. vec4 ip = vec4(1.0/294.0, 1.0/49.0, 1.0/7.0, 0.0) ; vec4 p0 = grad4(j0, ip); vec4 p1 = grad4(j1.x, ip); vec4 p2 = grad4(j1.y, ip); vec4 p3 = grad4(j1.z, ip); vec4 p4 = grad4(j1.w, ip); // Normalise gradients vec4 norm = taylorInvSqrt(vec4(dot(p0,p0), dot(p1,p1), dot(p2, p2), dot(p3,p3))); p0 *= norm.x; p1 *= norm.y; p2 *= norm.z; p3 *= norm.w; p4 *= taylorInvSqrt(dot(p4,p4)); // Mix contributions from the five corners vec3 m0 = max(0.6 - vec3(dot(x0,x0), dot(x1,x1), dot(x2,x2)), 0.0); vec2 m1 = max(0.6 - vec2(dot(x3,x3), dot(x4,x4) ), 0.0); m0 = m0 * m0; m1 = m1 * m1; float final = 49.0 * ( dot(m0*m0, vec3( dot( p0, x0 ), dot( p1, x1 ), dot( p2, x2 ))) + dot(m1*m1, vec2( dot( p3, x3 ), dot( p4, x4 ) ) ) ) ; return (final + 1.) / 2.; } `; const oklab$1 = ` float fixedpow(float a, float x) { return pow(abs(a), x) * sign(a); } float cbrt(float a) { return fixedpow(a, 0.3333333333); } vec3 lsrgb2oklab(vec3 c) { float l = 0.4122214708 * c.r + 0.5363325363 * c.g + 0.0514459929 * c.b; float m = 0.2119034982 * c.r + 0.6806995451 * c.g + 0.1073969566 * c.b; float s = 0.0883024619 * c.r + 0.2817188376 * c.g + 0.6299787005 * c.b; float l_ = cbrt(l); float m_ = cbrt(m); float s_ = cbrt(s); return vec3( 0.2104542553 * l_ + 0.7936177850 * m_ - 0.0040720468 * s_, 1.9779984951 * l_ - 2.4285922050 * m_ + 0.4505937099 * s_, 0.0259040371 * l_ + 0.7827717662 * m_ - 0.8086757660 * s_ ); } vec3 oklab2lsrgb(vec3 c) { float l_ = c.r + 0.3963377774 * c.g + 0.2158037573 * c.b; float m_ = c.r - 0.1055613458 * c.g - 0.0638541728 * c.b; float s_ = c.r - 0.0894841775 * c.g - 1.2914855480 * c.b; float l = l_ * l_ * l_; float m = m_ * m_ * m_; float s = s_ * s_ * s_; return vec3( 4.0767416621 * l - 3.3077115913 * m + 0.2309699292 * s, -1.2684380046 * l + 2.6097574011 * m - 0.3413193965 * s, -0.0041960863 * l - 0.7034186147 * m + 1.7076147010 * s ); } `; const vertHead$2 = ` precision highp float; varying vec2 vUv; varying float terrain; varying float terrain_perc; varying vec3 vfNormal; uniform float radius; uniform float time; uniform vec3 color; ${noise4D} `; const vert$2 = ` vec3 pos = position; terrain = 0.; float u_time = time * 0.25; terrain += 1. * pow(snoise(vec4(pos.xyz * 0.15, u_time + 100.)), 1.); terrain += 0.8 * pow(snoise(vec4(pos.xyz * 0.2, u_time + 200.)), 1.5); terrain += 0.4 * pow(snoise(vec4(pos.xyz * 0.8, u_time + 300.)), 2.); terrain += 0.2 * pow(snoise(vec4(pos.xyz * 1.6, u_time + 400.)), 8.); terrain_perc = terrain / (1. + 0.8 + 0.4 + 0.2); terrain_perc = terrain_perc; pos = pos + normal * 2. * 2. * (terrain_perc - 0.5); vfNormal = normal; gl_Position = projectionMatrix * modelViewMatrix * vec4(pos, 1.); `; const fragHead$1 = ` precision highp float; varying vec2 vUv; varying vec3 vfNormal; varying float terrain; varying float terrain_perc; uniform float time; uniform vec3 axiom; uniform float range; uniform vec3 up_norm; ${rgb_helper} ${oklab$1} `; const frag$2 = ` vec3 hsv_col = rgb2hsv(gl_FragColor.rgb); // todo: offset vfNormal by up_norm vec3 oklab_axiom = lsrgb2oklab(axiom); vec3 oklab_range_idea = lsrgb2oklab(axiom + 0.35 * range * vfNormal); vec3 col = oklab2lsrgb(mix(oklab_axiom, oklab_range_idea, 1. - terrain_perc)); gl_FragColor.rgb = col * pow(hsv_col.z, 1.3); gl_FragColor.rgb *= 0.15 + 1.85 * pow((1. - terrain_perc), 1.5); `; // @ts-ignore /** * an idea is the fundamental substrate of reality. */ class Idea { // identifiers // mediation // [0, 1) // [0, 1] // [0, 1] constructor(m, s, u) { if (m === void 0) { m = 0; } if (s === void 0) { s = 0; } if (u === void 0) { u = 0.5; } this.setFromCreation(m, s, u); return this; } setFromCreation(m, s, u) { if (m === void 0) { m = 0; } if (s === void 0) { s = 0; } if (u === void 0) { u = 0.5; } this.mediation = m; this.specificity = s; this.utility = u; return this; } setFromHex(hex) { const color = culori.oklch(culori.rgb(hex)); if (!color) { console.warn("idea :: setFromHex - invalid hex color"); return this; } this.mediation = color.h / 360; this.specificity = color.c / 0.322; this.utility = color.l; return this; } updateFromText(text) { const len = text.length; this.mediation = hashStringToRange$1(text); this.specificity = (1 - (len == 0 ? 1 : 1 / len)) * 0.5; return this; } setUtility(utility) { this.utility = utility; return this; } getHex() { const fixedColor = culori.rgb({ mode: "oklch", l: this.utility, c: this.specificity * 0.322, h: this.mediation * 360 }); return culori.formatHex(fixedColor); } getOpposite() { const newM = this.mediation + 0.5 > 1 ? this.mediation - 0.5 : this.mediation + 0.5; const newS = this.specificity; const newU = 0.5 - (this.utility - 0.5); return new Idea().setFromCreation(newM, newS, newU); } clone() { return new Idea(this.mediation, this.specificity, this.utility); } } const AVG_CHAR_VAL$1 = 100; // each char is roughly 100, so loop every ~50 chars const hashStringToRange$1 = function (str, loop) { if (loop === void 0) { loop = 20; } let count = 0; for (let i = 0; i < str.length; i++) { count += str.substr(i, 1).charCodeAt(0); } const scaledLoop = loop * AVG_CHAR_VAL$1; return count % scaledLoop / scaledLoop; }; function VisualWorld(props) { const { world, ...rest } = props; const RADIUS = 4; const SEED = useMemo(() => Math.random(), []); const mat = useMemo(() => { const material = new MeshStandardMaterial({ metalness: 0.18, roughness: 0.49, side: DoubleSide }); material.onBeforeCompile = function (shader) { const uniforms = { time: new Uniform(0), axiom: new Uniform(new Color("#888888")), up_norm: new Uniform(new Vector3(0, 1, 0)), range: new Uniform(0) }; shader.uniforms = { ...shader.uniforms, ...uniforms }; shader.vertexShader = vertHead$2 + shader.vertexShader.replace("#include <worldpos_vertex>", "#include <worldpos_vertex>\n" + vert$2); shader.fragmentShader = fragHead$1 + shader.fragmentShader.replace("#include <dithering_fragment>", "#include <dithering_fragment>\n" + frag$2); material.userData.shader = shader; }; material.needsUpdate = true; return material; }, []); useEffect(() => { if (!mat || !mat.userData.shader || !world) return; const unifs = mat.userData.shader.uniforms; const axiom = world ? world.getAxiom() : new Idea(); unifs.axiom.value.set(new Color(axiom.getHex())); unifs.up_norm.value = world == null ? void 0 : world.getUpNorm(); unifs.range.value = world == null ? void 0 : world.getRange(); }, [world, mat]); useLimitedFrame(50, _ref => { let { clock } = _ref; if (!mat || !mat.userData.shader) return; mat.userData.shader.uniforms.time.value = clock.elapsedTime + SEED * 500; }); return /*#__PURE__*/React.createElement("group", _extends({ name: "spacesvr-basis-world" }, rest), /*#__PURE__*/React.createElement("mesh", { material: mat, scale: 0.2 }, /*#__PURE__*/React.createElement("sphereBufferGeometry", { args: [RADIUS, 48, 32] }))); } const DOWN_AXIS = new Vector3(0, -1, 0); /** * Will smoothly rotate its children to face the camera along the Y axis, regardless of the parent's rotation. */ function LookAtPlayer(props) { const { enabled = true, children } = props; const group = useRef(null); const flatDelta = useMemo(() => new Vector2(), []); const worldPos = useMemo(() => new Vector3(), []); const worldQuat = useMemo(() => new Quaternion(), []); const targetQuat = useMemo(() => new Quaternion(), []); const parentQuat = useMemo(() => new Quaternion(), []); const offsetRot = useMemo(() => new Euler(), []); useLimitedFrame(50, (_ref, delta) => { var _group$current$parent; let { camera } = _ref; if (!group.current) return; (_group$current$parent = group.current.parent) == null ? void 0 : _group$current$parent.getWorldQuaternion(parentQuat); offsetRot.setFromQuaternion(parentQuat, "YXZ"); targetQuat.set(0, 0, 0, 1); if (enabled) { group.current.getWorldPosition(worldPos); group.current.getWorldQuaternion(worldQuat); flatDelta.x = camera.position.x - worldPos.x; flatDelta.y = camera.position.z - worldPos.z; const angle = flatDelta.angle() - Math.PI / 2 + offsetRot.y; targetQuat.setFromAxisAngle(DOWN_AXIS, angle); } group.current.quaternion.slerp(targetQuat, 0.11); }); return /*#__PURE__*/React.createElement("group", { name: "look-at-player", ref: group }, children); } function Background$1(props) { const { color } = props; const scene = useThree(state => state.scene); useLayoutEffect(() => { const oldBackground = scene.background; const col = color instanceof Color ? color : new Color(color); scene.background = col; return () => { scene.background = oldBackground; }; }, [color]); return null; } function Fog(props) { const { color = "white", near = 10, far = 80 } = props; const scene = useThree(state => state.scene); useEffect(() => { const col = color instanceof Color ? color : new Color(color); scene.fog = new Fog$1(col, near, far); return () => { scene.fog = null; }; }, [scene, color, near, far]); return null; } function InfinitePlane(props) { const { height = -0.0001, size = [100, 100], visible } = props; const [ref] = usePlane(() => ({ rotation: [-Math.PI / 2, 0, 0], position: [0, height, 0], args: size, type: "Static" })); if (!visible) return null; return /*#__PURE__*/React.createElement("mesh", { name: "spacesvr-infinite-plane", ref: ref }, /*#__PURE__*/React.createElement("planeBufferGeometry", { args: size }), /*#__PURE__*/React.createElement("meshPhongMaterial", { color: "#660000" })); } function Audio$1(props) { const { url, dCone = new Vector3(180, 230, 0.1), rollOff = 1, volume = 1, setAudioAnalyser, fftSize = 128, ...rest } = props; const [speaker, setSpeaker] = useState(); const camera = useThree(state => state.camera); const audio = useMemo(() => { const a = document.createElement("audio"); a.src = url; a.autoplay = false; a.preload = "auto"; a.crossOrigin = "Anonymous"; a.loop = true; return a; }, []); useEffect(() => { const setupAudio = () => { if (!audio.paused && !speaker) { const listener = new AudioListener(); camera.add(listener); const speak = new PositionalAudio(listener); speak.setMediaElementSource(audio); speak.setRefDistance(0.75); speak.setRolloffFactor(rollOff); speak.setVolume(volume); speak.setDirectionalCone(dCone.x, dCone.y, dCone.z); if (setAudioAnalyser) { setAudioAnalyser(new AudioAnalyser(speak, fftSize)); } setSpeaker(speak); } }; const playAudio = () => audio.play().then(() => setupAudio()); if (audio) { audio.setAttribute("src", url); audio.play().then(() => setupAudio()); document.addEventListener("click", playAudio); return () => { document.removeEventListener("click", playAudio); }; } }, [speaker, audio, url]); useEffect(() => { if (!speaker) return; speaker.setRolloffFactor(rollOff); speaker.setVolume(volume); speaker.setDirectionalCone(dCone.x, dCone.y, dCone.z); }, [dCone, rollOff, volume]); return /*#__PURE__*/React.createElement("group", _extends({ name: "spacesvr-audio" }, rest), speaker && /*#__PURE__*/React.createElement("primitive", { object: speaker })); } function HDRI(props) { const { src, disableBackground, disableEnvironment } = props; return /*#__PURE__*/React.createElement(Suspense, { fallback: null }, /*#__PURE__*/React.createElement(Environment$1, { files: src, background: !disableBackground && !disableEnvironment ? true : disableEnvironment && !disableBackground ? "only" : false })); } let fallbackTexture; const SIZE = 128; const SIZE_2 = SIZE / 2; const RAD = 12; const LINE_W = 1; /** * Provides a default texture that is created locally */ function getFallbackTexture() { if (fallbackTexture) return fallbackTexture; const canvas = document.createElement("canvas"); canvas.height = SIZE; canvas.width = SIZE; const context = canvas.getContext("2d"); context.fillStyle = "#FFFFFF"; context.fillRect(0, 0, SIZE, SIZE); // main circle context.fillStyle = "#000000"; context.beginPath(); context.arc(SIZE_2, SIZE_2, RAD, 0, 2 * Math.PI); context.fill(); // draw a white line down the middle of the circle context.strokeStyle = "#FFFFFF"; context.lineWidth = Math.ceil(LINE_W); context.beginPath(); context.moveTo(SIZE_2, SIZE_2 - RAD); context.lineTo(SIZE_2, SIZE_2 + RAD); context.stroke(); // draw a horizontal line across the middle of the circle context.beginPath(); context.moveTo(SIZE_2 - RAD, SIZE_2); context.lineTo(SIZE_2 + RAD, SIZE_2); context.stroke(); fallbackTexture = new CanvasTexture(canvas); return fallbackTexture; } const KTX_CDN = "https://cdn.jsdelivr.net/gh/pmndrs/drei-assets@master/basis/"; const textureLoader = new TextureLoader(); let ktx2loader; // it's inconvenient to have to produce a gl object to check for ktx2 support, especially when it comes to the cache keys // solution is to create a skeleton object that provides the minimum requirements to check for ktx support, defined below // https://github.com/mrdoob/three.js/blob/master/examples/jsm/loaders/KTX2Loader.js#L113-L135 const setupKtx2 = () => { if (ktx2loader) return; ktx2loader = new KTX2Loader(); ktx2loader.setTranscoderPath(KTX_CDN); let supportsWebgl2; const el = document.createElement("canvas"); let gl = el.getContext("webgl2"); if (gl) { supportsWebgl2 = true; } else { gl = el.getContext("webgl"); supportsWebgl2 = false; } if (!gl) { throw new Error("No WebGL support"); } el.remove(); const minimumGL = { extensions: new WebGLExtensions(gl), capabilities: { isWebGL2: supportsWebgl2 } }; // @ts-ignore ktx2loader.detectSupport(minimumGL); }; function loadimage() { return function (url) { const IS_KTX2 = url.toLowerCase().endsWith("ktx2"); setupKtx2(); const loader = IS_KTX2 ? ktx2loader : textureLoader; return new Promise(res => loader.load(url, res, undefined, error => { console.error(error); res(getFallbackTexture()); })); }; } /** * A single hook akin to useTexture but with ktx support * * KTX_CDN is from drei so that we don't download two separate transcoders when using the useKtx2 hook elsewhere * https://github.com/pmndrs/drei/blob/a2daf02853f624ef6062c70ba0b218bc03e5b626/src/core/useKTX2.tsx#L7 * @param url */ function useImage(url) { return suspend(loadimage(), [url]); } useImage.preload = function (url) { return preload(loadimage(), [url]); }; useImage.clear = function (url) { return clear([url]); }; /** * A hook to load gltf models with draco, meshopt, and ktx2 support out of the box * * For all cases, functionality is to only download decoder files if needed by the file * @param url */ function useModel(url) { return useGLTF(url, true, true, loader => { setupKtx2(); loader.setKTX2Loader(ktx2loader); }); } useModel.preload = function (url) { return useGLTF.preload(url, true, true, loader => { setupKtx2(); loader.setKTX2Loader(ktx2loader); }); }; useModel.clear = function (url) { return useGLTF.clear([url]); }; /** * * Builds a frame for a mesh with a texture (image, video, etc.) * * In the code, the frame is the back panel and the border is the * four meshes that make up the top, left, right, and bottom sides * of the border. * * @param props * @constructor */ function Frame(props) { const { width, height, thickness = 1, material: passedMaterial, innerFrameMaterial } = props; const material = useMemo(() => passedMaterial || new MeshStandardMaterial({ color: 0x333333, roughness: 0.8, metalness: 0.05 }), [passedMaterial]); const frameDepth = 0.075; const frameWidth = 0.06; const borderDepth = 0.08; const borderThickness = 0.05 * thickness; const meshOffset = 0.0005; const geometry = useMemo(() => { const backPanel = new BoxBufferGeometry(width + frameWidth, height + frameWidth, frameDepth); backPanel.translate(0, 0, -frameDepth - meshOffset); const topFrame = new BoxBufferGeometry(width + frameWidth, borderThickness, borderDepth); topFrame.translate(0, height / 2 + frameWidth / 2 - borderThickness / 2, 0); const bottomFrame = new BoxBufferGeometry(width + frameWidth, borderThickness, borderDepth); bottomFrame.translate(0, -height / 2 - frameWidth / 2 + borderThickness / 2, 0); const leftFrame = new BoxBufferGeometry(borderThickness, height + frameWidth, borderDepth); leftFrame.translate(-width / 2 - frameWidth / 2 + borderThickness / 2, 0, 0); const rightFrame = new BoxBufferGeometry(borderThickness, height + frameWidth, borderDepth); rightFrame.translate(width / 2 + frameWidth / 2 - borderThickness / 2, 0, 0); const geos = [backPanel, topFrame, bottomFrame, leftFrame, rightFrame]; const geo = mergeBufferGeometries(geos); backPanel.dispose(); topFrame.dispose(); bottomFrame.dispose(); leftFrame.dispose(); rightFrame.dispose(); return geo; }, [innerFrameMaterial, borderThickness, width, height]); const backFrameGeometry = useMemo(() => { if (!innerFrameMaterial) return undefined; const backPanel = new BoxBufferGeometry(width + frameWidth, height + frameWidth, frameDepth); backPanel.translate(0, 0, -frameDepth - meshOffset); return backPanel; }, [innerFrameMaterial, width, height]); return /*#__PURE__*/React.createElement("group", { name: "spacesvr-frame" }, /*#__PURE__*/React.createElement("mesh", { geometry: geometry, material: material }), backFrameGeometry && innerFrameMaterial && /*#__PURE__*/React.createElement("mesh", { geometry: backFrameGeometry, material: innerFrameMaterial })); } function UnsuspensedImage(props) { const { src, size = 1, framed, frameMaterial, frameWidth = 1, innerFrameMaterial, ...rest } = props; const tex = useImage(src); const { width, height } = tex.image; const max = Math.max(width, height); const WIDTH = width / max * size; const HEIGHT = height / max * size; const IS_COMPRESSED = tex.isCompressedTexture; return /*#__PURE__*/React.createElement("group", _extends({ name: "spacesvr-image" }, rest), /*#__PURE__*/React.createElement("mesh", { rotation: IS_COMPRESSED ? [0, Math.PI, Math.PI] : [0, 0, 0] }, /*#__PURE__*/React.createElement("planeBufferGeometry", { args: [WIDTH, HEIGHT] }), /*#__PURE__*/React.createElement("meshBasicMaterial", { map: tex, side: DoubleSide, transparent: true })), framed && /*#__PURE__*/React.createElement(Frame, { width: WIDTH, height: HEIGHT, thickness: frameWidth, material: frameMaterial, innerFrameMaterial: innerFrameMaterial })); } function Image$1(props) { return /*#__PURE__*/React.createElement(Suspense, { fallback: null }, /*#__PURE__*/React.createElement(UnsuspensedImage, props)); } /** * a world is a set of ideas */ class World { constructor() { return this; } getIdea() { return new Idea().setFromCreation(hashStringToRange(JSON.stringify(this.tree || this.id), 3), 0.3 + 0.7 * hashStringToRange(this.id), 0.8); } getAxiom() { const str = JSON.stringify(this.tree || this.id); const strHash = new Array(10).fill(1).map(() => str).join(""); return new Idea().setFromCreation(hashStringToRange(strHash, 15), 0.3 + 0.7 * hashStringToRange(strHash, 10), 0.8); } getUpNorm() { // 4 digit long hex values const x = parseInt(this.id.split("-")[1], 16) / Math.pow(16, 4); const y = parseInt(this.id.split("-")[2], 16) / Math.pow(16, 4); const z = parseInt(this.id.split("-")[3], 16) / Math.pow(16, 4); return new Vector3(x, y, z).normalize(); } getRange() { const r = parseInt(this.id.split("-")[0], 16) / Math.pow(16, 8); return 0.3 + 0.7 * r; } getHex() { return this.getIdea().getHex(); } } const AVG_CHAR_VAL = 100; // each char is roughly 100, so loop every ~50 chars const hashStringToRange = function (str, loop) { if (loop === void 0) { loop = 8; } let count = 0; for (let i = 0; i < str.length; i++) { count += str.substr(i, 1).charCodeAt(0); } const scaledLoop = loop * AVG_CHAR_VAL; return count % scaledLoop / scaledLoop; }; /** * A site is a delivery method of a world. */ class Site { constructor() { return this; } } /** * Gets the number of triangles in a geometry */ const getGeometryTriCount$1 = geometry => { return geometry.index ? geometry.index.count / 3 : geometry.attributes.position.count / 3; }; /** * For a given mesh, set up bvh raycasting for it if it meets the threshold for * amount of triangles to use * * @param mesh * @param threshold */ const enableBVHRaycast = function (mesh, threshold) { if (threshold === void 0) { threshold = 0; } if (!mesh.geometry || !mesh.geometry.isBufferGeometry) { return; } const geometry = mesh.geometry; const triCount = getGeometryTriCount$1(geometry); if (geometry.boundsTree || triCount < threshold) return; mesh.raycast = acceleratedRaycast; geometry.computeBoundsTree = computeBoundsTree; geometry.disposeBoundsTree = disposeBoundsTree; geometry.computeBoundsTree({ verbose: true }); }; const universe_cache = new Map(); function getResource(key, constructor, opts) { let resource = universe_cache.get(key); if (!resource) { if (opts != null && opts.verbose) console.log(`[CACHE] ${key} not found, creating new`); resource = constructor(); universe_cache.set(key, resource); } else { if (opts != null && opts.verbose) console.log(`[CACHE] ${key} found, returning`); } return resource; } const cache = { getResource, useResource: (key, constructor, opts) => { const [resource, setResource] = useState(getResource(key, constructor, opts)); useEffect(() => { setResource(getResource(key, constructor, opts)); }, [key]); return resource; }, get mat_standard_white() { return getResource("mat_standard_white", () => new MeshStandardMaterial({ color: "white" })); }, get mat_standard_cream_double() { return getResource("mat_standard_cream_double", () => new MeshStandardMaterial({ color: "#aaa", side: DoubleSide })); }, get mat_standard_black() { return getResource("mat_standard_black", () => new MeshStandardMaterial({ color: "black" })); }, get mat_standard_rose() { return getResource("mat_standard_rose", () => new MeshStandardMaterial({ color: "#ff007f" })); }, get mat_standard_red() { return getResource("mat_standard_red", () => new MeshStandardMaterial({ color: "#ff0000" })); }, get mat_basic_white() { return getResource("mat_basic_white", () => new MeshBasicMaterial({ color: "white" })); }, get mat_basic_black() { return getResource("mat_basic_black", () => new MeshBasicMaterial({ color: "black" })); }, get mat_basic_gray() { return getResource("mat_basic_gray", () => new MeshBasicMaterial({ color: "#828282" })); }, get mat_basic_red() { return getResource("mat_basic_red", () => new MeshBasicMaterial({ color: "red" })); }, get mat_basic_black_wireframe() { return getResource("mat_basic_black_wireframe", () => new MeshBasicMaterial({ color: "black", wireframe: true })); } }; const useTrimeshCollision$1 = geometry => { const indices = geometry.index.array; const isInterleaved = // @ts-ignore geometry.attributes.position.isInterleavedBufferAttribute; let vertices = []; if (isInterleaved) { const attr = geometry.attributes.position; const data = attr.data; for (let i = attr.offset; i < data.array.length; i += data.stride) { for (let x = 0; x < attr.itemSize; x++) { vertices.push(data.array[i + x]); } } } else { vertices = geometry.attributes.position.array; } const [hitbox] = useTrimesh(() => ({ type: "Static", args: [vertices, indices] })); return hitbox; }; // check whether the user is currently typing const isTyping = () => { var _document, _document$activeEleme, _document2; return ((_document = document) == null ? void 0 : (_document$activeEleme = _document.activeElement) == null ? void 0 : _document$activeEleme.tagName) === "INPUT" && ((_document2 = document) == null ? void 0 : _document2.hasFocus()); }; const useDrag = function (callback, domElem, deps) { if (deps === void 0) { deps = []; } const { clock, size, viewport } = useThree(); const aspect = size.width / viewport.width; const [downPoint] = useState(new Vector2()); const [dragPoint] = useState(new Vector2()); const [velocity] = useState(new Vector2()); const [delta] = useState(new Vector2()); const lastTouchRead = useRef(0); const onStart = useCallback(p => { if (callback.onStart) callback.onStart(p); }, [...deps]); const onMove = useCallback(p => { if (callback.onMove) callback.onMove(p); }, [...deps]); const onEnd = useCallback(p => { if (callback.onEnd) callback.onEnd(p); }, [...deps]); const startDrag = useCallback(e => { e.preventDefault(); const touch = e.changedTouches[0]; downPoint.set(touch.clientX, touch.clientY); onStart({ e, touch, downPoint, dragPoint: downPoint, velocity }); }, [onStart, downPoint, velocity]); const moveDrag = useCallback(e => { const touch = e.touches[0]; dragPoint.set(touch.clientX, touch.clientY); const delta = dragPoint.sub(downPoint); const time = clock.elapsedTime; const elapsed = time - lastTouchRead.current; velocity.set(delta.x / elapsed / aspect, delta.y / elapsed / aspect); lastTouchRead.current = time; onMove({ e, touch, downPoint, dragPoint, velocity, delta }); }, [aspect, onMove, clock, downPoint, dragPoint, velocity]); const endDrag = useCallback(e => { const touch = e.changedTouches[0]; dragPoint.set(touch.clientX, touch.clientY); delta.copy(dragPoint).sub(downPoint); onEnd({ e, touch, downPoint, dragPoint, velocity, delta }); }, [onEnd, delta, downPoint, dragPoint, velocity]); useEffect(() => { const elem = domElem || document; elem.addEventListener("touchstart", startDrag); elem.addEventListener("touchmove", moveDrag); elem.addEventListener("touchend", endDrag); return () => { elem.removeEventListener("touchstart", startDrag); elem.removeEventListener("touchmove", moveDrag); elem.removeEventListener("touchend", endDrag); }; }, [domElem, endDrag, moveDrag, startDrag]); return { startDrag, moveDrag, endDrag }; }; const PADDING_X = 0.125; const PADDING_X_2 = PADDING_X * 2; const PADDING_Y = 0.125; const PADDING_Y_2 = PADDING_Y * 2; const RAD_PER_DEG_2 = Math.PI / 180 / 2; const getHudPos = (pos, camera, distance, target) => { const vFOV = camera.fov * RAD_PER_DEG_2; const height = 2 * Math.tan(vFOV) * Math.abs(distance); const width = height * camera.aspect; const px = pos.x || pos[0]; const py = pos.y || pos[1]; const x = px * (width - PADDING_X_2) * 0.5; const y = py * (height - PADDING_Y_2) * 0.5; if (target) { target.x = x; target.y = y; return target; } return new Vector2(x, y); }; const getHudDims = (camera, distance) => { const vFOV = camera.fov * RAD_PER_DEG_2; const height = 2 * Math.tan(vFOV) * Math.abs(distance); const width = height * camera.aspect; return { width, height }; }; const useHudDims = function (distance) { if (distance === void 0) { distance = 1; } const camera = useThree(state => state.camera); return useMemo(() => { return getHudDims(camera, distance); // make sure aspect is there }, [camera, distance, camera.aspect]); }; const Container$2 = styled.div` position: absolute; width: 100%; height: 100%; overflow: hidden; canvas { position: absolute; top: 0; right: 0; bottom: 0; left: 0; outline: 0; } `; const globalStyles = css` @font-face { font-family: "Quicksand"; src: url("https://cdn.websessions.co/select/fonts/FTBasicSpace-Regular.woff2"); } html { position: fixed; height: 100%; overflow: hidden; } body { margin: 0; width: 100vw; height: 100vh; user-select: none; overflow: hidden; touch-action: none; -webkit-overflow-scrolling: touch; font-family: "Quicksand", sans-serif; font-size: 18px; @media screen and (max-width: 500px) { font-size: 16px; } } `; function GlobalStyles() { useEffect(() => { const view = document.createElement("meta"); view.name = "viewport"; view.content = "initial-scale=1, viewport-fit=cover"; document.head.append(view); return () => { document.head.removeChild(view); }; }, []); return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(Global, { styles: globalStyles })); } /** * A modified version of the controlled progress hooks that adds * - a minimum wait time, in case it takes a second to register an asset * - a delay after it reaches 100 in case it goes back down * - a timeout when it reaches > 50%, marked as stuck */ const useControlledProgress = () => { const MIN_TIME = 400; // minimum time to wait before moving to 100 const AFTER_TIME = 300; // extra time to prevent bouncing at reaching 100 const { progress, total } = useProgress(); const startTime = useRef(new Date()); const controlledProgress = useRef(0); const finished = useRef(false); const [, setForceRender] = useState(0); useEffect(() => { const newTime = new Date(); const timeElapsed = newTime.getTime() - startTime.current.getTime(); const diff = Math.min(progress - controlledProgress.current, timeElapsed < MIN_TIME ? 99 : 100); if (diff > 0) { if (progress === 100) { finished.current = true; // if progress 100, check in AFTER_TIME ms to make sure it hasn't // bounced back down setTimeout(() => { if (finished.current) { controlledProgress.current = progress; // set state to force re render setForceRender(Math.random()); } }, AFTER_TIME); } else { finished.current = false; controlledProgress.current = progress; } } if (progress !== 100) { finished.current = false; } }, [progress]); // wait TIMEOUT (ms) to check if any objects are waiting to be loaded const [counter, setCounter] = useState(0); const [skip, setSkip] = useState(false); useEffect(() => { if (total > 0) { return; } else if (counter > 0) { setSkip(true); } else { setTimeout(() => setCounter(counter + 1), MIN_TIME); } }, [counter]); return skip ? 100 : Math.floor(controlledProgress.current); }; const float = keyframes` 0% { transform: translatey(0px); } 50% { transform: translatey(-15px); } 100% { transform: translatey(0px); } `; const grow = keyframes` 0% { opacity: 0; } 100% { opacity: 1; } `; const Container$1 = styled.div` width: 100%; height: 100%; position: absolute; top: 0; left: 0; z-index: 200; background: white; transition: opacity 0.75s ease-in; transition-delay: 0.5s; opacity: ${props => props.finished ? "0" : "1"}; pointer-events: ${props => props.finished ? "none" : "all"}; display: flex; justify-content: center; align-items: center; flex-direction: column; `; const Text$1 = styled.div` z-index: 999999999999; position: fixed; left: 50%; top: 50%; transform: translate(-50%, -50%); animation: ${grow} 1.2s ease-in-out infinite; /* animation: ${float} 7s ease-in-out infinite; */ `; const ProgressBar = styled(motion.div)` position: fixed; bottom: 0; left: 0; /* right: 0; */ width: 100%; /* height: 10px; */ background: #f2f2f2; transform-origin: 0%; `; const Wrapper$1 = styled.div` position: relative; &:before { pointer-events: none; position: absolute; content: ""; top: 100%; left: 5%; height: 10px; width: 90%; } `; function LoadingScreen() { // const [seconds, setSeconds] = useState(0); const progress = useControlledProgress(); const [seconds, setSeconds] = useState(true); useEffect(() => { const interval = setInterval(() => { // setSeconds((seconds) => seconds + 1); setSeconds(!seconds); }, 1000); return () => clearInterval(interval); }); // console.log('progress', progress) return /*#__PURE__*/React$1.createElement(Container$1, { finished: progress === 100 }, /*#__PURE__*/React$1.createElement(Wrapper$1, null, /*#__PURE__*/React$1.createElement(ProgressBar, { animate: { height: progress + "%" }, transition: { stiffness: 100, damping: 30, restDelta: 0.001 } }), /*#__PURE__*/React$1.createElement(Text$1, null, seconds ? progress + "%" : "Experience Loading"))); } const Container = styled.div` width: 100%; height: 100%; position: absolute; top: 0; left: 0; z-index: 100; transition: opacity 0.25s ease; background: rgba(0, 0, 0, ${props => props.dev ? 0 : 0.25}); display: flex; justify-content: center; flex-direction: column; align-items: center; opacity: ${props => props.paused ? 1 : 0}; pointer-events: ${props => props.paused ? "all" : "none"}; font-family: "Quicksand", sans-serif; `; const ClickContainer = styled.div` width: 100%; height: 100%; position: absolute; top: 0; left: 0; z-index: -1; `; const Window = styled.div` width: 100%; height: 100%; padding: 20px 20px; color: black; display: flex; flex-direction: column; align-items: center; justify-content: space-between; position: relative; /* background-color: #00000011; backdrop-filter: blur(2px); */ background: rgb(255 255 255 / 20%); backdrop-filter: blur(7px); /* box-shadow: 12px 12px 16px 0 rgba(0, 0, 0, 0.25), -8px -8px 12px 0 rgba(255, 255, 255, 0.3); */ `; const Continue = styled.div` border-radius: 5px; padding: 10px 20px; background: linear-gradient( 281.48deg, rgba(255, 255, 255, 0.4) 0%, rgba(255, 255, 255, 0) 96.42% ); border: 0.5px solid #ffffff; filter: drop-shadow(0px 4px 24px rgba(255, 255, 255, 0.4)); /* backdrop-filter: blur(16px); */ margin: 0 auto; z-index: 10000; color: white; cursor: pointer; font-size: 13px; &:hover { transition: fill 0.4s ease-in-out; fill: #78e41f; } `; const Instructions = styled.div` width: 100%; height: auto; margin: 30px 0; font-size: 18px; text-align: center; display: flex; flex-direction: column; justify-content: center; color: white; & > p { margin: 1em 0; } `; styled.div` border: 1px solid black; border-radius: 10px; background: rgba(255, 255, 255, 0); padding: 5px 10px; margin: 8px 4px; transition: background 0.15s linear; font-size: 0.5em; cursor: pointer; &:hover { background: rgba(0, 0, 0, 0.15); } `; styled.a` border: 1px solid black; border-radius: 10px; background: rgba(255, 255, 255, 0); padding: 5px 10px; margin: 8px 4px; transition: background 0.15s linear; font-size: 0.5em; cursor: pointer