// site.jsx — YuHouse Academy TOP page
// Luxury hair-academy site. Photo placeholders use subtly-striped SVG with
// monospace labels per the design system; the user will swap in real imagery.
const { useState, useEffect, useRef } = React;
// ── Brand tokens ────────────────────────────────────────────────────────────
// Palette pivot: white-first, with pink + gold as accent pair.
// Pink = warm primary accent, Gold = secondary / metallic accent.
const BRAND = {
black: '#1A1A1A', // reserved for text only — no full-bleed black surfaces
ink: '#1A1A1A',
mute: '#8A847C',
white: '#FFFFFF',
cream: '#FBF7F0', // soft warm white — primary surface
beige: '#F5EFE6', // section divider tone
beigeDeep: '#ECE3D2',
pink: '#EF859A', // primary accent
pinkSoft: '#F8DDE2',
gold: '#E5B34F', // secondary accent
goldSoft: '#F1D89A',
hair: 'rgba(26,26,26,.08)',
};
// Back-compat aliases: existing call sites used BRAND.goldLight; the new
// palette names this goldSoft. Keep both so unrelated files keep working
// while the redesign rolls through.
BRAND.goldLight = BRAND.goldSoft;
// ── Placeholder photo ──────────────────────────────────────────────────────
// Striped tonal block + monospace caption describing intended subject.
// Tone is "warm" (beige) or "dark" (black). Aspect = w/h in CSS aspect-ratio.
function Photo({ label, tone = 'warm', ratio = '4 / 5', radius = 0, style = {} }) {
// Tones map to the new white-based palette:
// 'warm' → cream / beige stripes (default light surface)
// 'blush' → soft pink stripes (hero / feature accents)
// 'dark' → kept for legacy callers; renders as a softer charcoal
const stripes = {
warm: ['#F0E8D7', '#E8DEC6'],
blush: ['#FBE3E6', '#F4D2D8'],
dark: ['#26221E', '#2E2925'],
}[tone] || ['#F0E8D7', '#E8DEC6'];
const isDark = tone === 'dark';
const cap = isDark ? 'rgba(255,255,255,.55)' : 'rgba(26,26,26,.42)';
const tag = isDark ? 'rgba(229,179,79,.75)' : 'rgba(239,133,154,.85)';
return (
{'// PHOTO'}
{label}
);
}
// ── Eyebrow ────────────────────────────────────────────────────────────────
// English label above a Japanese heading. Standard rhythm device for this site.
function Eyebrow({ children, color = BRAND.gold }) {
return (
{children}
);
}
// ── Section title (EN big serif + JP smaller mincho) ───────────────────────
function SectionTitle({ en, jp, light = false, align = 'left' }) {
return (
{en}
{jp && (
{jp}
)}
);
}
// ── Button ─────────────────────────────────────────────────────────────────
// Primary = ink fill, white text, pink fill on hover (pink is the lead accent).
// Secondary = transparent w/ ink border on light surfaces, white on dark.
function Btn({ children, variant = 'primary', onLight = true, arrow = true, href = '#' }) {
const [hov, setHov] = useState(false);
const isPrimary = variant === 'primary';
const base = {
display: 'inline-flex', alignItems: 'center', gap: 14,
padding: '18px 28px', minWidth: 220,
fontFamily: '"Noto Sans JP", sans-serif',
fontSize: 13, fontWeight: 500, letterSpacing: '.16em',
textDecoration: 'none', cursor: 'pointer',
border: '1px solid', borderRadius: 0,
transition: 'background .35s ease, color .35s ease, border-color .35s ease',
justifyContent: 'space-between',
};
let styled;
if (isPrimary) {
styled = hov
? { ...base, background: BRAND.pink, color: BRAND.white, borderColor: BRAND.pink }
: { ...base, background: BRAND.ink, color: BRAND.white, borderColor: BRAND.ink };
} else {
// onLight=true means we're sitting on a white/cream surface
styled = hov
? { ...base,
background: onLight ? BRAND.ink : BRAND.white,
color: onLight ? BRAND.white : BRAND.ink,
borderColor: onLight ? BRAND.ink : BRAND.white }
: { ...base, background: 'transparent',
color: onLight ? BRAND.ink : BRAND.white,
borderColor: onLight ? BRAND.ink : BRAND.white };
}
return (
setHov(true)} onMouseLeave={() => setHov(false)}>
{children}
{arrow && (
→
)}
);
}
// ── Top navigation ─────────────────────────────────────────────────────────
// Always-light variant: cream-tinted blur, dark text. The hairline border
// fades in on scroll so the nav has presence over content.
function Nav() {
const [scrolled, setScrolled] = useState(false);
useEffect(() => {
const on = () => setScrolled(window.scrollY > 40);
on();
window.addEventListener('scroll', on, { passive: true });
return () => window.removeEventListener('scroll', on);
}, []);
const items = [
['CONCEPT', '#concept'],
['MESSAGE', '#message'],
['LECTURERS', '#lecturers'],
['COURSES', '#courses'],
['FAQ', '#faq'],
['ACCESS', '#access'],
];
return (
);
}
// ── Hero ───────────────────────────────────────────────────────────────────
// Editorial split: oversized serif headline left, hero photo right.
// Cream surface, pink + gold accents. A single half-bleed photo (using one of
// the lecturer shots until a proper hero is supplied) anchors the right side.
function Hero() {
return (
{/* Faint outsized word watermark */}
hair.
{/* Hairline frame */}
{/* Vertical EN tagline at left edge */}