// ===========================================================================
// hueve-core.jsx — shared data + primitives for LP / Onboarding / App Store
// Source of truth: k3lab1014/hueve  (engine/pigments.ts, constants/theme.ts)
// ===========================================================================

// ---- The 12 pigments (verbatim from engine/pigments.ts) -------------------
const PIGMENTS = [
  { id:'ember',    name:'Ember',    swatch:'#C98A5E', anchor:0.30, blurb:'記憶の中の暖かさ。黒を起こし暗部に赤みを乗せ、彩度をそっと落とす色褪せたレトロ。', blurbEn:'Warmth from memory. Lifts blacks, reddens shadows, and gently fades — a faded retro.' },
  { id:'vellum',   name:'Vellum',   swatch:'#D9CBB8', anchor:0.50, blurb:'地味に見えて奥行きを生む。黒を少し起こした柔らかな下地。', blurbEn:'Plain-looking but adds depth. A soft base with blacks barely lifted.' },
  { id:'aurum',    name:'Aurum',    swatch:'#D6A53A', anchor:0.50, blurb:'ゴールデンで濃い映画的なパンチ。明部に温み、暗部に僅かティール。迷ったらまずここから。', blurbEn:'Golden, deep, cinematic punch. Warm highlights, a touch of teal in shadows. Start here.' },
  { id:'cedar',    name:'Cedar',    swatch:'#6E7A4B', anchor:0.28, blurb:'緑が印象的な和フィルム。自然を豊かに、暖色を相殺して深みを出す。', blurbEn:'Green-forward Japanese film. Enriches nature and offsets warmth for depth.' },
  { id:'porto',    name:'Porto',    swatch:'#C8B6C0', anchor:0.50, blurb:'ニュートラルにパープルが滲む基準色。混ぜすぎを戻す時にも。', blurbEn:'A neutral base with purple bleeding in. Good for dialing back an over-mix.' },
  { id:'nocturne', name:'Nocturne', swatch:'#5C6E86', anchor:0.18, blurb:'シャドウに静かな青。乾いた夜気、シアン系を中和もする。', blurbEn:'Quiet blue in the shadows — dry night air. Also neutralizes cyans.' },
  { id:'halcyon',  name:'Halcyon',  swatch:'#CBB87A', anchor:0.82, blurb:'優しい日本のフィルムライク。黄寄りの暖色で空気だけを纏わせる。', blurbEn:'Gentle Japanese-film feel. A yellow-leaning warmth that drapes just the air.' },
  { id:'reverie',  name:'Reverie',  swatch:'#8A6FB0', anchor:0.60, blurb:'夢は紫。少し混ぜるだけで奥行きと神秘が宿る非現実の色。', blurbEn:'Dreams are purple. A little adds depth and an unreal mystery.' },
  { id:'lumen',    name:'Lumen',    swatch:'#D24E3A', anchor:0.62, blurb:'16mm 由来の元気で鮮やかな個性。軽やかなのにどこかレトロ。', blurbEn:'Lively, vivid 16mm character. Light, yet somehow retro.' },
  { id:'marlow',   name:'Marlow',   swatch:'#2E6E72', anchor:0.50, blurb:'シャドウにティール、ハイライトにオレンジ。万能なシネマティック。', blurbEn:'Teal shadows, orange highlights. An all-round cinematic.' },
  { id:'sahara',   name:'Sahara',   swatch:'#C98A3E', anchor:0.80, blurb:'海外フィルム由来の開放的な暖色。日本っぽさから離れたい時に。', blurbEn:'Open, foreign-film warmth. For stepping away from a Japanese look.' },
  { id:'quartz',   name:'Quartz',   swatch:'#E8E8EC', anchor:0.50, blurb:'ほぼ素のクリーンな基準。混ぜた世界をニュートラルに整える。', blurbEn:'A clean, near-neutral base. Tidies up a mixed world.' },
];
const PIG = Object.fromEntries(PIGMENTS.map(p => [p.id, p]));
const FREE_IDS = ['aurum','halcyon','nocturne','quartz'];

