// Mr. Hair Club — main React app // Single-page site, trilingual, scroll-based with anchored sections. const { useState, useEffect, useMemo, useRef } = React; const C = window.COPY; const TEAM = window.TEAM; const BOOKING_URL = window.BOOKING_URL; const WHATSAPP_URL = window.WHATSAPP_URL; const MAPS_URL = window.MAPS_URL; const IG_URL = window.IG_URL; const USD_RATE = window.USD_RATE; const fmtNum = window.fmtNum; // ─────────────────────────────────────────────────────────────────────────── // TWEAK DEFAULTS const TWEAK_DEFAULTS = /*EDITMODE-BEGIN*/{ "accentHue": "cyan", "density": "comfortable", "showUsd": true, "heroBg": "glow", "heroTreatment": "duotone", "heroComposition": "fullbleed", "heroLayout": "dense", "language": "pt" }/*EDITMODE-END*/; const HUE_OPTIONS = { cyan: { name: "Electric cyan", c1: "#2BA5C7", c2: "#56C7E3", c3: "#0F4F60" }, azure: { name: "Brand azure", c1: "#3A8FE0", c2: "#6BB5F5", c3: "#1A4A82" }, teal: { name: "Deep teal", c1: "#19B5A2", c2: "#5FD9C8", c3: "#0E5249" }, amber: { name: "Hot amber", c1: "#E89B3C", c2: "#F5C275", c3: "#7A4E15" }, }; // ─────────────────────────────────────────────────────────────────────────── // PRIMITIVES function BookLink({ children, className, style, onClick, secondary, small }) { const cls = ["btn", secondary ? "btn-ghost" : "btn-primary", small ? "btn-sm" : "", className || ""].join(" "); return ( {children} ); } function SectionLabel({ kicker, title, sub, align }) { return (
{kicker}

{title}

{sub &&

{sub}

}
); } // Brand logo built from type function BrandLogo({ size = 1 }) { return (
MR. HAIR club
); } // Striped placeholder for missing imagery function PhotoPlaceholder({ label, aspect = "4/5" }) { return (
{label}
); } // ─────────────────────────────────────────────────────────────────────────── // HEADER function Header({ lang, setLang, t, scrolled }) { const [open, setOpen] = useState(false); const links = [ ["services", t.nav.services], ["team", t.nav.team], ["casa", t.nav.casa], ["club", t.nav.club], ["visit", t.nav.visit], ]; return (
{t.nav.book}
{open && (
{links.map(([id, label]) => ( setOpen(false)}>{label} ))} {t.nav.book}
)}
); } function LangSwitcher({ lang, setLang }) { return (
{["pt", "en", "es"].map((l) => ( ))}
); } // ─────────────────────────────────────────────────────────────────────────── // HERO function Hero({ t, lang, heroBg, heroLayout, heroTreatment, heroComposition }) { const minimal = heroLayout === "minimal"; const split = heroComposition === "split"; const HP = window.__heroPhotos || {}; const photoSrc = heroBg === "storefront" ? (HP.storefront || "assets/storefront.jpg") : heroBg === "interior" ? (HP.interior || "assets/hero-interior.jpg") : null; // glow return (
{photoSrc ? ( ) : (
)} {heroTreatment === "duotone" &&
}
{minimal ? (

{t.hero.title1} {t.hero.title2} {" "}{t.hero.title3}

) : (

{t.hero.title1} {t.hero.title2} {t.hero.title3}

)} {!minimal &&

{t.hero.sub}

}
{t.hero.primary} {t.hero.secondary}
{!minimal && }
); } // Thin authority strip used in minimal hero mode (sits between Hero and Why) function AuthorityStrip({ t, lang }) { return (
); } function AuthorityBar({ t, lang }) { const totalCuts = TEAM.reduce((s, b) => s + b.cuts, 0); const items = [ { big: "4.9★", small: t.authority.rating }, { big: fmtNum(1512, lang), small: t.authority.reviews }, { big: fmtNum(totalCuts, lang) + "+", small: t.authority.cuts }, { big: "15", small: t.authority.years }, ]; return (
{items.map((it, i) => (
{it.big}
{it.small}
))}
); } // ─────────────────────────────────────────────────────────────────────────── // WHY function Why({ t }) { return (
{t.why.items.map((it, i) => (
{it.n}

{it.t}

{it.d}

))}
); } // ─────────────────────────────────────────────────────────────────────────── // SERVICES function Services({ t, lang, showUsd }) { return (
{t.services.groups.map((g, gi) => (
0{gi + 1}

{g.name}

{g.items.map(([name, dur, price], i) => (
{name}
{dur}
R$ {price} {showUsd && ( ~USD {Math.round(price / USD_RATE)} )}
{t.services.book}
))}
))}
); } // ─────────────────────────────────────────────────────────────────────────── // TEAM function Team({ t, lang }) { const totalCuts = TEAM.reduce((s, b) => s + b.cuts, 0); return (
{fmtNum(totalCuts, lang)}+
{lang === "pt" && "cortes feitos. Seis barbeiros. Todos com nota 5,00."} {lang === "en" && "cuts performed. Six barbers. All rated 5.00."} {lang === "es" && "cortes realizados. Seis barberos. Todos con 5,00."}
{TEAM.map((b) => ( ))}
); } function BarberCard({ barber, t, lang }) { const hasPhoto = barber.id !== "sebastian"; return (
{hasPhoto ? ( <>
{barber.name} ) : (
)}
{barber.rating.toFixed(2)}

{barber.name}

{t.team.role}
{fmtNum(barber.cuts, lang)} {t.team.cuts}

{t.team.bios[barber.name]}

{barber.tags.map((tag) => ( {t.team.tags[tag]} ))}
{t.team.bookWith} {barber.name}
); } // ─────────────────────────────────────────────────────────────────────────── // WORK / GALLERY (user-fillable image slots) function Work({ t }) { const tags = ["all", "cuts", "beards", "interior", "before"]; const [filter, setFilter] = useState("all"); // Bento grid spans — paired with slot order. Keeps composition visually rhythmic. const spans = [ { c: "span 2", r: "span 2" }, // big { c: "span 1", r: "span 1" }, { c: "span 1", r: "span 2" }, // tall { c: "span 2", r: "span 1" }, // wide { c: "span 1", r: "span 1" }, { c: "span 1", r: "span 1" }, { c: "span 1", r: "span 1" }, { c: "span 2", r: "span 1" }, // wide { c: "span 1", r: "span 1" }, { c: "span 1", r: "span 1" }, ]; return (
{tags.map((tag) => ( ))}
{t.work.slots.map((s, i) => { const hide = filter !== "all" && s.tag !== filter; const sp = spans[i] || { c: "span 1", r: "span 1" }; const realSrc = window.WORK_PHOTOS && window.WORK_PHOTOS[s.id]; return (
{t.work.tags[s.tag]}
); })}
{t.work.cta}
); } // ─────────────────────────────────────────────────────────────────────────── // CLUB (Subscription) function Club({ t, lang }) { const [tab, setTab] = useState("flex"); const plans = tab === "flex" ? t.club.flex : t.club.essencial; const compareText = tab === "flex" ? (t.club.compareFlex || t.club.compare) : (t.club.compareEssencial || t.club.compare); return (
{plans.map((p, i) => (
{p.hot &&
{t.club.most}
}
{p.name}
{p.forWho &&
{p.forWho}
}
R$ {p.price} {t.club.monthly}
{t.club.days}
{p.days}
{t.club.includes}
    {p.inc.map((it, j) => (
  • {it}
  • ))}
{p.save &&
{p.save}
} {t.club.subscribe}
))}

