/* ========================================================= Contact page Full-bleed page with a two-column layout on desktop and single-column on mobile. Left: copy + contact info + socials. Right: visual-only form with floating labels. All CSS scoped with the frc- prefix. ========================================================= */ const { useRef: useRefCP, useEffect: useEffectCP, useState: useStateCP } = React; /* ---------- Inline icons (currentColor, 24x24 viewBox) ---------- */ const IconEmail = () => ( ); const IconPin = () => ( ); const IconLinkedIn = () => ( ); const IconGitHub = () => ( ); const IconInstagram = () => ( ); /* ---------- Styles ---------- */ const STYLE = ` .frc-page { width: 100%; background: var(--bg); padding: clamp(80px, 10vw, 140px) clamp(20px, 4vw, 64px); box-sizing: border-box; overflow-x: hidden; } .frc-grid { max-width: 1200px; margin: 0 auto; display: grid; grid-template-columns: 1fr; gap: 48px; align-items: start; } @media (min-width: 900px) { .frc-grid { grid-template-columns: 1fr 1fr; gap: 64px; } } /* Entry animation — left slides in from the left, right from the right. */ .frc-left, .frc-form { opacity: 0; transition: opacity 0.7s cubic-bezier(0.22, 1, 0.36, 1), transform 0.7s cubic-bezier(0.22, 1, 0.36, 1); will-change: transform, opacity; } .frc-left { transform: translateX(-30px); transition-delay: 0ms; } .frc-form { transform: translateX(30px); transition-delay: 100ms; } .frc-left.in, .frc-form.in { opacity: 1; transform: none; } /* Left column ───────────────────────────────────────────── */ .frc-badge { display: inline-block; font-family: var(--font-mono); font-size: 11px; letter-spacing: 0.18em; text-transform: uppercase; color: var(--text-mute); margin-bottom: 24px; } .frc-title { font-family: var(--font-display); font-size: clamp(40px, 5vw, 72px); font-weight: 800; line-height: 1.05; letter-spacing: -0.025em; color: var(--text); margin: 0 0 20px; text-wrap: balance; word-break: normal; overflow-wrap: break-word; hyphens: none; } [data-theme="dark"] .frc-title { color: #f3ede2; } .frc-sub { font-size: 18px; line-height: 1.55; color: var(--text-2); margin: 0 0 48px; max-width: 48ch; text-wrap: pretty; } [data-theme="dark"] .frc-sub { color: #9b9a96; } .frc-info { display: flex; flex-direction: column; gap: 14px; margin: 0 0 40px; } .frc-info-row { display: inline-flex; align-items: center; gap: 12px; font-size: 15px; color: var(--text); text-decoration: none; flex-wrap: wrap; transition: color 0.2s ease; } .frc-info-row svg { flex: 0 0 18px; color: var(--text-mute); transition: color 0.2s ease; } a.frc-info-row:hover, a.frc-info-row:hover svg { color: var(--accent); } /* Availability dot — solid green with a slow pulsing halo */ .frc-pulse { flex: 0 0 10px; width: 10px; height: 10px; border-radius: 50%; background: #2ec48a; position: relative; display: inline-block; } .frc-pulse::after { content: ""; position: absolute; inset: 0; border-radius: 50%; background: #2ec48a; opacity: 0.45; animation: frc-pulse 1.8s ease-out infinite; } @keyframes frc-pulse { 0% { transform: scale(1); opacity: 0.45; } 100% { transform: scale(2.6); opacity: 0; } } .frc-socials { display: flex; gap: 20px; flex-wrap: wrap; } .frc-social { display: inline-flex; align-items: center; gap: 8px; font-size: 14px; color: var(--text); text-decoration: none; transition: color 0.2s ease; } .frc-social svg { color: var(--text-mute); transition: color 0.2s ease; } .frc-social:hover, .frc-social:hover svg { color: var(--accent); } /* Right column — form ───────────────────────────────────── */ .frc-form { display: flex; flex-direction: column; gap: 14px; width: 100%; } .frc-field { position: relative; width: 100%; } .frc-field input, .frc-field textarea, .frc-field select { width: 100%; box-sizing: border-box; font-family: inherit; font-size: 15px; color: var(--text); background: var(--surface); border: 1px solid var(--line); border-radius: 12px; padding: 24px 16px 8px; outline: none; transition: border-color 0.2s ease, background 0.2s ease; } [data-theme="dark"] .frc-field input, [data-theme="dark"] .frc-field textarea, [data-theme="dark"] .frc-field select { background: #1a1a1a; border-color: rgba(255, 255, 255, 0.08); color: #f3ede2; } .frc-field textarea { min-height: 140px; padding-top: 28px; resize: vertical; font-family: inherit; line-height: 1.4; } .frc-field input:focus, .frc-field textarea:focus, .frc-field select:focus { border-color: var(--accent); } /* Floating label */ .frc-field label { position: absolute; left: 16px; top: 18px; font-size: 15px; color: var(--text-mute); pointer-events: none; transition: top 0.2s ease, font-size 0.2s ease, color 0.2s ease, letter-spacing 0.2s ease; } /* Float when the field has a value (placeholder-shown trick) or is focused */ .frc-field input:not(:placeholder-shown) + label, .frc-field input:focus + label, .frc-field textarea:not(:placeholder-shown) + label, .frc-field textarea:focus + label, .frc-field--select label { top: 8px; font-size: 11px; letter-spacing: 0.04em; } .frc-field input:focus + label, .frc-field textarea:focus + label, .frc-field--select select:focus + label { color: var(--accent); } /* Custom select chevron */ .frc-field--select select { appearance: none; -webkit-appearance: none; -moz-appearance: none; background-image: url("data:image/svg+xml;utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8'%3E%3Cpath d='M1 1l5 5 5-5' fill='none' stroke='%23888' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E"); background-repeat: no-repeat; background-position: right 16px center; padding-right: 40px; } /* Submit button */ .frc-submit { width: 100%; height: 52px; border-radius: 12px; background: var(--accent); color: #fff; font-weight: 600; font-size: 15px; font-family: inherit; border: none; cursor: pointer; margin-top: 12px; transition: all 0.25s ease; } .frc-submit:hover { transform: translateY(-2px); box-shadow: 0 8px 24px rgba(232, 98, 74, 0.3); } .frc-submit:active { transform: translateY(0); } /* Mobile — single column, tight padding, scaled-down type ───── */ @media (max-width: 899px) { .frc-page { padding: clamp(60px, 12vw, 100px) 20px; } .frc-grid { grid-template-columns: 1fr; gap: 48px; } .frc-title { font-size: clamp(32px, 8vw, 48px); } .frc-sub { font-size: 16px; margin-bottom: 40px; } .frc-info-row { gap: 8px; } .frc-socials { gap: 20px; flex-wrap: wrap; } .frc-form { width: 100%; } .frc-field input, .frc-field textarea, .frc-field select { width: 100%; box-sizing: border-box; } .frc-submit { width: 100%; } } `; /* ---------- Component ---------- */ const ContactPage = ({ t }) => { const c = (t && t.contact) || {}; const leftRef = useRefCP(null); const rightRef = useRefCP(null); const [leftIn, setLeftIn] = useStateCP(false); const [rightIn, setRightIn] = useStateCP(false); // Two separate observers so each column can flip in independently — // useful at narrow widths where one might enter the viewport before // the other. useEffectCP(() => { if (typeof IntersectionObserver === "undefined") { setLeftIn(true); setRightIn(true); return; } const watchers = []; const observe = (el, set) => { const io = new IntersectionObserver( (entries) => { for (const e of entries) { if (e.isIntersecting) { set(true); io.disconnect(); break; } } }, { threshold: 0.1 } ); io.observe(el); watchers.push(io); }; if (leftRef.current) observe(leftRef.current, setLeftIn); if (rightRef.current) observe(rightRef.current, setRightIn); return () => watchers.forEach((io) => io.disconnect()); }, []); return (