/* global React */
// ============================================
// A_LIZ HAIRDRESSER — Home page
// ============================================
const { SERVICES, PARTNERS, REVIEWS, GALLERY } = window.AL_DATA;
const { useLang, useT, useRoute, useReveal, useTilt, useCountUp, Icon, CtaStrip, WA_LINK, BrandLogo, usePageSEO } = window;
const { useState, useEffect, useRef, useMemo, useCallback } = React;
// ========== PETALS ==========
function Petals({ count = 18 }) {
// Pre-compute petal placements so they're stable across renders
const petals = React.useMemo(
() =>
Array.from({ length: count }).map((_, i) => {
const left = Math.random() * 100;
const delay = -Math.random() * 18;
const fall = 16 + Math.random() * 14;
const sway = 4 + Math.random() * 5;
const scale = 0.6 + Math.random() * 0.9;
const op = 0.12 + Math.random() * 0.22;
return { left, delay, fall, sway, scale, op, key: i };
}),
[count]
);
return (
{petals.map((p) => (
))}
);
}
// ========== FLOW DIVIDER ==========
function FlowDivider() {
return (
);
}
// ========== HERO ==========
function Hero() {
const t = useT();
const { lang } = useLang();
const { go } = useRoute();
const stageRef = useRef(null);
const [slide, setSlide] = useState(0);
const [prevSlide, setPrevSlide] = useState(null);
const slideIdxRef = useRef(0);
slideIdxRef.current = slide;
const goToSlide = useCallback((next) => {
if (next === slideIdxRef.current) return;
setPrevSlide(slideIdxRef.current);
setSlide(next);
setTimeout(() => setPrevSlide(null), 980);
}, []);
const slidesData = [
{ src: "assets/img/hero-2.webp", label: "SALON", title: "Behind the chair" },
{ src: "assets/img/style-3.webp", label: "RENK", title: "Honey highlights" },
{ src: "assets/img/style-1.webp", label: "FÖN", title: "Soft volume wave" },
{ src: "assets/img/style-6.webp", label: "OMBRÉ", title: "Caramel gradient" },
];
// Hero marquee: rAF ile kaydir
useEffect(() => {
const el = document.querySelector('.marquee-track');
if (!el) return;
if (el.getAnimations) el.getAnimations().forEach(a => a.cancel());
let offset = 0, lastTs = null, paused = false, raf;
const step = (ts) => {
if (lastTs !== null && !paused) {
const dt = Math.min(ts - lastTs, 50);
const total = el.scrollWidth / 2;
if (total > 0) {
offset -= (total / 42000) * dt;
if (offset < -total) offset += total;
el.style.cssText = `transform: translateX(${offset}px) !important;`;
}
}
lastTs = ts;
raf = requestAnimationFrame(step);
};
raf = requestAnimationFrame(step);
const p = () => { paused = true; };
const r = () => { paused = false; lastTs = null; };
el.addEventListener('mouseenter', p);
el.addEventListener('mouseleave', r);
return () => { cancelAnimationFrame(raf); el.removeEventListener('mouseenter', p); el.removeEventListener('mouseleave', r); el.style.transform = ''; };
}, []);
// Slide carousel
useEffect(() => {
const tm = setInterval(() => {
const next = (slideIdxRef.current + 1) % slidesData.length;
goToSlide(next);
}, 5800);
return () => clearInterval(tm);
}, [slidesData.length, goToSlide]);
// Mouse parallax tilt
useEffect(() => {
const el = stageRef.current;
if (!el) return;
const onMove = (e) => {
const rect = el.getBoundingClientRect();
const x = (e.clientX - rect.left) / rect.width - 0.5;
const y = (e.clientY - rect.top) / rect.height - 0.5;
el.style.transform = `rotateX(${-y * 6}deg) rotateY(${x * 6}deg) translateZ(0)`;
};
const onLeave = () => {
el.style.transform = `rotateX(0) rotateY(0) translateZ(0)`;
};
const parent = el.parentElement;
parent.addEventListener("mousemove", onMove);
parent.addEventListener("mouseleave", onLeave);
return () => {
parent.removeEventListener("mousemove", onMove);
parent.removeEventListener("mouseleave", onLeave);
};
}, []);
// Hero phrase tokens per lang (5 spans)
const phraseMap = {
tr: ["Boğaz'ın", "yakınında", "seçkin bir", "lüks", "salon."],
en: ["Quiet", "luxury,", "by the", "Bosphorus", "salon."],
ar: ["هدوء", "الفخامة", "على ضفاف", "البوسفور", "."],
ru: ["Тихая", "роскошь", "у", "Босфора", "."],
it: ["Lusso", "silenzioso", "sul", "Bosforo", "."],
de: ["Stille", "Eleganz", "am", "Bosporus", "."],
fr: ["Le luxe", "discret,", "au bord du", "Bosphore", "."],
es: ["Lujo", "silencioso", "junto al", "Bósforo", "."],
};
const phrase = phraseMap[lang] || phraseMap.tr;
return (
{/* ── TOP: Full-width cinematic image carousel ── */}
{slidesData.map((s, i) => (
))}
{slidesData.map((_, i) => (
goToSlide(i)}
/>
))}
{/* ── BOTTOM: Text content on clean background ── */}
{t("hero_eyebrow")}
{phrase[0]} {" "}
{phrase[1]} {" "}
{phrase[2]} {" "}
{phrase[3]} {" "}
{phrase[4]}
{t("hero_lede")}
{t("hero_meta_1_k")}
{t("hero_meta_1_v")}
{t("hero_meta_2_k")}
{t("hero_meta_2_v")}
{t("hero_meta_3_k")}
{t("hero_meta_3_v")}
{/* ── Brand marquee ── */}
{[1, 2].map((dup) => (
))}
);
}
// Tiltable service card
function ServiceCard({ svc, idx, lang }) {
const tilt = useTilt({ max: 6, scale: 1.03 });
const { go } = useRoute();
return (
go('services')}>
{String(idx + 1).padStart(2, "0")} / 14
{svc.names[lang] || svc.names.tr}
{lang !== "en" &&
{svc.names.en}
}
);
}
// ========== SERVICES (home preview) ==========
function ServicesPreview() {
const t = useT();
const { lang } = useLang();
const { go } = useRoute();
const ref = useReveal();
const [isMobile, setIsMobile] = useState(false);
useEffect(() => {
const check = () => setIsMobile(window.innerWidth <= 640);
check();
window.addEventListener('resize', check);
return () => window.removeEventListener('resize', check);
}, []);
// Mobile: show 4, Desktop: show 8
const showing = SERVICES.slice(0, isMobile ? 4 : 8);
return (
{t("svc_eyebrow")}
{t("svc_title")}
{showing.map((svc, i) => (
))}
{/* Mobile only: "Tüm Hizmetler" CTA */}
{isMobile && (
)}
);
}
// ========== BEFORE / AFTER ==========
function BeforeAfter() {
const t = useT();
const { lang } = useLang();
const { go } = useRoute();
const ref = useReveal();
const sliderRef = useRef(null);
const [pos, setPos] = useState(50);
const [pairIdx, setPairIdx] = useState(0);
const pairs = [
{
before: "assets/img/style-6.webp",
after: "assets/img/style-8.webp",
cat: { tr: "Renk", en: "Colour", ar: "صبغة", ru: "Цвет", it: "Colore", de: "Farbe", fr: "Couleur", es: "Color" },
label: { tr: "Koyu → Bakır Kızıl", en: "Dark → Copper Red", ar: "غامق → نحاسي", ru: "Тёмный → Медный", it: "Scuro → Ramato", de: "Dunkel → Kupferrot", fr: "Foncé → Cuivré", es: "Oscuro → Cobrizo" },
},
{
before: "assets/img/style-4.webp",
after: "assets/img/style-1.webp",
cat: { tr: "Kesim", en: "Cut", ar: "قص", ru: "Стрижка", it: "Taglio", de: "Schnitt", fr: "Coupe", es: "Corte" },
label: { tr: "Uzun → Soft Bob", en: "Long → Soft Bob", ar: "طويل → بوب ناعم", ru: "Длинный → Софт Боб", it: "Lungo → Soft Bob", de: "Lang → Soft Bob", fr: "Long → Soft Bob", es: "Largo → Soft Bob" },
},
{
before: "https://images.unsplash.com/photo-1508214751196-bcfd4ca60f91?w=600&h=750&fit=crop&crop=faces,top",
after: "https://images.unsplash.com/photo-1487412947147-5cebf100ffc2?w=600&h=750&fit=crop&crop=faces,top",
cat: { tr: "Makyaj", en: "Makeup", ar: "مكياج", ru: "Макияж", it: "Trucco", de: "Makeup", fr: "Maquillage", es: "Maquillaje" },
label: { tr: "Doğal → Editorial", en: "Natural → Editorial", ar: "طبيعي → تحريري", ru: "Натуральный → Editorial", it: "Naturale → Editorial", de: "Natürlich → Editorial", fr: "Naturel → Éditorial", es: "Natural → Editorial" },
},
{
before: "assets/img/style-4.webp",
after: "assets/img/style-7.webp",
cat: { tr: "Ombré", en: "Ombré", ar: "أومبري", ru: "Омбре", it: "Ombré", de: "Ombré", fr: "Ombré", es: "Ombré" },
label: { tr: "Tek Ton → Caramel", en: "Solid → Caramel", ar: "لون واحد → كراميل", ru: "Один тон → Карамель", it: "Uniforme → Caramello", de: "Einfarbig → Karamell", fr: "Uni → Caramel", es: "Liso → Caramelo" },
},
{
before: "https://images.pexels.com/photos/3997388/pexels-photo-3997388.jpeg?auto=compress&cs=tinysrgb&w=600&h=750&fit=crop",
after: "https://images.pexels.com/photos/704815/pexels-photo-704815.jpeg?auto=compress&cs=tinysrgb&w=600&h=750&fit=crop",
cat: { tr: "Tırnak", en: "Nails", ar: "أظافر", ru: "Ногти", it: "Unghie", de: "Nägel", fr: "Ongles", es: "Uñas" },
label: { tr: "Klasik → Couture", en: "Classic → Couture", ar: "كلاسيكي → كوتور", ru: "Классика → Couture", it: "Classico → Couture", de: "Klassisch → Couture", fr: "Classique → Couture", es: "Clásico → Couture" },
},
];
const current = pairs[pairIdx];
const move = (clientX) => {
if (!sliderRef.current) return;
const rect = sliderRef.current.getBoundingClientRect();
const x = clientX - rect.left;
const p = Math.max(0, Math.min(100, (x / rect.width) * 100));
setPos(p);
};
const onMouseMove = (e) => { if (e.buttons === 1) move(e.clientX); };
const onMouseDown = (e) => { move(e.clientX); };
const onTouchMove = (e) => { if (e.touches[0]) move(e.touches[0].clientX); };
// Switching pair: keep handle position, just swap images
const changePair = (idx) => {
setPairIdx(idx);
setPos(50);
};
return (
{t("ga_eyebrow")}
{t("ga_title")}
{t("svc_lede")}
{pairs.map((p, i) => (
changePair(i)}
>
{String(i + 1).padStart(2, "0")}
{p.cat[lang] || p.cat.tr}
))}
go('gallery')}>
{t("cta_view_all")}
Before
After
{current.cat[lang] || current.cat.tr}
{current.label[lang] || current.label.tr}
);
}
// ========== RESULTS (Hakiki Sonuçlar) — web only ==========
function Results() {
const t = useT();
const { lang } = useLang();
const ref = useReveal();
const results = window.AL_DATA?.RESULTS_GALLERY || GALLERY.slice(0, 6);
return (
{t("rv_eyebrow")}
Hakiki Sonuçlar
);
}
// ========== PARTNERS ==========
function Partners() {
const t = useT();
const ref = useReveal();
const brands = ["schwarzkopf", "loreal", "olaplex", "kerastase", "wella", "rallis"];
return (
);
}
// ========== SCROLLING SHOWCASE (alternating marquee) ==========
function Showcase() {
const t = useT();
// Force autoplay across all browsers — solves Safari/iOS/Chrome edge cases
useEffect(() => {
const videos = document.querySelectorAll('.showcase-video');
const tryPlay = (v) => {
v.muted = true;
const p = v.play();
if (p && p.catch) p.catch(() => {});
};
videos.forEach(v => {
v.muted = true;
v.playsInline = true;
v.setAttribute('webkit-playsinline', '');
tryPlay(v);
v.addEventListener('canplay', () => tryPlay(v));
v.addEventListener('loadeddata', () => tryPlay(v));
});
// Retry on first user gesture (iOS Low Power Mode fallback)
const onGesture = () => videos.forEach(tryPlay);
window.addEventListener('touchstart', onGesture, { once: true, passive: true });
window.addEventListener('click', onGesture, { once: true });
// Pause when off-screen to save CPU/battery
const obs = new IntersectionObserver(entries => {
entries.forEach(e => {
if (e.isIntersecting) tryPlay(e.target);
else e.target.pause();
});
}, { threshold: 0.1 });
videos.forEach(v => obs.observe(v));
return () => {
window.removeEventListener('touchstart', onGesture);
window.removeEventListener('click', onGesture);
obs.disconnect();
};
}, []);
return (
{t("sc_eyebrow")}
{t("sc_title")}
{/* Video Promo Block */}
);
}
// ========== INSTAGRAM ==========
const IG_PROFILE = "https://www.instagram.com/a_lizhairdresser";
function InstagramFeed() {
const t = useT();
const { lang } = useLang();
const ref = useReveal();
const copy = {
sub: { tr: "Istanbul'dan anlık kareler.", en: "Live moments from Istanbul.", ar: "لحظات حية من إسطنبول.", ru: "Живые моменты из Стамбула.", de: "Livebilder aus Istanbul.", fr: "Moments en direct d'Istanbul.", it: "Momenti live da Istanbul.", es: "Momentos en vivo desde Istanbul." },
followers: { tr: "Takipçi", en: "Followers", ar: "متابع", ru: "Подписчиков", it: "Follower", de: "Follower", fr: "Abonnés", es: "Seguidores" },
posts: { tr: "Gönderi", en: "Posts", ar: "منشور", ru: "Постов", it: "Post", de: "Beiträge", fr: "Posts", es: "Posts" },
cta: { tr: "Takip Et", en: "Follow", ar: "تابعنا", ru: "Подписаться", it: "Seguici", de: "Folgen", fr: "Suivre", es: "Seguir" },
};
const L = (o) => o[lang] || o.en;
return (
{/* ── MOBILE: Stories Ring Layout ── */}
@a_lizhairdresser
A_Liz Hairdresser · Istanbul
120+
{L(copy.posts)}
4.800+
{L(copy.followers)}
4.9★
Google
{L(copy.cta)}
{L(copy.sub)}
{/* ── WEB & TABLET: Existing Pro Layout ── */}
{/* Top row: eyebrow + handle */}
{/* Divider */}
{/* Middle: stats + tagline */}
4.800+
{L(copy.followers)}
120+
{L(copy.posts)}
4.9 ★
Google
{L(copy.sub)}
{/* Divider */}
{/* Bottom: CTA */}
{/* end shell */}
);
}
// ========== LOCATION / MAP ==========
function Location() {
const t = useT();
const { go } = useRoute();
const ref = useReveal();
return (
{t("loc_eyebrow")}
{t("loc_title")}
{t("loc_address_k")}
{t("loc_address_v")}
{t("loc_address_sub")}
{t("loc_hours_k")}
{t("loc_hours_v")}
{t("loc_hours_sub")}
{t("loc_phone_k")}
{t("loc_phone_v")}
{t("loc_email_k")}
{t("loc_email_v")}
);
}
// ========== TRUST STRIP ==========
function TrustStrip() {
const t = useT();
const { lang } = useLang();
const [years, yearsRef] = useCountUp(15);
const [langs, langsRef] = useCountUp(8);
const [services, servicesRef] = useCountUp(14);
const labels = {
exp: { tr: "Yıl Usta Tecrübesi", en: "Years of Master Craft", ar: "سنة خبرة", ru: "лет мастерства", it: "Anni di Maestria", de: "Jahre Meisterhandwerk", fr: "Ans de Savoir-Faire", es: "Años de Maestría" },
lang: { tr: "Dilde Hizmet", en: "Languages Spoken", ar: "لغات الخدمة", ru: "языков сервиса", it: "Lingue Parlate", de: "Sprachen im Service", fr: "Langues Parlées", es: "Idiomas" },
svc: { tr: "Uzmanlık Alanı", en: "Disciplines", ar: "تخصص", ru: "направлений", it: "Discipline", de: "Disziplinen", fr: "Disciplines", es: "Disciplinas" },
open: { tr: "Her Gün Açık", en: "Open Every Day", ar: "مفتوح يومياً", ru: "Открыто каждый день", it: "Aperto Ogni Giorno", de: "Täglich Geöffnet", fr: "Ouvert Tous les Jours", es: "Abierto Cada Día" },
openSub: { tr: "10:00 – 24:00", en: "10:00 – 24:00", ar: "10:00 – 24:00", ru: "10:00 – 24:00", it: "10:00 – 24:00", de: "10:00 – 24:00", fr: "10h – 24h", es: "10:00 – 24:00" },
};
const L = (o) => o[lang] || o.tr;
return (
{Math.round(years)}+
{L(labels.exp)}
{Math.round(langs)}
{L(labels.lang)}
{Math.round(services)}
{L(labels.svc)}
2'
{L({ tr: "Sultanahmet'e", en: "To Sultanahmet", de: "Bis Sultanahmet", fr: "De Sultanahmet", ar: "إلى السلطان أحمد", ru: "До Султанахмет", it: "Da Sultanahmet", es: "A Sultanahmet" })}
);
}
// ========== PRESS STRIP ==========
function PressStrip() {
const t = useT();
const press = ["vogue", "cosmo", "elle", "timeout", "marie", "harpers"];
// Press logos marquee: rAF ile kaydir
useEffect(() => {
const el = document.querySelector('.press-logos-track');
if (!el) return;
if (el.getAnimations) el.getAnimations().forEach(a => a.cancel());
let offset = 0, lastTs = null, paused = false, raf;
const step = (ts) => {
if (lastTs !== null && !paused) {
const dt = Math.min(ts - lastTs, 50);
const total = el.scrollWidth / 2;
if (total > 0) {
offset -= (total / 38000) * dt;
if (offset < -total) offset += total;
el.style.cssText = `transform: translateX(${offset}px) !important;`;
}
}
lastTs = ts;
raf = requestAnimationFrame(step);
};
raf = requestAnimationFrame(step);
const parent = el.closest('.press-strip');
const p = () => { paused = true; };
const r = () => { paused = false; lastTs = null; };
parent?.addEventListener('mouseenter', p);
parent?.addEventListener('mouseleave', r);
return () => { cancelAnimationFrame(raf); parent?.removeEventListener('mouseenter', p); parent?.removeEventListener('mouseleave', r); el.style.transform = ''; };
}, []);
return (
{t("press_eyebrow")}
{/* Desktop: static logos */}
{press.map((p) => )}
{/* Mobile: infinite scroll marquee */}
{[...press, ...press].map((p, i) => )}
);
}
// ========== FAQ ==========
function FAQ() {
const t = useT();
const { go } = useRoute();
const ref = useReveal();
const [openIdx, setOpenIdx] = useState(0);
const faqs = [
["faq_1_q", "faq_1_a"],
["faq_2_q", "faq_2_a"],
["faq_3_q", "faq_3_a"],
["faq_4_q", "faq_4_a"],
["faq_5_q", "faq_5_a"],
["faq_6_q", "faq_6_a"],
];
return (
{t("faq_eyebrow")}
{t("faq_title")}
{t("ann_msg")}
{faqs.map(([qk, ak], i) => (
setOpenIdx(openIdx === i ? -1 : i)}>
{t(qk)}
))}
);
}
// ========== FEATURED OFFER (Ayak Masajı · €10) ==========
function FeaturedOffer() {
const t = useT();
const { lang } = useLang();
const ref = useReveal();
const offerMsgMap = {
tr: "Merhaba, sezon önerisi 30 dakikalık ayak masajı (€10) için randevu almak istiyorum.",
en: "Hello, I'd like to book the seasonal foot massage (30 min · €10).",
ar: "مرحباً، أود حجز جلسة تدليك القدمين الموسمية (30 دقيقة · €10).",
ru: "Здравствуйте, хочу записаться на сезонный массаж стоп (30 мин · €10).",
it: "Ciao, vorrei prenotare il massaggio plantare (30 min · €10).",
de: "Hallo, ich möchte die Fußmassage (30 Min · €10) buchen.",
fr: "Bonjour, je souhaite réserver le massage des pieds (30 min · €10).",
es: "Hola, quisiera reservar el masaje de pies (30 min · €10).",
};
const offerWaLink = `https://wa.me/905388745166`;
return (
);
}
// ========== HOME PAGE ==========
function HomePage() {
usePageSEO('home');
return (
<>
>
);
}
window.HomePage = HomePage;