diff --git a/package.json b/package.json index 490b756..0f1d23a 100644 --- a/package.json +++ b/package.json @@ -48,7 +48,8 @@ "react-page-visibility": "^7.0.0", "react-redux": "^8.0.2", "react-ticker": "^1.3.2", - "slugify": "^1.6.5" + "slugify": "^1.6.5", + "three": "^0.158.0" }, "devDependencies": { "@types/gatsby-transformer-remark": "^2.9.1", @@ -56,6 +57,7 @@ "@types/react": "^18.0.15", "@types/react-dom": "^18.0.6", "@types/react-page-visibility": "^6.4.1", + "@types/three": "^0.158.3", "@types/webpack-env": "^1.17.0", "escape-string-regexp": "^5.0.0", "fast-glob": "^3.2.11", diff --git a/src/components/Common/Header/Header.tsx b/src/components/Common/Header/Header.tsx index 812ae6a..4b9a901 100644 --- a/src/components/Common/Header/Header.tsx +++ b/src/components/Common/Header/Header.tsx @@ -20,6 +20,7 @@ const OuterOnTop = styled(Outer)` top: 0; left: 0; right: 0; + z-index: 10; @media (max-width: ${theme.breakpoints.sm}px) { background-size: auto; } diff --git a/src/components/Home/Hero/Background.tsx b/src/components/Home/Hero/Background.tsx new file mode 100644 index 0000000..8bfd651 --- /dev/null +++ b/src/components/Home/Hero/Background.tsx @@ -0,0 +1,268 @@ +import React, { memo, useEffect } from 'react'; +import styled from '@emotion/styled'; +import { + AmbientLight, + Box2, + CylinderGeometry, + DirectionalLight, + Mesh, + MeshPhongMaterial, + OrthographicCamera, + PointLight, + RepeatWrapping, + Scene, + SRGBColorSpace, + Texture, + TextureLoader, + Vector2, + WebGLRenderer, +} from 'three'; +import faceBumpMapUrl from '!url-loader?limit=false!../../../images/hero/coin-face-bump-map.png'; +import sideBumpMapUrl from '!url-loader?limit=false!../../../images/hero/coin-side-bump-map.png'; +import { getNetworkIconSrc } from '../../../utils/network-utils'; +import { theme } from '../../../theme'; + +const Container = styled.div` + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; +`; + +const chains = Object.entries(theme.chains).map(([chainId, color]) => ({ + chainId, + color: chainId === 'base' ? '#0052FF' : color, + src: getNetworkIconSrc(chainId), +})); + +class Coins { + private camera: OrthographicCamera; + private scene: Scene; + private renderer: WebGLRenderer; + private rendering: boolean = false; + private frameRequest: number | undefined; + private coins: Mesh[]; + private velocities: Vector2[]; + private textureLoader: TextureLoader; + private lastTime: DOMHighResTimeStamp | undefined; + private faceBumpMap: Texture | undefined; + private sideBumpMap: Texture | undefined; + private coinGeometry: CylinderGeometry | undefined; + private coinRadius: number = 5 / 100; + private coinHeight: number = 1 / 100; + private boundingBox: Box2; + + constructor(protected container: HTMLDivElement) { + this.render = this.render.bind(this); + this.resize = this.resize.bind(this); + + this.textureLoader = new TextureLoader(); + this.camera = this.createCamera(); + this.boundingBox = this.createBoundingBox(); + this.coins = this.createCoins(); + this.velocities = this.coins.map(() => new Vector2(this.randomSpeed(), this.randomSpeed())); + this.scene = this.createScene(); + this.renderer = this.createRenderer(); + this.attachEvents(); + } + + private loadTexture(url: string) { + const texture = this.textureLoader.load(url); + texture.colorSpace = SRGBColorSpace; + return texture; + } + + private createCoins() { + this.faceBumpMap = this.loadTexture(faceBumpMapUrl); + this.sideBumpMap = this.loadTexture(sideBumpMapUrl); + this.coinGeometry = new CylinderGeometry(this.coinRadius, this.coinRadius, this.coinHeight, 32); + this.coinGeometry.rotateX(Math.PI / 2); + this.coinGeometry.rotateZ(Math.PI / 2); + + return chains.map(({ chainId, color, src }) => this.createCoin(chainId, color, src)); + } + + private createCoin(chainId: string, color: string, src: string) { + const textureMap = this.loadTexture(src); + const oppositeFaceTextureMap = this.loadTexture(src); + oppositeFaceTextureMap.flipY = false; + oppositeFaceTextureMap.wrapS = RepeatWrapping; + oppositeFaceTextureMap.repeat.x = -1; + + const side = new MeshPhongMaterial({ + color, + specular: 0xffffff, + shininess: 10, + bumpMap: this.sideBumpMap!, + bumpScale: 10, + }); + + const face = new MeshPhongMaterial({ + specular: 0xffffff, + shininess: 10, + bumpMap: this.faceBumpMap!, + bumpScale: 10, + map: textureMap, + }); + + const oppositeFace = new MeshPhongMaterial({ + specular: 0xffffff, + shininess: 10, + bumpMap: this.faceBumpMap!, + bumpScale: 10, + map: oppositeFaceTextureMap, + }); + + return new Mesh(this.coinGeometry!, [side, oppositeFace, face]); + } + + private createScene() { + const scene = new Scene(); + const ambientLight = new AmbientLight(0xffffff, 1); + const directionalLight = new DirectionalLight(0xffffff, 5); + const spotLight = new PointLight(0xffffff, 10); + spotLight.position.set(3.5, 0, 50); + + this.coins.forEach((coin, i) => { + const x = Math.random() * this.camera.right; + const y = Math.random(); + coin.position.set(x, y, i); + coin.rotation.y = Math.random() * Math.PI * 2; + }); + + scene.add(...this.coins); + scene.add(ambientLight); + scene.add(directionalLight); + scene.add(spotLight); + + return scene; + } + + private createRenderer() { + const renderer = new WebGLRenderer({ + antialias: true, + }); + renderer.setSize(this.container.offsetWidth, this.container.offsetHeight); + renderer.setClearColor('#141520', 0); + this.container.appendChild(renderer.domElement); + return renderer; + } + + private createBoundingBox(): Box2 { + return new Box2( + new Vector2(this.coinRadius, this.coinRadius), + new Vector2(this.camera.right - this.coinRadius, 1 - this.coinRadius) + ); + } + + private createCamera() { + const aspect = this.container.offsetWidth / this.container.offsetHeight; + const camera = new OrthographicCamera(0, aspect, 0, 1); + camera.position.z = 50; + return camera; + } + + public startRendering() { + if (!this.rendering) { + this.rendering = true; + this.frameRequest = requestAnimationFrame(this.render); + } + } + + public stopRendering() { + this.rendering = false; + this.lastTime = undefined; + if (this.frameRequest) { + cancelAnimationFrame(this.frameRequest); + this.frameRequest = undefined; + } + } + + private randomSpeed() { + return Math.max(Math.random(), 0.1) / 2000; + } + + private update(time: DOMHighResTimeStamp) { + const delta = this.lastTime === undefined ? 0 : time - this.lastTime; + this.lastTime = time; + + this.coins.forEach((coin, i) => { + const velocity = this.velocities[i]; + + coin.rotation.y += delta / 1000; + coin.position.set( + coin.position.x + velocity.x * delta, + coin.position.y + velocity.y * delta, + i + ); + + if (coin.position.y >= this.boundingBox.max.y) { + coin.position.y = this.boundingBox.max.y; + velocity.y = -this.randomSpeed(); + } else if (coin.position.y <= this.boundingBox.min.y) { + coin.position.y = this.boundingBox.min.y; + velocity.y = this.randomSpeed(); + } + + if (coin.position.x >= this.boundingBox.max.x) { + coin.position.x = this.boundingBox.max.x; + velocity.x = -this.randomSpeed(); + } else if (coin.position.x <= this.boundingBox.min.x) { + coin.position.x = this.boundingBox.min.x; + velocity.x = this.randomSpeed(); + } + }); + } + + private render(time: DOMHighResTimeStamp) { + if (!this.rendering) { + return; + } + this.update(time); + this.frameRequest = requestAnimationFrame(this.render); + this.renderer.render(this.scene, this.camera); + } + + private resize() { + this.camera.right = this.container.offsetWidth / this.container.offsetHeight; + this.camera.updateProjectionMatrix(); + this.renderer.setSize(this.container.offsetWidth, this.container.offsetHeight); + this.boundingBox.max.set(this.camera.right - this.coinRadius, 1 - this.coinRadius); + } + + private attachEvents() { + ['load', 'resize', 'orientationchange'].forEach(event => { + window.addEventListener(event, this.resize); + }); + } + + private detachEvents() { + ['load', 'resize', 'orientationchange'].forEach(event => { + window.removeEventListener(event, this.resize); + }); + } + + private destroyRenderer() { + this.stopRendering(); + this.renderer.domElement.remove(); + this.renderer.dispose(); + } + + public cleanup() { + this.detachEvents(); + this.destroyRenderer(); + } +} + +export const Background = memo(function Background() { + const containerRef = React.useRef(null); + + useEffect(() => { + const coins = new Coins(containerRef.current!); + coins.startRendering(); + return () => coins.cleanup(); + }, []); + + return ; +}); diff --git a/src/components/Home/Hero/Hero.tsx b/src/components/Home/Hero/Hero.tsx index e8dc095..e2a5adf 100644 --- a/src/components/Home/Hero/Hero.tsx +++ b/src/components/Home/Hero/Hero.tsx @@ -1,14 +1,11 @@ import React, { memo } from 'react'; import styled from '@emotion/styled'; import { FluidInner } from '../../Common/Inner'; -import bottomLeft from '../../../images/hero-bottom-left.png'; -import bottomRight from '../../../images/hero-bottom-right.png'; -import topLeft from '../../../images/hero-top-left.png'; -import topRight from '../../../images/hero-top-right.png'; import { theme } from '../../../theme'; import { PrimaryExternalLink, SecondaryExternalLink } from '../../Common/Buttons'; import { useAppUrl } from '../../../utils/react-utils'; import { useChainCount } from '../../../data/queries/total-chains'; +import { Background } from './Background'; // Background images const w = 390; @@ -16,27 +13,14 @@ const h = 320; const r = h / w; const CustomInner = styled(FluidInner)` + position: relative; display: flex; align-items: center; padding: ${40 + 24 * 2}px 0px; - background-image: url(${topLeft}), url(${topRight}), url(${bottomLeft}), url(${bottomRight}); - background-repeat: no-repeat; - background-position: left 64px, right 64px, left bottom, right bottom; - background-size: ${(360 - 48) / 2}px ${((360 - 48) / 2) * r}px; min-height: 100vh; text-align: center; flex-direction: column; justify-content: center; - - @media (min-width: ${theme.breakpoints.sm}px) { - background-position: left 5%, right 5%, left bottom, right bottom; - background-size: ${(theme.breakpoints.sm - 48) / 2}px ${((theme.breakpoints.sm - 48) / 2) * r}px; - } - - @media (min-width: ${theme.breakpoints.lg}px) { - width: ${theme.containers.lg}px; - background-size: ${w}px ${h}px; - } `; const Title = styled.div` @@ -74,7 +58,8 @@ const Buttons = styled.div` justify-content: center; `; -const Container = styled.div` +const Centered = styled.div` + position: relative; display: flex; align-items: center; flex-direction: column; @@ -91,7 +76,8 @@ export const Hero = memo(function Hero() { return ( - + + Multichain Yield Optimizer Earn the highest APYs across {chains} chains with safety and efficiency in mind. @@ -104,7 +90,7 @@ export const Hero = memo(function Hero() { View Docs - + ); }); diff --git a/src/images/hero/coin-face-bump-map.png b/src/images/hero/coin-face-bump-map.png new file mode 100644 index 0000000..7481a3e Binary files /dev/null and b/src/images/hero/coin-face-bump-map.png differ diff --git a/src/images/hero/coin-side-bump-map.png b/src/images/hero/coin-side-bump-map.png new file mode 100644 index 0000000..76e82e1 Binary files /dev/null and b/src/images/hero/coin-side-bump-map.png differ diff --git a/src/images/networks/arbitrum.svg b/src/images/networks/arbitrum.svg index a843636..607c4b1 100644 --- a/src/images/networks/arbitrum.svg +++ b/src/images/networks/arbitrum.svg @@ -1 +1,14 @@ - \ No newline at end of file + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/images/networks/aurora.svg b/src/images/networks/aurora.svg index 5d0cdb8..f787c03 100644 --- a/src/images/networks/aurora.svg +++ b/src/images/networks/aurora.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/src/images/networks/avax.svg b/src/images/networks/avax.svg index cced36f..38b1192 100644 --- a/src/images/networks/avax.svg +++ b/src/images/networks/avax.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/src/images/networks/base.svg b/src/images/networks/base.svg new file mode 100644 index 0000000..fa62005 --- /dev/null +++ b/src/images/networks/base.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/images/networks/bsc.svg b/src/images/networks/bsc.svg index 8f1dc72..0b3d003 100644 --- a/src/images/networks/bsc.svg +++ b/src/images/networks/bsc.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/src/images/networks/canto.svg b/src/images/networks/canto.svg index 107cd10..c4e14c1 100644 --- a/src/images/networks/canto.svg +++ b/src/images/networks/canto.svg @@ -1,5 +1,4 @@ - - + \ No newline at end of file diff --git a/src/images/networks/celo.svg b/src/images/networks/celo.svg index d9a900a..fe00d18 100644 --- a/src/images/networks/celo.svg +++ b/src/images/networks/celo.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/src/images/networks/cronos.svg b/src/images/networks/cronos.svg index 5772990..c5dc593 100644 --- a/src/images/networks/cronos.svg +++ b/src/images/networks/cronos.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/src/images/networks/emerald.svg b/src/images/networks/emerald.svg index 047bc09..99bfaa5 100644 --- a/src/images/networks/emerald.svg +++ b/src/images/networks/emerald.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/src/images/networks/ethereum.svg b/src/images/networks/ethereum.svg index 311e1e7..2789162 100644 --- a/src/images/networks/ethereum.svg +++ b/src/images/networks/ethereum.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/src/images/networks/fantom.svg b/src/images/networks/fantom.svg index e8bda3d..fc2d60c 100644 --- a/src/images/networks/fantom.svg +++ b/src/images/networks/fantom.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/src/images/networks/fuse.svg b/src/images/networks/fuse.svg index 523fbd7..a2fd1e0 100644 --- a/src/images/networks/fuse.svg +++ b/src/images/networks/fuse.svg @@ -1,4 +1,4 @@ - + diff --git a/src/images/networks/gnosis.svg b/src/images/networks/gnosis.svg new file mode 100644 index 0000000..3f6c466 --- /dev/null +++ b/src/images/networks/gnosis.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/images/networks/harmony.svg b/src/images/networks/harmony.svg index 8b80aae..f81909e 100644 --- a/src/images/networks/harmony.svg +++ b/src/images/networks/harmony.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/src/images/networks/heco.svg b/src/images/networks/heco.svg index 6fdcaaa..810327f 100644 --- a/src/images/networks/heco.svg +++ b/src/images/networks/heco.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/src/images/networks/kava.svg b/src/images/networks/kava.svg index 3bb3364..4ae7438 100644 --- a/src/images/networks/kava.svg +++ b/src/images/networks/kava.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/src/images/networks/metis.svg b/src/images/networks/metis.svg index caefe10..640b124 100644 --- a/src/images/networks/metis.svg +++ b/src/images/networks/metis.svg @@ -1,4 +1,4 @@ - + \ No newline at end of file diff --git a/src/images/networks/moonbeam.svg b/src/images/networks/moonbeam.svg index b31c84e..e38180e 100644 --- a/src/images/networks/moonbeam.svg +++ b/src/images/networks/moonbeam.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/src/images/networks/moonriver.svg b/src/images/networks/moonriver.svg index 167a848..539587d 100644 --- a/src/images/networks/moonriver.svg +++ b/src/images/networks/moonriver.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/src/images/networks/optimism.svg b/src/images/networks/optimism.svg index d3cafba..cbb1a40 100644 --- a/src/images/networks/optimism.svg +++ b/src/images/networks/optimism.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/src/images/networks/polygon.svg b/src/images/networks/polygon.svg index 7cf2d69..1008cc8 100644 --- a/src/images/networks/polygon.svg +++ b/src/images/networks/polygon.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/src/images/networks/zkevm.svg b/src/images/networks/zkevm.svg index a9ccf26..c19ec96 100644 --- a/src/images/networks/zkevm.svg +++ b/src/images/networks/zkevm.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/src/images/networks/zksync.svg b/src/images/networks/zksync.svg index 6cf65bf..61d60c2 100644 --- a/src/images/networks/zksync.svg +++ b/src/images/networks/zksync.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/src/theme.ts b/src/theme.ts index 64c2005..2e11639 100644 --- a/src/theme.ts +++ b/src/theme.ts @@ -90,10 +90,10 @@ class Theme { fantom: '#1969FF', harmony: '#01d8af', arbitrum: '#2d374b', - celo: '#35cf7f', + celo: '#FCFF52', moonriver: '#c3136f', cronos: '#121926', - fuse: '#c0db64', + fuse: '#B4F9BA', metis: '#00dacc', aurora: '#70d44b', moonbeam: '#211438', @@ -104,6 +104,8 @@ class Theme { canto: '#06fc99', zksync: '#fff', zkevm: '#8247e4', + base: '#fff', + gnosis: '#133629', }; public spacing(times: number = 1) { diff --git a/src/utils/network-utils.ts b/src/utils/network-utils.ts new file mode 100644 index 0000000..9f12a07 --- /dev/null +++ b/src/utils/network-utils.ts @@ -0,0 +1,12 @@ +const networkIconRequire = require.context('../images/networks', false, /\.svg$/); +const networkToFile = Object.fromEntries( + networkIconRequire.keys().map(path => [path.substring(2, path.lastIndexOf('.')), path]) +); + +export function getNetworkIconSrc(chainId: string) { + if (chainId in networkToFile) { + return networkIconRequire(networkToFile[chainId]).default; + } + + throw new Error(`No network icon found for ${chainId}`); +} diff --git a/yarn.lock b/yarn.lock index 45fa9cb..f5ade3d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2995,6 +2995,21 @@ dependencies: "@types/node" "*" +"@types/stats.js@*": + version "0.17.3" + resolved "https://registry.yarnpkg.com/@types/stats.js/-/stats.js-0.17.3.tgz#705446e12ce0fad618557dd88236f51148b7a935" + integrity sha512-pXNfAD3KHOdif9EQXZ9deK82HVNaXP5ZIF5RP2QG6OQFNTaY2YIetfrE9t528vEreGQvEPRDDc8muaoYeK0SxQ== + +"@types/three@^0.158.3": + version "0.158.3" + resolved "https://registry.yarnpkg.com/@types/three/-/three-0.158.3.tgz#f53e26c50709c7f972934e4e8c1ca51add0cfa9f" + integrity sha512-6Qs1rUvLSbkJ4hlIe6/rdwIf61j1x2UKvGJg7s8KjswYsz1C1qDTs6voVXXB8kYaI0hgklgZgbZUupfL1l9xdA== + dependencies: + "@types/stats.js" "*" + "@types/webxr" "*" + fflate "~0.6.10" + meshoptimizer "~0.18.1" + "@types/tmp@^0.0.33": version "0.0.33" resolved "https://registry.yarnpkg.com/@types/tmp/-/tmp-0.0.33.tgz#1073c4bc824754ae3d10cfab88ab0237ba964e4d" @@ -3022,6 +3037,11 @@ dependencies: "@types/node" "*" +"@types/webxr@*": + version "0.5.10" + resolved "https://registry.yarnpkg.com/@types/webxr/-/webxr-0.5.10.tgz#19c76208ec015ca3f139505e14d94d6b740f518a" + integrity sha512-n3u5sqXQJhf1CS68mw3Wf16FQ4cRPNBBwdYLFzq3UddiADOim1Pn3Y6PBdDilz1vOJF3ybLxJ8ZEDlLIzrOQZg== + "@types/yoga-layout@1.9.2": version "1.9.2" resolved "https://registry.yarnpkg.com/@types/yoga-layout/-/yoga-layout-1.9.2.tgz#efaf9e991a7390dc081a0b679185979a83a9639a" @@ -5882,6 +5902,11 @@ fd@~0.0.2: resolved "https://registry.yarnpkg.com/fd/-/fd-0.0.3.tgz#b3240de86dbf5a345baae7382a07d4713566ff0c" integrity sha512-iAHrIslQb3U68OcMSP0kkNWabp7sSN6d2TBSb2JO3gcLJVDd4owr/hKM4SFJovFOUeeXeItjYgouEDTMWiVAnA== +fflate@~0.6.10: + version "0.6.10" + resolved "https://registry.yarnpkg.com/fflate/-/fflate-0.6.10.tgz#5f40f9659205936a2d18abf88b2e7781662b6d43" + integrity sha512-IQrh3lEPM93wVCEczc9SaAOvkmcoQn/G8Bo1e8ZPlY3X3bnAxWaBdvTdvM1hP62iZp0BXWDy4vTAy4fF0+Dlpg== + figures@^3.0.0: version "3.2.0" resolved "https://registry.yarnpkg.com/figures/-/figures-3.2.0.tgz#625c18bd293c604dc4a8ddb2febf0c88341746af" @@ -8608,6 +8633,11 @@ meros@1.1.4: resolved "https://registry.yarnpkg.com/meros/-/meros-1.1.4.tgz#c17994d3133db8b23807f62bec7f0cb276cfd948" integrity sha512-E9ZXfK9iQfG9s73ars9qvvvbSIkJZF5yOo9j4tcwM5tN8mUKfj/EKN5PzOr3ZH0y5wL7dLAHw3RVEfpQV9Q7VQ== +meshoptimizer@~0.18.1: + version "0.18.1" + resolved "https://registry.yarnpkg.com/meshoptimizer/-/meshoptimizer-0.18.1.tgz#cdb90907f30a7b5b1190facd3b7ee6b7087797d8" + integrity sha512-ZhoIoL7TNV4s5B6+rx5mC//fw8/POGyNxS/DZyCJeiZ12ScLfVwRE/GfsxwiTkMYYD5DmK2/JXnEVXqL4rF+Sw== + methods@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" @@ -11508,6 +11538,11 @@ text-table@^0.2.0: resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" integrity sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw== +three@^0.158.0: + version "0.158.0" + resolved "https://registry.yarnpkg.com/three/-/three-0.158.0.tgz#03ddd5b60ce9c31be8fb69f27a6d9efd49908ac3" + integrity sha512-TALj4EOpdDPF1henk2Q+s17K61uEAAWQ7TJB68nr7FKxqwyDr3msOt5IWdbGm4TaWKjrtWS8DJJWe9JnvsWOhQ== + through@^2.3.6, through@^2.3.8: version "2.3.8" resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5"