// ---- color utils ----------------------------------------------------------
function hexToRgb(h){ const n=parseInt(h.slice(1),16); return [n>>16&255,n>>8&255,n&255]; }
function mix(a,b,t){ const A=hexToRgb(a),B=hexToRgb(b); return `rgb(${Math.round(A[0]+(B[0]-A[0])*t)},${Math.round(A[1]+(B[1]-A[1])*t)},${Math.round(A[2]+(B[2]-A[2])*t)})`; }
function rgba(h,a){ const [r,g,b]=hexToRgb(h); return `rgba(${r},${g},${b},${a})`; }

// ---- Paint dab (pure CSS, vector — perfect circle, crisp at any size) ------
// Reads as a thick scoop of paint, not a glass marble: pooled dark rim
// (paint body), mottled surface texture, a broad soft wet sheen, one small
// specular glint, and a soft contact shadow. Colour-driven.
const PAINT_NOISE = "url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='90' height='90'%3E%3Cfilter id='p'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.12' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23p)'/%3E%3C/svg%3E\")";
function Bead({ color, size = 48, style }) {
  const coreHi = mix(color, '#ffffff', 0.30);   // raised, lit center
  const bodyLo = mix(color, '#000000', 0.30);
  const rim    = mix(color, '#000000', 0.55);   // pooled thick edge
  const sheen  = mix(color, '#ffffff', 0.80);   // wet sheen (tinted, not white)
  const bounce = mix(color, '#ffffff', 0.45);
  return (
    <div style={{ position:'relative', width:size, height:size, flex:'0 0 auto',
      filter:`drop-shadow(0 ${size*0.1}px ${size*0.12}px rgba(0,0,0,0.55))`, ...style }}>
      <div style={{
        position:'absolute', inset:0, borderRadius:'50%', overflow:'hidden',
        background: [
          `radial-gradient(circle at 50% 95%, ${bounce} 0%, rgba(255,255,255,0) 24%)`,      // bottom rim bounce
          `radial-gradient(circle at 50% 44%, ${coreHi} 0%, ${color} 44%, ${bodyLo} 80%, ${rim} 100%)`, // body + pooled rim
        ].join(', '),
        boxShadow: `inset 0 0 ${size*0.14}px ${size*0.05}px rgba(0,0,0,0.38), inset ${size*0.05}px ${size*0.07}px ${size*0.1}px rgba(255,255,255,0.16)`,
      }}>
        {/* paint surface texture */}
        <div style={{ position:'absolute', inset:'-25%', backgroundImage:PAINT_NOISE, backgroundSize:`${Math.max(40,size*0.95)}px`, opacity:0.28, mixBlendMode:'overlay' }} />
        {/* broad soft wet sheen */}
        <div style={{ position:'absolute', left:'13%', top:'9%', width:'60%', height:'44%', borderRadius:'50%',
          background:`radial-gradient(closest-side, ${sheen} 0%, rgba(255,255,255,0) 70%)`, opacity:0.85, transform:'rotate(-20deg)' }} />
        {/* small sharp specular glint */}
        <div style={{ position:'absolute', left:'27%', top:'19%', width:'18%', height:'12%', borderRadius:'50%',
          background:'radial-gradient(closest-side, rgba(255,255,255,0.95) 0%, rgba(255,255,255,0) 78%)' }} />
      </div>
    </div>
  );
}

// ---- Real oil-paint dab (texture lifted from a real palette-knife photo,
// recolored to each pigment → assets/paint/<id>.png). Perfect circle. --------
function PaintDab({ id, color, size = 48, style }) {
  const pid = id || (color && (PIGMENTS.find(p => p.swatch.toLowerCase() === String(color).toLowerCase()) || {}).id);
  if (!pid) return <Bead color={color || '#888'} size={size} style={style} />;
  return <img src={`assets/paint/${pid}.png`} alt={(PIG[pid] && PIG[pid].name) || pid} draggable="false"
    style={{ width: size, height: size, display: 'block', borderRadius: '50%', filter: `drop-shadow(0 ${Math.max(2,size*0.08)}px ${Math.max(4,size*0.18)}px rgba(0,0,0,0.5))`, ...style }} />;
}

