Brand-presence site for J.R & Sons, a multi-brand menswear store and bespoke tailoring atelier. Not e-commerce: there are no prices, no cart, no checkout.
npm install
npm run dev # http://localhost:5180
npm run build # -> dist/1. Contact details are placeholders. Everything under contact in
src/data/site.js is invented, address, phone, WhatsApp number, email, hours.
Replace all of it. The phone number appears as a tel: link and the WhatsApp
number is embedded in a wa.me URL, so both must be real.
2. Photography is licensed stock from Pexels. See
public/img/CREDITS.md for photographer and source URL per file. Pexels does
not require attribution, but these are generic stock images of other people's
workshops. Replace them with photographs of the actual store, workshop and
tailors as soon as you have them, that is the single biggest upgrade available
to this site.
3. Check the retail brand list in src/data/site.js (retailBrands)
matches what is actually on the floor. Brand names are set as type, not logos;
using the brands' own logo files may require their permission.
4. Outfit count formatting. 688,000 renders as 6,88,000 (Indian lakh
grouping) via toLocaleString('en-IN') in src/lib/motion.jsx. Change the
format prop on <Counter> if you want international grouping.
Vite + React + React Router. No CSS framework, the design system is plain CSS
custom properties in src/styles/global.css, and each page owns a scoped
stylesheet beside it.
The brief asks for retail energy and bespoke royalty to feel distinct. That's
done with a data-world attribute set per route in App.jsx:
atelier, dark ink ground, Fraunces serif, brass accent. Home, Bespoke, Craft.store, light sand ground, wide heavy Archivo, house red accent. About, Retail, Contact.
Both worlds re-point the same token names (--bg, --fg, --accent, glass
tints), so components work in either without conditional styling.
/ renders Gateway.jsx on a first visit, a tailor's pattern draft split by a
stitched seam, with two doors. Choosing one sets a sessionStorage flag, after
which / redirects to /home. Clearing session storage brings it back.
src/lib/motion.jsx holds the primitives: Reveal, Parallax, Counter,
plus Lenis smooth scroll. All parallax layers share one rAF-throttled scroll
listener rather than one per element.
Two robustness notes, both deliberate:
- Scroll-reveal styles are scoped to
.js(set inmain.jsx). Without scripting, content renders visible rather than stuck atopacity: 0. <Counter>renders its true value by default and only drops to zero once it knows it can animate, so the page never states "0 outfits delivered".
prefers-reduced-motion disables parallax, reveals, counters and the gateway's
draw-in sequence.
scripts/logo.mjs regenerates public/logo-mark.png from
assets/logo.jpeg: it finds the red mark's bounding box, crops square, and
knocks the white paper out to transparency using saturation (which drops the
source's grey diagonal watermark). Re-run with node scripts/logo.mjs if the
source logo changes.