This commit is contained in:
2026-10-05 13:52:45 -04:00
parent 9eb71e9855
commit e90ebde399
34 changed files with 465 additions and 581 deletions
+14 -15
View File
@@ -1,21 +1,14 @@
@font-face {
font-family: "Instrument Serif";
src: url("/fonts/instrument-serif-regular.ttf") format("truetype");
font-weight: 400;
font-family: "Roboto Condensed";
src: url("/fonts/roboto-condensed.woff2") format("woff2");
font-weight: 100 900;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: "Instrument Serif";
src: url("/fonts/instrument-serif-italic.ttf") format("truetype");
font-weight: 400;
font-style: italic;
font-display: swap;
}
@font-face {
font-family: "DM Sans";
src: url("/fonts/dm-sans.woff2") format("woff2");
font-weight: 100 1000;
font-family: "Noto Sans";
src: url("/fonts/noto-sans.woff2") format("woff2");
font-weight: 100 900;
font-style: normal;
font-display: swap;
}
@@ -26,8 +19,10 @@
--line: #d9d9cf;
--wash: #ecebe4;
--accent: #8f725c;
--serif: "Instrument Serif", Georgia, serif;
--sans: "DM Sans", Arial, sans-serif;
--industrial: #496678;
--industrial-deep: #273f4d;
--serif: "Roboto Condensed", "Arial Narrow", sans-serif;
--sans: "Noto Sans", Arial, sans-serif;
--mono: "Courier New", monospace;
--page-gutter: clamp(20px, 4vw, 72px);
color-scheme: light;
@@ -48,6 +43,10 @@ body {
margin: 0;
min-width: 320px;
}
em {
font-style: normal;
font-weight: 600;
}
button,
input,
textarea,
+7 -5
View File
@@ -10,13 +10,15 @@ export default function Gallery({ preview = false }) {
<section className="gallery" id="developments" tabIndex={-1}>
<SectionHeading
centered
label="Selected developments"
label={
preview ? "Featured fabric developments" : "Selected developments"
}
title={
preview ? (
<>
Structure changes
What we make.
<br />
<em>everything.</em>
<em>What we can develop.</em>
</>
) : (
<>
@@ -29,12 +31,12 @@ export default function Gallery({ preview = false }) {
>
<p className="gallery__intro">
{preview
? "From subtle surface structures to more expressive knitted textures, different yarn combinations and machine setups produce entirely different fabric characteristics."
? "A selection of knitted structures showing how yarn, composition and machine setup shape the final fabric character."
: "Selected developments are processed separately as reference samples to explore how a construction can translate after dyeing and finishing. RRS commercially manufactures greige fabric only."}
</p>
{preview && (
<Action to="/fabrics#developments" variant="outline">
View developments
Explore all fabrics
</Action>
)}
</SectionHeading>
+13 -30
View File
@@ -31,7 +31,9 @@
top: 10px;
}
.site-header {
border-top: 4px solid var(--industrial);
border-bottom: 1px solid var(--line);
background: #faf9f5;
}
.site-header__inner {
display: flex;
@@ -44,27 +46,11 @@
.brand {
display: inline-flex;
align-items: center;
gap: 16px;
text-decoration: none;
color: var(--ink);
}
.brand__mark {
font-family: var(--serif);
font-size: 42px;
font-weight: 600;
line-height: 1;
letter-spacing: -0.065em;
}
.brand__period {
color: var(--accent);
}
.brand__name {
border-left: 1px solid var(--line);
padding-left: 16px;
font-size: 9px;
line-height: 1.65;
text-transform: uppercase;
letter-spacing: 0.12em;
.brand__logo {
display: block;
width: 92px;
height: auto;
}
.site-nav {
display: flex;
@@ -100,15 +86,15 @@
gap: 30px;
margin-left: 45px;
padding: 12px 20px;
border: 1px solid var(--ink);
border: 1px solid var(--industrial-deep);
border-radius: 30px;
color: var(--ink);
background: var(--industrial-deep);
color: var(--paper);
text-decoration: none;
font-size: 11px;
}
.site-nav__contact:hover {
background: var(--ink);
color: var(--paper);
background: var(--industrial);
}
.site-header__toggle {
display: none;
@@ -209,7 +195,7 @@
display: flex;
align-items: center;
min-height: 420px;
background: var(--ink);
background: var(--industrial-deep);
color: var(--paper);
padding: 64px;
border-radius: 6px;
@@ -282,11 +268,8 @@
flex-wrap: wrap;
gap: 0;
}
.brand__mark {
font-size: 35px;
}
.brand__name {
font-size: 8px;
.brand__logo {
width: 78px;
}
.site-header__toggle {
display: flex;
+9 -10
View File
@@ -7,14 +7,13 @@ import "./Layout.css";
function Brand() {
return (
<Link className="brand" to="/" aria-label="RRS home">
<span className="brand__mark">
RRS<span className="brand__period">.</span>
</span>
<span className="brand__name">
Radhe Radhe
<br />
Synthetics
</span>
<img
className="brand__logo"
src="/images/RRSLOGO.png"
alt="Radhe Radhe Synthetics"
width="1024"
height="809"
/>
</Link>
);
}
@@ -68,7 +67,7 @@ function Header() {
to="/contact#whatsapp"
onClick={closeMenu}
>
Let's talk <Arrow diagonal />
Request a quote <Arrow diagonal />
</Link>
</nav>
</div>
@@ -93,7 +92,7 @@ function Footer() {
))}
</nav>
<div className="site-footer__contact">
<span className="site-footer__label">Start a conversation</span>
<span className="site-footer__label">Commercial enquiries</span>
<Link className="site-footer__link" to="/contact#whatsapp">
WhatsApp <Arrow diagonal />
</Link>
+185 -62
View File
@@ -2,76 +2,124 @@
position: fixed;
inset: 0;
z-index: 1000;
display: grid;
grid-template-rows: auto 1fr;
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);
color: var(--ink);
background: var(--paper);
}
.loader--closing {
opacity: 0;
clip-path: inset(0 0 100% 0);
pointer-events: none;
}
.loader__canvas,
.loader__veil {
.loader__scene {
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__center {
position: relative;
.loader__yarns {
position: absolute;
z-index: 1;
top: 0;
left: 50%;
width: min(72vw, 920px);
height: 34%;
overflow: visible;
transform: translateX(-50%);
}
.loader__topline {
.loader__yarns path {
fill: none;
stroke: var(--industrial);
stroke-width: 5;
stroke-linecap: round;
vector-effect: non-scaling-stroke;
opacity: 0;
stroke-dasharray: 1200;
stroke-dashoffset: 1200;
animation: yarn-feed 780ms 480ms cubic-bezier(0.22, 1, 0.36, 1) forwards;
}
.loader__yarns path:nth-child(2),
.loader__yarns path:nth-child(6) {
stroke: var(--accent);
animation-delay: 580ms;
}
.loader__yarns path:nth-child(3),
.loader__yarns path:nth-child(5) {
animation-delay: 660ms;
}
.loader__yarns path:nth-child(4) {
stroke: var(--ink);
stroke-width: 6;
animation-delay: 540ms;
}
.loader__machine {
position: absolute;
z-index: 2;
inset: 5% 0 7%;
display: grid;
place-items: center;
clip-path: inset(0 0 100%);
animation: machine-reveal 900ms 680ms cubic-bezier(0.22, 1, 0.36, 1) forwards;
}
.loader__machine-image {
display: block;
width: auto;
height: min(84vh, 1050px);
max-width: 84vw;
object-fit: contain;
filter: grayscale(1) contrast(1.08);
transform: scale(1.04);
animation: machine-colour 850ms 1050ms ease-out forwards;
}
.loader__brand {
position: absolute;
z-index: 4;
left: 50%;
bottom: clamp(18px, 2.2vw, 36px);
display: flex;
align-items: center;
justify-content: space-between;
gap: 18px;
font-size: 10px;
font-weight: 600;
letter-spacing: 0.16em;
text-transform: uppercase;
padding: 10px 18px;
border: 1px solid #282a2324;
border-radius: 999px;
background: #f5f3eee8;
backdrop-filter: blur(8px);
opacity: 0;
transform: translate(-50%, 14px);
animation: brand-arrive 450ms 1500ms ease-out forwards;
}
.loader__center {
align-self: center;
justify-self: center;
text-align: center;
.loader__logo {
width: 82px;
height: auto;
}
.loader__eyebrow {
margin: 0 0 18px;
font-size: 11px;
letter-spacing: 0.14em;
text-transform: uppercase;
}
.loader__title {
.loader__message {
margin: 0;
padding-left: 18px;
border-left: 1px solid var(--line);
font-family: var(--serif);
font-size: clamp(58px, 8.5vw, 132px);
font-weight: 400;
line-height: 0.84;
letter-spacing: -0.055em;
font-size: 18px;
line-height: 1.1;
white-space: nowrap;
}
.loader__title em {
font-weight: 400;
.loader__handoff {
position: absolute;
z-index: 10;
inset: 0;
background:
repeating-linear-gradient(115deg, transparent 0 8px, #f5f3ee13 8px 9px),
var(--industrial-deep);
transform: translateX(-101%);
will-change: transform;
}
.loader--covering {
pointer-events: none;
}
.loader--covering .loader__handoff {
animation: handoff-cover 450ms cubic-bezier(0.76, 0, 0.24, 1) forwards;
}
.loader--revealing {
background: transparent;
pointer-events: none;
}
.loader--revealing .loader__scene {
opacity: 0;
}
.loader--revealing .loader__handoff {
transform: translateX(0);
animation: handoff-reveal 600ms cubic-bezier(0.76, 0, 0.24, 1) forwards;
}
.loader__announcement {
position: absolute;
@@ -81,16 +129,91 @@
clip-path: inset(50%);
white-space: nowrap;
}
@media (max-width: 600px) {
.loader__title {
font-size: clamp(54px, 19vw, 86px);
@keyframes yarn-feed {
0% {
opacity: 0;
stroke-dashoffset: 1200;
}
.loader__topline span:last-child {
display: none;
4% {
opacity: 0.82;
}
100% {
opacity: 0.82;
stroke-dashoffset: 0;
}
}
@keyframes machine-reveal {
to {
clip-path: inset(0);
}
}
@keyframes machine-colour {
to {
filter: grayscale(0) contrast(1);
transform: scale(1);
}
}
@keyframes brand-arrive {
to {
opacity: 1;
transform: translate(-50%, 0);
}
}
@keyframes handoff-cover {
to {
transform: translateX(0);
}
}
@keyframes handoff-reveal {
to {
transform: translateX(101%);
}
}
@media (max-width: 600px) {
.loader__yarns {
width: 108vw;
height: 32%;
}
.loader__machine {
inset: 9% 0 12%;
}
.loader__machine-image {
width: 98vw;
height: auto;
max-width: none;
}
.loader__brand {
bottom: 22px;
gap: 11px;
padding: 8px 12px;
}
.loader__logo {
width: 66px;
}
.loader__message {
padding-left: 11px;
font-size: 15px;
}
}
@media (prefers-reduced-motion: reduce) {
.loader {
transition: none;
.loader__yarns,
.loader__handoff {
display: none;
}
.loader__machine,
.loader__machine-image,
.loader__brand {
animation: none;
}
.loader__machine {
clip-path: inset(0);
}
.loader__machine-image {
filter: none;
transform: none;
}
.loader__brand {
opacity: 1;
transform: translate(-50%, 0);
}
}
+46 -80
View File
@@ -1,106 +1,72 @@
import { useEffect, useRef, useState } from "react";
import weaveShader from "../shaders/loader.wgsl?raw";
import { useEffect, useState } from "react";
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 coveringTimer = window.setTimeout(
() => setPhase("covering"),
reducedMotion ? 0 : 2100,
);
const revealTimer = window.setTimeout(
() => setPhase("revealing"),
reducedMotion ? 25 : 2550,
);
const removeTimer = window.setTimeout(
() => setPhase("hidden"),
reducedMotion ? 50 : 2550,
reducedMotion ? 50 : 3150,
);
return () => {
window.clearTimeout(closeTimer);
window.clearTimeout(coveringTimer);
window.clearTimeout(revealTimer);
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 className="loader__scene">
<svg
className="loader__yarns"
viewBox="0 0 1000 430"
preserveAspectRatio="none"
aria-hidden="true"
>
<path pathLength="1" d="M60 0 C90 150 120 285 180 430" />
<path pathLength="1" d="M205 0 C230 155 255 310 285 430" />
<path pathLength="1" d="M350 0 C365 170 380 320 395 430" />
<path pathLength="1" d="M500 0 L500 430" />
<path pathLength="1" d="M650 0 C635 170 620 320 605 430" />
<path pathLength="1" d="M795 0 C770 155 745 310 715 430" />
<path pathLength="1" d="M940 0 C910 150 880 285 820 430" />
</svg>
<div className="loader__machine" aria-hidden="true">
<img
className="loader__machine-image"
src="/images/knitting-machine.webp"
alt=""
width="1113"
height="1316"
/>
</div>
<div className="loader__brand">
<img
className="loader__logo"
src="/images/RRSLOGO.png"
alt="Radhe Radhe Synthetics"
width="1024"
height="809"
/>
<p className="loader__message">From yarn to repeat production.</p>
</div>
</div>
<div className="loader__handoff" aria-hidden="true" />
<span className="loader__announcement">
Loading Radhe Radhe Synthetics
</span>
+3 -3
View File
@@ -3,8 +3,8 @@
grid-template-columns: repeat(4, minmax(0, 1fr));
gap: 32px;
border-block: 1px solid var(--line);
padding: 42px 0;
margin-top: 78px;
padding: 38px 0 42px;
margin-top: 42px;
}
.stats__item {
min-width: 0;
@@ -14,7 +14,7 @@
font-size: 13px;
font-weight: 500;
line-height: 1.6;
color: var(--muted);
color: var(--industrial);
}
.stats__value {
font-family: var(--serif);
+15 -10
View File
@@ -12,25 +12,30 @@ export const company = Object.freeze({
export const navigation = [
{ to: "/", label: "Home" },
{ to: "/fabrics", label: "Fabrics" },
{ to: "/about", label: "About" },
{ to: "/about", label: "Manufacturing" },
{ to: "/contact", label: "Contact" },
];
export const capabilities = [
{
title: "Develop",
subtitle: "Continuous fabric development",
text: "Different structures call for different yarns, blends and machine configurations. RRS continually develops new knitted constructions for evolving apparel requirements.",
title: "01 · Develop",
subtitle: "Define the construction",
text: "The requirement begins with the intended application, yarn, blend, structure and fabric character.",
},
{
title: "Repeat",
subtitle: "Consistency in bulk production",
text: "A successful development must remain dependable when production scales. RRS focuses on maintaining repeatability and quality across bulk manufacturing.",
title: "02 · Configure",
subtitle: "Match yarn to machine",
text: "Machine configuration and yarn selection are aligned to the construction being developed.",
},
{
title: "Manufacture",
subtitle: "Extensive circular-knitting capability",
text: "A broad range of circular-knitting configurations allows RRS to work across different structures, materials and fabric characteristics.",
title: "03 · Manufacture",
subtitle: "Circular knitting at scale",
text: "The approved construction moves into greige production across the appropriate circular-knitting setup.",
},
{
title: "04 · Repeat",
subtitle: "Maintain bulk consistency",
text: "Successful developments are reproduced with repeatability across commercial manufacturing quantities.",
},
];
+5 -5
View File
@@ -81,12 +81,12 @@ export default function About() {
return (
<>
<PageHero
label="About RRS"
label="Manufacturing at RRS"
title={
<>
Built around
<br />
<em>the fabric.</em>
<em>repeatable production.</em>
</>
}
>
@@ -100,12 +100,12 @@ export default function About() {
<section className="philosophy">
<SectionHeading
centered
label="Our philosophy"
label="Our manufacturing discipline"
title={
<>
Consistency
Develop with curiosity.
<br />
<em>and curiosity.</em>
<em>Produce with consistency.</em>
</>
}
>
+5 -5
View File
@@ -52,19 +52,19 @@ export default function Contact() {
return (
<>
<PageHero
label="Contact RRS"
label="Request a quote"
title={
<>
Let’s talk
Bring us your
<br />
<em>fabric.</em>
<em>fabric requirement.</em>
</>
}
texture="structured"
>
<p>
Whether you’re discussing an existing requirement, bulk production or
a new fabric construction, connect directly with RRS.
Share an existing requirement, bulk quantity or new construction with
RRS and begin a direct manufacturing conversation.
</p>
</PageHero>
<section className="whatsapp" id="whatsapp" tabIndex={-1}>
+19 -12
View File
@@ -4,9 +4,8 @@
flex-direction: column;
justify-content: center;
overflow: hidden;
min-height: 590px;
margin-top: 24px;
border-radius: 7px;
min-height: 768px;
margin-inline: calc(0px - var(--page-gutter));
color: var(--paper);
background: var(--ink);
}
@@ -21,11 +20,14 @@
.home-hero__shade {
position: absolute;
inset: 0;
background: #1419108f;
background:
linear-gradient(90deg, #132630e8 0%, #1f3239a8 54%, #17262f73 100%),
linear-gradient(0deg, #111b22b5 0%, transparent 45%);
}
.home-hero__content {
position: relative;
z-index: 1;
max-width: 900px;
padding: 65px 62px 90px;
}
.home-hero__title {
@@ -51,8 +53,8 @@
.home-hero__bottom {
position: absolute;
bottom: 0;
left: 32px;
right: 32px;
left: var(--page-gutter);
right: var(--page-gutter);
display: flex;
justify-content: space-between;
gap: 20px;
@@ -128,6 +130,9 @@
padding: 58px 0;
border-bottom: 1px solid var(--line);
}
.process__item:nth-child(even) {
background: linear-gradient(90deg, transparent, #49667808, transparent);
}
.process__item:nth-child(even) .process__visual {
order: -1;
}
@@ -137,9 +142,12 @@
}
.process__verb {
margin: 0 0 24px;
font-size: 14px;
font-family: var(--mono);
font-size: 11px;
font-weight: 500;
color: var(--muted);
color: var(--industrial);
letter-spacing: 0.05em;
text-transform: uppercase;
}
.process__title {
margin: 0 0 22px;
@@ -175,19 +183,18 @@
}
@media (max-width: 760px) {
.home-hero {
margin-top: 18px;
min-height: 560px;
}
.home-hero__content {
padding: 42px 26px 130px;
padding: 42px var(--page-gutter) 130px;
}
.home-hero__title {
font-size: clamp(44px, 8.9vw, 68px);
margin-top: 32px;
}
.home-hero__bottom {
left: 26px;
right: 26px;
left: var(--page-gutter);
right: var(--page-gutter);
flex-direction: column;
gap: 8px;
padding: 15px 0;
+23 -31
View File
@@ -1,35 +1,22 @@
import { Link } from "react-router-dom";
import { capabilities, company } from "../data";
import { capabilities } from "../data";
import {
Action,
Arrow,
Eyebrow,
SectionHeading,
Textile,
WhatsAppAction,
} from "../components/Primitives";
import { ContactBanner } from "../components/Layout";
import Gallery from "../components/Gallery";
import Stats from "../components/Stats";
import "./Home.css";
const processItems = [
{
...capabilities[0],
image: "fine",
href: "/fabrics#developments",
},
{
...capabilities[2],
image: "structured",
href: "/fabrics#circular-knitting",
},
{
...capabilities[1],
image: "natural",
href: "/fabrics#production",
},
];
const processItems = capabilities.map((capability, index) => ({
...capability,
image: ["fine", "natural", "structured", "fine"][index],
href: index < 2 ? "/fabrics#applications" : "/fabrics#production",
}));
export default function Home() {
return (
@@ -45,29 +32,35 @@ export default function Home() {
/>
<div className="home-hero__shade" />
<div className="home-hero__content">
<Eyebrow>Circular knitted fabrics · RRS</Eyebrow>
<Eyebrow>Specialist circular-knitting manufacturer · RRS</Eyebrow>
<h1 className="home-hero__title">
Built to develop.
<br />
<em>Built to repeat.</em>
</h1>
<p className="home-hero__description">{company.description}</p>
<p className="home-hero__description">
Greige knitted fabrics developed around yarn, structure and
application—then manufactured for consistent commercial supply.
</p>
<div className="home-hero__actions">
<Action to="/fabrics" variant="light">
Explore our fabrics
</Action>
<WhatsAppAction variant="ghost">Talk to RRS</WhatsAppAction>
<Action to="/contact#whatsapp" variant="ghost">
Request a quote
</Action>
</div>
</div>
<div className="home-hero__bottom">
<span className="home-hero__tagline">
Consistency at scale. Continuous development.
Development · Circular knitting · Bulk production
</span>
<span className="home-hero__image-note">
Illustrative texture · Photography to follow
Factory photography scheduled for content handover
</span>
</div>
</section>
<Stats />
<Gallery preview />
<section className="home-intro">
<div className="home-intro__heading">
@@ -98,21 +91,20 @@ export default function Home() {
</Link>
</div>
</section>
<Stats />
<section className="process">
<SectionHeading
label="How we work"
label="Manufacturing process"
title={
<>
Behind the fabric:
From requirement
<br />
<em>our process.</em>
<em>to repeat production.</em>
</>
}
>
<p>
Development, circular knitting and repeatable bulk production work
as one discipline.
A direct sequence connects fabric development with dependable
commercial manufacturing.
</p>
</SectionHeading>
<div className="process__list">
@@ -123,7 +115,7 @@ export default function Home() {
<h3 className="process__title">{subtitle}</h3>
<p className="process__text">{text}</p>
<Link className="process__link" to={href}>
Explore this capability <Arrow diagonal />
See our capability <Arrow diagonal />
</Link>
</div>
<Textile className="process__visual" kind={image} />
-46
View File
@@ -1,46 +0,0 @@
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);
}