// Build an abstract "graded color study" background from a set of pigment ids.
// Low-anchor pigments tint the shadows (bottom), high-anchor tint highlights (top)
// — mirroring the app's split-tone mixing. Returns a CSS background string.
function lookField(ids, { base = '#2A2622' } = {}) {
  if (!ids || !ids.length) return base;
  const layers = ids.map(id => {
    const p = PIG[id]; if (!p) return null;
    const y = Math.round((1 - p.anchor) * 100);          // anchor 1 -> top
    const x = 40 + (p.anchor * 30);
    const strong = rgba(p.swatch, 0.62);
    const fade   = rgba(p.swatch, 0);
    return `radial-gradient(120% 90% at ${x}% ${y}%, ${strong}, ${fade} 60%)`;
  }).filter(Boolean);
  // a soft vertical tone ramp underneath gives it photographic falloff
  layers.push(`linear-gradient(180deg, ${mix(base,'#ffffff',0.10)} 0%, ${base} 55%, ${mix(base,'#000000',0.45)} 100%)`);
  return layers.join(', ');
}

// ---- Reveal on scroll (with bulletproof fallbacks so nothing stays hidden)
function Reveal({ children, delay = 0, as: Tag = 'div', style, className = '' }) {
  const ref = React.useRef(null);
  const [seen, setSeen] = React.useState(false);
  React.useEffect(() => {
    const el = ref.current;
    if (!el) { setSeen(true); return; }
    // already in (or near) the viewport on mount -> show immediately
    const r = el.getBoundingClientRect();
    const vh = window.innerHeight || 800;
    if (r.top < vh * 0.96 && r.bottom > 0) setSeen(true);
    let io;
    if ('IntersectionObserver' in window) {
      io = new IntersectionObserver(([e]) => { if (e.isIntersecting) { setSeen(true); io.disconnect(); } }, { threshold: 0.08 });
      io.observe(el);
    } else { setSeen(true); }
    const t = setTimeout(() => setSeen(true), 900); // safety net
    return () => { if (io) io.disconnect(); clearTimeout(t); };
  }, []);
  return <Tag ref={ref} className={`reveal ${seen ? 'in' : ''} ${className}`} style={{ transitionDelay: `${delay}ms`, ...style }}>{children}</Tag>;
}

// ---- Icon (Lucide-derived line set, stroke 1.75, currentColor) ------------
// hueve ships no icon font of its own; these match Lucide's geometry/weight.
const ICON_PATHS = {
  arrowRight: 'M5 12h14M13 6l6 6-6 6',
  arrowLeft:  'M19 12H5M11 18l-6-6 6-6',
  download:   'M12 3v12M7 11l5 5 5-5M5 21h14',
  share:      'M4 12v7a1 1 0 0 0 1 1h14a1 1 0 0 0 1-1v-7M12 15V3M8 7l4-4 4 4',
  lock:       'M6 10V8a6 6 0 0 1 12 0v2M5 10h14v10H5z',
  sliders:    'M4 6h11M19 6h1M4 12h3M11 12h9M4 18h8M16 18h4M15 4v4M7 10v4M12 16v4',
  layers:     'M12 3 3 8l9 5 9-5-9-5ZM3 13l9 5 9-5M3 18l9 5 9-5',
  check:      'M5 13l4 4 10-11',
  image:      'M4 5h16v14H4zM4 16l5-5 4 4 3-3 4 4',
  code:       'M9 8l-5 4 5 4M15 8l5 4-5 4',
  grain:      'M5 6h.01M11 6h.01M17 6h.01M8 11h.01M14 11h.01M20 11h.01M5 16h.01M11 16h.01M17 16h.01',
  sun:        'M12 4V2M12 22v-2M5 5 4 4M20 20l-1-1M4 12H2M22 12h-2M5 19l-1 1M20 4l-1 1M12 8a4 4 0 1 0 0 8 4 4 0 0 0 0-8Z',
  contrast:   'M12 3a9 9 0 1 0 0 18zM12 3a9 9 0 0 1 0 18',
  drop:       'M12 3s6 6.5 6 10.5a6 6 0 0 1-12 0C6 9.5 12 3 12 3Z',
  plus:       'M12 5v14M5 12h14',
  spark:      'M12 3l1.8 5.2L19 10l-5.2 1.8L12 17l-1.8-5.2L5 10l5.2-1.8Z',
};
function Icon({ name, size = 20, color = 'currentColor', sw = 1.75, fill = 'none', style }) {
  return (
    <svg width={size} height={size} viewBox="0 0 24 24" fill={fill} stroke={color}
         strokeWidth={sw} strokeLinecap="round" strokeLinejoin="round" style={{ display:'block', ...style }}>
      <path d={ICON_PATHS[name] || ''} />
    </svg>
  );
}

