/* global React, ReactDOM */ const { useState, useEffect, useMemo, useRef } = React; // ================== ORIGIN DATA ================== const ORIGINS = [ { id: 'br', code: 'BR', name: { jp: 'ブラジル', en: 'Brazil' }, region: { jp: 'Campo das Vertentes · Minas Gerais', en: 'Campo das Vertentes · Minas Gerais' }, importer: 'Mirai Seeds Inc.', importerSub: { jp: 'グアリロバ農園 オフィシャルパートナー', en: 'Official partner — Fazenda Guariroba' }, varieties: 'Yellow Topázio · Catuaí · Mundo Novo', process: { jp: 'Natural / Honey / 嫌気性', en: 'Natural / Honey / Anaerobic' }, notes: { jp: 'チョコレート、キャラメル、トロピカルフルーツ', en: 'Chocolate, caramel, tropical fruit' }, pos: { x: 30.5, y: 64 }, // map % flag: 'br' }, { id: 'pa', code: 'PA', name: { jp: 'パナマ', en: 'Panama' }, region: { jp: 'Boquete · Volcán · Chiriquí', en: 'Boquete · Volcán · Chiriquí' }, importer: 'Brisa and Tierra', importerSub: { jp: 'パナマ専門商社', en: 'Panama-focused importer' }, varieties: 'Geisha · Caturra · Typica', process: { jp: 'Washed / Natural / Anaerobic', en: 'Washed / Natural / Anaerobic' }, notes: { jp: 'ジャスミン、ベルガモット、ピーチ', en: 'Jasmine, bergamot, peach' }, pos: { x: 24.5, y: 52 }, flag: 'pa' }, { id: 'tw', code: 'TW', name: { jp: '台湾', en: 'Taiwan' }, region: { jp: 'Yunlin · Nantou · Alishan', en: 'Yunlin · Nantou · Alishan' }, importer: 'ORIOWL Co., Ltd.', importerSub: { jp: '台湾珈琲専門商社', en: 'Taiwan coffee specialist' }, varieties: 'Typica · SL34 · Geisha', process: { jp: 'Natural / Honey / Wine', en: 'Natural / Honey / Wine' }, notes: { jp: '紅茶、ライチ、和柑橘', en: 'Black tea, lychee, citrus' }, pos: { x: 78.5, y: 48 }, flag: 'tw' }, { id: 'cr', code: 'CR', name: { jp: 'コスタリカ', en: 'Costa Rica' }, region: { jp: 'Tarrazú · West Valley · Naranjo', en: 'Tarrazú · West Valley · Naranjo' }, importer: 'PuraVida', importerSub: { jp: 'コスタリカ専門商社', en: 'Costa Rica specialist' }, varieties: 'Caturra · Catuaí · Villa Sarchi', process: { jp: 'White / Yellow / Black Honey', en: 'White / Yellow / Black Honey' }, notes: { jp: 'シトラス、シュガーケーン、フローラル', en: 'Citrus, sugarcane, floral' }, pos: { x: 25, y: 50.5 }, flag: 'cr' }, { id: 'id', code: 'ID', name: { jp: 'インドネシア', en: 'Indonesia' }, region: { jp: 'Sumatra · Sulawesi · Java', en: 'Sumatra · Sulawesi · Java' }, importer: 'Rational Idea Inc.', importerSub: { jp: 'Asosiasi Kopi Indonesia 日本総代理店', en: 'Japan agent for Asosiasi Kopi Indonesia' }, varieties: 'Tim Tim · Lini-S · Sigararutang', process: { jp: 'Wet Hulled / Natural / Honey', en: 'Wet Hulled / Natural / Honey' }, notes: { jp: 'スパイス、ハーブ、ダークチョコ', en: 'Spice, herbs, dark chocolate' }, pos: { x: 80, y: 64 }, flag: 'id' } ]; // ================== FLAG SVGs ================== const Flag = ({ code }) => { switch(code){ case 'br': return ( ); case 'pa': return ( ); case 'tw': return ( ); case 'cr': return ( ); case 'id': return ( ); default: return null; } }; // ================== ORIGIN MAP APP ================== const OriginApp = () => { const lang = useLang(); const [activeId, setActiveId] = useState('br'); const active = ORIGINS.find(o => o.id === activeId) || ORIGINS[0]; const idx = ORIGINS.findIndex(o => o.id === activeId); const next = () => setActiveId(ORIGINS[(idx+1) % ORIGINS.length].id); const prev = () => setActiveId(ORIGINS[(idx-1+ORIGINS.length) % ORIGINS.length].id); return ( <>
{/* simplified continents */} {/* North America */} {/* South America */} {/* Europe */} {/* Africa */} {/* Asia */} {/* SE Asia / Indonesia */} {/* Australia */} {/* Pins */} {ORIGINS.map(o => ( setActiveId(o.id)}> {o.code} ))}
{lang==='jp'?'産地ネットワーク':'Origin Network'}
N · 東 · S · 西
5 origins · growing
{active.code}

{active.name[lang]}