{compareText}

{t.club.note}

); } // ─────────────────────────────────────────────────────────────────────────── // REVIEWS const REVIEWS = window.REVIEWS; function Reviews({ t, lang }) { const items = REVIEWS[lang] || []; const hasPlaceholders = items.some((r) => r.placeholder); const withPhoto = items.filter((r) => !r.placeholder && (r.photo || (r.photos && r.photos.length))); const textOnly = items.filter((r) => r.placeholder || !(r.photo || (r.photos && r.photos.length))); const renderCard = (r, i) => (
{r.combinedBeforeAfter && r.photo ? (
{lang === "en" ? "Before" : lang === "es" ? "Antes" : "Antes"} {lang === "en" ? "After" : lang === "es" ? "Después" : "Depois"}
) : r.photos && r.photos.length > 1 ? (
{r.photos.map((p, j) => (
{r.beforeAfter && ( {j === 0 ? (lang === "en" ? "Before" : lang === "es" ? "Antes" : "Antes") : (lang === "en" ? "After" : lang === "es" ? "Después" : "Depois")} )}
))}
) : r.photo ? (
) : null}
★★★★★

{r.placeholder ? r.text : `“${r.text}”`}

{r.name} · {r.date}
); return (
{hasPlaceholders && (
{lang === "pt" && "Reviews reais em português ainda não importados. Os cards abaixo são placeholders — substituir antes do launch."} {lang === "es" && "Reseñas reales en español aún no importadas. Los cards abajo son placeholders — sustituir antes del lanzamiento."}
)} {withPhoto.length > 0 && (
{withPhoto.map(renderCard)}
)} {textOnly.length > 0 && (
{textOnly.map(renderCard)}
)}
4.9
{fmtNum(1512, lang)} {t.authority.reviews} · Google
{t.reviews.cta}
); } // ─────────────────────────────────────────────────────────────────────────── // VISIT / LOCATION function Visit({ t }) { return (
{t.location.hours}
{t.location.hoursWeek}
{t.location.hoursSun}
{t.location.phone}
+55 21 2523-0926
{t.location.walk}
    {t.location.walkFrom.map(([place, time], i) => (
  • {place} {time}
  • ))}
{t.location.directions} {t.location.whatsapp}