// ---- iPhone frame ----------------------------------------------------------
function Phone({ children, width = 300, glow, style }) {
  const ratio = 19.5 / 9;
  const height = Math.round(width * ratio);
  return (
    <div style={{ position:'relative', width, ...style }}>
      {glow && (
        <div style={{ position:'absolute', inset:'-12% -18%', background:glow, filter:'blur(48px)', opacity:0.55, zIndex:0, pointerEvents:'none' }} />
      )}
      <div style={{
        position:'relative', zIndex:1, width, height,
        background:'#000', borderRadius: width*0.155,
        padding: Math.max(7, width*0.026),
        boxShadow:'0 0 0 1.5px rgba(255,255,255,0.10), 0 1px 0 rgba(255,255,255,0.14) inset, 0 40px 80px -28px rgba(0,0,0,0.85), 0 18px 40px -22px rgba(0,0,0,0.7)',
      }}>
        <div style={{ position:'relative', width:'100%', height:'100%', background:'var(--bg)', borderRadius: width*0.125, overflow:'hidden', display:'flex', flexDirection:'column' }}>
          {/* dynamic island */}
          <div style={{ position:'absolute', top: width*0.035, left:'50%', transform:'translateX(-50%)', width: width*0.30, height: width*0.085, background:'#000', borderRadius:width*0.05, zIndex:20 }} />
          {children}
        </div>
      </div>
    </div>
  );
}

// status bar for inside-phone screens
function StatusBar({ dark = true }){
  const col = dark ? '#F2F2F4' : '#1B1B1F';
  return (
    <div style={{ display:'flex', justifyContent:'space-between', alignItems:'center', padding:'13px 26px 0', fontFamily:'var(--sans)', fontSize:13, fontWeight:600, color:col, flex:'0 0 auto' }}>
      <span>9:41</span>
      <span style={{ display:'inline-flex', gap:6, alignItems:'center' }}>
        <svg width="17" height="11" viewBox="0 0 17 11" fill={col}><rect x="0" y="6" width="3" height="5" rx="1"/><rect x="4.5" y="4" width="3" height="7" rx="1"/><rect x="9" y="2" width="3" height="9" rx="1"/><rect x="13.5" y="0" width="3" height="11" rx="1"/></svg>
        <svg width="24" height="11" viewBox="0 0 24 11" fill="none"><rect x="0.5" y="0.5" width="20" height="10" rx="2.5" stroke={col} opacity="0.5"/><rect x="2" y="2" width="15" height="7" rx="1.2" fill={col}/><rect x="21.5" y="3.5" width="1.5" height="4" rx="0.75" fill={col} opacity="0.5"/></svg>
      </span>
    </div>
  );
}

// ---- i18n (ja/en) ----------------------------------------------------------
// 日本=日本語、海外=英語を既定（navigator.language）。手動切替は Nav の LangToggle。
// 現在言語は window.__LANG（index.html の App が render 毎に設定）。t() がそれを読む。
function detectLang() {
  try {
    const l = (navigator.language || 'en').toLowerCase();
    return l.startsWith('ja') ? 'ja' : 'en';
  } catch (e) { return 'en'; }
}
function getInitialLang() {
  try {
    const v = localStorage.getItem('hueve.lang');
    if (v === 'ja' || v === 'en') return v;
  } catch (e) {}
  return detectLang();
}

