RRS First version

This commit is contained in:
2026-09-12 03:08:23 -04:00
commit c050d25a84
41 changed files with 5575 additions and 0 deletions
+86
View File
@@ -0,0 +1,86 @@
@font-face {
font-family: "Instrument Serif";
src: url("/fonts/instrument-serif-regular.ttf") format("truetype");
font-weight: 400;
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-style: normal;
font-display: swap;
}
:root {
--paper: #f5f3ee;
--ink: #282a23;
--muted: #686b61;
--line: #d9d9cf;
--wash: #ecebe4;
--accent: #8f725c;
--serif: "Instrument Serif", Georgia, serif;
--sans: "DM Sans", Arial, sans-serif;
--mono: "Courier New", monospace;
--page-gutter: clamp(20px, 4vw, 72px);
color-scheme: light;
font-family: var(--sans);
color: var(--ink);
background: var(--paper);
font-synthesis: none;
-webkit-font-smoothing: antialiased;
}
/* Only box sizing, base document defaults and accessibility are global.
Every visual component owns a BEM block in its own stylesheet. */
*,
*::before,
*::after {
box-sizing: border-box;
}
body {
margin: 0;
min-width: 320px;
}
button,
input,
textarea,
select {
font: inherit;
}
button,
a {
-webkit-tap-highlight-color: transparent;
}
:focus-visible:not([tabindex="-1"]) {
outline: 2px solid var(--accent);
outline-offset: 5px;
}
::selection {
background: #d1d3bf;
color: #282a23;
}
.not-found {
padding: 100px 0;
}
.not-found__label {
font-size: 14px;
font-weight: 500;
color: var(--muted);
}
.not-found__title {
font-family: var(--serif);
font-size: 72px;
font-weight: 400;
margin: 20px 0;
}
.not-found__text {
margin-bottom: 35px;
color: var(--muted);
}
+63
View File
@@ -0,0 +1,63 @@
.capacity-value {
display: inline-flex;
align-items: baseline;
line-height: 1;
white-space: nowrap;
}
.capacity-value__window {
display: inline-block;
height: 1em;
overflow: hidden;
}
.capacity-value__reel {
display: flex;
flex-direction: column;
height: 4em;
}
.capacity-value__reel--up {
transform: translateY(0);
}
.capacity-value__reel--down {
transform: translateY(-3em);
}
.capacity-value__step {
display: block;
height: 1em;
flex: 0 0 1em;
line-height: 1;
}
.capacity-value__dash {
display: inline-block;
}
.reveal--visible .capacity-value__reel--up {
animation: capacity-roll-up 1.2s cubic-bezier(0.22, 1, 0.36, 1) 180ms both;
}
.reveal--visible .capacity-value__reel--down {
animation: capacity-roll-down 1.2s cubic-bezier(0.22, 1, 0.36, 1) 180ms both;
}
@keyframes capacity-roll-up {
from {
transform: translateY(0);
}
to {
transform: translateY(-3em);
}
}
@keyframes capacity-roll-down {
from {
transform: translateY(-3em);
}
to {
transform: translateY(0);
}
}
@media (prefers-reduced-motion: reduce) {
.reveal--visible .capacity-value__reel--up {
animation: none;
transform: translateY(-3em);
}
.reveal--visible .capacity-value__reel--down {
animation: none;
transform: translateY(0);
}
}
+29
View File
@@ -0,0 +1,29 @@
import "./CapacityValue.css";
function Reel({ values, direction }) {
return (
<span className="capacity-value__window" aria-hidden="true">
<span
className={`capacity-value__reel capacity-value__reel--${direction}`}
>
{values.map((value) => (
<span className="capacity-value__step" key={value}>
{value}
</span>
))}
</span>
</span>
);
}
export default function CapacityValue() {
return (
<span className="capacity-value" aria-label="60 to 70">
<Reel values={["00", "20", "40", "60"]} direction="up" />
<span className="capacity-value__dash" aria-hidden="true">
–
</span>
<Reel values={["70", "80", "90", "99"]} direction="down" />
</span>
);
}
+125
View File
@@ -0,0 +1,125 @@
.gallery {
padding-top: 100px;
scroll-margin-top: 30px;
}
.gallery__intro {
margin: 0 0 22px;
}
.gallery__grid {
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: 38px 26px;
}
.sample {
min-width: 0;
}
.sample__visual {
position: relative;
aspect-ratio: 1 / 1.12;
overflow: hidden;
border-radius: 4px;
background: var(--wash);
}
.sample__image {
width: 100%;
height: 100%;
display: block;
object-fit: cover;
transition: transform 700ms ease;
}
.sample:hover .sample__image {
transform: scale(1.045);
}
.sample__badge {
position: absolute;
top: 16px;
left: 16px;
padding: 7px 10px;
background: #f5f3eedb;
border-radius: 2px;
font-size: 11px;
}
.sample__heading {
display: flex;
justify-content: space-between;
align-items: baseline;
gap: 12px;
padding-top: 20px;
}
.sample__name {
font-family: var(--serif);
font-size: 27px;
font-weight: 400;
letter-spacing: -0.035em;
margin: 0;
}
.sample__detail {
font-size: 11px;
color: var(--muted);
text-align: right;
}
.sample__pending {
font-size: 11px;
color: var(--muted);
margin: 9px 0 0;
}
.gallery__notice {
display: flex;
align-items: flex-start;
gap: 14px;
margin-top: 32px;
padding: 19px 22px;
background: var(--wash);
border-radius: 4px;
color: var(--muted);
}
.gallery__notice-mark {
flex-shrink: 0;
width: 17px;
height: 17px;
display: grid;
place-content: center;
border: 1px solid currentColor;
border-radius: 50%;
font-family: var(--serif);
font-size: 12px;
}
.gallery__notice-text {
font-size: 11px;
line-height: 1.8;
margin: 0;
}
@media (max-width: 900px) {
.sample__heading {
align-items: flex-start;
flex-direction: column;
gap: 6px;
}
}
@media (max-width: 600px) {
.gallery {
padding-top: 60px;
}
.gallery__grid {
grid-template-columns: 1fr;
gap: 30px;
}
.sample__visual {
aspect-ratio: 1.15;
}
.sample__heading {
flex-direction: row;
align-items: baseline;
}
.gallery__notice {
padding: 16px;
}
}
@media (prefers-reduced-motion: reduce) {
.sample__image {
transition: none;
}
.sample:hover .sample__image {
transform: none;
}
}
+83
View File
@@ -0,0 +1,83 @@
import { developments } from "../data";
import { Action, SectionHeading } from "./Primitives";
import "./Gallery.css";
const textures = ["fine", "natural", "structured"];
export default function Gallery({ preview = false }) {
const items = preview ? developments.slice(0, 3) : developments;
return (
<section className="gallery" id="developments" tabIndex={-1}>
<SectionHeading
centered
label="Selected developments"
title={
preview ? (
<>
Structure changes
<br />
<em>everything.</em>
</>
) : (
<>
From greige
<br />
<em>to possibility.</em>
</>
)
}
>
<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."
: "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
</Action>
)}
</SectionHeading>
<div className="gallery__grid">
{items.map((name, index) => (
<article className="sample" key={name}>
<div className="sample__visual">
<img
className="sample__image"
src={`/images/knit-${textures[index % textures.length]}.jpg`}
alt={`Illustrative placeholder for ${name}; RRS sample photograph not yet supplied`}
width="1600"
height="1200"
loading="lazy"
/>
<span className="sample__badge">Image placeholder</span>
</div>
<div className="sample__heading">
<h3 className="sample__name">{name}</h3>
<span className="sample__detail">Development reference</span>
</div>
<p className="sample__pending">RRS sample photography to follow.</p>
</article>
))}
</div>
<aside className="gallery__notice">
<span className="gallery__notice-mark" aria-hidden="true">
i
</span>
<div>
<p className="gallery__notice-text">
Illustrative textures shown, not actual RRS samples. Development
names are references, not technical categories.
</p>
<p className="gallery__notice-text">
When supplied, processed images will be labelled{" "}
<strong>Processed Development Reference</strong>. Processed samples
are for visual reference; RRS commercially manufactures and supplies
greige fabric only. Finished appearance may vary depending on the
buyer’s processing and finishing method.
</p>
</div>
</aside>
</section>
);
}
+353
View File
@@ -0,0 +1,353 @@
.site-main {
margin-inline: var(--page-gutter);
outline: none;
}
.reveal {
opacity: 0;
clip-path: inset(0 0 12% 0);
transform: translateY(36px);
transition:
opacity 700ms ease,
clip-path 850ms cubic-bezier(0.22, 1, 0.36, 1),
transform 850ms cubic-bezier(0.22, 1, 0.36, 1);
will-change: opacity, clip-path, transform;
}
.reveal--visible {
opacity: 1;
clip-path: inset(0);
transform: translateY(0);
will-change: auto;
}
.skip-link {
position: fixed;
left: 20px;
top: -80px;
z-index: 100;
padding: 16px 24px;
background: var(--ink);
color: var(--paper);
}
.skip-link:focus {
top: 10px;
}
.site-header {
border-bottom: 1px solid var(--line);
}
.site-header__inner {
display: flex;
justify-content: space-between;
align-items: center;
min-height: 100px;
margin-inline: var(--page-gutter);
gap: 30px;
}
.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;
}
.site-nav {
display: flex;
align-items: center;
gap: 34px;
}
.site-nav__link {
position: relative;
padding: 14px 0;
color: var(--muted);
font-size: 12px;
text-decoration: none;
}
.site-nav__link--active {
color: var(--ink);
}
.site-nav__link--active::after {
content: "";
position: absolute;
bottom: 3px;
left: calc(50% - 2px);
width: 4px;
height: 4px;
background: var(--ink);
border-radius: 50%;
}
.site-nav__link:hover {
color: var(--ink);
}
.site-nav__contact {
display: flex;
align-items: center;
gap: 30px;
margin-left: 45px;
padding: 12px 20px;
border: 1px solid var(--ink);
border-radius: 30px;
color: var(--ink);
text-decoration: none;
font-size: 11px;
}
.site-nav__contact:hover {
background: var(--ink);
color: var(--paper);
}
.site-header__toggle {
display: none;
}
.site-footer {
margin-top: 100px;
padding: 55px var(--page-gutter) 24px;
background: var(--wash);
}
.site-footer__top {
display: grid;
grid-template-columns: 1.6fr 0.7fr 1fr;
gap: 60px;
}
.site-footer__name {
font-family: var(--serif);
font-size: 23px;
margin: 0 0 15px;
}
.site-footer__description {
max-width: 310px;
font-size: 13px;
color: var(--muted);
line-height: 1.8;
}
.site-footer__nav,
.site-footer__contact {
display: flex;
align-items: flex-start;
flex-direction: column;
gap: 12px;
}
.site-footer__label {
font-size: 13px;
font-weight: 500;
margin-bottom: 10px;
color: var(--muted);
}
.site-footer__link {
display: flex;
gap: 14px;
align-items: center;
color: var(--ink);
font-size: 12px;
text-decoration: none;
}
.site-footer__link:hover {
text-decoration: underline;
text-underline-offset: 4px;
}
.site-footer__pending {
margin: 0;
color: var(--muted);
font-size: 11px;
}
.site-footer__bottom {
display: flex;
justify-content: space-between;
align-items: flex-end;
padding-top: 60px;
}
.site-footer__wordmark {
font-family: var(--serif);
font-weight: 600;
font-size: clamp(110px, 18vw, 240px);
line-height: 0.88;
letter-spacing: -0.075em;
}
.site-footer__signature {
margin: 0 0 15px;
text-align: right;
font-family: var(--serif);
font-size: clamp(25px, 3vw, 40px);
line-height: 1.15;
letter-spacing: -0.03em;
}
.site-footer__legal {
display: flex;
justify-content: space-between;
gap: 20px;
margin-top: 38px;
padding-top: 23px;
border-top: 1px solid var(--line);
font-family: var(--mono);
font-size: 8px;
line-height: 1.6;
text-transform: uppercase;
letter-spacing: 0.02em;
color: var(--muted);
}
.site-footer__top-link {
color: var(--ink);
text-decoration: none;
}
.contact-banner {
position: relative;
overflow: hidden;
display: flex;
align-items: center;
min-height: 420px;
background: var(--ink);
color: var(--paper);
padding: 64px;
border-radius: 6px;
margin-top: 95px;
}
.contact-banner__copy {
position: relative;
z-index: 1;
max-width: 650px;
}
.contact-banner__label {
margin: 0 0 24px;
font-size: 14px;
font-weight: 500;
color: #c2c3b8;
}
.contact-banner__title {
margin: 0 0 22px;
font-family: var(--serif);
font-size: clamp(36px, 4.2vw, 60px);
font-weight: 400;
line-height: 1.1;
letter-spacing: -0.04em;
}
.contact-banner__text {
max-width: 440px;
color: #c2c3b8;
font-size: 14px;
line-height: 1.9;
margin: 0 0 30px;
}
.contact-banner__motif {
position: absolute;
display: flex;
gap: 25px;
right: -60px;
bottom: -80px;
transform: rotate(30deg);
}
.contact-banner__loop {
display: block;
width: 100px;
height: 430px;
border: 1px solid #74776955;
border-radius: 50%;
}
@media (prefers-reduced-motion: reduce) {
.reveal,
.reveal--visible {
opacity: 1;
clip-path: none;
transform: none;
transition: none;
}
}
@media (max-width: 950px) {
.site-nav {
gap: 23px;
}
.site-nav__contact {
margin-left: 12px;
}
.site-footer__top {
gap: 30px;
}
}
@media (max-width: 760px) {
.site-header__inner {
min-height: 80px;
flex-wrap: wrap;
gap: 0;
}
.brand__mark {
font-size: 35px;
}
.brand__name {
font-size: 8px;
}
.site-header__toggle {
display: flex;
justify-content: space-between;
gap: 25px;
align-items: center;
min-height: 44px;
padding: 8px 0 8px 15px;
border: 0;
color: var(--ink);
background: transparent;
font-size: 12px;
cursor: pointer;
}
.site-nav {
display: none;
width: 100%;
padding: 14px 0 24px;
}
.site-nav--open {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 8px 24px;
}
.site-nav__link {
padding: 15px 4px;
}
.site-nav__link--active::after {
left: auto;
right: 15px;
bottom: 20px;
}
.site-nav__contact {
grid-column: 1 / -1;
margin: 12px 0 0;
justify-content: space-between;
}
.contact-banner {
padding: 40px 25px;
min-height: 400px;
margin-top: 60px;
}
.site-footer {
padding: 40px 20px 24px;
margin-top: 65px;
}
.site-footer__top {
grid-template-columns: 1fr 1fr;
gap: 32px 20px;
}
.site-footer__intro {
grid-column: 1 / -1;
}
.site-footer__bottom {
padding-top: 45px;
gap: 15px;
}
.site-footer__legal {
flex-wrap: wrap;
}
.site-footer__signature {
font-size: 24px;
}
}
+213
View File
@@ -0,0 +1,213 @@
import { useEffect, useState } from "react";
import { Link, NavLink, Outlet, useLocation } from "react-router-dom";
import { company, navigation } from "../data";
import { Arrow, WhatsAppAction } from "./Primitives";
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>
</Link>
);
}
function Header() {
const [menuOpen, setMenuOpen] = useState(false);
const closeMenu = () => setMenuOpen(false);
return (
<header
className="site-header"
onKeyDown={(event) => {
if (menuOpen && event.key === "Escape") {
closeMenu();
document.getElementById("menu-toggle")?.focus();
}
}}
>
<div className="site-header__inner">
<Brand />
<button
id="menu-toggle"
className="site-header__toggle"
type="button"
aria-controls="primary-navigation"
aria-expanded={menuOpen}
onClick={() => setMenuOpen((open) => !open)}
>
{menuOpen ? "Close" : "Menu"}
<span aria-hidden="true">{menuOpen ? "−" : "+"}</span>
</button>
<nav
id="primary-navigation"
aria-label="Main navigation"
className={`site-nav${menuOpen ? " site-nav--open" : ""}`}
>
{navigation.map(({ to, label }) => (
<NavLink
key={to}
to={to}
end={to === "/"}
onClick={closeMenu}
className={({ isActive }) =>
`site-nav__link${isActive ? " site-nav__link--active" : ""}`
}
>
{label}
</NavLink>
))}
<Link
className="site-nav__contact"
to="/contact#whatsapp"
onClick={closeMenu}
>
Let's talk <Arrow diagonal />
</Link>
</nav>
</div>
</header>
);
}
function Footer() {
return (
<footer className="site-footer">
<div className="site-footer__top">
<div className="site-footer__intro">
<p className="site-footer__name">{company.name}</p>
<p className="site-footer__description">{company.description}</p>
</div>
<nav aria-label="Footer navigation" className="site-footer__nav">
<span className="site-footer__label">Explore</span>
{navigation.map(({ to, label }) => (
<Link className="site-footer__link" to={to} key={to}>
{label}
</Link>
))}
</nav>
<div className="site-footer__contact">
<span className="site-footer__label">Start a conversation</span>
<Link className="site-footer__link" to="/contact#whatsapp">
WhatsApp <Arrow diagonal />
</Link>
<Link className="site-footer__link" to="/contact#direct-contact">
Phone · Email · Address <Arrow diagonal />
</Link>
<p className="site-footer__pending">
Contact details to be supplied.
</p>
</div>
</div>
<div className="site-footer__bottom">
<span className="site-footer__wordmark" aria-hidden="true">
RRS.
</span>
<p className="site-footer__signature">
Built to develop.
<br />
<em>Built to repeat.</em>
</p>
</div>
<div className="site-footer__legal">
<span>
© {new Date().getFullYear()} Radhe Radhe Synthetics Pvt. Ltd.
</span>
<span>Commercial supply: greige fabric only.</span>
<a className="site-footer__top-link" href="#top">
Back to top ↑
</a>
</div>
</footer>
);
}
export function ContactBanner({
title = (
<>
Developing your
<br />
<em>next construction?</em>
</>
),
text = "Talk to RRS about fabric structure, yarn combinations, bulk requirements or new developments.",
}) {
return (
<section className="contact-banner">
<div className="contact-banner__copy">
<p className="contact-banner__label">
A conversation is where it begins
</p>
<h2 className="contact-banner__title">{title}</h2>
<p className="contact-banner__text">{text}</p>
<WhatsAppAction variant="light" />
</div>
<div className="contact-banner__motif" aria-hidden="true">
<span className="contact-banner__loop" />
<span className="contact-banner__loop" />
<span className="contact-banner__loop" />
</div>
</section>
);
}
export default function Layout() {
const { pathname, hash, key } = useLocation();
useEffect(() => {
const title =
navigation.find(({ to }) => to === pathname)?.label ?? "Page not found";
document.title = `${title === "Home" ? "RRS" : `${title} — RRS`} | Radhe Radhe Synthetics`;
const target = hash
? document.getElementById(hash.slice(1))
: document.getElementById("main-content");
if (hash && target) target.scrollIntoView({ behavior: "instant" });
else window.scrollTo({ top: 0, behavior: "instant" });
target?.focus({ preventScroll: true });
}, [pathname, hash, key]);
useEffect(() => {
const sections = [...document.querySelectorAll("#main-content > section")];
const reducedMotion = window.matchMedia(
"(prefers-reduced-motion: reduce)",
).matches;
if (reducedMotion || !("IntersectionObserver" in window)) {
sections.forEach((section) => section.classList.add("reveal--visible"));
return undefined;
}
sections.forEach((section) => section.classList.add("reveal"));
const observer = new IntersectionObserver(
(entries) => {
entries.forEach((entry) => {
if (!entry.isIntersecting) return;
entry.target.classList.add("reveal--visible");
observer.unobserve(entry.target);
});
},
{ threshold: 0.08, rootMargin: "0px 0px -8% 0px" },
);
sections.forEach((section) => observer.observe(section));
return () => observer.disconnect();
}, [pathname, key]);
return (
<div className="site-shell" id="top">
<a className="skip-link" href="#main-content">
Skip to content
</a>
<Header key={key} />
<main className="site-main" id="main-content" tabIndex={-1}>
<Outlet />
</main>
<Footer />
</div>
);
}
+194
View File
@@ -0,0 +1,194 @@
.arrow {
flex: 0 0 auto;
}
.action {
display: inline-flex;
align-items: center;
justify-content: space-between;
gap: 30px;
min-height: 48px;
padding: 14px 23px;
border: 1px solid transparent;
border-radius: 100px;
font-size: 12px;
font-weight: 600;
text-decoration: none;
cursor: pointer;
transition:
background 180ms ease,
color 180ms ease;
}
.action--dark {
background: var(--ink);
color: var(--paper);
}
.action--light {
background: var(--paper);
color: var(--ink);
}
.action--outline {
background: transparent;
color: var(--ink);
border-color: var(--line);
}
.action--ghost {
background: transparent;
color: var(--paper);
border-color: #ffffff60;
}
.action--dark:hover:not(:disabled) {
background: #474b3f;
}
.action--light:hover {
background: #dedbcf;
}
.action--outline:hover {
background: var(--wash);
}
.action--ghost:hover {
background: #ffffff15;
}
.action:disabled {
cursor: not-allowed;
opacity: 0.55;
}
.eyebrow {
margin: 0 0 18px;
font-family: var(--sans);
font-size: 14px;
line-height: 1.6;
font-weight: 500;
}
.section-heading {
display: flex;
align-items: flex-end;
justify-content: space-between;
gap: 45px;
margin-bottom: 44px;
}
.section-heading__title {
max-width: 680px;
}
.section-heading__heading {
margin: 0;
font-family: var(--serif);
font-size: clamp(32px, 3.6vw, 50px);
line-height: 1.12;
font-weight: 400;
letter-spacing: -0.045em;
}
.section-heading__aside {
max-width: 360px;
color: var(--muted);
font-size: 14px;
line-height: 1.8;
}
.section-heading--centered {
display: block;
text-align: center;
}
.section-heading--centered .section-heading__title {
max-width: none;
}
.section-heading--centered .section-heading__aside {
margin: 24px auto 0;
}
.textile {
position: relative;
min-width: 0;
margin: 0;
overflow: hidden;
border-radius: 5px;
background: #aba08b;
}
.textile__image {
display: block;
width: 100%;
height: 100%;
min-height: 300px;
object-fit: cover;
transition: transform 700ms ease;
}
.textile:hover .textile__image {
transform: scale(1.035);
}
.textile__caption {
position: absolute;
left: 18px;
bottom: 16px;
right: 18px;
width: fit-content;
padding: 7px 10px;
border-radius: 2px;
background: #f5f3eeeb;
color: #34352d;
font-size: 11px;
line-height: 1.5;
}
.page-hero {
display: grid;
grid-template-columns: 1.15fr 1fr;
gap: 70px;
padding: 66px 0 74px;
}
.page-hero__copy {
display: flex;
flex-direction: column;
align-items: flex-start;
padding: 22px 0 0;
}
.page-hero__title {
margin: 6px 0 28px;
font-family: var(--serif);
font-weight: 400;
font-size: clamp(48px, 5.6vw, 78px);
letter-spacing: -0.055em;
line-height: 1.04;
}
.page-hero__description {
max-width: 470px;
color: var(--muted);
font-size: 16px;
line-height: 1.9;
}
.page-hero__footnote {
display: block;
padding-top: 46px;
margin-top: auto;
font-size: 12px;
color: var(--muted);
}
@media (max-width: 760px) {
.section-heading {
align-items: flex-start;
flex-direction: column;
gap: 22px;
margin-bottom: 30px;
}
.page-hero {
grid-template-columns: 1fr;
gap: 30px;
padding: 35px 0 50px;
}
.page-hero__footnote {
padding-top: 26px;
}
.page-hero__title {
font-size: clamp(44px, 10vw, 70px);
}
.page-hero__copy {
padding: 0;
}
.textile__image {
max-height: 460px;
}
}
@media (prefers-reduced-motion: reduce) {
.action,
.textile__image {
transition: none;
}
.textile:hover .textile__image {
transform: none;
}
}
+125
View File
@@ -0,0 +1,125 @@
import { Link } from "react-router-dom";
import { company, whatsappUrl } from "../data";
import "./Primitives.css";
export function Arrow({ diagonal = false }) {
return (
<svg
className="arrow"
width="18"
height="18"
viewBox="0 0 24 24"
fill="none"
aria-hidden="true"
>
<path
d={diagonal ? "M6 18 18 6M6 6h12v12" : "M4 12h15m-6-6 6 6-6 6"}
stroke="currentColor"
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
);
}
export function Action({ children, to, variant = "dark" }) {
return (
<Link className={`action action--${variant}`} to={to}>
{children}
<Arrow />
</Link>
);
}
export function WhatsAppAction({
children = "Talk to us on WhatsApp",
variant = "dark",
direct = false,
}) {
const url = whatsappUrl(company.whatsapp);
const className = `action action--${variant}`;
if (url)
return (
<a className={className} href={url} target="_blank" rel="noreferrer">
{children}
<Arrow diagonal />
</a>
);
if (direct)
return (
<button
className={className}
type="button"
disabled
aria-describedby="whatsapp-status"
>
{children}
<Arrow diagonal />
</button>
);
return (
<Action to="/contact#whatsapp" variant={variant}>
{children}
</Action>
);
}
export function Eyebrow({ children }) {
return <p className="eyebrow">{children}</p>;
}
export function SectionHeading({ label, title, children, centered = false }) {
return (
<header
className={`section-heading${centered ? " section-heading--centered" : ""}`}
>
<div className="section-heading__title">
<Eyebrow>{label}</Eyebrow>
<h2 className="section-heading__heading">{title}</h2>
</div>
{children && <div className="section-heading__aside">{children}</div>}
</header>
);
}
export function Textile({
kind = "natural",
className = "",
caption = true,
eager = false,
}) {
return (
<figure className={`textile textile--${kind} ${className}`}>
<img
className="textile__image"
src={`/images/knit-${kind}.jpg`}
alt="Illustrative knitted texture — not an RRS product photograph"
width="1600"
height="1200"
loading={eager ? "eager" : "lazy"}
/>
{caption && (
<figcaption className="textile__caption">
Illustrative texture · RRS photography to follow
</figcaption>
)}
</figure>
);
}
export function PageHero({ label, title, children, texture = "natural" }) {
return (
<section className="page-hero">
<div className="page-hero__copy">
<Eyebrow>{label}</Eyebrow>
<h1 className="page-hero__title">{title}</h1>
<div className="page-hero__description">{children}</div>
<span className="page-hero__footnote">
Radhe Radhe Synthetics · Greige knitted fabrics
</span>
</div>
<Textile kind={texture} eager />
</section>
);
}
+56
View File
@@ -0,0 +1,56 @@
.stats {
display: grid;
grid-template-columns: repeat(4, minmax(0, 1fr));
gap: 32px;
border-block: 1px solid var(--line);
padding: 42px 0;
margin-top: 78px;
}
.stats__item {
min-width: 0;
}
.stats__label {
margin: 0 0 26px;
font-size: 13px;
font-weight: 500;
line-height: 1.6;
color: var(--muted);
}
.stats__value {
font-family: var(--serif);
font-size: clamp(38px, 4vw, 54px);
letter-spacing: -0.035em;
line-height: 1.1;
margin: 0 0 10px;
}
.stats__unit {
font-size: 12px;
font-weight: 500;
line-height: 1.6;
margin: 0 0 16px;
}
.stats__text {
max-width: 230px;
font-size: 13px;
color: var(--muted);
line-height: 1.8;
margin: 0;
}
@media (max-width: 850px) {
.stats {
grid-template-columns: 1fr 1fr;
gap: 36px 24px;
}
}
@media (max-width: 480px) {
.stats {
margin-top: 45px;
padding: 30px 0;
}
.stats__value {
font-size: 36px;
}
.stats__label {
margin-bottom: 18px;
}
}
+44
View File
@@ -0,0 +1,44 @@
import CapacityValue from "./CapacityValue";
import "./Stats.css";
const facts = [
{
label: "Production capacity",
value: <CapacityValue />,
unit: "Tonnes / month",
text: "Approximate monthly manufacturing capacity.",
},
{
label: "Our specialisation",
value: "Greige",
unit: "Knitted fabrics only",
text: "The starting point for a world of possibilities.",
},
{
label: "The capability",
value: "Circular",
unit: "Knitting · multiple constructions",
text: "Different structures. Different configurations.",
},
{
label: "The approach",
value: "Continuous",
unit: "Fabric development",
text: "Exploring yarns, blends and new constructions.",
},
];
export default function Stats() {
return (
<section className="stats" aria-label="RRS at a glance">
{facts.map(({ label, value, unit, text }) => (
<div className="stats__item" key={label}>
<p className="stats__label">{label}</p>
<p className="stats__value">{value}</p>
<p className="stats__unit">{unit}</p>
<p className="stats__text">{text}</p>
</div>
))}
</section>
);
}
+82
View File
@@ -0,0 +1,82 @@
export const company = Object.freeze({
name: "Radhe Radhe Synthetics Pvt. Ltd.",
description:
"Greige knitted fabrics engineered for diverse apparel applications.",
whatsapp: "", // Supply the business number in international digits, without + or spaces.
phone: "",
email: "",
address: "",
mapsUrl: "",
});
export const navigation = [
{ to: "/", label: "Home" },
{ to: "/fabrics", label: "Fabrics" },
{ to: "/about", label: "About" },
{ 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: "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: "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.",
},
];
export const applications = [
{
title: "Shirting constructions",
text: "Greige knitted fabrics developed for shirt and apparel applications.",
},
{
title: "Textured knits",
text: "Knitted constructions where yarn and machine configuration are used to create distinctive surface character.",
},
{
title: "Structured knits",
text: "More pronounced knitted structures developed using different yarn combinations and knitting setups.",
},
{
title: "Developed constructions",
text: "Fabric developments created around specific visual, structural or material requirements.",
},
];
export const yarns = [
"Polyester",
"Cotton",
"Rayon",
"Stretch yarns",
"Lycra / elastane combinations",
"Specialty yarn combinations",
"Blended yarn constructions",
"Other yarns selected according to fabric requirements",
];
// Development names are references, not technical categories or specifications.
export const developments = [
"Micro Break",
"Luxe",
"Jerry",
"Killer",
"Joker",
"Ice Cool",
"Maple",
];
export function whatsappUrl(number) {
return /^[1-9]\d{7,14}$/.test(number)
? `https://wa.me/${number}?text=${encodeURIComponent("Hello RRS, I'd like to discuss a fabric requirement.")}`
: null;
}
+39
View File
@@ -0,0 +1,39 @@
import React from "react";
import { createRoot } from "react-dom/client";
import { BrowserRouter, Route, Routes } from "react-router-dom";
import Layout from "./components/Layout";
import { Action } from "./components/Primitives";
import Home from "./pages/Home";
import Fabrics from "./pages/Fabrics";
import About from "./pages/About";
import Contact from "./pages/Contact";
import "./base.css";
function NotFound() {
return (
<section className="not-found">
<p className="not-found__label">404 / Page not found</p>
<h1 className="not-found__title">A loose thread.</h1>
<p className="not-found__text">
This page doesn’t exist. Let’s return to the beginning.
</p>
<Action to="/">Back to home</Action>
</section>
);
}
createRoot(document.getElementById("root")).render(
<React.StrictMode>
<BrowserRouter>
<Routes>
<Route element={<Layout />}>
<Route index element={<Home />} />
<Route path="fabrics" element={<Fabrics />} />
<Route path="about" element={<About />} />
<Route path="contact" element={<Contact />} />
<Route path="*" element={<NotFound />} />
</Route>
</Routes>
</BrowserRouter>
</React.StrictMode>,
);
+263
View File
@@ -0,0 +1,263 @@
.philosophy {
border-top: 1px solid var(--line);
padding-top: 70px;
}
.philosophy__grid {
display: grid;
grid-template-columns: 1fr 1fr;
border-block: 1px solid var(--line);
padding-block: 48px;
}
.philosophy__item {
padding: 10px clamp(24px, 4vw, 72px);
}
.philosophy__item:first-child {
text-align: right;
}
.philosophy__item + .philosophy__item {
border-left: 1px solid var(--line);
}
.philosophy__title {
margin: 0 0 18px;
font-family: var(--serif);
font-weight: 400;
font-size: clamp(40px, 4vw, 58px);
letter-spacing: -0.04em;
}
.philosophy__text {
max-width: 390px;
margin: 0;
font-size: 14px;
line-height: 1.9;
color: var(--muted);
}
.philosophy__item:first-child .philosophy__text {
margin-left: auto;
}
.leadership {
padding-top: 100px;
}
.leadership__grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 28px;
}
.owner {
margin: 0;
}
.owner__portrait {
display: flex;
flex-direction: column;
align-items: center;
justify-content: space-between;
position: relative;
min-height: 370px;
padding: 24px;
background: #e5e2d9;
color: #7e8271;
border: 1px solid var(--line);
border-radius: 4px;
}
.owner__image-label {
align-self: flex-start;
font-size: 12px;
font-weight: 500;
color: var(--muted);
}
.owner__silhouette {
width: 185px;
height: 220px;
}
.owner__pending {
font-size: 11px;
text-align: center;
color: var(--muted);
}
.owner__caption {
padding: 22px 0 0;
}
.owner__identity {
display: flex;
align-items: baseline;
gap: 20px;
justify-content: space-between;
}
.owner__name {
margin: 0;
font-family: var(--serif);
font-weight: 400;
font-size: 26px;
}
.owner__role {
color: var(--muted);
font-size: 11px;
}
.owner__bio {
max-width: 370px;
margin: 13px 0 0;
font-size: 13px;
color: var(--muted);
line-height: 1.9;
}
.approach {
padding-top: 100px;
}
.approach__grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 35px;
}
.approach__step {
border-top: 1px solid var(--ink);
padding-top: 20px;
}
.approach__title {
margin: 8px 0 18px;
font-family: var(--serif);
font-size: 36px;
font-weight: 400;
letter-spacing: -0.04em;
}
.approach__text {
font-size: 13px;
line-height: 1.9;
color: var(--muted);
margin: 0;
}
.location {
padding-top: 100px;
}
.location__grid {
display: grid;
grid-template-columns: 1.4fr 1fr;
gap: 50px;
}
.location__map {
position: relative;
display: grid;
place-items: center;
min-height: 360px;
border: 1px solid #d2d5c7;
border-radius: 4px;
overflow: hidden;
background-color: #e4e6dc;
background-image:
linear-gradient(#75866b16 1px, transparent 1px),
linear-gradient(90deg, #75866b16 1px, transparent 1px);
background-size: 40px 40px;
}
.location__map-label {
position: absolute;
top: 20px;
left: 20px;
right: 20px;
font-size: 11px;
line-height: 1.6;
color: var(--muted);
}
.location__pin {
display: flex;
align-items: center;
gap: 12px;
border: 1px solid var(--ink);
padding: 14px 21px;
background: var(--paper);
border-radius: 50px;
font-family: var(--serif);
font-size: 24px;
}
.location__pin-dot {
width: 9px;
height: 9px;
background: var(--accent);
border-radius: 50%;
outline: 1px solid #8f725c50;
outline-offset: 5px;
margin-right: 4px;
}
.location__map-caption {
position: absolute;
bottom: 22px;
font-family: var(--serif);
font-style: italic;
font-size: 18px;
color: var(--muted);
}
.location__details {
align-self: center;
}
.location__name {
max-width: 360px;
margin: 0 0 25px;
font-family: var(--serif);
font-size: 31px;
font-weight: 400;
line-height: 1.2;
letter-spacing: -0.025em;
}
.location__address {
font-size: 14px;
line-height: 1.9;
color: var(--muted);
margin-bottom: 25px;
}
.location__button {
display: inline-block;
padding: 15px 20px;
border: 1px solid var(--ink);
background: var(--ink);
color: var(--paper);
border-radius: 30px;
font-size: 11px;
text-decoration: none;
}
.location__button:disabled {
opacity: 0.5;
cursor: not-allowed;
}
.location__status {
font-size: 11px;
color: var(--muted);
line-height: 1.8;
}
@media (max-width: 650px) {
.philosophy {
padding-top: 45px;
}
.philosophy__grid,
.leadership__grid,
.approach__grid,
.location__grid {
grid-template-columns: 1fr;
}
.philosophy__grid {
padding-block: 0;
}
.philosophy__item {
padding: 32px 0;
}
.philosophy__item:first-child {
text-align: left;
}
.philosophy__item:first-child .philosophy__text {
margin-left: 0;
}
.philosophy__item + .philosophy__item {
border-top: 1px solid var(--line);
border-left: 0;
}
.leadership,
.approach,
.location {
padding-top: 60px;
}
.leadership__grid {
gap: 35px;
}
.owner__portrait {
min-height: 330px;
}
.location__grid {
gap: 30px;
}
}
+232
View File
@@ -0,0 +1,232 @@
import { company } from "../data";
import { Eyebrow, PageHero, SectionHeading } from "../components/Primitives";
import { ContactBanner } from "../components/Layout";
import "./About.css";
const principles = [
{
title: "Built to repeat.",
text: "A fabric developed successfully should remain dependable when manufactured at scale.",
},
{
title: "Built to develop.",
text: "New yarn combinations, structures and constructions create new possibilities for apparel.",
},
];
const process = [
{
title: "Develop.",
text: "A construction begins with the relationship between yarn, machine and structure, with the eventual application in mind.",
},
{
title: "Test.",
text: "Selected constructions are evaluated through processed reference samples to explore their possibilities.",
},
{
title: "Repeat.",
text: "Successful developments are reproduced consistently in commercial greige production.",
},
];
function PortraitPlaceholder({ number }) {
return (
<figure className="owner">
<div
className="owner__portrait"
role="img"
aria-label={`Owner ${number} portrait placeholder; professional photograph to be supplied`}
>
<span className="owner__image-label">Portrait placeholder</span>
<svg
className="owner__silhouette"
viewBox="0 0 200 230"
fill="none"
aria-hidden="true"
>
<circle
cx="100"
cy="76"
r="35"
stroke="currentColor"
strokeWidth="1"
/>
<path
d="M28 218v-29c0-40 32-72 72-72s72 32 72 72v29"
stroke="currentColor"
/>
<path
d="M8 8h26M8 8v26m184-26h-26m26 0v26M8 222h26M8 222v-26m184 26h-26m26 0v-26"
stroke="currentColor"
/>
</svg>
<span className="owner__pending">
Professional photograph to follow
</span>
</div>
<figcaption className="owner__caption">
<div className="owner__identity">
<h3 className="owner__name">[Owner Name]</h3>
<span className="owner__role">[Director / Role]</span>
</div>
<p className="owner__bio">
Biography to be supplied. This profile will introduce the person
behind the fabric.
</p>
</figcaption>
</figure>
);
}
export default function About() {
return (
<>
<PageHero
label="About RRS"
title={
<>
Built around
<br />
<em>the fabric.</em>
</>
}
>
{" "}
<p>
Radhe Radhe Synthetics is a specialist manufacturer of greige knitted
fabrics with a focus on consistent bulk production and continuous
fabric development.
</p>
</PageHero>
<section className="philosophy">
<SectionHeading
centered
label="Our philosophy"
title={
<>
Consistency
<br />
<em>and curiosity.</em>
</>
}
>
<p>
Two ideas guide the way RRS works. Together, they define how we
approach knitting and production.
</p>
</SectionHeading>
<div className="philosophy__grid">
{principles.map(({ title, text }) => (
<article className="philosophy__item" key={title}>
<h3 className="philosophy__title">{title}</h3>
<p className="philosophy__text">{text}</p>
</article>
))}
</div>
</section>
<section className="leadership">
<SectionHeading
label="Our people"
title={
<>
The people
<br />
<em>behind RRS.</em>
</>
}
>
<p>
Meet our owners and directors.
<br />
Names, roles and biographies will be added when supplied.
</p>
</SectionHeading>
<div className="leadership__grid">
<PortraitPlaceholder number={1} />
<PortraitPlaceholder number={2} />
</div>
</section>
<section className="approach">
<SectionHeading
label="Our manufacturing approach"
title={
<>
Develop. Test. <em>Repeat.</em>
</>
}
/>
<div className="approach__grid">
{process.map(({ title, text }) => (
<article className="approach__step" key={title}>
<h3 className="approach__title">{title}</h3>
<p className="approach__text">{text}</p>
</article>
))}
</div>
</section>
<section className="location">
<SectionHeading
label="Our location"
title={
<>
Where <em>we work.</em>
</>
}
/>
<div className="location__grid">
<div
className="location__map"
role="img"
aria-label="Map placeholder. Verified factory or office address has not yet been supplied."
>
<span className="location__map-label">
Map placeholder / Not a geographical location
</span>
<div className="location__pin">
<span className="location__pin-dot" />
<span>RRS</span>
</div>
<span className="location__map-caption">
A place for our next conversation.
</span>
</div>
<div className="location__details">
<Eyebrow>Factory or office</Eyebrow>
<h3 className="location__name">{company.name}</h3>
<p className="location__address">
{company.address || (
<>
[Full Factory / Office Address]
<br />
[City, State, PIN, India]
</>
)}
</p>
{company.mapsUrl ? (
<a
className="location__button"
href={company.mapsUrl}
target="_blank"
rel="noreferrer"
>
Open in Google Maps ↗
</a>
) : (
<button
className="location__button"
type="button"
disabled
aria-describedby="map-status"
>
Open in Google Maps ↗
</button>
)}
<p className="location__status" id="map-status">
Verified location details to be supplied.
</p>
</div>
</div>
</section>
<ContactBanner />
</>
);
}
+148
View File
@@ -0,0 +1,148 @@
.whatsapp {
display: grid;
grid-template-columns: 1.5fr 1fr;
align-items: center;
gap: 50px;
border-top: 2px solid var(--ink);
padding: 48px;
background: #fbfaf7;
border-radius: 5px;
scroll-margin-top: 24px;
}
.whatsapp__title {
margin: 0 0 22px;
font-family: var(--serif);
font-size: clamp(32px, 3.8vw, 48px);
font-weight: 400;
line-height: 1.13;
letter-spacing: -0.04em;
}
.whatsapp__text {
max-width: 460px;
font-size: 14px;
line-height: 1.9;
color: var(--muted);
}
.whatsapp__message {
margin: 28px 0 0;
padding: 18px 22px;
border-left: 2px solid var(--accent);
background: var(--wash);
font-family: var(--serif);
font-size: 20px;
font-style: italic;
line-height: 1.5;
}
.whatsapp__clipboard {
display: flex;
align-items: flex-start;
flex-direction: column;
gap: 8px;
margin-top: 12px;
}
.whatsapp__copy-button {
display: inline-flex;
gap: 18px;
align-items: center;
min-height: 44px;
padding: 8px 0;
border: none;
background: transparent;
color: var(--ink);
font-size: 11px;
cursor: pointer;
}
.whatsapp__copy-button:hover {
text-decoration: underline;
text-underline-offset: 5px;
}
.whatsapp__copy-status {
font-size: 11px;
color: var(--muted);
line-height: 1.6;
}
.whatsapp__action {
display: flex;
align-items: center;
flex-direction: column;
text-align: center;
}
.whatsapp__icon {
display: block;
width: 58px;
height: 58px;
margin-bottom: 28px;
}
.whatsapp__status {
max-width: 270px;
margin: 18px 0 0;
font-size: 11px;
color: var(--muted);
line-height: 1.8;
}
.channels {
padding-top: 100px;
scroll-margin-top: 25px;
}
.channels__grid {
display: grid;
grid-template-columns: repeat(4, minmax(0, 1fr));
gap: 30px;
}
.channels__item {
min-width: 0;
border-top: 1px solid var(--line);
padding-top: 20px;
}
.channels__title {
margin: 8px 0 16px;
font-size: 13px;
color: var(--muted);
font-weight: 500;
}
.channels__value {
display: block;
font-family: var(--serif);
font-size: 24px;
color: var(--ink);
text-decoration: none;
line-height: 1.4;
margin: 0;
overflow-wrap: anywhere;
}
.channels__note {
font-size: 12px;
color: var(--muted);
line-height: 1.9;
}
@media (max-width: 900px) {
.whatsapp {
grid-template-columns: 1fr;
gap: 35px;
padding: 32px;
}
.whatsapp__action {
align-items: flex-start;
text-align: left;
}
.channels__grid {
grid-template-columns: 1fr 1fr;
}
}
@media (max-width: 600px) {
.whatsapp {
padding: 30px 22px;
}
.whatsapp__message {
padding: 16px;
}
.channels {
padding-top: 60px;
}
.channels__grid {
gap: 25px;
}
.channels__value {
font-size: 21px;
}
}
+165
View File
@@ -0,0 +1,165 @@
import { useState } from "react";
import { company } from "../data";
import {
Eyebrow,
PageHero,
SectionHeading,
WhatsAppAction,
} from "../components/Primitives";
import { ContactBanner } from "../components/Layout";
import "./Contact.css";
const message = "Hello RRS, I'd like to discuss a fabric requirement.";
export default function Contact() {
const [copyStatus, setCopyStatus] = useState("");
async function copyMessage() {
try {
await navigator.clipboard.writeText(message);
setCopyStatus("Message copied.");
} catch {
setCopyStatus(
"Could not copy automatically. Please select and copy the message above.",
);
}
}
const channels = [
{
title: "WhatsApp",
value: company.whatsapp || "[Number]",
href: "#whatsapp",
note: "Discuss a fabric requirement.",
},
{
title: "Phone",
value: company.phone || "[Number]",
href: company.phone ? `tel:${company.phone}` : null,
note: "A direct conversation with RRS.",
},
{
title: "Email",
value: company.email || "[Email Address]",
href: company.email ? `mailto:${company.email}` : null,
note: "Share your requirement or reference.",
},
{
title: "Address",
value: company.address || "[Full Business Address]",
href: company.mapsUrl || null,
note: "Factory / office location.",
},
];
return (
<>
<PageHero
label="Contact RRS"
title={
<>
Let’s talk
<br />
<em>fabric.</em>
</>
}
texture="structured"
>
<p>
Whether you’re discussing an existing requirement, bulk production or
a new fabric construction, connect directly with RRS.
</p>
</PageHero>
<section className="whatsapp" id="whatsapp" tabIndex={-1}>
<div className="whatsapp__copy">
<Eyebrow>A direct conversation</Eyebrow>
<h2 className="whatsapp__title">
Your next construction.
<br />
<em>Let’s begin here.</em>
</h2>
<p className="whatsapp__text">
Share your fabric requirement, intended application, composition or
reference with our team.
</p>
<blockquote className="whatsapp__message">{message}</blockquote>
<div className="whatsapp__clipboard">
<button
className="whatsapp__copy-button"
type="button"
onClick={copyMessage}
>
Copy conversation starter <span aria-hidden="true">↗</span>
</button>
<span className="whatsapp__copy-status" role="status">
{copyStatus}
</span>
</div>
</div>
<div className="whatsapp__action">
<svg
className="whatsapp__icon"
viewBox="0 0 48 48"
fill="none"
aria-hidden="true"
>
<path
d="M40 23a16 16 0 0 1-24 14L7 40l3-9A16 16 0 1 1 40 23Z"
stroke="currentColor"
strokeWidth="1.5"
/>
<path
d="M17 17c1 7 6 12 13 14l3-4-5-3-2 2c-3-1-5-3-6-6l2-2-3-4-2 3Z"
stroke="currentColor"
strokeWidth="1.5"
strokeLinejoin="round"
/>
</svg>
<WhatsAppAction direct />
<p className="whatsapp__status" id="whatsapp-status">
{company.whatsapp
? "Opens WhatsApp with a conversation starter."
: "Business WhatsApp number to be supplied. Chat will be available once details are confirmed."}
</p>
</div>
</section>
<section className="channels" id="direct-contact" tabIndex={-1}>
<SectionHeading
label="Direct contact"
title={
<>
A line <em>to RRS.</em>
</>
}
>
<p>
Contact information is awaiting confirmation. No phone numbers,
email addresses or locations have been assumed.
</p>
</SectionHeading>
<div className="channels__grid">
{channels.map(({ title, value, href, note }) => (
<article className="channels__item" key={title}>
<h3 className="channels__title">{title}</h3>
{href ? (
<a className="channels__value" href={href}>
{value}
</a>
) : (
<p className="channels__value">{value}</p>
)}
<p className="channels__note">{note}</p>
</article>
))}
</div>
</section>
<ContactBanner
title={
<>
Have a construction
<br />
<em>in mind?</em>
</>
}
text="Share your fabric requirement, intended application, composition or reference with our team and start a conversation about what can be developed."
/>
</>
);
}
+232
View File
@@ -0,0 +1,232 @@
.applications {
border-top: 1px solid var(--line);
padding-top: 70px;
scroll-margin-top: 30px;
}
.applications__list {
border-top: 1px solid var(--line);
}
.applications__item {
border-bottom: 1px solid var(--line);
}
.applications__summary {
display: flex;
align-items: center;
gap: 30px;
padding: 25px 4px;
list-style: none;
cursor: pointer;
}
.applications__summary::-webkit-details-marker {
display: none;
}
.applications__title {
font-family: var(--serif);
font-size: clamp(24px, 2.6vw, 35px);
letter-spacing: -0.03em;
}
.applications__toggle {
margin-left: auto;
width: 28px;
height: 28px;
border: 1px solid var(--line);
border-radius: 50%;
display: grid;
place-items: center;
flex: 0 0 auto;
}
.applications__toggle::after {
content: "+";
font-size: 18px;
font-weight: 300;
}
.applications__item[open] .applications__toggle::after {
content: "−";
}
.applications__text {
max-width: 680px;
margin: 0 40px 28px 4px;
color: var(--muted);
font-size: 14px;
line-height: 1.9;
}
.materials {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 100px;
padding: 100px 0;
}
.materials__title {
font-family: var(--serif);
font-size: clamp(34px, 4vw, 52px);
line-height: 1.13;
letter-spacing: -0.045em;
font-weight: 400;
margin: 0 0 28px;
}
.materials__text {
font-size: 14px;
color: var(--muted);
line-height: 1.9;
max-width: 400px;
}
.materials__note {
display: block;
margin-top: 26px;
font-size: 12px;
font-weight: 500;
color: var(--muted);
line-height: 1.8;
}
.materials__list {
list-style: none;
margin: 0;
padding: 0;
}
.materials__item {
padding: 16px 0;
border-bottom: 1px solid var(--line);
font-size: 14px;
line-height: 1.6;
}
.materials__item:first-child {
padding-top: 0;
}
.knitting {
display: grid;
grid-template-columns: 1.1fr 1fr;
gap: 65px;
padding: 60px;
border-radius: 5px;
background: var(--ink);
color: var(--paper);
scroll-margin-top: 25px;
}
.knitting__title {
font-family: var(--serif);
font-size: clamp(38px, 4.1vw, 56px);
line-height: 1.13;
letter-spacing: -0.04em;
font-weight: 400;
margin: 0 0 25px;
}
.knitting__text {
font-size: 13px;
color: #c2c3b8;
line-height: 1.9;
}
.knitting__diagram {
align-self: center;
margin: 0;
color: #c1c0a9;
}
.knitting__drawing {
display: block;
width: 100%;
height: auto;
}
.knitting__caption {
font-size: 11px;
text-align: center;
}
.knitting__variables {
display: flex;
justify-content: space-between;
gap: 10px;
margin-top: 28px;
padding-top: 18px;
border-top: 1px solid #ffffff25;
color: #c2c3b8;
font-size: 10px;
}
.production {
padding-top: 100px;
scroll-margin-top: 25px;
}
.production__proof {
display: grid;
grid-template-columns: 1.2fr 1fr;
align-items: center;
gap: 50px;
padding: 42px 50px;
background: var(--wash);
border-radius: 4px;
}
.production__number {
font-family: var(--serif);
font-size: clamp(80px, 10vw, 140px);
line-height: 1;
letter-spacing: -0.065em;
margin: 0;
}
.production__unit {
display: block;
margin-top: 18px;
font-family: var(--sans);
font-size: 13px;
font-weight: 500;
letter-spacing: 0;
}
.production__label {
display: block;
font-size: 13px;
font-weight: 500;
line-height: 1.8;
}
.production__text {
margin: 20px 0 28px;
max-width: 320px;
color: var(--muted);
font-size: 14px;
line-height: 1.9;
}
.production__rule {
display: block;
height: 20px;
border-block: 1px solid var(--line);
background: repeating-linear-gradient(
to right,
transparent 0 9px,
#7c806b65 9px 10px
);
}
@media (max-width: 850px) {
.materials {
gap: 45px;
}
.knitting {
padding: 35px;
gap: 25px;
}
.production__proof {
padding: 35px;
}
}
@media (max-width: 600px) {
.applications {
padding-top: 45px;
}
.applications__summary {
gap: 15px;
}
.applications__text {
margin-left: 49px;
}
.materials {
grid-template-columns: 1fr;
padding: 60px 0;
gap: 30px;
}
.knitting {
grid-template-columns: 1fr;
padding: 35px 24px;
}
.production {
padding-top: 60px;
}
.production__proof {
grid-template-columns: 1fr;
gap: 32px;
padding: 30px 24px;
}
}
+173
View File
@@ -0,0 +1,173 @@
import { applications, yarns } from "../data";
import { Eyebrow, PageHero, SectionHeading } from "../components/Primitives";
import { ContactBanner } from "../components/Layout";
import CapacityValue from "../components/CapacityValue";
import Gallery from "../components/Gallery";
import "./Fabrics.css";
export default function Fabrics() {
return (
<>
<PageHero
label="Fabrics & capabilities"
title={
<>
Different yarns.
<br />
<em>Different structures.</em>
</>
}
>
<p>
RRS manufactures greige knitted fabrics across diverse yarns, fibres
and blends, with constructions developed according to the required
texture, appearance and apparel application.
</p>
</PageHero>
<section className="applications" id="applications">
<SectionHeading
label="Applications"
title={
<>
A construction for
<br />
<em>the application.</em>
</>
}
>
<p>
Explore some of the ways yarn and structure come together. These are
examples, not the limits of our manufacturing capability.
</p>
</SectionHeading>
<div className="applications__list">
{applications.map(({ title, text }, index) => (
<details
className="applications__item"
key={title}
open={index === 0}
>
<summary className="applications__summary">
<span className="applications__title">{title}</span>
<span className="applications__toggle" aria-hidden="true" />
</summary>
<p className="applications__text">{text}</p>
</details>
))}
</div>
</section>
<section className="materials">
<div className="materials__intro">
<Eyebrow>Materials & yarns</Eyebrow>
<h2 className="materials__title">
The structure starts
<br />
<em>with the yarn.</em>
</h2>
<p className="materials__text">
Different fabric designs require different yarn characteristics. RRS
manufactures fabrics using a wide variety of yarns and blends,
selected around the construction.
</p>
<span className="materials__note">
We manufacture the fabric, not the yarn.
</span>
</div>
<ul className="materials__list">
{yarns.map((yarn) => (
<li className="materials__item" key={yarn}>
{yarn}
</li>
))}
</ul>
</section>
<section className="knitting" id="circular-knitting" tabIndex={-1}>
<div className="knitting__copy">
<Eyebrow>Circular knitting</Eyebrow>
<h2 className="knitting__title">
One medium.
<br />
<em>Many constructions.</em>
</h2>
<p className="knitting__text">
Extensive circular-knitting capability allows RRS to change yarn,
composition, structure and machine configuration according to the
fabric being developed.
</p>
<p className="knitting__text">
The result is a broad manufacturing vocabulary ranging from
understated shirting constructions to highly textured knitted
surfaces.
</p>
</div>
<figure className="knitting__diagram">
<svg
className="knitting__drawing"
viewBox="0 0 420 350"
fill="none"
role="img"
aria-label="Conceptual circular knitting illustration, not a machine specification"
>
<ellipse cx="210" cy="90" rx="148" ry="50" stroke="currentColor" />
<ellipse cx="210" cy="90" rx="116" ry="33" stroke="currentColor" />
<path
d="M62 90v150c0 68 296 68 296 0V90M92 120v145m38-133v147m40-141v148m40-146v149m40-151v148m40-154v147m38-159v145"
stroke="currentColor"
strokeDasharray="3 4"
/>
<ellipse cx="210" cy="238" rx="148" ry="50" stroke="currentColor" />
<path
d="M210 9v38m0 241v45M30 163h30m300 0h30"
stroke="currentColor"
/>
<circle cx="210" cy="90" r="4" fill="currentColor" />
</svg>
<figcaption className="knitting__caption">
Circular knitting / Concept illustration
</figcaption>
<div className="knitting__variables">
<span>Yarn</span>
<span>Composition</span>
<span>Structure</span>
</div>
</figure>
</section>
<section className="production" id="production" tabIndex={-1}>
<SectionHeading
label="Production"
title={
<>
Developed
<br />
<em>to scale.</em>
</>
}
>
<p>
Development is only one part of the process. RRS is built to
reproduce successful constructions consistently across commercial
quantities.
</p>
</SectionHeading>
<div className="production__proof">
<p className="production__number">
<CapacityValue />
<span className="production__unit">tonnes / month</span>
</p>
<div className="production__detail">
<span className="production__label">
Approximate monthly production capacity
</span>
<p className="production__text">
Consistency across bulk orders remains one of our core
manufacturing priorities.
</p>
<span className="production__rule" aria-hidden="true" />
</div>
</div>
</section>
<Gallery />
<ContactBanner />
</>
);
}
+219
View File
@@ -0,0 +1,219 @@
.home-hero {
position: relative;
display: flex;
flex-direction: column;
justify-content: center;
overflow: hidden;
min-height: 590px;
margin-top: 24px;
border-radius: 7px;
color: var(--paper);
background: var(--ink);
}
.home-hero__image {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
object-fit: cover;
object-position: center 65%;
}
.home-hero__shade {
position: absolute;
inset: 0;
background: #1419108f;
}
.home-hero__content {
position: relative;
z-index: 1;
padding: 65px 62px 90px;
}
.home-hero__title {
margin: 38px 0 25px;
font-family: var(--serif);
font-size: clamp(54px, 6.7vw, 94px);
font-weight: 400;
line-height: 1.02;
letter-spacing: -0.055em;
}
.home-hero__description {
max-width: 400px;
margin: 0 0 30px;
color: #eeece5;
font-size: 15px;
line-height: 1.8;
}
.home-hero__actions {
display: flex;
flex-wrap: wrap;
gap: 12px;
}
.home-hero__bottom {
position: absolute;
bottom: 0;
left: 32px;
right: 32px;
display: flex;
justify-content: space-between;
gap: 20px;
padding: 20px 0;
border-top: 1px solid #ffffff35;
font-size: 11px;
line-height: 1.6;
}
.home-hero__tagline {
font-size: clamp(14px, 1.2vw, 16px);
font-weight: 500;
color: #ffffff;
letter-spacing: 0.01em;
}
.home-hero__image-note {
color: #e2e2dc;
}
.home-intro {
display: grid;
gap: 44px;
padding-top: 92px;
}
.home-intro__heading {
text-align: center;
}
.home-intro__body {
width: min(100%, 820px);
margin-inline: auto;
}
.home-intro__title {
margin: 0;
font-family: var(--serif);
font-size: clamp(36px, 4.2vw, 57px);
font-weight: 400;
line-height: 1.08;
letter-spacing: -0.045em;
}
.home-intro__lead {
font-family: var(--serif);
font-size: clamp(25px, 2.5vw, 34px);
line-height: 1.4;
letter-spacing: -0.015em;
margin: 0 0 24px;
}
.home-intro__text {
color: var(--muted);
font-size: 14px;
line-height: 1.9;
margin: 0 0 15px;
}
.home-intro__link {
display: inline-flex;
align-items: center;
gap: 30px;
margin-top: 12px;
padding-bottom: 7px;
border-bottom: 1px solid var(--ink);
color: var(--ink);
font-size: 11px;
text-decoration: none;
}
.process {
padding-top: 95px;
}
.process__list {
border-top: 1px solid var(--line);
}
.process__item {
display: grid;
grid-template-columns: minmax(0, 0.9fr) minmax(0, 1.1fr);
align-items: center;
gap: clamp(45px, 7vw, 120px);
padding: 58px 0;
border-bottom: 1px solid var(--line);
}
.process__item:nth-child(even) .process__visual {
order: -1;
}
.process__copy {
max-width: 560px;
padding-inline: clamp(0px, 2vw, 34px);
}
.process__verb {
margin: 0 0 24px;
font-size: 14px;
font-weight: 500;
color: var(--muted);
}
.process__title {
margin: 0 0 22px;
font-family: var(--serif);
font-size: clamp(35px, 3.3vw, 52px);
font-weight: 400;
line-height: 1.08;
letter-spacing: -0.04em;
}
.process__text {
margin: 0 0 28px;
color: var(--muted);
font-size: 14px;
line-height: 1.9;
}
.process__link {
display: inline-flex;
align-items: center;
gap: 24px;
padding-bottom: 7px;
border-bottom: 1px solid var(--ink);
color: var(--ink);
font-size: 12px;
text-decoration: none;
}
.process__visual {
width: 100%;
min-height: 320px;
aspect-ratio: 16 / 10;
}
.process__link:hover {
color: var(--accent);
}
@media (max-width: 760px) {
.home-hero {
margin-top: 18px;
min-height: 560px;
}
.home-hero__content {
padding: 42px 26px 130px;
}
.home-hero__title {
font-size: clamp(44px, 8.9vw, 68px);
margin-top: 32px;
}
.home-hero__bottom {
left: 26px;
right: 26px;
flex-direction: column;
gap: 8px;
padding: 15px 0;
}
.home-intro {
gap: 30px;
padding-top: 55px;
}
.process {
padding-top: 60px;
}
.process__item {
grid-template-columns: 1fr;
gap: 28px;
padding: 42px 0;
}
.process__visual,
.process__item:nth-child(even) .process__visual {
order: -1;
}
.process__copy {
max-width: none;
padding-inline: 0;
}
.process__visual {
min-height: 280px;
aspect-ratio: 4 / 3;
}
}
+137
View File
@@ -0,0 +1,137 @@
import { Link } from "react-router-dom";
import { capabilities, company } 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",
},
];
export default function Home() {
return (
<>
<section className="home-hero">
<img
className="home-hero__image"
src="/images/knit-natural.jpg"
alt="Original illustrative macro knit texture, pending RRS photography"
width="1600"
height="1200"
fetchPriority="high"
/>
<div className="home-hero__shade" />
<div className="home-hero__content">
<Eyebrow>Circular knitted fabrics · 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>
<div className="home-hero__actions">
<Action to="/fabrics" variant="light">
Explore our fabrics
</Action>
<WhatsAppAction variant="ghost">Talk to RRS</WhatsAppAction>
</div>
</div>
<div className="home-hero__bottom">
<span className="home-hero__tagline">
Consistency at scale. Continuous development.
</span>
<span className="home-hero__image-note">
Illustrative texture · Photography to follow
</span>
</div>
</section>
<Gallery preview />
<section className="home-intro">
<div className="home-intro__heading">
<Eyebrow>Our fabrics</Eyebrow>
<h2 className="home-intro__title">
Greige is where
<br />
<em>it begins.</em>
</h2>
</div>
<div className="home-intro__body">
<p className="home-intro__lead">
RRS specialises in circular-knitted greige fabrics developed for
varied apparel applications.
</p>
<p className="home-intro__text">
Different constructions require different yarns, blends and machine
configurations. Our manufacturing capability allows us to develop
fabrics around structure, texture, composition and intended use.
</p>
<p className="home-intro__text">
From shirting constructions to textured and structured knits, we
work across polyester, cotton, rayon, stretch yarns and varied
blends.
</p>
<Link className="home-intro__link" to="/about">
Get to know RRS <Arrow diagonal />
</Link>
</div>
</section>
<Stats />
<section className="process">
<SectionHeading
label="How we work"
title={
<>
Behind the fabric:
<br />
<em>our process.</em>
</>
}
>
<p>
Development, circular knitting and repeatable bulk production work
as one discipline.
</p>
</SectionHeading>
<div className="process__list">
{processItems.map(({ title, subtitle, text, image, href }) => (
<article className="process__item" key={title}>
<div className="process__copy">
<p className="process__verb">{title}</p>
<h3 className="process__title">{subtitle}</h3>
<p className="process__text">{text}</p>
<Link className="process__link" to={href}>
Explore this capability <Arrow diagonal />
</Link>
</div>
<Textile className="process__visual" kind={image} />
</article>
))}
</div>
</section>
<ContactBanner />
</>
);
}