final website is ready babeeehhhh

This commit is contained in:
2026-10-05 23:57:08 -04:00
parent 845d846808
commit 53f8a961c0
17 changed files with 126 additions and 252 deletions
+2 -2
View File
@@ -6,10 +6,10 @@
<meta name="theme-color" content="#f5f3ee" />
<meta
name="description"
content="Radhe Radhe Synthetics — greige knitted fabrics engineered for diverse apparel applications. Consistency at scale. Continuous development."
content="Radhe Radhe Synthetics - greige knitted fabrics engineered for diverse apparel applications. Consistency at scale. Continuous development."
/>
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<title>RRS — Radhe Radhe Synthetics</title>
<title>RRS - Radhe Radhe Synthetics</title>
</head>
<body>
<div id="root"></div>
+1 -1
View File
@@ -21,7 +21,7 @@ export default function CapacityValue() {
<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>
+3 -2
View File
@@ -27,7 +27,8 @@
object-fit: cover;
transition: transform 700ms ease;
}
.sample:hover .sample__image {
/* Hover zoom only on the home page's featured developments. */
.gallery--preview .sample:hover .sample__image {
transform: scale(1.045);
}
.sample__badge {
@@ -119,7 +120,7 @@
.sample__image {
transition: none;
}
.sample:hover .sample__image {
.gallery--preview .sample:hover .sample__image {
transform: none;
}
}
+6 -3
View File
@@ -15,7 +15,11 @@ const textures = [
export default function Gallery({ preview = false }) {
const items = preview ? developments.slice(0, 3) : developments;
return (
<section className="gallery" id="developments" tabIndex={-1}>
<section
className={`gallery${preview ? " gallery--preview" : ""}`}
id="developments"
tabIndex={-1}
>
<SectionHeading
centered
label={
@@ -40,7 +44,7 @@ export default function Gallery({ preview = false }) {
<p className="gallery__intro">
{preview
? "A selection of knitted structures showing how yarn, composition and machine setup shape the final fabric character."
: "Selected developments are processed separately as reference samples to explore how a construction can translate after dyeing and finishing. RRS commercially manufactures grey fabric only."}
: "Selected developments are processed separately as samples to explore how a construction can translate after dyeing and finishing. RRS commercially manufactures grey fabric only."}
</p>
{preview && (
<Action to="/fabrics#developments" variant="outline">
@@ -63,7 +67,6 @@ export default function Gallery({ preview = false }) {
</div>
<div className="sample__heading">
<h3 className="sample__name">{name}</h3>
<span className="sample__detail">Development reference</span>
</div>
</article>
))}
+1 -6
View File
@@ -48,22 +48,17 @@
animation-delay: 25s;
}
/* Each slide fades IN over the previous one (which stays opaque beneath it),
so there is never a moment with no image on screen — works for any count. */
so there is never a moment with no image on screen - works for any count. */
@keyframes carousel-fade {
0% {
opacity: 0;
transform: scale(1);
}
4%,
96% {
opacity: 1;
}
96% {
transform: scale(1.015);
}
100% {
opacity: 0;
transform: scale(1.015);
}
}
@media (prefers-reduced-motion: reduce) {
+1 -6
View File
@@ -176,11 +176,6 @@
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;
@@ -209,7 +204,7 @@
margin-top: 38px;
padding-top: 23px;
border-top: 1px solid var(--line);
font-family: var(--mono);
font-family: var(--sans);
font-size: 8px;
line-height: 1.6;
text-transform: uppercase;
+5 -16
View File
@@ -1,6 +1,6 @@
import { useEffect, useState } from "react";
import { Link, NavLink, Outlet, useLocation } from "react-router-dom";
import { company, navigation } from "../data";
import { company, navigation, phoneHref } from "../data";
import { Arrow, WhatsAppAction } from "./Primitives";
import "./Layout.css";
@@ -35,17 +35,9 @@ function Header() {
<div className="contact-strip">
<span>Contact RRS</span>
<div className="contact-strip__details">
{company.email ? (
<a href={`mailto:${company.email}`}>{company.email}</a>
) : (
<span>Email to be supplied</span>
)}
<a href={`mailto:${company.email}`}>{company.email}</a>
<span aria-hidden="true">·</span>
{company.phone ? (
<a href={`tel:${company.phone}`}>{company.phone}</a>
) : (
<span>Phone to be supplied</span>
)}
<a href={phoneHref(company.phone)}>{company.phone}</a>
</div>
</div>
<div className="site-header__inner">
@@ -84,7 +76,7 @@ function Header() {
to="/contact#whatsapp"
onClick={closeMenu}
>
Talk to us <Arrow diagonal />
Text us on WhatsApp <Arrow diagonal />
</Link>
</nav>
</div>
@@ -116,9 +108,6 @@ function Footer() {
<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">
@@ -178,7 +167,7 @@ export default function Layout() {
useEffect(() => {
const title =
navigation.find(({ to }) => to === pathname)?.label ?? "Page not found";
document.title = `${title === "Home" ? "RRS" : `${title} — RRS`} | Radhe Radhe Synthetics`;
document.title = `${title === "Home" ? "RRS" : `${title} - RRS`} | Radhe Radhe Synthetics`;
const target = hash
? document.getElementById(hash.slice(1))
: document.getElementById("main-content");
+10 -20
View File
@@ -52,6 +52,16 @@
cursor: not-allowed;
opacity: 0.55;
}
.action__label {
display: inline-flex;
align-items: center;
gap: 8px;
}
.action__icon {
flex: 0 0 auto;
width: 15px;
height: 15px;
}
.eyebrow {
margin: 0 0 18px;
font-family: var(--sans);
@@ -107,23 +117,6 @@
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;
@@ -188,7 +181,4 @@
.textile__image {
transition: none;
}
.textile:hover .textile__image {
transform: none;
}
}
+35 -15
View File
@@ -32,17 +32,47 @@ export function Action({ children, to, variant = "dark" }) {
);
}
export function WhatsAppIcon({ className = "", strokeWidth = 2.4 }) {
return (
<svg
className={className}
viewBox="0 0 48 48"
fill="none"
aria-hidden="true"
focusable="false"
>
<path
d="M40 23a16 16 0 0 1-24 14L7 40l3-9A16 16 0 1 1 40 23Z"
stroke="currentColor"
strokeWidth={strokeWidth}
/>
<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={strokeWidth}
strokeLinejoin="round"
/>
</svg>
);
}
export function WhatsAppAction({
children = "Talk to us",
children = "Text us on WhatsApp",
variant = "dark",
direct = false,
}) {
const url = whatsappUrl(company.whatsapp);
const className = `action action--${variant}`;
const label = (
<span className="action__label">
<WhatsAppIcon className="action__icon" />
{children}
</span>
);
if (url)
return (
<a className={className} href={url} target="_blank" rel="noreferrer">
{children}
{label}
<Arrow diagonal />
</a>
);
@@ -54,7 +84,7 @@ export function WhatsAppAction({
disabled
aria-describedby="whatsapp-status"
>
{children}
{label}
<Arrow diagonal />
</button>
);
@@ -83,27 +113,17 @@ export function SectionHeading({ label, title, children, centered = false }) {
);
}
export function Textile({
src,
className = "",
caption = true,
eager = false,
}) {
export function Textile({ src, className = "", eager = false }) {
return (
<figure className={`textile ${className}`}>
<img
className="textile__image"
src={src}
alt="Reference fabric photograph"
alt="Fabric photograph"
width="1600"
height="1200"
loading={eager ? "eager" : "lazy"}
/>
{caption && (
<figcaption className="textile__caption">
Reference fabric
</figcaption>
)}
</figure>
);
}
+20 -13
View File
@@ -1,13 +1,16 @@
const address =
"508, Accord Classic, Jay Prakash Nagar, Above Anupam Stationary, Goregaon East, Mumbai, Maharashtra - 400 063";
export const company = Object.freeze({
name: "Radhe Radhe Synthetics Pvt. Ltd.",
shortName: "RadheRadhe Pvt. Ltd.",
description:
"Grey knitted fabrics engineered for diverse apparel applications.",
whatsapp: "", // Supply the business number in international digits, without + or spaces.
phone: "",
email: "",
address: "",
mapsUrl: "",
whatsapp: "919769665899", // International digits, no + or spaces.
phone: "+91 97696 65899",
email: "hituagarwal2905@gmail.com",
address,
mapsUrl: `https://www.google.com/maps/search/?api=1&query=${encodeURIComponent(address)}`,
});
export const navigation = [
@@ -115,17 +118,21 @@ export const yarns = [
"Other yarns selected according to fabric requirements",
];
// Development names are references, not technical categories or specifications.
// Development names are sample labels, not technical categories or specifications.
export const developments = [
"Micro Break",
"Luxe",
"Jerry",
"Killer",
"Joker",
"Ice Cool",
"Maple",
"Lehar",
"Shahi",
"Jhalar",
"Vajra",
"Natkhat",
"Chandni",
"Ashoka",
];
export function phoneHref(number) {
return number ? `tel:+${number.replace(/\D/g, "")}` : null;
}
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.")}`
-55
View File
@@ -58,47 +58,10 @@
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;
}
@@ -146,15 +109,6 @@
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;
@@ -211,15 +165,6 @@
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;
+16 -59
View File
@@ -20,7 +20,7 @@ const process = [
},
{
title: "Test.",
text: "Selected constructions are evaluated through processed reference samples to explore their possibilities.",
text: "Selected constructions are evaluated through processed samples to explore their possibilities.",
},
{
title: "Repeat.",
@@ -28,15 +28,14 @@ const process = [
},
];
function PortraitPlaceholder({ number }) {
function Portrait({ number }) {
return (
<figure className="owner">
<div
className="owner__portrait"
role="img"
aria-label={`Owner ${number} portrait placeholder; professional photograph to be supplied`}
aria-label={`Owner ${number} portrait`}
>
<span className="owner__image-label">Portrait placeholder</span>
<svg
className="owner__silhouette"
viewBox="0 0 200 230"
@@ -59,20 +58,7 @@ function PortraitPlaceholder({ number }) {
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>
);
}
@@ -135,15 +121,11 @@ export default function About() {
</>
}
>
<p>
Meet our owners and directors.
<br />
Names, roles and biographies will be added when supplied.
</p>
<p>Meet our owners and directors.</p>
</SectionHeading>
<div className="leadership__grid">
<PortraitPlaceholder number={1} />
<PortraitPlaceholder number={2} />
<Portrait number={1} />
<Portrait number={2} />
</div>
</section>
<section className="approach">
@@ -177,11 +159,8 @@ export default function About() {
<div
className="location__map"
role="img"
aria-label="Map placeholder. Verified factory or office address has not yet been supplied."
aria-label="Decorative map graphic."
>
<span className="location__map-label">
Map placeholder / Not a geographical location
</span>
<div className="location__pin">
<span className="location__pin-dot" />
<span>RRS</span>
@@ -193,37 +172,15 @@ export default function About() {
<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>
<p className="location__address">{company.address}</p>
<a
className="location__button"
href={company.mapsUrl}
target="_blank"
rel="noreferrer"
>
Open in Google Maps ↗
</a>
</div>
</div>
</section>
+1 -1
View File
@@ -86,7 +86,7 @@
}
.channels__grid {
display: grid;
grid-template-columns: repeat(4, minmax(0, 1fr));
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: 30px;
}
.channels__item {
+14 -38
View File
@@ -1,10 +1,11 @@
import { useState } from "react";
import { company } from "../data";
import { company, phoneHref } from "../data";
import {
Eyebrow,
PageHero,
SectionHeading,
WhatsAppAction,
WhatsAppIcon,
} from "../components/Primitives";
import { ContactBanner } from "../components/Layout";
import "./Contact.css";
@@ -24,27 +25,21 @@ export default function Contact() {
}
}
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,
value: company.phone,
href: phoneHref(company.phone),
note: "A direct conversation with RRS.",
},
{
title: "Email",
value: company.email || "[Email Address]",
value: company.email,
href: company.email ? `mailto:${company.email}` : null,
note: "Share your requirement or reference.",
note: "Share your requirement.",
},
{
title: "Address",
value: company.address || "[Full Business Address]",
value: company.address,
href: company.mapsUrl || null,
note: "Factory / office location.",
},
@@ -76,8 +71,8 @@ export default function Contact() {
<em>Let’s begin here.</em>
</h2>
<p className="whatsapp__text">
Share your fabric requirement, intended application, composition or
reference with our team.
Share your fabric requirement, intended application, composition
with our team.
</p>
<blockquote className="whatsapp__message">{message}</blockquote>
<div className="whatsapp__clipboard">
@@ -94,29 +89,10 @@ export default function Contact() {
</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>
<WhatsAppIcon className="whatsapp__icon" strokeWidth={1.5} />
<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."}
Opens WhatsApp with a conversation starter.
</p>
</div>
</section>
@@ -130,8 +106,8 @@ export default function Contact() {
}
>
<p>
Contact information is awaiting confirmation. No phone numbers,
email addresses or locations have been assumed.
Reach RRS directly on WhatsApp or by phone or email - a direct
manufacturing conversation is the fastest way to begin.
</p>
</SectionHeading>
<div className="channels__grid">
@@ -158,7 +134,7 @@ export default function Contact() {
<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."
text="Share your fabric requirement, intended application, composition with our team and start a conversation about what can be developed."
/>
</>
);
+1 -4
View File
@@ -67,9 +67,6 @@
color: var(--ink);
letter-spacing: 0.01em;
}
.home-hero__image-note {
color: var(--muted);
}
.home-intro {
display: grid;
gap: 44px;
@@ -140,7 +137,7 @@
}
.process__verb {
margin: 0 0 24px;
font-family: var(--mono);
font-family: var(--sans);
font-size: 11px;
font-weight: 500;
color: var(--industrial);
+2 -5
View File
@@ -39,14 +39,14 @@ export default function Home() {
</h1>
<p className="home-hero__description">
Grey knitted fabrics developed around yarn, structure and
application—then manufactured for consistent commercial supply.
application - then manufactured for consistent commercial supply.
</p>
<div className="home-hero__actions">
<Action to="/fabrics" variant="dark">
Explore our fabrics
</Action>
<Action to="/contact#whatsapp" variant="outline">
Talk to us
Text us on WhatsApp
</Action>
</div>
</div>
@@ -54,9 +54,6 @@ export default function Home() {
<span className="home-hero__tagline">
Development · Circular knitting · Bulk production
</span>
<span className="home-hero__image-note">
Factory photography scheduled for content handover
</span>
</div>
</section>
<Stats />
+8 -6
View File
@@ -81,22 +81,24 @@ test("desktop navigation, anchored CTA and browser back work", async ({
).toHaveAttribute("aria-current", "page");
await page
.locator(".contact-banner")
.getByRole("link", { name: "Talk to us", exact: true })
.getByRole("link", { name: "Text us on WhatsApp", exact: true })
.click();
await expect(page).toHaveURL("/contact#whatsapp");
await expect(page.locator("#whatsapp")).toBeInViewport();
await expect(
page.getByRole("button", { name: "Talk to us", exact: true }),
).toBeDisabled();
page
.locator(".whatsapp")
.getByRole("link", { name: "Text us on WhatsApp", exact: true }),
).toHaveAttribute("href", /wa\.me/);
await expect(page.locator("#whatsapp-status")).toContainText(
"number to be supplied",
"Opens WhatsApp",
);
await page.goBack();
await expect(page).toHaveURL("/fabrics");
await page.goto("/contact#whatsapp");
await page
.locator(".contact-banner")
.getByRole("link", { name: "Talk to us", exact: true })
.getByRole("link", { name: "Text us on WhatsApp", exact: true })
.click();
await expect(page.locator("#whatsapp")).toBeInViewport();
});
@@ -240,7 +242,7 @@ test("home and products heroes cycle through four product images", async ({
await expect(page.getByText("Women’s wear", { exact: true })).toBeVisible();
});
test("capacity values roll in opposite directions and settle at 60–70", async ({
test("capacity values roll in opposite directions and settle at 60-70", async ({
page,
}) => {
for (const path of ["/", "/fabrics"]) {