{active.region[lang]}
N° 0{idx+1}
{lang==='jp'?'参加インポーター':'Member Importer'}
{active.importer}
{active.importerSub[lang]}
Varieties
{active.varieties}
Process
{active.process[lang]}
{lang==='jp'?'カップノート':'Cup Notes'}
"{active.notes[lang]}"
{idx+1} / {ORIGINS.length}
); }; const OriginTabs = () => { const lang = useLang(); const [activeId, setActiveId] = useState('br'); // sync with map by listening to clicks on .pin (kept simple — internal) return ( <> {ORIGINS.map(o => ( ))} ); }; // ================== Lang hook ================== function useLang(){ const [lang, setLang] = useState(document.body.dataset.lang || 'jp'); useEffect(() => { const obs = new MutationObserver(() => { setLang(document.body.dataset.lang || 'jp'); }); obs.observe(document.body, { attributes: true, attributeFilter: ['data-lang'] }); return () => obs.disconnect(); }, []); return lang; } // ================== Combined Origins (single state, two views) ================== const OriginsModule = () => { const lang = useLang(); const [activeId, setActiveId] = useState('br'); const active = ORIGINS.find(o => o.id === activeId) || ORIGINS[0]; const idx = ORIGINS.findIndex(o => o.id === activeId); const next = () => setActiveId(ORIGINS[(idx+1) % ORIGINS.length].id); const prev = () => setActiveId(ORIGINS[(idx-1+ORIGINS.length) % ORIGINS.length].id); return ( <>
{ORIGINS.map(o => ( setActiveId(o.id)}> {o.code} ))}
{lang==='jp'?'産地ネットワーク':'Origin Network'}
{lang==='jp'?'5 産地 · 拡大中':'5 origins · growing'}
click any pin
{active.code}

{active.name[lang]}

{active.region[lang]}
N° 0{idx+1}
{lang==='jp'?'参加インポーター':'Member Importer'}
{active.importer}
{active.importerSub[lang]}
Varieties
{active.varieties}
Process
{active.process[lang]}
{lang==='jp'?'カップノート':'Cup Notes'}
"{active.notes[lang]}"
{idx+1} / {ORIGINS.length}
{ORIGINS.map((o, i) => ( ))}
); }; // ================== SAMPLE FORM (multi-step) ================== const SampleForm = () => { const lang = useLang(); const [step, setStep] = useState(0); const [data, setData] = useState({ company: '', name: '', email: '', phone: '', role: '', origins: [], quantity: 'small', process: [], purpose: '', timing: 'asap', notes: '' }); const [done, setDone] = useState(false); const update = (k, v) => setData(d => ({ ...d, [k]: v })); const toggleArr = (k, v) => setData(d => { const arr = d[k]; return { ...d, [k]: arr.includes(v) ? arr.filter(x => x !== v) : [...arr, v] }; }); const t = (jp, en) => lang === 'jp' ? jp : en; const steps = [ { key: 'origins', label: t('産地選択', 'Origins') }, { key: 'detail', label: t('詳細', 'Details') }, { key: 'contact', label: t('連絡先', 'Contact') } ]; const canNext = () => { if(step === 0) return data.origins.length > 0; if(step === 1) return data.purpose.length > 0; if(step === 2) return data.company && data.name && data.email; return true; }; const submit = () => { // Submit to Contact Form 7 if linkoneCF7 (window.linkoneCF7) is configured if (window.linkoneCF7 && window.linkoneCF7.sampleFormId && window.linkoneCF7.restUrl) { const form = new FormData(); form.append('your-company', data.company); form.append('your-name', data.name); form.append('your-email', data.email); form.append('your-phone', data.phone || ''); form.append('your-role', data.role || ''); form.append('your-origins', data.origins.map(id => { const o = ORIGINS.find(x => x.id === id); return o ? o.name.en : id; }).join(', ')); form.append('your-process', data.process.join(', ')); form.append('your-quantity', data.quantity); form.append('your-purpose', data.purpose); form.append('your-timing', data.timing); form.append('your-message', data.notes || ''); const url = window.linkoneCF7.restUrl + 'contact-form-7/v1/contact-forms/' + window.linkoneCF7.sampleFormId + '/feedback'; fetch(url, { method: 'POST', body: form, credentials: 'same-origin' }) .then(r => r.json()) .then(res => { if (res.status === 'mail_sent' || res.status === 'mail_failed') { setDone(true); } else { // Validation issue: show message but still mark done with a soft warning console.warn('CF7 response:', res); setDone(true); } }) .catch(err => { console.error('CF7 submit error:', err); setDone(true); // optimistic }); } else { // Fallback: just mark complete (dev / no CF7 installed) setDone(true); } }; if(done){ return (

{t('依頼を受け付けました', 'Request received')}

{t(`${data.company} 様、ありがとうございます。担当者より3営業日以内にご連絡を差し上げます。`, `Thank you, ${data.company}. A LinkOne member will reach out within 3 business days.`)}

); } return ( <>
{steps.map((s, i) => (
{i {s.label}
))}
{/* Step 0 - Origins */} {step === 0 && (
{t('複数選択可。今後参加予定の産地も対象です。', 'Multiple selections welcome.')}
{ORIGINS.map(o => ( ))}
{['Washed','Natural','Honey','Anaerobic','Wet Hulled'].map(p => ( ))}
)} {/* Step 1 - Detail */} {step === 1 && (