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
+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>
);
}