const STR = {
  ja: {
    'nav.how': '使い方', 'nav.pigments': '絵の具', 'nav.pricing': '料金', 'nav.faq': 'FAQ',
    'nav.cta': '近日公開・iOS',
    'hero.eyebrow': 'Photo grading · iOS',
    'hero.cta1': '7日間 無料で試す', 'hero.cta2': '12色の絵の具を見る',
    'hero.pigments': '12 pigments',
    'footer.ctaHead': 'あなたの一枚に、\nあなただけの色を。',
    'footer.cta': '7日間 無料で試す',
    'footer.comingSoon': '近日公開 · iOS',
    'footer.tagline': '色を選ぶんじゃない。混ぜて作る。',
    'footer.privacy': 'プライバシー', 'footer.terms': '利用規約', 'footer.contact': 'お問い合わせ',
    'pos.lead': '「写真をフィルム調にする」アプリは数えきれないほどあります。hueve が立つのは、ワンタップの手軽さと、プロツールの自由の——その中間。',
    'pos.note': '※ 主要アプリの調査結果（2026年5月時点・自社調べ）。各カテゴリの全アプリを示すものではありません。',
    'pos.preset.k': 'プリセット型', 'pos.preset.sub': 'VSCO · Dazz · RNI', 'pos.preset.body': '用意されたルックを1つ選んで、強さを調整する。手軽。でも、みんな同じ顔になりがち。', 'pos.preset.cost': '浅い',
    'pos.hueve.sub': '混ぜて作る', 'pos.hueve.body': '設計された少数の絵の具を混ぜて、自分だけのルックを作る。混ぜる比喩だから、学ばなくても作れる。', 'pos.hueve.cost': 'ちょうどいい',
    'pos.pro.k': 'プロ LUT 型', 'pos.pro.sub': 'Lightroom · Affinity', 'pos.pro.body': '.cube を取り込み、レイヤーとカーブで追い込む。自由。でも、理解していないと使えない。', 'pos.pro.cost': '難しい',
    'pos.costLabel': '学習コスト',
    'how.head': '絵の具のように、混ぜて、重ねて、仕上げる。',
    'how.s1.title': '混ぜる', 'how.s1.body': '用意されたフィルターを選ぶのではなく、少数の「絵の具」を選んで混ぜる。迷ったら Aurum からひと匙。',
    'how.s2.title': '重ねる', 'how.s2.body': '複数の色を混ぜると、その比率に応じて滑らかに混ざり合う。50:50 ならちょうど中間。少し足すだけで表情が変わる。',
    'how.s3.title': '仕上げる', 'how.s3.body': '露出・コントラスト・退色・グレインで整えて、書き出し。作ったルックは保存して、コードで共有もできる。',
    'pig.head': '12 色の、絵の具。',
    'pig.lead': '大量のプリセットではなく、性格を設計した 12 色。それぞれが暗部と明部のどこを担当するかまで決まっているから、混ぜても濁らず、別々の方向にきれいに色づきます。',
    'pig.tone.shadow': '暗部', 'pig.tone.mid': '中間', 'pig.tone.high': '明部',
    'mix.head': '触って、混ぜてみる。',
    'mix.lead': '絵の具をタップして混ぜると、比率に応じて滑らかに混ざり合います。自分の写真を下のプレビューにドラッグすると、その場でグレーディングされます。',
    'mix.dragPhoto': '写真をドラッグしてグレーディング',
    'mix.emptyHint': '絵の具を選ぶと、ここに比率が出ます。',
    'mix.shareCode': '共有コード',
    'ctl.pigments': '絵の具（タップで混ぜる）', 'ctl.mix': '混合比', 'ctl.finishing': '仕上げ',
    'ctl.amount': '効き具合', 'ctl.exposure': '露出 (EV)', 'ctl.contrast': 'コントラスト', 'ctl.saturation': '彩度', 'ctl.wbBY': 'WB 青⇔黄', 'ctl.wbGP': 'WB 緑⇔紫', 'ctl.aging': '退色', 'ctl.grain': 'グレイン',
    'sc.back': '戻る', 'sc.compare': '長押しで元画像', 'sc.reset': 'リセット', 'sc.export': '書き出す', 'sc.dragPhoto': '写真をドラッグ',
    'fin.lead.pre': '露出・コントラスト・彩度・ホワイトバランスで土台を整え、', 'fin.lead.aging': '退色', 'fin.lead.and': 'と', 'fin.lead.grain': 'グレイン', 'fin.lead.post': 'で空気をまとわせる。やりすぎない範囲設計だから、何を動かしても破綻しません。',
    'fin.note': '退色・グレインは Pro の仕上げ。それ以外は無料でも使えます。',
    'looks.eyebrow': 'Looks · 共有',
    'looks.lead.pre': '気に入った混ぜ方は名前を付けて保存。', 'looks.lead.post': ' の短いコードにして、友達やSNSで共有できます。受け取ったコードの読み込みは、無料でも。',
    'looks.ui.looks': 'ルック', 'looks.ui.import': 'コード読込', 'looks.ui.share': '共有', 'looks.ui.save': '保存', 'looks.ui.placeholder': 'hue1;W=marlow:0.62,… を貼り付け', 'looks.ui.apply': '適用',
    'looks.name1': '夕方の窓', 'looks.name2': '8mm の夏', 'looks.name3': '雨の喫茶店', 'looks.name4': '海辺フェード',
    'price.head': '無料で、混ぜはじめられる。',
    'price.lead': '「混ぜて作る」体験の核は、無料でそのまま。もっと色がほしくなったら、Pro へ。',
    'price.freeSub': 'まずは混ぜてみる。', 'price.proSub': '混ぜて作る楽しさを、すべて解放。',
    'price.perYear': ' / 年', 'price.orMonth': 'または ¥480 / 月', 'price.cta': '7日間 無料で試す',
    'price.legal': 'トライアル終了後は選択中のプランで自動更新。期間終了の24時間以上前に解約しない限り更新されます。解約は App Store からいつでも可能です。',
    'price.free.f1': '4色の絵の具（暖・柔・寒・中立）', 'price.free.f2': '露出・コントラストなど基本の仕上げ', 'price.free.f3': '書き出し（長辺 1600px まで）', 'price.free.f4': '共有コードの読み込み',
    'price.pro.f1': '12色すべての絵の具を解放', 'price.pro.f2': '退色・グレインなど全ての仕上げ', 'price.pro.f3': 'フル解像度で書き出し', 'price.pro.f4': 'ルックの保存・共有コード',
    'faq.head': 'よくある質問',
    'faq.q1': 'フィルターアプリと何が違うの？', 'faq.a1': '用意されたルックを選ぶのではなく、設計された少数の「絵の具」を混ぜて、自分だけのルックを作るアプリです。プリセット選択ほど浅くなく、プロの LUT ツールほど難しくない、その中間を狙っています。',
    'faq.q2': '無料でどこまで使える？', 'faq.a2': '4色の絵の具（暖・柔・寒・中立をカバー）と、露出・コントラストなどの基本の仕上げ、書き出し（長辺 1600px まで）が無料で使えます。受け取った共有コードの読み込みも無料です。',
    'faq.q3': 'Pro は何ができる？', 'faq.a3': '12色すべての絵の具、退色・グレインを含む全ての仕上げ、フル解像度での書き出し、ルックの保存と共有コードが使えます。7日間の無料トライアル付きです。',
    'faq.q4': '動画にも使える？', 'faq.a4': 'いまは写真（静止画）専用です。書き出しまで端末内の描画エンジンで処理します。',
    'faq.q5': 'いつ公開される？', 'faq.a5': 'iOS 向けに近日公開予定です。',
  },
  en: {
    'nav.how': 'How', 'nav.pigments': 'Pigments', 'nav.pricing': 'Pricing', 'nav.faq': 'FAQ',
    'nav.cta': 'Coming soon · iOS',
    'hero.eyebrow': 'Photo grading · iOS',
    'hero.cta1': 'Try 7 days free', 'hero.cta2': 'See all 12 pigments',
    'hero.pigments': '12 pigments',
    'footer.ctaHead': 'Your shot,\nyour own color.',
    'footer.cta': 'Try 7 days free',
    'footer.comingSoon': 'Coming soon · iOS',
    'footer.tagline': 'Don’t pick a color. Mix your own.',
    'footer.privacy': 'Privacy', 'footer.terms': 'Terms', 'footer.contact': 'Contact',
    'pos.lead': 'There are countless apps that “make photos look like film.” hueve stands in between — the ease of one tap and the freedom of a pro tool.',
    'pos.note': '* Based on our survey of major apps (as of May 2026). Not a complete list of every app in each category.',
    'pos.preset.k': 'Preset apps', 'pos.preset.sub': 'VSCO · Dazz · RNI', 'pos.preset.body': 'Pick one ready-made look and adjust its strength. Easy — but everyone ends up with the same face.', 'pos.preset.cost': 'Shallow',
    'pos.hueve.sub': 'Mix to make', 'pos.hueve.body': 'Blend a small set of designed pigments into a look that’s yours. It’s a mixing metaphor, so you can create without learning.', 'pos.hueve.cost': 'Just right',
    'pos.pro.k': 'Pro LUT tools', 'pos.pro.sub': 'Lightroom · Affinity', 'pos.pro.body': 'Import .cube and dial it in with layers and curves. Free — but useless if you don’t understand it.', 'pos.pro.cost': 'Hard',
    'pos.costLabel': 'Learning curve',
    'how.head': 'Mix, layer, and finish — like paint.',
    'how.s1.title': 'Mix', 'how.s1.body': 'Instead of choosing a filter, pick and blend a few “pigments.” Not sure? Start with a spoonful of Aurum.',
    'how.s2.title': 'Layer', 'how.s2.body': 'Mix several colors and they blend smoothly by ratio. 50:50 is right in the middle. A little goes a long way.',
    'how.s3.title': 'Finish', 'how.s3.body': 'Tune exposure, contrast, fade and grain, then export. Save your looks and share them with a code.',
    'pig.head': 'Twelve pigments.',
    'pig.lead': 'Not a pile of presets — 12 colors with designed personalities. Each owns a part of the shadows-to-highlights range, so mixes stay clean and color in different directions instead of going muddy.',
    'pig.tone.shadow': 'Shadows', 'pig.tone.mid': 'Mids', 'pig.tone.high': 'Highlights',
    'mix.head': 'Touch it. Mix it.',
    'mix.lead': 'Tap pigments to mix; they blend smoothly by ratio. Drag your own photo onto the preview below to grade it on the spot.',
    'mix.dragPhoto': 'Drag a photo to grade',
    'mix.emptyHint': 'Pick pigments to see the ratios here.',
    'mix.shareCode': 'Share code',
    'ctl.pigments': 'Pigments (tap to mix)', 'ctl.mix': 'Mix ratio', 'ctl.finishing': 'Finishing',
    'ctl.amount': 'Strength', 'ctl.exposure': 'Exposure (EV)', 'ctl.contrast': 'Contrast', 'ctl.saturation': 'Saturation', 'ctl.wbBY': 'WB Blue–Yellow', 'ctl.wbGP': 'WB Green–Purple', 'ctl.aging': 'Fade', 'ctl.grain': 'Grain',
    'sc.back': 'Back', 'sc.compare': 'Hold for original', 'sc.reset': 'Reset', 'sc.export': 'Export', 'sc.dragPhoto': 'Drag a photo',
    'fin.lead.pre': 'Set the base with exposure, contrast, saturation and white balance, then wrap it in air with ', 'fin.lead.aging': 'fade', 'fin.lead.and': ' and ', 'fin.lead.grain': 'grain', 'fin.lead.post': '. The ranges are designed not to overdo it, so nothing breaks no matter what you move.',
    'fin.note': 'Fade and grain are Pro finishing. Everything else is free.',
    'looks.eyebrow': 'Looks · Share',
    'looks.lead.pre': 'Save a mix you like with a name. Turn it into a short ', 'looks.lead.post': ' code and share it with friends or on social. Importing a received code is free.',
    'looks.ui.looks': 'Looks', 'looks.ui.import': 'Import', 'looks.ui.share': 'Share', 'looks.ui.save': 'Save', 'looks.ui.placeholder': 'Paste hue1;W=marlow:0.62,…', 'looks.ui.apply': 'Apply',
    'looks.name1': 'Evening window', 'looks.name2': '8mm summer', 'looks.name3': 'Rainy café', 'looks.name4': 'Seaside fade',
    'price.head': 'Start mixing for free.',
    'price.lead': 'The heart of “mix to make” is free as-is. Want more colors? Go Pro.',
    'price.freeSub': 'Start by mixing.', 'price.proSub': 'Unlock the full joy of mixing.',
    'price.perYear': ' / yr', 'price.orMonth': 'or ¥480 / mo', 'price.cta': 'Try 7 days free',
    'price.legal': 'After the trial, the selected plan auto-renews. It renews unless cancelled at least 24 hours before the period ends. Cancel anytime in the App Store.',
    'price.free.f1': '4 pigments (warm · soft · cool · neutral)', 'price.free.f2': 'Basic finishing — exposure, contrast & more', 'price.free.f3': 'Export up to 1600px on the long edge', 'price.free.f4': 'Import share codes',
    'price.pro.f1': 'All 12 pigments unlocked', 'price.pro.f2': 'All finishing — fade, grain & more', 'price.pro.f3': 'Full-resolution export', 'price.pro.f4': 'Save looks & share codes',
    'faq.head': 'Frequently asked',
    'faq.q1': 'How is this different from a filter app?', 'faq.a1': 'Instead of choosing a ready-made look, you blend a small set of designed “pigments” into a look that’s yours. Not as shallow as picking a preset, not as hard as a pro LUT tool — we aim for the middle.',
    'faq.q2': 'How far can I go for free?', 'faq.a2': '4 pigments (covering warm, soft, cool, neutral), basic finishing like exposure and contrast, and export up to 1600px on the long edge are free. Importing a received share code is free too.',
    'faq.q3': 'What does Pro add?', 'faq.a3': 'All 12 pigments, all finishing including fade and grain, full-resolution export, and saving looks with share codes. It comes with a 7-day free trial.',
    'faq.q4': 'Does it work for video?', 'faq.a4': 'For now it’s photos (stills) only. Everything through export runs on the on-device rendering engine.',
    'faq.q5': 'When does it launch?', 'faq.a5': 'Coming soon for iOS.',
  },
};

