added loading screen

This commit is contained in:
2026-09-15 06:21:03 -04:00
parent c050d25a84
commit 8b527e062f
7 changed files with 477 additions and 4 deletions
+139
View File
@@ -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;
}
}
+116
View File
@@ -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
View File
@@ -2,6 +2,7 @@ import React from "react";
import { createRoot } from "react-dom/client";
import { BrowserRouter, Route, Routes } from "react-router-dom";
import Layout from "./components/Layout";
import Loader from "./components/Loader";
import { Action } from "./components/Primitives";
import Home from "./pages/Home";
import Fabrics from "./pages/Fabrics";
@@ -24,6 +25,7 @@ function NotFound() {
createRoot(document.getElementById("root")).render(
<React.StrictMode>
<Loader />
<BrowserRouter>
<Routes>
<Route element={<Layout />}>
+46
View File
@@ -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);
}