RRS First version
This commit is contained in:
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user