function t(key, params) {
  const lang = (typeof window !== 'undefined' && window.__LANG) || 'ja';
  let s = (STR[lang] && STR[lang][key]) || (STR.ja && STR.ja[key]) || key;
  if (params) for (const k in params) s = s.split('{' + k + '}').join(params[k]);
  return s;
}

// 現在言語（window.__LANG）。マークアップ付きコピーは lang で分岐描画する。
function lang() { return (typeof window !== 'undefined' && window.__LANG) || 'ja'; }

function LangToggle() {
  const cur = lang();
  const next = cur === 'ja' ? 'en' : 'ja';
  return (
    <button
      onClick={() => { if (window.__setLang) window.__setLang(next); }}
      aria-label="Language"
      style={{
        background: 'transparent', border: '1px solid var(--divider-strong)', color: 'var(--text-mute)',
        borderRadius: 'var(--r-pill)', padding: '6px 12px', fontFamily: 'var(--mono)', fontSize: 11.5,
        letterSpacing: '0.06em', cursor: 'pointer',
      }}
    >
      {cur === 'ja' ? 'EN' : '日本語'}
    </button>
  );
}

Object.assign(window, { PIGMENTS, PIG, FREE_IDS, lookField, mix, rgba, hexToRgb, Reveal, Icon, Bead, PaintDab, Phone, StatusBar, t, lang, STR, detectLang, getInitialLang, LangToggle });
