// Page sections for HOS
/* ───────────── Announcement bar (marquee) ───────────── */
function Announcement() {
const { t } = useLang();
const messages = [
t('New · Summer Édition is now online', 'Nouveau · l’Édition Été est en ligne'),
t('Become a member — early access to drops', 'Devenez membre — accès anticipé aux drops'),
t('Follow @houseofsheylas', 'Suivez @houseofsheylas'),
t('Discover the Summer lookbook', 'Découvrez le lookbook Été'),
];
const row = [...messages, ...messages]; // duplicate for seamless marquee
return (
);
}
/* ───────────── Hero ─────────────
Cinematic full-bleed video. French editorial: a woman in a flowing gown
walking through a glass garden pergola. Nav floats over it in ivory.
*/
function Hero() {
const { t, lang } = useLang();
const { siteData } = useSiteData();
const v = HERO_VIDEOS[0];
// Homepage CMS: read editable hero content (bilingual) with the original copy as fallback.
const hp = (siteData.homepage && siteData.homepage.hero) || {};
const hv = (field, en, fr) => {
const node = hp[field];
if (node && typeof node === 'object') return node[lang] || node.en || t(en, fr);
if (typeof node === 'string' && node.trim()) return node;
return t(en, fr);
};
const videoSrc = (typeof hp.videoUrl === 'string' && hp.videoUrl.trim()) ? hp.videoUrl : '/uploads/hero.mp4';
return (
<>
{/* Background video — House of Sheylas brand film (single source, no foreign poster flash) */}
{/* faint film grain */}
{/* Content */}
{/* Meta row, clearing the nav */}
{hv('metaLeft', 'Boutique · Paris · Est. 2025', 'Boutique · Paris · Depuis 2025')}
{hv('metaRight', 'N°08 · Summer', 'N°08 · Été')}
{/* Bottom editorial block */}
{hv('eyebrow', 'Summer · Édition 2026', 'Édition Été 2026')}
{hv('titleLine', 'Soft elegance', 'Élégance douce')}
{hv('titleAccent', 'for every woman.', 'pour chaque femme.')}
{hv('description',
'A curated edit of feminine pieces — selected to make you feel confident, graceful and unforgettable.',
'Une sélection de pièces féminines — choisies pour vous sentir confiante, gracieuse et inoubliable.'
)}
);
}
// Read an editable homepage section heading (bilingual) with a fallback.
function homepageSectionGetter(siteData, lang, t) {
const sec = (siteData.homepage && siteData.homepage.sections) || {};
return (field, en, fr) => {
const n = sec[field];
if (n && typeof n === 'object') return n[lang] || n.en || t(en, fr);
return (typeof n === 'string' && n.trim()) ? n : t(en, fr);
};
}
function Categories() {
const { t, lang } = useLang();
const { siteData } = useSiteData();
const sv = homepageSectionGetter(siteData, lang, t);
const categories = (siteData.categories || []).filter((c) => c && c.name);
if (!categories.length) return null;
return (
{sv('categoriesEyebrow', 'Curated for her', 'Sélection pour elle')}
{sv('categoriesTitle', 'Shop by category', 'Par catégorie')}
{t(
'From everyday softness to occasion-ready silhouettes — find the piece your wardrobe is missing.',
'De la douceur du quotidien aux silhouettes pour les grandes occasions — trouvez la pièce qui manque à votre vestiaire.'
)}
);
}
/* ───────────── Brand story ───────────── */
// Read an editable homepage group field (bilingual {en,fr} or plain string) with a fallback.
function homepageGroupGetter(siteData, lang, t, group) {
const g = (siteData.homepage && siteData.homepage[group]) || {};
return {
val: (field, en, fr) => {
const n = g[field];
if (n && typeof n === 'object') return n[lang] || n.en || t(en, fr);
return (typeof n === 'string' && n.trim()) ? n : t(en, fr);
},
raw: (field, fallback) => {
const n = g[field];
return (typeof n === 'string' && n.trim()) ? n : fallback;
},
};
}
function BrandStory() {
const { t, lang } = useLang();
const { siteData } = useSiteData();
const s = homepageGroupGetter(siteData, lang, t, 'story');
const imgPrimary = s.raw('imagePrimary', STORY_IMG_1);
const imgSecondary = s.raw('imageSecondary', STORY_IMG_2);
return (
{/* Texture */}
{/* Image collage */}
{/* Copy */}
{s.val('eyebrow', 'Our story', 'Notre histoire')}
{s.val('titleLine1', 'For women who love', 'Pour les femmes qui aiment')}
{s.val('titleLine2', 'elegance, confidence,', 'l’élégance, la confiance,')} {s.val('titleLine3', 'and timeless femininity.', 'et la féminité intemporelle.')}
{s.val('body',
'House of Sheylas is a curated boutique, bringing together feminine, elevated pieces — chosen with care for the women who wear them.',
'House of Sheylas est une boutique qui rassemble des pièces féminines et raffinées — choisies avec soin pour les femmes qui les portent.'
)}
{[
[t('Curated','Sélection'), t('Hand-picked pieces', 'Pièces choisies')],
[t('Edit','Édit'), t('Refined & feminine', 'Raffinées & féminines')],
[t('For her','Pour elle'), t('Women who dress for themselves','Pour les femmes qui s’habillent pour elles')],
].map(([k,v]) => (
{k}
{v}
))}
);
}
/* ───────────── Best sellers ───────────── */
// Best sellers = ONLY products the admin badges as best-seller / hot / most-loved /
// restocked. Featured ("Hero") products are intentionally excluded so a New-badged hero
// piece doesn't leak in here. Capped to 10.
function selectBest(products = []) {
return products
.filter((p) => /best|most|loved|restock|réassort|seller|hot/i.test(String(p.badge || '')))
.slice(0, 10);
}
function BestSellers() {
const cart = useCart();
const { t, lang } = useLang();
const { siteData } = useSiteData();
const sv = homepageSectionGetter(siteData, lang, t);
const items = selectBest(siteData.products || []);
if (!items.length) return null;
return (
{sv('bestEyebrow', 'Always in rotation', 'Toujours en rotation')}
{t(
'The pieces our community returns for — restocked, re-loved, and rarely in stock for long.',
'Les pièces que notre communauté redemande — réassorties, ré-adorées, et rarement en stock longtemps.'
)}
{t(
'Get early access to new drops, private sales, and styling inspiration delivered with care to your inbox.',
'Accès anticipé aux nouvelles collections, ventes privées et inspirations de style — livré dans votre boîte mail avec soin.'
)}