added loading screen
This commit is contained in:
Generated
+162
-3
@@ -10,7 +10,8 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"react": "^19.1.0",
|
"react": "^19.1.0",
|
||||||
"react-dom": "^19.1.0",
|
"react-dom": "^19.1.0",
|
||||||
"react-router-dom": "^7.6.0"
|
"react-router-dom": "^7.6.0",
|
||||||
|
"vgpu": "^0.5.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@playwright/test": "^1.52.0",
|
"@playwright/test": "^1.52.0",
|
||||||
@@ -792,6 +793,31 @@
|
|||||||
"@jridgewell/sourcemap-codec": "^1.4.14"
|
"@jridgewell/sourcemap-codec": "^1.4.14"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@modelcontextprotocol/core": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@modelcontextprotocol/core/-/core-2.0.0.tgz",
|
||||||
|
"integrity": "sha512-pJCEwGG7Lfr/+PQp9ZTwKXNeO5wzbfKL7H3MYpCorM4oFBoQrdjnBgEoqG+RjhsvS1FKrDbKux+M1HhlnGWqcA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"zod": "^4.2.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=20"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@modelcontextprotocol/server": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@modelcontextprotocol/server/-/server-2.0.0.tgz",
|
||||||
|
"integrity": "sha512-YhHWdHfpFMQfd0prsEnxKeS3Qz3ytIGmsS0sth4KDjnacIT7hxk6hXHkJ9KysxlkvTM+WZAtQbbcUhdoP4Hvtw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@modelcontextprotocol/core": "2.0.0",
|
||||||
|
"zod": "^4.2.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=20"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@napi-rs/lzma-linux-x64-gnu": {
|
"node_modules/@napi-rs/lzma-linux-x64-gnu": {
|
||||||
"version": "1.5.1",
|
"version": "1.5.1",
|
||||||
"resolved": "https://registry.npmjs.org/@napi-rs/lzma-linux-x64-gnu/-/lzma-linux-x64-gnu-1.5.1.tgz",
|
"resolved": "https://registry.npmjs.org/@napi-rs/lzma-linux-x64-gnu/-/lzma-linux-x64-gnu-1.5.1.tgz",
|
||||||
@@ -1276,6 +1302,58 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/@vgpu/adapter-mock": {
|
||||||
|
"version": "0.5.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@vgpu/adapter-mock/-/adapter-mock-0.5.0.tgz",
|
||||||
|
"integrity": "sha512-YtgXOFn1qI+8oTJenG2wWbQPA28V0CfE5UJs5Xqbt6qyR/xaZ5LHH2XWTwFcjnqn2b1xQCyTatJCMyyOdweUnw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@vgpu/core": "0.5.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@vgpu/adapter-node": {
|
||||||
|
"version": "0.5.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@vgpu/adapter-node/-/adapter-node-0.5.0.tgz",
|
||||||
|
"integrity": "sha512-lgzqFcLDejwRgwlaNStzkwLNU9b4aN2OoPpWqT1UyPmK521BSw2OIQKzAuDIkzNlREla6KNC12G6bthhOzZKzw==",
|
||||||
|
"hasInstallScript": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@vgpu/core": "0.5.0",
|
||||||
|
"webgpu": "0.4.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@vgpu/core": {
|
||||||
|
"version": "0.5.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@vgpu/core/-/core-0.5.0.tgz",
|
||||||
|
"integrity": "sha512-z80DHRHViv6+FKKkPzggEtT1ZZs/WCKRQbzJuwYdA135DZaSbCMoRK4anhM+I/nOHuDpf7H3xjkwE6rfolWZUw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@vgpu/wgsl": "0.5.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@vgpu/wgsl": {
|
||||||
|
"version": "0.5.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@vgpu/wgsl/-/wgsl-0.5.0.tgz",
|
||||||
|
"integrity": "sha512-PDlDJem7J28C9bk+vPy2kYZEoW8+nYIx2oN4nu7/WF2V7ETx32BtmWcawwMbhiu/CVsobFafML0h0eqLRBNa3Q==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@vgpu/wgsl-std": "0.5.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@vgpu/adapter-node": ">=0.1.6 <1.0.0 || ^0.5.0-rc.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@vgpu/adapter-node": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@vgpu/wgsl-std": {
|
||||||
|
"version": "0.5.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@vgpu/wgsl-std/-/wgsl-std-0.5.0.tgz",
|
||||||
|
"integrity": "sha512-NjMyJxwy4evFNCSqFQy4EqAbZ7iU+NUyJYyG9tLUhbdDLzBmfZlk1S3PyRZ9olBFFfEiVW2JNVw10maPKLEs1A==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/@vitejs/plugin-react": {
|
"node_modules/@vitejs/plugin-react": {
|
||||||
"version": "4.7.0",
|
"version": "4.7.0",
|
||||||
"resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.7.0.tgz",
|
"resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.7.0.tgz",
|
||||||
@@ -1297,6 +1375,12 @@
|
|||||||
"vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0"
|
"vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@webgpu/types": {
|
||||||
|
"version": "0.1.72",
|
||||||
|
"resolved": "https://registry.npmjs.org/@webgpu/types/-/types-0.1.72.tgz",
|
||||||
|
"integrity": "sha512-0cF7RFM2edNoiIS1ODJp0/Gzv4/xSXhwoR0YCza+OWpJWtn4wmo9DvK91aLlH9+uUnwIriP7ZiC3WitmyhuzBw==",
|
||||||
|
"license": "BSD-3-Clause"
|
||||||
|
},
|
||||||
"node_modules/baseline-browser-mapping": {
|
"node_modules/baseline-browser-mapping": {
|
||||||
"version": "2.11.21",
|
"version": "2.11.21",
|
||||||
"resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.11.21.tgz",
|
"resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.11.21.tgz",
|
||||||
@@ -1389,7 +1473,6 @@
|
|||||||
"version": "4.4.3",
|
"version": "4.4.3",
|
||||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
|
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
|
||||||
"integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
|
"integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"ms": "^2.1.3"
|
"ms": "^2.1.3"
|
||||||
@@ -1552,7 +1635,6 @@
|
|||||||
"version": "2.1.3",
|
"version": "2.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
||||||
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
|
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/nanoid": {
|
"node_modules/nanoid": {
|
||||||
@@ -1604,6 +1686,18 @@
|
|||||||
"url": "https://github.com/sponsors/jonschlinkert"
|
"url": "https://github.com/sponsors/jonschlinkert"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/pixelmatch": {
|
||||||
|
"version": "7.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/pixelmatch/-/pixelmatch-7.2.0.tgz",
|
||||||
|
"integrity": "sha512-xhcb4yHu9sM/G7foGzoLtXYcC0zHEaOXXjRKhGup0fw78Nf2Tkiapv4EQyMzrbcmQPsllAI7DbFY2UT7PlI9Pg==",
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"pngjs": "^7.0.0"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"pixelmatch": "bin/pixelmatch"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/playwright": {
|
"node_modules/playwright": {
|
||||||
"version": "1.63.0",
|
"version": "1.63.0",
|
||||||
"resolved": "https://registry.npmjs.org/playwright/-/playwright-1.63.0.tgz",
|
"resolved": "https://registry.npmjs.org/playwright/-/playwright-1.63.0.tgz",
|
||||||
@@ -1633,6 +1727,15 @@
|
|||||||
"node": ">=20"
|
"node": ">=20"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/pngjs": {
|
||||||
|
"version": "7.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/pngjs/-/pngjs-7.0.0.tgz",
|
||||||
|
"integrity": "sha512-LKWqWJRhstyYo9pGvgor/ivk2w94eSjE3RGVuzLGlr3NmD8bf7RcYGze1mNdEHRP6TRP6rMuDHk5t44hnTRyow==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14.19.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/postcss": {
|
"node_modules/postcss": {
|
||||||
"version": "8.5.28",
|
"version": "8.5.28",
|
||||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.28.tgz",
|
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.28.tgz",
|
||||||
@@ -1857,6 +1960,36 @@
|
|||||||
"browserslist": ">= 4.21.0"
|
"browserslist": ">= 4.21.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/vgpu": {
|
||||||
|
"version": "0.5.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/vgpu/-/vgpu-0.5.0.tgz",
|
||||||
|
"integrity": "sha512-isQfMHRTysYFfT8UWTzqQI/ROw2L7lVkj7xoUJlvIF6FjYxBNS/YNG7qp3tZ7ZvZZ3uwYjso+geQv/bEq5Hl3w==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@modelcontextprotocol/server": "2.0.0",
|
||||||
|
"@vgpu/adapter-mock": "0.5.0",
|
||||||
|
"@vgpu/adapter-node": "0.5.0",
|
||||||
|
"@vgpu/core": "0.5.0",
|
||||||
|
"@vgpu/wgsl": "0.5.0",
|
||||||
|
"@vgpu/wgsl-std": "0.5.0",
|
||||||
|
"@webgpu/types": "^0.1.69",
|
||||||
|
"pixelmatch": "^7.2.0",
|
||||||
|
"pngjs": "^7.0.0",
|
||||||
|
"wgpu-matrix": "^3.4.2",
|
||||||
|
"zod": "^4.4.3"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"vgpu": "bin/vgpu.js"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"three": ">=0.180.0 <0.200.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"three": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/vite": {
|
"node_modules/vite": {
|
||||||
"version": "6.4.3",
|
"version": "6.4.3",
|
||||||
"resolved": "https://registry.npmjs.org/vite/-/vite-6.4.3.tgz",
|
"resolved": "https://registry.npmjs.org/vite/-/vite-6.4.3.tgz",
|
||||||
@@ -1932,12 +2065,38 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/webgpu": {
|
||||||
|
"version": "0.4.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/webgpu/-/webgpu-0.4.0.tgz",
|
||||||
|
"integrity": "sha512-F5pimn3Aoi0zWjuRdiVs5TnrUwSzD2lESBohsIUsqyitWkGRQlXU2fhV6ycXlQTa1bvAf3sjqiUpBEpmSQ5ptA==",
|
||||||
|
"hasInstallScript": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@webgpu/types": "^0.1.69",
|
||||||
|
"debug": "^4.4.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/wgpu-matrix": {
|
||||||
|
"version": "3.4.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/wgpu-matrix/-/wgpu-matrix-3.4.2.tgz",
|
||||||
|
"integrity": "sha512-IfZFbG7olEYBD76VCZy2TzqM57GKy3LT++AZh37tMHPPByO6R2h61zXGBZFNPi2IzxNivNi8y5dvze5PjaczjA==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/yallist": {
|
"node_modules/yallist": {
|
||||||
"version": "3.1.1",
|
"version": "3.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
|
||||||
"integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==",
|
"integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "ISC"
|
"license": "ISC"
|
||||||
|
},
|
||||||
|
"node_modules/zod": {
|
||||||
|
"version": "4.6.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/zod/-/zod-4.6.5.tgz",
|
||||||
|
"integrity": "sha512-v5l/aFXZQeai4awLbOpSoHecE9UiMrnfx75tEXLjNonXVARxQ5mOeipTjROUchszUNCqnE+hqAMujRsRHsut2Q==",
|
||||||
|
"license": "MIT",
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/colinhacks"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-1
@@ -12,7 +12,8 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"react": "^19.1.0",
|
"react": "^19.1.0",
|
||||||
"react-dom": "^19.1.0",
|
"react-dom": "^19.1.0",
|
||||||
"react-router-dom": "^7.6.0"
|
"react-router-dom": "^7.6.0",
|
||||||
|
"vgpu": "^0.5.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@playwright/test": "^1.52.0",
|
"@playwright/test": "^1.52.0",
|
||||||
|
|||||||
@@ -0,0 +1,139 @@
|
|||||||
|
.loader {
|
||||||
|
position: fixed;
|
||||||
|
inset: 0;
|
||||||
|
z-index: 1000;
|
||||||
|
display: grid;
|
||||||
|
grid-template-rows: auto 1fr auto;
|
||||||
|
overflow: hidden;
|
||||||
|
padding: clamp(24px, 3.5vw, 56px);
|
||||||
|
color: #f5f3ee;
|
||||||
|
background: #24231e;
|
||||||
|
transition:
|
||||||
|
opacity 650ms cubic-bezier(0.76, 0, 0.24, 1),
|
||||||
|
clip-path 650ms cubic-bezier(0.76, 0, 0.24, 1);
|
||||||
|
clip-path: inset(0);
|
||||||
|
}
|
||||||
|
.loader--closing {
|
||||||
|
opacity: 0;
|
||||||
|
clip-path: inset(0 0 100% 0);
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
.loader__canvas,
|
||||||
|
.loader__veil {
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
.loader__canvas {
|
||||||
|
display: block;
|
||||||
|
opacity: 0;
|
||||||
|
transition: opacity 180ms ease-out;
|
||||||
|
}
|
||||||
|
.loader__canvas[data-ready="true"] {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
.loader__veil {
|
||||||
|
background: linear-gradient(90deg, #17171238, transparent 45%, #17171238);
|
||||||
|
}
|
||||||
|
.loader__topline,
|
||||||
|
.loader__footer,
|
||||||
|
.loader__center {
|
||||||
|
position: relative;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
.loader__topline,
|
||||||
|
.loader__footer {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 18px;
|
||||||
|
font-size: 10px;
|
||||||
|
font-weight: 600;
|
||||||
|
letter-spacing: 0.16em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
.loader__center {
|
||||||
|
align-self: center;
|
||||||
|
justify-self: center;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.loader__eyebrow {
|
||||||
|
margin: 0 0 18px;
|
||||||
|
font-size: 11px;
|
||||||
|
letter-spacing: 0.14em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
.loader__title {
|
||||||
|
margin: 0;
|
||||||
|
font-family: var(--serif);
|
||||||
|
font-size: clamp(58px, 8.5vw, 132px);
|
||||||
|
font-weight: 400;
|
||||||
|
line-height: 0.84;
|
||||||
|
letter-spacing: -0.055em;
|
||||||
|
}
|
||||||
|
.loader__title em {
|
||||||
|
font-weight: 400;
|
||||||
|
}
|
||||||
|
.loader__footer {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: auto 1fr auto;
|
||||||
|
}
|
||||||
|
.loader__track {
|
||||||
|
height: 1px;
|
||||||
|
overflow: hidden;
|
||||||
|
background: #ffffff38;
|
||||||
|
}
|
||||||
|
.loader__progress {
|
||||||
|
position: relative;
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background: #f5f3ee;
|
||||||
|
transform: translateX(-100%);
|
||||||
|
animation: loader-progress 1.9s linear forwards;
|
||||||
|
}
|
||||||
|
.loader__progress::after {
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
right: 0;
|
||||||
|
width: 7px;
|
||||||
|
height: 7px;
|
||||||
|
border: 1px solid #f5f3ee;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: #29271f;
|
||||||
|
content: "";
|
||||||
|
transform: translate(50%, -50%);
|
||||||
|
}
|
||||||
|
.loader__announcement {
|
||||||
|
position: absolute;
|
||||||
|
width: 1px;
|
||||||
|
height: 1px;
|
||||||
|
overflow: hidden;
|
||||||
|
clip-path: inset(50%);
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
@keyframes loader-progress {
|
||||||
|
to {
|
||||||
|
transform: translateX(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@media (max-width: 600px) {
|
||||||
|
.loader__title {
|
||||||
|
font-size: clamp(54px, 19vw, 86px);
|
||||||
|
}
|
||||||
|
.loader__topline span:last-child {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@media (prefers-reduced-motion: reduce) {
|
||||||
|
.loader {
|
||||||
|
transition: none;
|
||||||
|
}
|
||||||
|
.loader__progress {
|
||||||
|
animation: none;
|
||||||
|
}
|
||||||
|
.loader__progress {
|
||||||
|
transform: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,116 @@
|
|||||||
|
import { useEffect, useRef, useState } from "react";
|
||||||
|
import weaveShader from "../shaders/loader.wgsl?raw";
|
||||||
|
import "./Loader.css";
|
||||||
|
|
||||||
|
export default function Loader() {
|
||||||
|
const canvasRef = useRef(null);
|
||||||
|
const [phase, setPhase] = useState("active");
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const reducedMotion = window.matchMedia(
|
||||||
|
"(prefers-reduced-motion: reduce)",
|
||||||
|
).matches;
|
||||||
|
const closeTimer = window.setTimeout(
|
||||||
|
() => setPhase("closing"),
|
||||||
|
reducedMotion ? 0 : 1900,
|
||||||
|
);
|
||||||
|
const removeTimer = window.setTimeout(
|
||||||
|
() => setPhase("hidden"),
|
||||||
|
reducedMotion ? 50 : 2550,
|
||||||
|
);
|
||||||
|
return () => {
|
||||||
|
window.clearTimeout(closeTimer);
|
||||||
|
window.clearTimeout(removeTimer);
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (phase !== "active") return undefined;
|
||||||
|
let active = true;
|
||||||
|
let gpu;
|
||||||
|
let loop;
|
||||||
|
let unsubscribeResize;
|
||||||
|
|
||||||
|
async function renderWeave() {
|
||||||
|
try {
|
||||||
|
const { clock, effect, frameLoop, init, surface } = await import(
|
||||||
|
"vgpu"
|
||||||
|
);
|
||||||
|
gpu = await init({ powerPreference: "low-power" });
|
||||||
|
if (!active) return gpu.dispose();
|
||||||
|
|
||||||
|
const target = surface(gpu, canvasRef.current, { dpr: [1, 1.5] });
|
||||||
|
const weave = effect(gpu, weaveShader, {
|
||||||
|
set: { params: { time: 0, progress: 0, aspect: 1, padding: 0 } },
|
||||||
|
});
|
||||||
|
const timer = clock(gpu);
|
||||||
|
unsubscribeResize = target.onResize(({ width, height }) => {
|
||||||
|
weave.set({ params: { aspect: width / height } });
|
||||||
|
});
|
||||||
|
let presented = false;
|
||||||
|
loop = frameLoop(
|
||||||
|
gpu,
|
||||||
|
(frame) => {
|
||||||
|
weave.set({
|
||||||
|
params: {
|
||||||
|
time: timer.time,
|
||||||
|
progress: Math.min(1.2, timer.time / 1.8),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
frame.pass(target, weave);
|
||||||
|
if (!presented) {
|
||||||
|
presented = true;
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
if (active) canvasRef.current.dataset.ready = "true";
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ fps: 30 },
|
||||||
|
);
|
||||||
|
} catch {
|
||||||
|
// A flat background remains when WebGPU is unavailable.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!window.matchMedia("(prefers-reduced-motion: reduce)").matches)
|
||||||
|
renderWeave();
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
active = false;
|
||||||
|
loop?.stop();
|
||||||
|
unsubscribeResize?.();
|
||||||
|
gpu?.dispose();
|
||||||
|
};
|
||||||
|
}, [phase]);
|
||||||
|
|
||||||
|
if (phase === "hidden") return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={`loader loader--${phase}`} aria-live="polite">
|
||||||
|
<canvas className="loader__canvas" ref={canvasRef} aria-hidden="true" />
|
||||||
|
<div className="loader__veil" aria-hidden="true" />
|
||||||
|
<div className="loader__topline">
|
||||||
|
<span>RRS / 1987</span>
|
||||||
|
<span>Surat, India</span>
|
||||||
|
</div>
|
||||||
|
<div className="loader__center">
|
||||||
|
<p className="loader__eyebrow">Preparing the loom</p>
|
||||||
|
<p className="loader__title">
|
||||||
|
From yarn
|
||||||
|
<br />
|
||||||
|
<em>to structure.</em>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div className="loader__footer">
|
||||||
|
<span>Warp</span>
|
||||||
|
<span className="loader__track" aria-hidden="true">
|
||||||
|
<span className="loader__progress" />
|
||||||
|
</span>
|
||||||
|
<span>Weft</span>
|
||||||
|
</div>
|
||||||
|
<span className="loader__announcement">
|
||||||
|
Loading Radhe Radhe Synthetics
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -2,6 +2,7 @@ import React from "react";
|
|||||||
import { createRoot } from "react-dom/client";
|
import { createRoot } from "react-dom/client";
|
||||||
import { BrowserRouter, Route, Routes } from "react-router-dom";
|
import { BrowserRouter, Route, Routes } from "react-router-dom";
|
||||||
import Layout from "./components/Layout";
|
import Layout from "./components/Layout";
|
||||||
|
import Loader from "./components/Loader";
|
||||||
import { Action } from "./components/Primitives";
|
import { Action } from "./components/Primitives";
|
||||||
import Home from "./pages/Home";
|
import Home from "./pages/Home";
|
||||||
import Fabrics from "./pages/Fabrics";
|
import Fabrics from "./pages/Fabrics";
|
||||||
@@ -24,6 +25,7 @@ function NotFound() {
|
|||||||
|
|
||||||
createRoot(document.getElementById("root")).render(
|
createRoot(document.getElementById("root")).render(
|
||||||
<React.StrictMode>
|
<React.StrictMode>
|
||||||
|
<Loader />
|
||||||
<BrowserRouter>
|
<BrowserRouter>
|
||||||
<Routes>
|
<Routes>
|
||||||
<Route element={<Layout />}>
|
<Route element={<Layout />}>
|
||||||
|
|||||||
@@ -0,0 +1,46 @@
|
|||||||
|
struct Params {
|
||||||
|
time: f32,
|
||||||
|
progress: f32,
|
||||||
|
aspect: f32,
|
||||||
|
padding: f32,
|
||||||
|
}
|
||||||
|
|
||||||
|
@group(0) @binding(0) var<uniform> params: Params;
|
||||||
|
|
||||||
|
fn thread(value: f32, width: f32) -> f32 {
|
||||||
|
return pow(0.5 + 0.5 * cos(value), width);
|
||||||
|
}
|
||||||
|
|
||||||
|
@fragment
|
||||||
|
fn fs_main(@location(0) uv: vec2f) -> @location(0) vec4f {
|
||||||
|
let p = vec2f(uv.x * params.aspect, uv.y);
|
||||||
|
let drift = sin(p.y * 18.0 + params.time * 0.42) * 0.018;
|
||||||
|
let warp = thread((p.x + drift) * 310.0, 7.0);
|
||||||
|
let weft = thread((p.y + sin(p.x * 13.0) * 0.006) * 280.0, 8.0);
|
||||||
|
let crossing = step(0.5, fract(floor(p.x * 49.0) * 0.5 + floor(p.y * 46.0) * 0.5));
|
||||||
|
let overUnder = mix(warp * 0.58 + weft, warp + weft * 0.55, crossing);
|
||||||
|
let irregularity = sin(p.x * 127.0 + sin(p.y * 31.0)) * 0.025;
|
||||||
|
|
||||||
|
let revealEdge = params.progress + sin(uv.y * 9.0 + params.time * 0.55) * 0.035;
|
||||||
|
let woven = 1.0 - smoothstep(revealEdge - 0.15, revealEdge + 0.04, uv.x);
|
||||||
|
let looseYarn = thread((p.x + sin(p.y * 8.0 + params.time) * 0.035) * 74.0, 10.0);
|
||||||
|
let wovenMask = smoothstep(0.16, 0.72, overUnder + irregularity);
|
||||||
|
let looseMask = smoothstep(0.24, 0.78, looseYarn);
|
||||||
|
let threadMask = mix(looseMask * 0.72, wovenMask, woven);
|
||||||
|
|
||||||
|
let slider = smoothstep(0.0, 1.0, params.progress);
|
||||||
|
let dyed = 1.0 - smoothstep(slider - 0.025, slider + 0.025, uv.x);
|
||||||
|
let greige = vec3f(0.69, 0.62, 0.51);
|
||||||
|
let madder = vec3f(0.72, 0.24, 0.12);
|
||||||
|
let indigo = vec3f(0.08, 0.34, 0.58);
|
||||||
|
let dyeColor = mix(madder, indigo, smoothstep(0.15, 0.9, uv.x));
|
||||||
|
let threadColor = mix(greige, dyeColor, dyed);
|
||||||
|
let shadow = vec3f(0.105, 0.10, 0.082);
|
||||||
|
let highlight = mix(vec3f(0.90, 0.84, 0.72), dyeColor * 1.35, dyed);
|
||||||
|
var color = mix(shadow, threadColor, threadMask);
|
||||||
|
color += highlight * pow(max(warp, weft), 10.0) * 0.14 * woven;
|
||||||
|
|
||||||
|
let vignette = smoothstep(0.82, 0.28, distance(uv, vec2f(0.5)));
|
||||||
|
color *= 0.7 + vignette * 0.3;
|
||||||
|
return vec4f(color, 1.0);
|
||||||
|
}
|
||||||
@@ -255,6 +255,16 @@ test("section unveiling respects reduced-motion preferences", async ({
|
|||||||
).toBe(0);
|
).toBe(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("the woven loading page introduces the site and clears", async ({
|
||||||
|
page,
|
||||||
|
}) => {
|
||||||
|
await page.goto("/");
|
||||||
|
const loader = page.locator(".loader");
|
||||||
|
await expect(loader).toContainText("Loading Radhe Radhe Synthetics");
|
||||||
|
await expect(page.locator(".loader__canvas")).toHaveCount(1);
|
||||||
|
await expect(loader).toHaveCount(0, { timeout: 3500 });
|
||||||
|
});
|
||||||
|
|
||||||
test("unknown route provides a way home", async ({ page }) => {
|
test("unknown route provides a way home", async ({ page }) => {
|
||||||
await page.goto("/missing-page");
|
await page.goto("/missing-page");
|
||||||
await expect(page.getByRole("heading", { level: 1 })).toHaveText(
|
await expect(page.getByRole("heading", { level: 1 })).toHaveText(
|
||||||
|
|||||||
Reference in New Issue
Block a user