// Other pages: LINE flow, Shop, Ship, Learn, About
const { useState: useStateP, useEffect: useEffectP, useRef: useRefP } = React;
const TAIWAN_CITIES_FOR_CHECKOUT = ['台北市', '新北市', '桃園市', '台中市', '台南市', '高雄市', '基隆市', '新竹市', '嘉義市', '新竹縣', '苗栗縣', '彰化縣', '南投縣', '雲林縣', '嘉義縣', '屏東縣', '宜蘭縣', '花蓮縣', '台東縣', '澎湖縣', '金門縣', '連江縣'];

function shippingTimelineDate(offsetDays, estimated = false) {
  const date = new Date();
  date.setHours(12, 0, 0, 0);
  date.setDate(date.getDate() + offsetDays);
  const month = String(date.getMonth() + 1).padStart(2, '0');
  const day = String(date.getDate()).padStart(2, '0');
  return `${estimated ? '預計 ' : ''}${month}/${day}`;
}

function productSpecOptions(product) {
  const explicit = [
    ...(Array.isArray(product?.specOptions) ? product.specOptions : []),
    ...(Array.isArray(product?.specs) ? product.specs.map(item => item?.value || item?.label || item).filter(Boolean) : []),
  ].map(value => String(value || '').trim()).filter(Boolean);
  if (explicit.length) return Array.from(new Set(explicit)).slice(0, 6);

  const tags = Array.isArray(product?.tags) ? product.tags : [];
  const tagSpecs = tags
    .map(tag => String(tag || '').trim())
    .filter(tag => tag && /(入|g|kg|ml|l|個|枚|袋|盒|組|本|cm|包|瓶|罐|箱|片|粒)$/i.test(tag))
    .slice(0, 3);
  if (tagSpecs.length) return tagSpecs;
  return ['商品頁預設規格'];
}

function productDescriptionText(product) {
  return String(product?.description || '').trim()
    || '此商品由 JaFun 精選，可協助確認日本原站價格、庫存、規格與寄送條件。';
}

function productTemperatureLabel(value) {
  const labels = {
    ambient: '常溫',
    chilled: '冷藏',
    frozen: '冷凍',
  };
  return labels[value] || '依商品標示';
}

function getShippingTrackingSteps() {
  return [
    { s: '已下單', d: shippingTimelineDate(-4), state: 'done' },
    { s: '日本倉庫收貨', d: shippingTimelineDate(-2), state: 'done' },
    { s: '集貨打包', d: shippingTimelineDate(0), state: 'current' },
    { s: '空運運送中', d: shippingTimelineDate(1, true), state: '' },
    { s: '台灣送達', d: shippingTimelineDate(3, true), state: '' },
  ];
}

function ShopPage({ t, onNav, initialRegion = '', initialCategory = '' }) {
  const categoryPatterns = {
    souvenirs: /伴手禮|名物|菓子/,
    lifestyle: /生活雜貨|雜貨|日本製/,
    'toys-models': /玩具|模型|周邊/,
  };
  const categoryTabs = [
    { slug: '', label: '全部' },
    { slug: 'souvenirs', label: '伴手禮' },
    { slug: 'lifestyle', label: '生活雜貨' },
    { slug: 'toys-models', label: '玩具模型' },
  ];
  const [activeCategory, setActiveCategory] = useStateP(String(initialCategory || '').trim());
  const baseProducts = (window.PRODUCTS || []).map((p, i) => ({ ...p, listKey: `${p.id}-${i}` }));
  const allProducts = activeCategory
    ? baseProducts.filter(p => (p.categorySlugs || []).includes(activeCategory)
        || (categoryPatterns[activeCategory] && ((p.tags || []).some(tag => categoryPatterns[activeCategory].test(tag)) || categoryPatterns[activeCategory].test(p.name || ''))))
    : baseProducts;
  // 只顯示有商品的地區 tab，避免空白頁（TC-085）
  const regionOrder = ['北海道', '東京', '京都', '大阪', '名古屋', '福岡', '沖繩'];
  const regions = ['全部', ...regionOrder.filter(region =>
    allProducts.some(p => String(p.region || '').includes(region)))];
  const normalizedInitialRegion = String(initialRegion || '').split(/\s+/)[0];
  const initialIndex = Math.max(0, regions.indexOf(normalizedInitialRegion));
  const [active, setActive] = useStateP(initialIndex);
  const [visible, setVisible] = useStateP(8);
  const [autoLoadSupported, setAutoLoadSupported] = useStateP(true);
  const [mobileSearch, setMobileSearch] = useStateP('');
  const loadMoreRef = useRefP(null);
  useEffectP(() => {
    if (!normalizedInitialRegion) return;
    const index = regions.indexOf(normalizedInitialRegion);
    if (index >= 0) {
      setActive(index);
      setVisible(8);
    }
  }, [normalizedInitialRegion, regions.length]);
  const filteredProducts = active === 0 ? allProducts : allProducts.filter(p => String(p.region || '').includes(regions[active]));
  const hasMoreProducts = visible < filteredProducts.length;
  const loadNextProducts = () => {
    setVisible(current => Math.min(current + 8, filteredProducts.length));
  };
  const chooseCategory = (slug) => {
    setActiveCategory(slug);
    setActive(0);
    setVisible(8);
  };

  useEffectP(() => {
    if (!hasMoreProducts) return undefined;
    const sentinel = loadMoreRef.current;
    if (!sentinel) return undefined;
    if (!('IntersectionObserver' in window)) {
      setAutoLoadSupported(false);
      return undefined;
    }

    setAutoLoadSupported(true);
    const observer = new IntersectionObserver(entries => {
      if (entries.some(entry => entry.isIntersecting)) {
        setVisible(current => Math.min(current + 8, filteredProducts.length));
      }
    }, {
      rootMargin: '520px 0px',
    });

    observer.observe(sentinel);
    return () => observer.disconnect();
  }, [filteredProducts.length, hasMoreProducts, visible]);

  return (
    <div data-screen-label="03 Shop">
      <form className="shop-mobile-search-head" onSubmit={(event) => {
        event.preventDefault();
        window.submitMobileProductSearch?.(mobileSearch, onNav);
      }}>
        <button type="button" className="shop-mobile-logo" onClick={() => onNav('home')} aria-label="回到首頁">
          <Logo size={42} />
        </button>
        <label>
          <input
            value={mobileSearch}
            onChange={event => setMobileSearch(event.target.value)}
            placeholder="輸入想要尋找得日本名產、伴手禮"
          />
          <button type="submit" aria-label="搜尋">
            <svg width="21" height="21" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.2"><circle cx="11" cy="11" r="7" /><path d="M21 21l-4.3-4.3" /></svg>
          </button>
        </label>
      </form>
      <section className="page-hero">
        <div className="crumb">HOME / 日本商城</div>
        <h1>日本各地 名物</h1>
        <div className="jp">日本全国 お土産 — 編 集 部 厳 選</div>
        <p className="desc">編輯每週實地嚴選，從北海道到沖繩、從百年老舖到新銳職人，把日本的職人精神帶回家。</p>
      </section>
      <div className="region-bar">
        {categoryTabs.map(tab => (
          <span key={tab.slug || 'all'} className={`region-chip ${activeCategory === tab.slug ? 'active' : ''}`} onClick={() => chooseCategory(tab.slug)}>{tab.label}</span>
        ))}
      </div>
      <div className="region-bar">
        {regions.map((r, i) => (
          <span key={i} className={`region-chip ${active === i ? 'active' : ''}`} onClick={() => { setActive(i); setVisible(8); }}>{r}</span>
        ))}
      </div>
      <section className="section">
        <div className="section-narrow">
          <div className="product-grid">
            {filteredProducts.slice(0, visible).map((p) => (
              <ProductCard key={p.listKey} product={p} onClick={() => onNav('product', { product: p })} />
            ))}
          </div>
          {filteredProducts.length === 0 && (
            <div style={{ padding: 80, textAlign: 'center', fontFamily: 'var(--serif)', color: 'var(--jf-mute)', background: 'var(--jf-paper-2)' }}>
              此地區商品準備中，可先使用 LINE 代購貼上商品網址。
            </div>
          )}
          {hasMoreProducts && <div ref={loadMoreRef} className="infinite-load-sentinel" aria-hidden="true" />}
          <div style={{ textAlign: 'center', marginTop: hasMoreProducts && autoLoadSupported ? 0 : 60 }}>
            {hasMoreProducts && !autoLoadSupported ? (
              <button className="btn btn-ghost" onClick={loadNextProducts}>載入更多商品</button>
            ) : filteredProducts.length > 0 && !hasMoreProducts ? (
              <button className="btn btn-ghost" onClick={() => onNav('line')}>找不到商品？使用 LINE 代購</button>
            ) : null}
          </div>
        </div>
      </section>
    </div>
  );
}

function ProductDetailPage({ t, onNav, product, onAddToCart, cartCount = 0 }) {
  const p = product || window.PRODUCTS[0];
  const [qty, setQty] = useStateP(1);
  const [tab, setTab] = useStateP('desc');
  const specOptions = productSpecOptions(p);
  const [selectedSpec, setSelectedSpec] = useStateP(0);
  const [specSheetOpen, setSpecSheetOpen] = useStateP(false);
  useEffectP(() => {
    setSelectedSpec(0);
  }, [p.id]);
  const selectedSpecLabel = specOptions[Math.min(selectedSpec, specOptions.length - 1)] || '標準規格';
  const sizedGallery = Array.isArray(p.galleryImageSizes) && p.galleryImageSizes.length ? p.galleryImageSizes : null;
  const gallery = Array.from(new Set([
    ...(sizedGallery ? sizedGallery.map(image => imageVariantUrl(image, 'og', p.img)) : (Array.isArray(p.gallery) && p.gallery.length ? p.gallery : [p.img])),
  ].filter(Boolean)));
  const [active, setActive] = useStateP(0);
  useEffectP(() => {
    setActive(0);
  }, [p.id, gallery.length]);
  const [added, setAdded] = useStateP(false);
  const [favorite, setFavorite] = useStateP(() => Boolean(window.isFavoriteProduct?.(p)));
  const [favoritePromptOpen, setFavoritePromptOpen] = useStateP(false);
  const related = window.PRODUCTS.filter(x => x.id !== p.id).slice(0, 4);
  useEffectP(() => {
    setFavorite(Boolean(window.isFavoriteProduct?.(p)));
    const onFavoritesChanged = () => setFavorite(Boolean(window.isFavoriteProduct?.(p)));
    window.addEventListener('jafun:favorites-changed', onFavoritesChanged);
    window.addEventListener('storage', onFavoritesChanged);
    return () => {
      window.removeEventListener('jafun:favorites-changed', onFavoritesChanged);
      window.removeEventListener('storage', onFavoritesChanged);
    };
  }, [p.id, p.sku, p.slug]);
  useEffectP(() => {
    if (!window.isMemberLoggedIn?.()) return;
    const pending = window.readPendingFavoriteProduct?.();
    const pendingId = window.favoriteProductId?.(pending);
    const productId = window.favoriteProductId?.(p);
    if (!pendingId || pendingId !== productId) return;
    const result = window.completePendingFavoriteProduct?.();
    if (result?.productId === productId) {
      setFavorite(Boolean(result.isFavorite));
    }
  }, [p.id, p.sku, p.slug]);
  const cartProduct = () => ({
    ...p,
    cartId: `${p.id}-${selectedSpecLabel}`,
    optionSummary: `規格: ${selectedSpecLabel}`,
    selectedOptions: { spec: selectedSpecLabel },
    extraShippingTWD: Math.max(0, Math.round(Number(p.extraShippingTWD || 0))),
    weightKg: Math.max(0.1, Number(p.weightKg || 1)),
  });
  const addCurrent = () => {
    onAddToCart?.(cartProduct(), qty);
    setAdded(true);
    window.setTimeout(() => setAdded(false), 1800);
  };
  const buyNow = () => {
    onAddToCart?.(cartProduct(), qty);
    onNav('cart');
  };
  const toggleFavorite = () => {
    if (!favorite && !window.isMemberLoggedIn?.()) {
      setFavoritePromptOpen(true);
      return;
    }
    const result = window.toggleFavoriteProduct?.(p, !favorite);
    setFavorite(Boolean(result?.isFavorite));
  };
  const completeFavorite = () => {
    const result = window.toggleFavoriteProduct?.(p, true);
    setFavorite(Boolean(result?.isFavorite));
  };
  const closeFavoritePrompt = () => {
    const pending = window.readPendingFavoriteProduct?.();
    if (window.favoriteProductId?.(pending) === window.favoriteProductId?.(p)) {
      window.clearPendingFavoriteProduct?.();
    }
    setFavoritePromptOpen(false);
  };
  const goBack = () => {
    if (window.history.length > 1) {
      window.history.back();
      return;
    }
    onNav('shop');
  };

  return (
    <div data-screen-label="03b Product Detail" className="product-detail-page">
      <div className="product-mobile-view">
        <div className="product-mobile-toolbar">
          <div>
            <button type="button" onClick={goBack} aria-label="返回">
              <svg width="23" height="23" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><path d="M19 12H5M12 19l-7-7 7-7" /></svg>
            </button>
            <button type="button" onClick={() => onNav('home')} aria-label="首頁">
              <svg width="23" height="23" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.9"><path d="M3 11.5 12 4l9 7.5" /><path d="M5 10.5V20h14v-9.5" /><path d="M9.5 20v-5h5v5" /></svg>
            </button>
          </div>
          <div>
            <button type="button" className={favorite ? 'is-active' : ''} onClick={toggleFavorite} aria-label={favorite ? '取消收藏' : '收藏'}>
              <svg width="23" height="23" viewBox="0 0 24 24" fill={favorite ? 'currentColor' : 'none'} stroke="currentColor" strokeWidth="1.9"><path d="M20.8 4.6a5.3 5.3 0 0 0-7.5 0L12 5.9l-1.3-1.3a5.3 5.3 0 1 0-7.5 7.5l1.3 1.3L12 21l7.5-7.6 1.3-1.3a5.3 5.3 0 0 0 0-7.5z" /></svg>
            </button>
            <button type="button" onClick={() => onNav('cart')} aria-label="購物車" style={{ position: 'relative' }}>
              <svg width="23" height="23" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.9"><path d="M3 3h2l2.4 12.2a2 2 0 002 1.8h9.5a2 2 0 002-1.6L22 7H6" /><circle cx="9" cy="20" r="1.5" /><circle cx="18" cy="20" r="1.5" /></svg>
              {cartCount > 0 && <em style={{ position: 'absolute', top: -3, right: -3, minWidth: 16, height: 16, padding: '0 4px', borderRadius: 8, background: 'var(--jf-red)', color: '#fff', fontSize: 10, fontStyle: 'normal', fontWeight: 700, display: 'grid', placeItems: 'center', lineHeight: 1, boxSizing: 'border-box' }}>{Math.min(cartCount, 99)}</em>}
            </button>
          </div>
        </div>

        <div className="product-mobile-crumb">
          <span onClick={() => onNav('home')}>首頁</span> / <span onClick={() => onNav('shop', { region: p.region.split(' ')[0] })}>{p.region.split(' ')[0]}</span> / {p.name}
        </div>

        <div className="product-mobile-gallery">
          <div className="product-mobile-gallery-track" style={{ transform: `translateX(${7 - (active * 88)}%)` }}>
            {gallery.map((g, i) => (
              <button key={`${g}-${i}`} type="button" className={`product-mobile-slide ${active === i ? 'is-active' : ''}`} onClick={() => setActive(i)}>
                <img
                  src={imageVariantUrl(sizedGallery?.[i], 'og', g)}
                  srcSet={imageVariantSrcSet(sizedGallery?.[i])}
                  sizes="86vw"
                  alt={i === 0 ? p.name : ''}
                  loading={i === 0 ? 'eager' : 'lazy'}
                  decoding="async"
                />
              </button>
            ))}
          </div>
        </div>
        <div className="product-mobile-dots">
          {gallery.map((_, i) => (
            <button key={i} type="button" className={active === i ? 'active' : ''} onClick={() => setActive(i)} aria-label={`第 ${i + 1} 張圖片`} />
          ))}
        </div>

        <section className="product-mobile-content">
          <h1>{p.name}</h1>
          <div className="product-mobile-region">
            <svg width="19" height="19" viewBox="0 0 24 24" fill="currentColor"><path d="M12 2a7 7 0 0 0-7 7c0 5.2 7 13 7 13s7-7.8 7-13a7 7 0 0 0-7-7Zm0 9.5A2.5 2.5 0 1 1 12 6a2.5 2.5 0 0 1 0 5.5Z" /></svg>
            {productRegionLabel(p.region)}
          </div>
          <h2>商品介紹</h2>
          <p>{productDescriptionText(p)}</p>
          <h2>規格資訊</h2>
          <p>目前選擇：{selectedSpecLabel}。下單前仍會依日本原站庫存與規格重新確認。</p>
          <h2>運送說明</h2>
          <p>商品會先送抵 JaFun 日本倉庫，完成整理與打包後空運寄回台灣，會員中心可追蹤處理進度。</p>
        </section>

        <div className="product-mobile-buybar">
          <div>
            <strong>{p.name}</strong>
            <span><em>{p.badge && p.badge !== '舊站匯入' ? p.badge : '預購商品'}</em> <b>NT$ {p.price}</b> 數量: {qty}</span>
          </div>
          <button type="button" onClick={() => setSpecSheetOpen(true)}>選擇商品</button>
        </div>

        {specSheetOpen && (
          <div className="mobile-spec-backdrop" onClick={() => setSpecSheetOpen(false)}>
            <section className="mobile-spec-sheet" onClick={event => event.stopPropagation()} aria-label="選擇商品規格">
              <div className="mobile-spec-head">
                <button type="button" onClick={() => setSpecSheetOpen(false)} aria-label="關閉">×</button>
                <strong>選擇商品規格</strong>
                <span />
              </div>
              <div className="mobile-spec-body">
                <label>份量</label>
                <div className="mobile-spec-options">
                  {specOptions.map((s, i) => (
                    <button
                      key={`${s}-${i}`}
                      type="button"
                      className={selectedSpec === i ? 'selected' : ''}
                      onClick={() => setSelectedSpec(i)}
                    >
                      {s}
                      {selectedSpec === i && <span>✓</span>}
                    </button>
                  ))}
                </div>
                <label>數量</label>
                <div className="mobile-spec-qty">
                  <button type="button" onClick={() => setQty(Math.max(1, qty - 1))}>−</button>
                  <span>{qty}</span>
                  <button type="button" onClick={() => setQty(qty + 1)}>＋</button>
                </div>
              </div>
              <div className="mobile-spec-submit">
                <div>
                  <strong>{p.name}</strong>
                  <span><em>{p.badge && p.badge !== '舊站匯入' ? p.badge : '預購商品'}</em> <b>NT$ {p.price}</b> 數量: {qty}</span>
                </div>
                <button type="button" onClick={() => { addCurrent(); setSpecSheetOpen(false); }}>加入購物車</button>
              </div>
            </section>
          </div>
        )}
      </div>

      <div className="product-desktop-view">
      <div style={{ paddingTop: 100, paddingLeft: 80, paddingRight: 80, background: '#fff', borderBottom: '1px solid var(--jf-line)' }}>
        <div style={{ fontFamily: 'var(--sans)', fontSize: 11, letterSpacing: 3, color: 'var(--jf-mute)', padding: '20px 0' }}>
          <span onClick={() => onNav('home')} style={{ cursor: 'pointer' }}>HOME</span> / <span onClick={() => onNav('shop')} style={{ cursor: 'pointer' }}>日本商城</span> / <span onClick={() => onNav('shop', { region: p.region.split(' ')[0] })} style={{ color: 'var(--jf-red)', cursor: 'pointer' }} title={`查看${p.region.split(' ')[0]}商品`}>{formatRegionLabel(p.region)}</span>
        </div>
      </div>

      <section className="section section-narrow" style={{ paddingTop: 60 }}>
        <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 80, alignItems: 'start' }}>
          {/* Gallery */}
          <div>
            <div style={{ aspectRatio: '1/1', background: 'var(--jf-paper-2)', overflow: 'hidden', position: 'relative' }}>
              <img
                src={imageVariantUrl(sizedGallery?.[active], 'og', gallery[active])}
                srcSet={imageVariantSrcSet(sizedGallery?.[active])}
                sizes="(max-width: 860px) 100vw, 50vw"
                alt={p.name}
                fetchpriority="high"
                decoding="async"
                style={{ width: '100%', height: '100%', objectFit: 'cover' }}
              />
              {p.badge && p.badge !== '舊站匯入' && (
                <span style={{ position: 'absolute', top: 20, left: 20, background: p.badge === 'HOT' ? 'var(--jf-red)' : 'var(--jf-ink)', color: '#fff', padding: '6px 14px', fontFamily: 'var(--sans)', fontSize: 11, letterSpacing: 2 }}>{p.badge}</span>
              )}
            </div>
            <div style={{ display: 'grid', gridTemplateColumns: 'repeat(4, 1fr)', gap: 10, marginTop: 12 }}>
              {gallery.map((g, i) => (
                <div key={i} onClick={() => setActive(i)} style={{ aspectRatio: '1/1', overflow: 'hidden', cursor: 'pointer', border: active === i ? '2px solid var(--jf-red)' : '1px solid var(--jf-line)', background: 'var(--jf-paper-2)' }}>
                  <img src={imageVariantUrl(sizedGallery?.[i], 'card', g)} alt="" loading="lazy" decoding="async" style={{ width: '100%', height: '100%', objectFit: 'cover' }} />
                </div>
              ))}
            </div>
          </div>

          {/* Info */}
          <div>
            <div style={{ fontFamily: 'var(--sans)', fontSize: 11, letterSpacing: 3, color: 'var(--jf-red)' }}>{p.region}</div>
            <h1 style={{ fontFamily: 'var(--serif)', fontSize: 36, lineHeight: 1.4, letterSpacing: 1, margin: '12px 0 6px', fontWeight: 500 }}>{p.name}</h1>
            <div style={{ fontFamily: 'var(--serif)', fontSize: 14, color: 'var(--jf-mute)', letterSpacing: 2 }}>{p.jname}</div>

            <div style={{ display: 'flex', alignItems: 'baseline', gap: 14, margin: '24px 0' }}>
              <div style={{ fontFamily: 'var(--serif)', fontSize: 42, color: 'var(--jf-red)', fontWeight: 600 }}>NT$ {p.price}</div>
              <div style={{ fontFamily: 'var(--sans)', fontSize: 13, color: 'var(--jf-mute)' }}>¥{p.jpy.toLocaleString()} · 含代購手續費</div>
            </div>

            <div style={{ background: 'var(--jf-paper-2)', padding: 18, marginBottom: 24, fontFamily: 'var(--serif)', fontSize: 13, color: 'var(--jf-mute-2)', lineHeight: 1.8, borderLeft: '2px solid var(--jf-red)' }}>
              直接從日本倉庫合併出貨 · 空運 3-5 個工作天到達 · 商品到貨即攝影驗貨
            </div>

            <div style={{ marginBottom: 18 }}>
              <div style={{ fontFamily: 'var(--sans)', fontSize: 11, letterSpacing: 2, color: 'var(--jf-mute)', marginBottom: 10 }}>規格</div>
              <div style={{ display: 'flex', gap: 8, flexWrap: 'wrap' }}>
                {specOptions.map((s, i) => (
                  <button
                    key={`${s}-${i}`}
                    type="button"
                    aria-pressed={selectedSpec === i}
                    onClick={() => setSelectedSpec(i)}
                    style={{
                      padding: '10px 18px',
                      border: selectedSpec === i ? '1px solid var(--jf-ink)' : '1px solid var(--jf-line)',
                      fontFamily: 'var(--serif)',
                      fontSize: 13,
                      cursor: 'pointer',
                      background: selectedSpec === i ? 'var(--jf-ink)' : '#fff',
                      color: selectedSpec === i ? '#fff' : 'inherit',
                    }}
                  >
                    {s}
                  </button>
                ))}
              </div>
            </div>

            <div style={{ display: 'flex', alignItems: 'center', gap: 16, marginBottom: 24 }}>
              <span style={{ fontFamily: 'var(--sans)', fontSize: 11, letterSpacing: 2, color: 'var(--jf-mute)' }}>數量</span>
              <div style={{ display: 'flex', alignItems: 'center', border: '1px solid var(--jf-line)' }}>
                <button onClick={() => setQty(Math.max(1, qty - 1))} style={{ width: 38, height: 38 }}>−</button>
                <span style={{ fontFamily: 'var(--serif)', fontSize: 16, width: 50, textAlign: 'center' }}>{qty}</span>
                <button onClick={() => setQty(qty + 1)} style={{ width: 38, height: 38 }}>+</button>
              </div>
              <span style={{ fontFamily: 'var(--sans)', fontSize: 11, color: 'var(--jf-mute)' }}>庫存充足</span>
            </div>

            <div style={{ display: 'flex', gap: 10 }}>
              <button className="btn btn-primary" style={{ flex: 1, justifyContent: 'center' }} onClick={buyNow}>立即購買</button>
              <button className="btn btn-ghost" onClick={addCurrent}>{added ? '已加入' : '加入購物車'}</button>
              <button
                className="btn btn-ghost"
                type="button"
                aria-pressed={favorite}
                onClick={toggleFavorite}
                style={{
                  width: 50,
                  padding: 14,
                  justifyContent: 'center',
                  color: favorite ? 'var(--jf-red)' : 'var(--jf-ink)',
                  borderColor: favorite ? 'var(--jf-red)' : 'var(--jf-ink)',
                }}
                title={favorite ? '取消收藏' : '收藏'}
              >
                {favorite ? '♥' : '♡'}
              </button>
            </div>
            {added && (
              <div style={{ marginTop: 14, padding: '12px 14px', background: 'var(--jf-paper-2)', fontFamily: 'var(--serif)', fontSize: 13, color: 'var(--jf-mute-2)' }}>
                已加入購物車。
                <button onClick={() => onNav('cart')} style={{ marginLeft: 10, color: 'var(--jf-red)', borderBottom: '1px solid currentColor' }}>查看購物車</button>
              </div>
            )}

            <div style={{ marginTop: 30, display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 14, fontFamily: 'var(--sans)', fontSize: 12, color: 'var(--jf-mute-2)' }}>
              {[
                ['空運', '3-5 個工作天'],
                ['倉庫', '日本倉庫'],
                ['驗貨', '專人攝影記錄'],
                ['客服', 'LINE 即時回應'],
              ].map((r, i) => (
                <div key={i} style={{ display: 'flex', gap: 10, alignItems: 'center', padding: 12, background: 'var(--jf-paper-2)' }}>
                  <span style={{ width: 28, height: 28, border: '1px solid var(--jf-red)', color: 'var(--jf-red)', display: 'grid', placeItems: 'center', fontFamily: 'var(--serif)', fontSize: 13 }}>{r[0]}</span>
                  <span>{r[1]}</span>
                </div>
              ))}
            </div>
          </div>
        </div>

        {/* Tabs */}
        <div style={{ marginTop: 100, borderBottom: '1px solid var(--jf-line)', display: 'flex', gap: 40 }}>
          {[['desc', '商品介紹'], ['spec', '規格資訊'], ['ship', '運送說明']].map(([k, v]) => (
            <span key={k} onClick={() => setTab(k)} style={{ padding: '14px 0', fontFamily: 'var(--serif)', fontSize: 15, letterSpacing: 2, cursor: 'pointer', borderBottom: tab === k ? '2px solid var(--jf-red)' : '2px solid transparent', color: tab === k ? 'var(--jf-ink)' : 'var(--jf-mute)' }}>{v}</span>
          ))}
        </div>

        <div style={{ padding: '50px 0', maxWidth: 760, fontFamily: 'var(--serif)', fontSize: 15, lineHeight: 2, color: 'var(--jf-mute-2)' }}>
          {tab === 'desc' && (
            <>
              <h3 style={{ fontFamily: 'var(--serif)', fontSize: 22, color: 'var(--jf-ink)', margin: '0 0 18px', letterSpacing: 1 }}>商品說明</h3>
              <p>{productDescriptionText(p)}</p>
              <ul style={{ paddingLeft: 20, margin: '20px 0' }}>
                <li>產地：日本 {p.region.split(' ')[0]}</li>
                <li>規格：{selectedSpecLabel}</li>
                <li>溫層：{productTemperatureLabel(p.shippingTemperature)}</li>
                {p.shippingNotes && <li>配送備註：{p.shippingNotes}</li>}
                {Number(p.extraShippingTWD || 0) > 0 && <li>外加運費：NT$ {Number(p.extraShippingTWD).toLocaleString('zh-TW')}</li>}
              </ul>
            </>
          )}
          {tab === 'spec' && (
            <table style={{ width: '100%', borderCollapse: 'collapse', fontFamily: 'var(--serif)', fontSize: 14 }}>
              <tbody>
                {[
                  ['品名', p.name],
                  ['日文名稱', p.jname],
                  ['產地', '日本 ' + p.region.split(' ')[0]],
                  ['規格', selectedSpecLabel],
                  ['溫層', productTemperatureLabel(p.shippingTemperature)],
                  ['重量', p.weightKg ? `${Number(p.weightKg).toLocaleString('zh-TW')} kg` : '依商品頁標示'],
                  ['配送備註', p.shippingNotes || '依日本原站與包裝標示確認'],
                  ['外加運費', Number(p.extraShippingTWD || 0) > 0 ? `NT$ ${Number(p.extraShippingTWD).toLocaleString('zh-TW')}` : '無'],
                ].map((r, i) => (
                  <tr key={i} style={{ borderBottom: '1px solid var(--jf-line)' }}>
                    <td style={{ padding: '14px 0', width: 160, color: 'var(--jf-ink)', fontWeight: 500 }}>{r[0]}</td>
                    <td style={{ padding: '14px 0' }}>{r[1]}</td>
                  </tr>
                ))}
              </tbody>
            </table>
          )}
          {tab === 'ship' && (
            <>
              <p>所有商品先送達 JaFun 日本倉庫，經專人攝影驗貨後合併打包。空運直送台灣，平均 3-5 個工作天到達。下單後可即時於 LINE 追蹤包裹狀態。</p>
              <ul style={{ paddingLeft: 20 }}>
                <li>運費：依包裹重量計算，可於集運頁面試算</li>
                <li>包材費：NT$ 50（含緩衝材與專用箱）</li>
              </ul>
            </>
          )}
        </div>

        {/* Related */}
        <div style={{ marginTop: 60 }}>
          <div className="section-eyebrow">RELATED</div>
          <h3 style={{ fontFamily: 'var(--serif)', fontSize: 30, margin: '8px 0 36px', letterSpacing: 2, fontWeight: 500 }}>你可能也會喜歡</h3>
          <div className="product-grid">
            {related.map(rp => (
              <ProductCard key={rp.id} product={rp} onClick={() => { onNav('product', { product: rp }); }} />
            ))}
          </div>
        </div>
      </section>
      <CtaBanner t={t} onNav={onNav} />
      {favoritePromptOpen && (
        <FavoriteMemberPrompt
          product={p}
          onClose={closeFavoritePrompt}
          onCompleteFavorite={completeFavorite}
        />
      )}
      </div>
    </div>
  );
}

const IMPORT_RESTRICTION_GROUPS = [
  {
    label: '不承接空運代購',
    items: [
      ['酒類、菸草、電子菸', '日本酒、啤酒、威士忌、紅白酒、菸草、電子菸、加熱菸。'],
      ['生鮮、肉品、冷藏冷凍、生食', '生魚片、壽司、冷凍食品、牛豬雞肉、火腿香腸、肉乾、含肉食品。'],
      ['農產品、植物、種子、土壤', '水果、蔬菜、種子、苗木、盆栽、培養土、生米與未加工穀物。'],
      ['航空危險品與違禁品', '噴霧罐、香水、指甲油、打火機、煙火、鋰電池、武器、仿冒品。'],
    ],
  },
  {
    label: '需 LINE 人工確認',
    items: [
      ['藥品、醫療器材、隱形眼鏡', '眼藥水、止痛藥、處方藥、血糖試紙、醫療器材等可能需許可或查驗。'],
      ['保健食品、化妝品、液體粉末', '保健品、蛋白粉、化妝水、精華液、洗髮精等依成分、容量與數量確認。'],
    ],
  },
];

const IMPORT_RESTRICTION_REFERENCES = [
  ['財政部關務署郵包進口提醒', 'https://www.mof.gov.tw/singlehtml/384fb3077bb349ea973e7fc6f13b6974?cntId=013b422291044148909f9aa9e35be8e3'],
  ['台北關快遞貨物通關條件', 'https://web.customs.gov.tw/taipei/singlehtml/143?cntId=cus2_73703_143'],
  ['農業部動植物防疫檢疫署網購宣導', 'https://www.aphia.gov.tw/ws.php?id=20603'],
  ['日本郵便國際郵便禁寄品', 'https://www.post.japanpost.jp/int/use/restriction/'],
];

function ImportRestrictionSection() {
  return (
    <section className="section section-narrow" style={{ paddingTop: 20 }}>
      <div className="section-head">
        <div className="left">
          <div className="section-eyebrow">代購前確認</div>
          <h2 className="section-title">日本空運禁運 / 管制品提醒</h2>
          <p className="section-sub" style={{ marginTop: 12 }}>
            貼網址或 AI 找商品時，系統會先做初步判斷；涉及禁運、檢疫、食品藥物或航空危險品的商品，不會開放自動加入購物車。
          </p>
        </div>
      </div>
      <div className="restriction-grid">
        {IMPORT_RESTRICTION_GROUPS.map(group => (
          <div className="restriction-card" key={group.label}>
            <div className="restriction-card-label">{group.label}</div>
            {group.items.map(item => (
              <div className="restriction-card-row" key={item[0]}>
                <strong>{item[0]}</strong>
                <span>{item[1]}</span>
              </div>
            ))}
          </div>
        ))}
      </div>
      <div className="restriction-ref-list">
        <span>依公開規定整理：</span>
        {IMPORT_RESTRICTION_REFERENCES.map(([label, url]) => (
          <a key={url} href={url} target="_blank" rel="noreferrer">{label}</a>
        ))}
      </div>
    </section>
  );
}

function ShipPage({ t, onNav }) {
  // 逐件輸入(每件重量/尺寸可不同);合併打包一箱計費:重量與材積各自加總後只進位一次、只收一次派送費。
  const [boxes, setBoxes] = useStateP([{ id: 1, weight: 2, length: 30, width: 22, height: 12 }]);
  const updateBox = (id, patch) => setBoxes(list => list.map(box => (box.id === id ? { ...box, ...patch } : box)));
  const addBox = () => setBoxes(list => [...list, { id: Math.max(...list.map(box => box.id)) + 1, weight: '', length: 30, width: 22, height: 12 }]);
  const removeBox = (id) => setBoxes(list => (list.length > 1 ? list.filter(box => box.id !== id) : list));
  const boxWeightValid = (box) => Number.isFinite(Number(box.weight)) && Number(box.weight) > 0;
  const countedBoxes = boxes.filter(boxWeightValid); // 填了有效重量的包裹才計入試算
  const actualWeight = countedBoxes.reduce((sum, box) => sum + Number(box.weight), 0);
  const volumetricWeight = countedBoxes.reduce((sum, box) => sum + Math.max(0, (box.length * box.width * box.height) / 5000), 0);
  const nothingToQuote = countedBoxes.length === 0;
  const shipping = taiwanAirShippingEstimate(Math.max(actualWeight, volumetricWeight));
  const billableWeight = shipping.internationalShippingBillableWeightKg;
  const cost = shipping.internationalShippingEstimateTWD;
  return (
    <div data-screen-label="04 Shipping">
      <section className="page-hero">
        <div className="crumb">HOME / 集運服務</div>
        <h1>日本集運</h1>
        <div className="jp">集 荷 代 行 — 日本空運 · 合併出貨</div>
        <p className="desc">在不同網站買的商品，全部寄到 JaFun 日本倉庫，合併打包後空運送達台灣，3-5 個工作天到貨，省下重複的國際運費。</p>
      </section>

      <section className="section section-narrow">
        <div className="section-head">
          <div className="left">
            <div className="section-eyebrow">運費試算</div>
            <h2 className="section-title">即時計算 空運費用</h2>
            <p className="section-sub" style={{ marginTop: 12 }}>日本空運直送 · 3-5 個工作天到貨</p>
          </div>
        </div>
        <div className="calc-card">
          {boxes.map((box, index) => (
            <div key={box.id} style={{ border: '1px solid var(--jf-line)', background: '#fff', padding: '14px 16px', marginBottom: 12 }}>
              <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 10 }}>
                <span style={{ fontFamily: 'var(--sans)', fontSize: 11, letterSpacing: 3, color: 'var(--jf-mute)' }}>第 {index + 1} 件</span>
                {boxes.length > 1 && (
                  <button type="button" onClick={() => removeBox(box.id)} style={{ border: 'none', background: 'transparent', color: 'var(--jf-mute)', fontFamily: 'var(--sans)', fontSize: 12, cursor: 'pointer', textDecoration: 'underline' }}>移除</button>
                )}
              </div>
              <div className="calc-row">
                <div className="calc-field">
                  <label>重量（公斤）</label>
                  <input type="number" value={box.weight} onChange={e => updateBox(box.id, { weight: e.target.value })} min="0" step="0.5" />
                  {!boxWeightValid(box) && (
                    <div style={{ marginTop: 6, fontFamily: 'var(--sans)', fontSize: 12, color: '#b3261e' }}>
                      請輸入有效重量
                    </div>
                  )}
                </div>
                <div className="calc-field">
                  <label>長 × 寬 × 高（公分）</label>
                  <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr 1fr', gap: 8 }}>
                    <input type="number" value={box.length} onChange={e => updateBox(box.id, { length: +e.target.value || 0 })} min="0" />
                    <input type="number" value={box.width} onChange={e => updateBox(box.id, { width: +e.target.value || 0 })} min="0" />
                    <input type="number" value={box.height} onChange={e => updateBox(box.id, { height: +e.target.value || 0 })} min="0" />
                  </div>
                </div>
              </div>
            </div>
          ))}
          <button type="button" onClick={addBox} style={{ width: '100%', padding: '12px 16px', border: '1px dashed var(--jf-line)', background: 'transparent', fontFamily: 'var(--serif)', fontSize: 14, letterSpacing: 2, color: 'var(--jf-ink)', cursor: 'pointer' }}>
            ＋ 新增一件包裹（每件重量、尺寸可不同）
          </button>
          <div className="calc-row" style={{ marginTop: 14 }}>
            <div className="calc-field">
              <label>國際運費級距</label>
              <div style={{ width: '100%', padding: '14px 18px', border: '1px solid var(--jf-line)', background: '#fff', color: 'var(--jf-ink)', fontFamily: 'var(--serif)' }}>
                {nothingToQuote
                  ? '—'
                  : `${shipping.internationalShippingTierLabel} · NT$ ${shipping.internationalShippingRateTWDPerKg}/kg${shipping.internationalShippingDeliveryFeeTWD ? ` + 派送費 NT$ ${shipping.internationalShippingDeliveryFeeTWD}` : ''}`}
              </div>
            </div>
            <div className="calc-field">
              <label>合併打包說明</label>
              <div style={{ width: '100%', padding: '14px 18px', border: '1px solid var(--jf-line)', background: '#fff', color: 'var(--jf-mute-2)', fontFamily: 'var(--serif)', fontSize: 13, lineHeight: 1.7 }}>
                多件包裹於日本倉庫合併打包成一箱，重量加總後一次計費、只收一次派送費。
              </div>
            </div>
          </div>
          <div className="calc-result">
            <div>
              <div className="lab">預估運費</div>
              <div style={{ fontFamily: 'var(--sans)', fontSize: 11, color: 'var(--jf-mute)', marginTop: 6 }}>
                {nothingToQuote
                  ? '請輸入有效重量後試算'
                  : `共 ${countedBoxes.length} 件 · 總實重 ${actualWeight.toFixed(1)}kg · 總材積重 ${volumetricWeight.toFixed(1)}kg · 計費重 ${billableWeight.toFixed(1)}kg`}
              </div>
            </div>
            <div className="val">{nothingToQuote ? '—' : `NT$ ${cost.toLocaleString()}`}</div>
          </div>
          <div className="shipping-rate-table" aria-label="台灣空運價格表">
            <div className="shipping-rate-caption">
              <span>空運價格表 · 寄往台灣</span>
              <small>未滿 1kg 以 1kg 計，超過 1kg 每 0.5kg 進位；箱重未滿 6kg 加收派送費 NT$ 100。</small>
            </div>
            <div className="shipping-rate-head" aria-hidden="true">
              <span>重量級距</span>
              <span>台幣 / kg</span>
              <span>派送費</span>
            </div>
            <div className="shipping-rate-list">
              {[
                { label: '1 – 10 kg', rate: 240, handling: 'NT$ 100' },
                { label: '10.5 – 20 kg', rate: 200, handling: '—' },
                { label: '21 kg 以上', rate: 180, handling: '—' },
              ].map(row => (
                <div className="shipping-rate-row" key={row.label}>
                  <div className="shipping-rate-weight">
                    <strong>{row.label}</strong>
                  </div>
                  <div>
                    <small>台幣運費</small>
                    <strong>NT$ {row.rate.toLocaleString()}</strong>
                    <em>/kg</em>
                  </div>
                  <div>
                    <small>派送費</small>
                    <strong>{row.handling}</strong>
                  </div>
                </div>
              ))}
            </div>
            <div className="shipping-rate-notes">
              <div>
                <span>01</span>
                <p>重量為單一次出貨量級距式計算，分次寄出無法累計。</p>
              </div>
              <div>
                <span>02</span>
                <p>每箱需有一位已完成 EZWAY 實名認證的通關名義人資料。</p>
              </div>
              <div>
                <span>03</span>
                <p>箱重未滿 6kg 加收派送費 NT$ 100；實際運費以結帳試算為準。</p>
              </div>
            </div>
          </div>
          <button className="btn btn-primary" style={{ marginTop: 18 }} onClick={() => onNav('mypage', { tab: 'shipments' })}>申請合併出貨</button>
        </div>
      </section>

      <section className="section" style={{ background: 'var(--jf-paper-2)', paddingTop: 80 }}>
        <div className="section-narrow">
          <div className="section-head">
            <div className="left">
              <div className="section-eyebrow">追蹤包裹</div>
              <h2 className="section-title">物流即時透明</h2>
            </div>
          </div>
          <div className="track-line" style={{ margin: '40px auto' }}>
            {getShippingTrackingSteps().map((s, i) => (
              <div key={i} className={`track-step ${s.state}`}>
                <div className="dot">{i + 1}</div>
                <h5>{s.s}</h5>
                <p>{s.d}</p>
              </div>
            ))}
          </div>
        </div>
      </section>
      <ImportRestrictionSection />
      <CtaBanner t={t} onNav={onNav} />
    </div>
  );
}

function articleDateLabel(article) {
  const publishedAt = article?.publishedAt ? new Date(article.publishedAt) : null;
  if (publishedAt && !Number.isNaN(publishedAt.getTime())) {
    return publishedAt.toISOString().slice(0, 10).replace(/-/g, '.');
  }
  return article?.date ? `2026.${article.date}` : '2026.05.18';
}

function LearnPage({ t, onNav }) {
  const [activeCat, setActiveCat] = React.useState('all');
  const [search, setSearch] = React.useState('');
  const [visibleArticles, setVisibleArticles] = React.useState(6);
  const [newsletterEmail, setNewsletterEmail] = React.useState('');
  const [newsletterDone, setNewsletterDone] = React.useState(false);
  const [newsletterError, setNewsletterError] = React.useState('');
  const [sortBy, setSortBy] = React.useState('default');

  const fallbackFeatured = {
    id: 'japan-proxy-shopping-flow', slug: 'japan-proxy-shopping-flow', cat: 'guide',
    tag: '購物攻略', read: '8 分鐘', date: '05.18',
    title: '日本代購怎麼下單？從貼網址到收到包裹的完整流程',
    desc: '第一次使用日本代購時，最重要的是知道 JaFun 如何從商品網址解析、估價、代下單、收貨、合併打包到寄回台灣。',
    img: 'assets/articles/japan-proxy-shopping-flow.webp',
    keywords: ['日本代購', '網址報價', '購物車', '日本商品網址報價', 'JaFun 代購下單'],
  };

  const fallbackArticles = [
    fallbackFeatured,
    { id: 'amazon-jp-vs-rakuten-shopping-guide', slug: 'amazon-jp-vs-rakuten-shopping-guide', cat: 'guide', tag: '購物攻略', read: '7 分鐘', title: 'Amazon JP 與樂天市場怎麼選？價格、官方店、規格與運費比較', desc: '同一件日本商品可能同時出現在 Amazon JP 與樂天市場，價格、官方店、規格選項與出貨條件都會影響最後總額。', img: 'assets/articles/amazon-jp-vs-rakuten-shopping-guide.webp', date: '05.18', views: '22.1k' },
    { id: 'japan-air-shipping-restricted-items-taiwan', slug: 'japan-air-shipping-restricted-items-taiwan', cat: 'shipping', tag: '集運清關', read: '9 分鐘', title: '日本空運禁運與管制品清單：酒、肉品、生食、植物、藥品怎麼判斷', desc: '不是所有日本商品都能空運回台灣。酒類、菸品、肉品、生鮮、植物、種子、部分藥品與航空危險品都需要先排除或人工確認。', img: 'assets/articles/japan-air-shipping-restricted-items-taiwan.webp', date: '05.18', views: '19.4k' },
    { id: 'ezway-real-name-customs-guide', slug: 'ezway-real-name-customs-guide', cat: 'shipping', tag: '集運清關', read: '7 分鐘', title: 'EZ WAY 實名認證與收件資料怎麼填？日本包裹清關前必看', desc: '日本商品寄回台灣時，收件人需要配合 EZ WAY 實名認證與通關委任。姓名、手機、地址與身分資料不一致，可能造成清關延誤。', img: 'assets/articles/ezway-real-name-customs-guide.webp', date: '05.18', views: '16.7k' },
    { id: 'japan-forwarding-shipping-cost-guide', slug: 'japan-forwarding-shipping-cost-guide', cat: 'shipping', tag: '集運清關', read: '8 分鐘', title: '日本集運怎麼省運費？重量級距、合併打包與大型商品估算', desc: '集運費不是每件商品分開算，而是依單次出貨重量級距估算。小物合併通常省錢，大型家電則需要看材積與人工確認。', img: 'assets/articles/japan-forwarding-shipping-cost-guide.webp', date: '05.18', views: '14.0k' },
    { id: 'japan-souvenir-gift-selection-guide', slug: 'japan-souvenir-gift-selection-guide', cat: 'list', tag: '必買清單', read: '8 分鐘', title: '日本伴手禮怎麼挑？常溫、好保存、好送人的 20 個選品方向', desc: '送長輩、同事或客戶時，伴手禮不是越熱門越好，而是要看保存方式、甜度、包裝、數量與是否容易運送。', img: 'assets/articles/japan-souvenir-gift-selection-guide.webp', date: '05.18', views: '11.3k' },
    { id: 'japan-drugstore-supplement-proxy-guide', slug: 'japan-drugstore-supplement-proxy-guide', cat: 'list', tag: '必買清單', read: '8 分鐘', title: '日本藥妝與保健食品代購注意事項：哪些可買、哪些要人工確認', desc: '藥妝、保健食品、眼藥水、隱形眼鏡與醫療器材不能只看商品熱門度，還要確認成分、容量、用途與台灣進口限制。', img: 'assets/articles/japan-drugstore-supplement-proxy-guide.webp', date: '05.18', views: '8.6k' },
    { id: 'japan-fashion-size-proxy-guide', slug: 'japan-fashion-size-proxy-guide', cat: 'list', tag: '必買清單', read: '7 分鐘', title: 'UNIQLO、GU 與日本品牌服飾代購：尺寸、顏色、退換貨前要確認什麼', desc: '服飾代購最常出錯的不是價格，而是尺寸、顏色、版型、材質與日本網站限定規格。下單前務必確認商品頁細節。', img: 'assets/articles/japan-fashion-size-proxy-guide.webp', date: '05.18', views: '5.9k' },
    { id: 'japan-appliances-home-goods-proxy-guide', slug: 'japan-appliances-home-goods-proxy-guide', cat: 'list', tag: '必買清單', read: '8 分鐘', title: '日本小家電與生活雜貨代購：重量、電壓、材積與保固一次看懂', desc: '日本小家電、收納用品與生活雜貨常有價格優勢，但重量、體積、電壓、易碎與保固會直接影響是否值得代購。', img: 'assets/articles/japan-appliances-home-goods-proxy-guide.webp', date: '05.18', views: '3.2k' },
    { id: 'jafun-ai-product-search-guide', slug: 'jafun-ai-product-search-guide', cat: 'guide', tag: '購物攻略', read: '7 分鐘', title: '用 JaFun AI 與 LINE 找日本商品：中文需求如何變成可下單推薦', desc: 'JaFun AI 搜尋不是把中文直接丟進搜尋框，而是先判斷用途、限制、口味、預算、禁運風險與是否需要追問，再接到 LINE 或購物車下單。', img: 'assets/articles/jafun-ai-product-search-guide.webp', date: '05.18', views: '1.8k' },
  ];

  const articles = (window.ARTICLES && window.ARTICLES.length) ? window.ARTICLES : fallbackArticles;
  const featured = (window.EDITORIALS && window.EDITORIALS.find(article => article.featured)) || articles[0] || fallbackFeatured;
  const totalArticleCount = articles.length;
  const categoryDefinitions = [
    { k: 'all', label: '全部', jp: 'All' },
    { k: 'guide', label: '購物攻略', jp: 'ガイド' },
    { k: 'shipping', label: '集運清關', jp: '配送' },
    { k: 'list', label: '必買清單', jp: 'リスト' },
    { k: 'region', label: '地區特輯', jp: '地域' },
    { k: 'brand', label: '品牌深度', jp: 'ブランド' },
    { k: 'season', label: '季節限定', jp: '季節' },
    { k: 'faq', label: '常見問題', jp: 'FAQ' },
  ];
  const categoryCounts = articles.reduce((counts, article) => {
    counts[article.cat] = (counts[article.cat] || 0) + 1;
    return counts;
  }, {});
  const categories = categoryDefinitions
    .map(category => ({
      ...category,
      count: category.k === 'all' ? totalArticleCount : (categoryCounts[category.k] || 0),
    }))
    .filter(category => category.k === 'all' || category.count > 0);

  const parseViews = (value) => {
    const raw = String(value || '').trim().toLowerCase();
    const number = parseFloat(raw) || 0;
    return raw.includes('k') ? number * 1000 : number;
  };
  const articleSorters = {
    default: () => 0,
    latest: (a, b) => String(b.publishedAt || b.updatedAt || b.date || '').localeCompare(String(a.publishedAt || a.updatedAt || a.date || '')),
    popular: (a, b) => parseViews(b.views) - parseViews(a.views),
    readTime: (a, b) => (parseFloat(a.read) || 0) - (parseFloat(b.read) || 0),
  };
  const filtered = articles.filter(a => {
    if (activeCat !== 'all' && a.cat !== activeCat) return false;
    if (search && !a.title.includes(search) && !a.desc.includes(search)) return false;
    return true;
  }).slice().sort(articleSorters[sortBy] || articleSorters.default);
  const activeCategoryLabel = categories.find(c => c.k === activeCat)?.label || '全部';
  React.useEffect(() => setVisibleArticles(6), [activeCat, search]);
  const displayedArticles = filtered.slice(0, visibleArticles);

  const popular = articles.slice().sort((a, b) => parseFloat(b.views) - parseFloat(a.views)).slice(0, 5);

  return (
    <div data-screen-label="05 Learn">
      {/* Hero */}
      <section className="page-hero" style={{ paddingBottom: 50 }}>
        <div className="crumb">HOME / 購物指南</div>
        <h1>日本購物指南</h1>
        <div className="jp">買 い 物 ガ イ ド · BUYING GUIDE</div>
        <p className="desc" style={{ maxWidth: 700 }}>從日本 Amazon、樂天結帳教學，到藥妝、零食、伴手禮的必買清單，再到地區深度報導——JaFun 編輯部把跨海購物的眉角整理在這裡。</p>

        <div style={{ marginTop: 30, display: 'flex', gap: 12, alignItems: 'center', maxWidth: 560 }}>
          <div style={{ flex: 1, position: 'relative' }}>
            <input value={search} onChange={e => setSearch(e.target.value)} placeholder="搜尋指南文章…例如「藥妝」「樂天」「北海道」" style={{ width: '100%', padding: '14px 18px 14px 44px', border: '1px solid var(--jf-line)', fontFamily: 'var(--serif)', fontSize: 14, outline: 'none', background: '#fff', boxSizing: 'border-box' }} />
            <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" style={{ position: 'absolute', left: 16, top: '50%', transform: 'translateY(-50%)', color: 'var(--jf-mute)' }}><circle cx="11" cy="11" r="7" /><path d="m21 21-5-5" /></svg>
          </div>
          <div style={{ fontFamily: 'var(--sans)', fontSize: 11, letterSpacing: 2, color: 'var(--jf-mute)' }}>共 {totalArticleCount} 篇文章</div>
        </div>
      </section>

      {/* Category tabs */}
      <section style={{ borderTop: '1px solid var(--jf-line)', borderBottom: '1px solid var(--jf-line)', background: '#fff', position: 'sticky', top: 60, zIndex: 20 }}>
        <div style={{ maxWidth: 1280, margin: '0 auto', padding: '0 60px', display: 'flex', gap: 4, overflowX: 'auto' }}>
          {categories.map(c => (
            <button key={c.k} onClick={() => setActiveCat(c.k)} style={{
              padding: '20px 22px', background: 'transparent', border: 'none', cursor: 'pointer', whiteSpace: 'nowrap',
              borderBottom: activeCat === c.k ? '2px solid var(--jf-red)' : '2px solid transparent',
              color: activeCat === c.k ? 'var(--jf-red)' : 'var(--jf-ink)',
              fontFamily: 'var(--serif)', fontSize: 14, letterSpacing: 1,
            }}>
              {c.label} <span style={{ fontFamily: 'var(--sans)', fontSize: 10, color: 'var(--jf-mute)', marginLeft: 4 }}>{c.count}</span>
            </button>
          ))}
        </div>
      </section>

      {/* Featured pinned post */}
      {activeCat === 'all' && !search && (
        <section className="section section-narrow" style={{ paddingTop: 60, paddingBottom: 30 }}>
          <div onClick={() => onNav('article', { article: featured })} style={{ display: 'grid', gridTemplateColumns: '1.3fr 1fr', gap: 0, background: '#fff', border: '1px solid var(--jf-line)', cursor: 'pointer', overflow: 'hidden' }}>
            <div style={{ position: 'relative', minHeight: 440, overflow: 'hidden' }}>
              <img
                src={imageVariantUrl(featured.imageSizes, 'og', featured.img)}
                srcSet={imageVariantSrcSet(featured.imageSizes)}
                sizes="(max-width: 860px) 100vw, 55vw"
                alt={featured.imageAlt || featured.title}
                fetchpriority="high"
                decoding="async"
                style={{ width: '100%', height: '100%', objectFit: 'cover' }}
              />
              <div style={{ position: 'absolute', top: 18, left: 18, padding: '6px 12px', background: 'var(--jf-red)', color: '#fff', fontFamily: 'var(--sans)', fontSize: 10, letterSpacing: 3 }}>★ 編輯精選</div>
              <div style={{ position: 'absolute', top: 62, left: 18, padding: '10px 14px', background: '#fff' }}>
                <div style={{ fontFamily: 'var(--sans)', fontSize: 9, letterSpacing: 4, color: 'var(--jf-mute)', marginBottom: 5 }}>JAFUN GUIDE</div>
                <div style={{ fontFamily: 'var(--sans)', fontSize: 12, letterSpacing: 2, color: 'var(--jf-ink)' }}>{featured.tag}</div>
              </div>
            </div>
            <div style={{ padding: '50px 50px', display: 'flex', flexDirection: 'column', justifyContent: 'center' }}>
              <div style={{ display: 'flex', gap: 12, fontFamily: 'var(--sans)', fontSize: 11, letterSpacing: 2, color: 'var(--jf-mute)', marginBottom: 16 }}>
                <span style={{ color: 'var(--jf-red)' }}>{featured.tag}</span><span>·</span>
                <span>{featured.read}</span><span>·</span><span>{featured.date}</span>
              </div>
              <h2 style={{ fontFamily: 'var(--serif)', fontSize: 32, lineHeight: 1.4, letterSpacing: 1, margin: '0 0 18px', fontWeight: 500 }}>{featured.title}</h2>
              <p style={{ fontFamily: 'var(--serif)', fontSize: 15, color: 'var(--jf-mute-2)', lineHeight: 2, margin: 0 }}>{featured.desc}</p>
              <div style={{ marginTop: 22, display: 'flex', flexWrap: 'wrap', gap: 8 }}>
                {(featured.keywords || [featured.tag]).map(k => (
                  <span key={k} style={{ padding: '4px 10px', background: 'var(--jf-paper-2)', fontFamily: 'var(--sans)', fontSize: 11, color: 'var(--jf-mute-2)', letterSpacing: 1 }}>#{k}</span>
                ))}
              </div>
            </div>
          </div>
        </section>
      )}

      {/* Main grid + sidebar */}
      <section className="section section-narrow" style={{ paddingTop: 30, paddingBottom: 80 }}>
        <div style={{ display: 'grid', gridTemplateColumns: '1fr 320px', gap: 60, alignItems: 'start' }}>
          <div>
            <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'baseline', marginBottom: 28 }}>
              <h3 style={{ fontFamily: 'var(--serif)', fontSize: 22, margin: 0, letterSpacing: 2, fontWeight: 500 }}>
                {search ? `「${search}」搜尋結果` : activeCategoryLabel}
                <span style={{ fontFamily: 'var(--sans)', fontSize: 12, color: 'var(--jf-mute)', marginLeft: 12, letterSpacing: 1 }}>{filtered.length} 篇</span>
              </h3>
              <select
                value={sortBy}
                onChange={e => setSortBy(e.target.value)}
                style={{ padding: '8px 12px', border: '1px solid var(--jf-line)', fontFamily: 'var(--serif)', fontSize: 13, background: '#fff' }}
              >
                <option value="default">編輯推薦</option>
                <option value="latest">最新發佈</option>
                <option value="popular">熱門度</option>
                <option value="readTime">閱讀時間</option>
              </select>
            </div>

            <div style={{ display: 'grid', gridTemplateColumns: 'repeat(2, 1fr)', gap: 30 }}>
              {displayedArticles.map(a => (
                <div key={a.id} className="edit-card" onClick={() => onNav('article', { article: a })}>
                  <div className="pic" style={{ position: 'relative' }}>
                    <img
                      src={imageVariantUrl(a.imageSizes, 'card', a.img)}
                      srcSet={imageVariantSrcSet(a.imageSizes)}
                      sizes="(max-width: 860px) 100vw, 50vw"
                      alt={a.imageAlt || a.title}
                      loading="lazy"
                      decoding="async"
                    />
                    <div style={{ position: 'absolute', top: 12, left: 12, padding: '8px 12px', background: '#fff' }}>
                      <div style={{ fontFamily: 'var(--sans)', fontSize: 8, letterSpacing: 3, color: 'var(--jf-mute)', marginBottom: 4 }}>JAFUN GUIDE</div>
                      <div style={{ fontFamily: 'var(--sans)', fontSize: 11, letterSpacing: 2, color: 'var(--jf-ink)' }}>{a.tag}</div>
                    </div>
                  </div>
                  <div className="meta"><span>{a.tag}</span><span>{a.read}</span><span style={{ color: 'var(--jf-mute)' }}>● {a.views} 閱讀</span></div>
                  <h3>{a.title}</h3>
                  <p>{a.desc}</p>
                  <div style={{ marginTop: 10, fontFamily: 'var(--sans)', fontSize: 11, color: 'var(--jf-mute)', letterSpacing: 1 }}>{articleDateLabel(a)}</div>
                </div>
              ))}
            </div>

            {filtered.length === 0 && (
              <div style={{ padding: 80, textAlign: 'center', fontFamily: 'var(--serif)', color: 'var(--jf-mute)', background: 'var(--jf-paper-2)' }}>
                目前沒有符合的文章 — 試試其他關鍵字
              </div>
            )}

            <div style={{ marginTop: 50, textAlign: 'center' }}>
              {visibleArticles < filtered.length ? (
                <button className="btn btn-ghost" onClick={() => setVisibleArticles(visibleArticles + 6)}>載入更多文章 ↓</button>
              ) : (
                <button className="btn btn-ghost" onClick={() => setSearch('')}>清除篩選</button>
              )}
            </div>
          </div>

          {/* Sidebar */}
          <aside>
            <div style={{ background: 'var(--jf-paper-2)', padding: 26, marginBottom: 28 }}>
              <div className="jp" style={{ fontFamily: 'var(--serif-jp)', color: 'var(--jf-red)', letterSpacing: 4, fontSize: 12, marginBottom: 8 }}>人 気 記 事</div>
              <h4 style={{ fontFamily: 'var(--serif)', fontSize: 18, margin: '0 0 18px', letterSpacing: 1, fontWeight: 500 }}>本週熱門</h4>
              <div style={{ display: 'grid', gap: 14 }}>
                {popular.map((p, i) => (
                  <div key={p.id} onClick={() => onNav('article', { article: p })} style={{ display: 'grid', gridTemplateColumns: '32px 1fr', gap: 12, cursor: 'pointer', paddingBottom: 14, borderBottom: i < 4 ? '1px solid var(--jf-line)' : 'none' }}>
                    <div style={{ fontFamily: 'var(--serif)', fontSize: 28, color: 'var(--jf-red)', lineHeight: 1, fontWeight: 500 }}>0{i + 1}</div>
                    <div>
                      <div style={{ fontFamily: 'var(--serif)', fontSize: 13, lineHeight: 1.5, marginBottom: 4 }}>{p.title}</div>
                      <div style={{ fontFamily: 'var(--sans)', fontSize: 10, letterSpacing: 1, color: 'var(--jf-mute)' }}>{p.views} · {p.read}</div>
                    </div>
                  </div>
                ))}
              </div>
            </div>

            <div style={{ background: 'var(--jf-red)', color: '#fff', padding: 26, marginBottom: 28 }}>
              <div style={{ fontFamily: 'var(--sans)', fontSize: 10, letterSpacing: 4, opacity: .85, marginBottom: 8 }}>NEWSLETTER</div>
              <h4 style={{ fontFamily: 'var(--serif)', fontSize: 22, margin: '0 0 12px', letterSpacing: 2, fontWeight: 500, color: '#fff' }}>每週日本購物快報</h4>
              <p style={{ fontFamily: 'var(--serif)', fontSize: 13, lineHeight: 1.8, opacity: .9, margin: '0 0 16px' }}>編輯精選、限定優惠、新品搶先報——直送你的信箱。</p>
              <input value={newsletterEmail} onChange={e => { setNewsletterEmail(e.target.value); setNewsletterDone(false); setNewsletterError(''); }} placeholder="your@email.com" style={{ width: '100%', padding: 11, border: 'none', fontFamily: 'var(--serif)', fontSize: 13, marginBottom: 8, boxSizing: 'border-box' }} />
              <button
                onClick={() => {
                  if (/^[^@\s]+@[^@\s]+\.[^@\s]+$/.test(newsletterEmail.trim())) {
                    setNewsletterDone(true);
                    setNewsletterError('');
                  } else {
                    setNewsletterError('請輸入有效的 Email');
                  }
                }}
                style={{ width: '100%', padding: 11, background: '#1a1a1a', color: '#fff', border: 'none', fontFamily: 'var(--sans)', fontSize: 12, letterSpacing: 2, cursor: 'pointer' }}
              >
                {newsletterDone ? '已訂閱 ✓' : '免費訂閱'}
              </button>
              {newsletterError && <div style={{ marginTop: 8, fontFamily: 'var(--sans)', fontSize: 11, letterSpacing: 1, color: '#ffe1dd' }}>{newsletterError}</div>}
              {newsletterDone && <div style={{ marginTop: 8, fontFamily: 'var(--sans)', fontSize: 11, letterSpacing: 1, opacity: .9 }}>訂閱成功！每週快報將寄到 {newsletterEmail.trim()}</div>}
            </div>

            <div style={{ padding: 26, border: '1px solid var(--jf-line)' }}>
              <div className="jp" style={{ fontFamily: 'var(--serif-jp)', color: 'var(--jf-red)', letterSpacing: 4, fontSize: 12, marginBottom: 8 }}>タ グ</div>
              <h4 style={{ fontFamily: 'var(--serif)', fontSize: 18, margin: '0 0 16px', letterSpacing: 1, fontWeight: 500 }}>熱門關鍵字</h4>
              <div style={{ display: 'flex', flexWrap: 'wrap', gap: 6 }}>
                {['日本代購流程', '網址報價', 'Amazon JP 比價', '樂天市場官方店', 'EZ WAY', '日本集運運費', '禁運品', '台灣清關', '常溫伴手禮', '保健食品確認', 'UNIQLO JP', 'GU 日本代購', '日本小家電', 'AI 找日本商品', 'LINE 代購', '大型商品運費'].map(t => (
                  <span key={t} onClick={() => { setActiveCat('all'); setSearch(t); window.scrollTo({ top: 0, behavior: 'smooth' }); }} style={{ padding: '5px 10px', background: 'var(--jf-paper-2)', fontFamily: 'var(--sans)', fontSize: 11, color: 'var(--jf-mute-2)', letterSpacing: 1, cursor: 'pointer' }}>#{t}</span>
                ))}
              </div>
            </div>
          </aside>
        </div>
      </section>

      {/* SEO topic clusters */}
      <section style={{ background: 'var(--jf-paper-2)', padding: '80px 0' }}>
        <div style={{ maxWidth: 1280, margin: '0 auto', padding: '0 60px' }}>
          <div style={{ textAlign: 'center', marginBottom: 50 }}>
            <div className="jp" style={{ fontFamily: 'var(--serif-jp)', color: 'var(--jf-red)', letterSpacing: 6, marginBottom: 12 }}>ト ピ ッ ク 別</div>
            <h2 style={{ fontFamily: 'var(--serif)', fontSize: 36, margin: 0, letterSpacing: 4, fontWeight: 500 }}>依主題瀏覽</h2>
          </div>
          <div style={{ display: 'grid', gridTemplateColumns: 'repeat(4, 1fr)', gap: 20 }}>
            {[
              { t: '代購流程', sub: '貼網址 / 報價 / 付款 / 收貨', items: ['日本代購完整流程', '商品頁與搜尋頁差異', 'AI 需求搜尋', 'LINE 客服確認'] },
              { t: '平台比價', sub: 'Amazon JP / 樂天 / 官方店', items: ['Amazon JP 與樂天比較', '官方店與賣家確認', '規格與站內運費', '貼網址以原商品為主'] },
              { t: '集運清關', sub: '空運費 / EZ WAY / 禁運品', items: ['日本集運重量級距', 'EZ WAY 收件資料', '酒肉植物藥品限制', '大型商品人工估算'] },
              { t: '選品指南', sub: '伴手禮 / 藥妝 / 服飾 / 小家電', items: ['常溫伴手禮挑選', '保健食品人工確認', 'UNIQLO GU 尺寸', '小家電電壓與保固'] },
            ].map((c, i) => (
              <div key={i} style={{ background: '#fff', padding: 28 }}>
                <div style={{ fontFamily: 'var(--sans)', fontSize: 11, letterSpacing: 3, color: 'var(--jf-red)', marginBottom: 8 }}>0{i + 1}</div>
                <h4 style={{ fontFamily: 'var(--serif)', fontSize: 19, margin: '0 0 6px', letterSpacing: 1, fontWeight: 500 }}>{c.t}</h4>
                <div style={{ fontFamily: 'var(--serif)', fontSize: 12, color: 'var(--jf-mute)', marginBottom: 18 }}>{c.sub}</div>
                <ul style={{ listStyle: 'none', padding: 0, margin: 0, display: 'grid', gap: 8 }}>
                  {c.items.map(it => (
                    <li key={it} style={{ fontFamily: 'var(--serif)', fontSize: 13, color: 'var(--jf-ink)', paddingLeft: 14, position: 'relative', cursor: 'pointer' }}>
                      <span style={{ position: 'absolute', left: 0, color: 'var(--jf-red)' }}>›</span>{it}
                    </li>
                  ))}
                </ul>
              </div>
            ))}
          </div>
        </div>
      </section>
    </div>
  );
}

function ArticleContentBlock({ block }) {
  if (block.blockType === 'textBlock') {
    return (
      <section>
        {block.heading && <h2 style={{ fontFamily: 'var(--serif)', fontSize: 26, fontWeight: 500, letterSpacing: 1, margin: '50px 0 18px' }}>{block.heading}</h2>}
        <p>{block.body}</p>
      </section>
    );
  }
  if (block.blockType === 'stepGuide') {
    return (
      <section>
        <h2 style={{ fontFamily: 'var(--serif)', fontSize: 26, fontWeight: 500, letterSpacing: 1, margin: '50px 0 18px' }}>{block.title}</h2>
        <ol style={{ paddingLeft: 22, margin: '20px 0' }}>
          {(block.steps || []).map((step, index) => (
            <li key={`${step.title}-${index}`} style={{ marginBottom: 14 }}>
              <strong style={{ fontFamily: 'var(--serif)' }}>{step.title}：</strong>{step.body}
            </li>
          ))}
        </ol>
      </section>
    );
  }
  if (block.blockType === 'faqBlock') {
    return (
      <section>
        <h2 style={{ fontFamily: 'var(--serif)', fontSize: 26, fontWeight: 500, letterSpacing: 1, margin: '50px 0 18px' }}>常見問題</h2>
        {(block.items || []).map(item => (
          <div key={item.question} style={{ background: 'var(--jf-paper-2)', padding: 22, margin: '16px 0' }}>
            <h3 style={{ margin: '0 0 8px', fontFamily: 'var(--serif)', fontSize: 18 }}>{item.question}</h3>
            <p style={{ margin: 0 }}>{item.answer}</p>
          </div>
        ))}
      </section>
    );
  }
  if (block.blockType === 'productRecommendation') {
    return (
      <section>
        <h2 style={{ fontFamily: 'var(--serif)', fontSize: 26, fontWeight: 500, letterSpacing: 1, margin: '50px 0 18px' }}>{block.title}</h2>
        {(block.items || []).map(item => (
          <div key={item.name} style={{ border: '1px solid var(--jf-line)', padding: 22, margin: '16px 0' }}>
            <h3 style={{ margin: '0 0 8px', fontFamily: 'var(--serif)', fontSize: 18 }}>{item.name}</h3>
            <p style={{ margin: 0 }}>{item.reason}</p>
            {item.estimatedPriceTwd ? <div style={{ marginTop: 8, color: 'var(--jf-red)' }}>預估 NT$ {item.estimatedPriceTwd}</div> : null}
          </div>
        ))}
      </section>
    );
  }
  return null;
}

function ArticlePage({ t, onNav, article }) {
  const a = article || { id: 'japan-proxy-shopping-flow', tag: '購物攻略', read: '8 分鐘', date: '05.18', title: '日本代購怎麼下單？從貼網址到收到包裹的完整流程', desc: '第一次使用日本代購時，最重要的是知道 JaFun 如何從商品網址解析、估價、代下單、收貨、合併打包到寄回台灣。', img: 'assets/articles/japan-proxy-shopping-flow.webp', keywords: ['日本代購', '網址報價', '購物車'] };
  const hasCmsContent = Array.isArray(a.content) && a.content.length > 0;
  const allArticles = (window.ARTICLES && window.ARTICLES.length) ? window.ARTICLES : [];
  const related = allArticles
    .filter(item => item.id !== a.id && item.slug !== a.slug)
    .slice(0, 3);
  const fallbackTopics = {
    shipping: {
      intro: '跨境購物真正影響體驗的，往往是下單後的運送、清關與可否承運。這篇整理 JaFun 在報價與出貨前會檢查的重點，幫你先避開不能寄、容易延誤或需要人工確認的商品。',
      heading: '先確認能不能寄，再確認值不值得買',
      body: '日本商品寄回台灣前，需要同時考慮日本站內出貨、JaFun 日本倉庫收貨、航空運送限制、EZ WAY 實名認證與台灣清關。若商品涉及酒類、肉品、生鮮、植物、藥品、醫療器材、液體粉末或大型材積，系統會保留人工覆核，不會直接讓使用者付款。',
      checklist: ['確認商品是否屬於禁運或管制品', '確認收件人能完成 EZ WAY 實名認證', '以合併打包後重量估算空運費', '大型或易碎商品先由客服確認材積與包材'],
    },
    list: {
      intro: '日本必買清單不能只看熱門度。對台灣使用者來說，真正有用的選品要同時符合可代購、可空運、保存穩定、規格清楚與總額合理。',
      heading: '從購買情境回推商品條件',
      body: 'JaFun 建議先寫下使用情境：自用、送禮、公司分送、長輩需求、預算上限、是否接受替代品。再確認保存期限、尺寸規格、容量、重量、材積、電壓與退換貨限制。這樣 AI 搜尋與人工客服才有足夠條件找到真正適合下單的商品。',
      checklist: ['優先選常溫、保存期限較長的商品', '服飾要確認尺寸表、色號與版型', '藥妝保健食品先看成分與數量限制', '小家電要確認電壓、插頭、重量與保固'],
    },
    guide: {
      intro: '日本代購的核心不是把網址丟給客服，而是把商品、規格、價格、運費與風險整理成可以安心付款的資訊。',
      heading: 'JaFun 把購物流程拆成可確認的步驟',
      body: '使用者可以貼 Amazon JP、樂天市場、UNIQLO、GU 或日本品牌官網商品頁，也可以直接用中文描述需求。JaFun 會先判斷輸入是商品網址還是需求搜尋，再整理商品名稱、日幣價格、規格、服務費與空運費預估；如果資訊不足或商品高風險，會改由 LINE 客服確認。',
      checklist: ['貼商品頁網址時以原網址商品為主', '關鍵字需求才會啟動 AI 推薦候選商品', '付款前確認顏色、尺寸、容量與數量', '下單後由日本倉庫收貨、合併打包並空運回台灣'],
    },
  };
  const fallbackTopic = fallbackTopics[a.cat] || fallbackTopics.guide;
  const publishDate = articleDateLabel(a);

  return (
    <div data-screen-label="05b Article">
      {/* Hero */}
      <section style={{ paddingTop: 'clamp(96px, 14vw, 140px)', paddingBottom: 60, paddingLeft: 'clamp(20px, 5vw, 80px)', paddingRight: 'clamp(20px, 5vw, 80px)', background: 'var(--jf-paper-2)', borderBottom: '1px solid var(--jf-line)' }}>
        <div style={{ maxWidth: 820, margin: '0 auto', textAlign: 'center' }}>
          <div style={{ fontFamily: 'var(--sans)', fontSize: 11, letterSpacing: 3, color: 'var(--jf-mute)', marginBottom: 18 }}>
            <span onClick={() => onNav('home')} style={{ cursor: 'pointer' }}>HOME</span> / <span onClick={() => onNav('learn')} style={{ cursor: 'pointer' }}>購物指南</span> / <span style={{ color: 'var(--jf-red)' }}>{a.tag}</span>
          </div>
          <div style={{ display: 'inline-flex', flexWrap: 'wrap', justifyContent: 'center', gap: 14, fontFamily: 'var(--sans)', fontSize: 11, letterSpacing: 2, color: 'var(--jf-mute)', marginBottom: 22 }}>
            <span style={{ color: 'var(--jf-red)', whiteSpace: 'nowrap' }}>{a.tag}</span>
            <span>·</span>
            <span style={{ whiteSpace: 'nowrap' }}>閱讀時間 {a.read}</span>
            <span>·</span>
            <span style={{ whiteSpace: 'nowrap' }}>{publishDate}</span>
          </div>
          <h1 style={{ fontFamily: 'var(--serif)', fontSize: 'clamp(26px, 6vw, 48px)', lineHeight: 1.4, letterSpacing: 2, margin: '0 0 18px', fontWeight: 500 }}>{a.title}</h1>
          <p style={{ fontFamily: 'var(--serif)', fontSize: 17, color: 'var(--jf-mute-2)', lineHeight: 1.8, margin: 0 }}>{a.desc}</p>
          <div style={{ marginTop: 30, display: 'inline-flex', alignItems: 'center', gap: 12, fontFamily: 'var(--sans)', fontSize: 12, color: 'var(--jf-mute)' }}>
            <div style={{ width: 36, height: 36, borderRadius: '50%', background: 'var(--jf-red)', color: '#fff', display: 'grid', placeItems: 'center', fontFamily: 'var(--serif)' }}>編</div>
            <div style={{ textAlign: 'left' }}>
              <div style={{ color: 'var(--jf-ink)', fontFamily: 'var(--serif)', fontSize: 13 }}>JaFun 編輯部</div>
              <div>大阪駐地編輯</div>
            </div>
          </div>
        </div>
      </section>

      {/* Cover */}
      <div style={{ padding: '0 clamp(16px, 5vw, 80px)', marginTop: -40 }}>
        <div style={{ maxWidth: 1100, margin: '0 auto', aspectRatio: '16/8', overflow: 'hidden', boxShadow: '0 30px 60px -20px rgba(0,0,0,.2)' }}>
          <img
            src={imageVariantUrl(a.imageSizes, 'og', a.img)}
            srcSet={imageVariantSrcSet(a.imageSizes)}
            sizes="(max-width: 860px) 100vw, 1100px"
            alt={a.imageAlt || a.title}
            fetchpriority="high"
            decoding="async"
            style={{ width: '100%', height: '100%', objectFit: 'cover' }}
          />
        </div>
      </div>

      {/* Body */}
      <section className="section" style={{ paddingTop: 80 }}>
        <div style={{ maxWidth: 720, margin: '0 auto', fontFamily: 'var(--serif)', fontSize: 17, lineHeight: 2, color: 'var(--jf-ink-soft)' }}>
          {hasCmsContent ? (
            <>
              {a.quickAnswer && (
                <div style={{ background: 'var(--jf-paper-2)', padding: 28, margin: '0 0 40px', borderTop: '2px solid var(--jf-red)' }}>
                  <div style={{ fontFamily: 'var(--sans)', fontSize: 11, letterSpacing: 3, color: 'var(--jf-red)', marginBottom: 10 }}>快速答案</div>
                  <p style={{ margin: 0, fontSize: 15, color: 'var(--jf-mute-2)' }}>{a.quickAnswer}</p>
                </div>
              )}
              {a.content.map((block, index) => <ArticleContentBlock key={`${a.id || a.slug}-${index}`} block={block} />)}
              {(a.geoQuestions || []).length ? (
                <section>
                  <h2 style={{ fontFamily: 'var(--serif)', fontSize: 26, fontWeight: 500, letterSpacing: 1, margin: '50px 0 18px' }}>AI 摘要問答</h2>
                  {a.geoQuestions.map(item => (
                    <div key={item.question} style={{ background: 'var(--jf-paper-2)', padding: 22, margin: '16px 0' }}>
                      <h3 style={{ margin: '0 0 8px', fontFamily: 'var(--serif)', fontSize: 18 }}>{item.question}</h3>
                      <p style={{ margin: 0 }}>{item.answer}</p>
                    </div>
                  ))}
                </section>
              ) : null}
            </>
          ) : (
            <>
              <p style={{ fontSize: 19, lineHeight: 1.9, color: 'var(--jf-ink)' }}>{fallbackTopic.intro}</p>
              <h2 style={{ fontFamily: 'var(--serif)', fontSize: 26, fontWeight: 500, letterSpacing: 1, margin: '50px 0 18px' }}>{fallbackTopic.heading}</h2>
              <p>{fallbackTopic.body}</p>
              <div style={{ background: 'var(--jf-paper-2)', padding: 28, margin: '40px 0', borderTop: '2px solid var(--jf-red)' }}>
                <div style={{ fontFamily: 'var(--sans)', fontSize: 11, letterSpacing: 3, color: 'var(--jf-red)', marginBottom: 10 }}>本文重點</div>
                <p style={{ margin: 0, fontSize: 15, color: 'var(--jf-mute-2)' }}>{a.desc}</p>
              </div>
              <h2 style={{ fontFamily: 'var(--serif)', fontSize: 26, fontWeight: 500, letterSpacing: 1, margin: '50px 0 18px' }}>下單前檢查清單</h2>
              <ol style={{ paddingLeft: 22, margin: '20px 0' }}>
                {fallbackTopic.checklist.map(item => (
                  <li key={item} style={{ marginBottom: 14 }}>{item}</li>
                ))}
              </ol>
              <h2 style={{ fontFamily: 'var(--serif)', fontSize: 26, fontWeight: 500, letterSpacing: 1, margin: '50px 0 18px' }}>怎麼交給 JaFun 處理</h2>
              <p>可以直接貼上日本商品網址，或用中文描述需求與預算。JaFun 會先整理可確認資訊，能自動報價的商品會進入購物車；需要人工判斷的商品會回到 LINE 客服確認，避免使用者為不適合承接的商品付款。</p>
            </>
          )}
        </div>

        {/* Tags + share */}
        <div style={{ maxWidth: 720, margin: '60px auto 0', paddingTop: 30, borderTop: '1px solid var(--jf-line)', display: 'flex', justifyContent: 'space-between', alignItems: 'center', flexWrap: 'wrap', gap: 20 }}>
          <div style={{ display: 'flex', gap: 8 }}>
            {(a.keywords || [a.tag]).slice(0, 4).map(tg => (
              <span key={tg} style={{ padding: '6px 14px', border: '1px solid var(--jf-line)', fontFamily: 'var(--serif)', fontSize: 12, color: 'var(--jf-mute-2)' }}>#{tg}</span>
            ))}
          </div>
          <div style={{ display: 'flex', gap: 12, fontFamily: 'var(--sans)', fontSize: 12, color: 'var(--jf-mute)' }}>
            <span>分享</span>
            {['LINE', 'FB', 'X', '複製連結'].map(s => (
              <span key={s} style={{ cursor: 'pointer' }}>{s}</span>
            ))}
          </div>
        </div>
      </section>

      {/* Related */}
      <section className="section" style={{ background: 'var(--jf-paper-2)', paddingTop: 80 }}>
        <div className="section-narrow">
          <div className="section-eyebrow">RELATED ARTICLES</div>
          <h3 style={{ fontFamily: 'var(--serif)', fontSize: 30, margin: '8px 0 36px', letterSpacing: 2, fontWeight: 500 }}>延伸閱讀</h3>
          <div style={{ display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', gap: 40 }}>
            {related.map(r => (
              <div key={r.id} className="edit-card" onClick={() => onNav('article', { article: r })}>
                <div className="pic">
                  <img
                    src={imageVariantUrl(r.imageSizes, 'card', r.img)}
                    srcSet={imageVariantSrcSet(r.imageSizes)}
                    sizes="(max-width: 860px) 100vw, 33vw"
                    alt=""
                    loading="lazy"
                    decoding="async"
                  />
                </div>
                <div className="meta"><span>{r.tag}</span><span>{r.read}</span></div>
                <h3>{r.title}</h3>
              </div>
            ))}
          </div>
        </div>
      </section>
      <CtaBanner t={t} onNav={onNav} />
    </div>
  );
}

const FAQ_SECTIONS = [
  {
    title: '代購與下單',
    en: 'ORDERING',
    items: [
      { q: '怎麼開始代購？', a: '把日本商品網址（Amazon JP、樂天市場、UNIQLO、GU）貼到「LINE代購」頁的輸入框，系統會自動解析商品、規格與台幣報價，確認後即可下單。也可以加入 JaFun LINE 官方帳號，把網址貼給客服處理。' },
      { q: '報價包含哪些費用？', a: '報價＝商品價格（日幣換算台幣）＋日本國內運費（若有）＋服務費 8%（最低 NT$80）＋國際運費預估。結帳前都看得到明細，沒有隱藏費用。' },
      { q: '匯率怎麼計算？', a: '依 JaFun 公告匯率即時換算（顯示在報價明細中），下單當下鎖定價格，不受之後匯率波動影響。' },
      { q: '商品有多種規格（顏色／尺寸／入數）怎麼選？', a: '報價頁會自動列出商品的規格選項，點選後價格與圖片會同步更新；不同規格若有價差或影響重量，報價也會即時重算。' },
      { q: '找不到商品或網站不支援怎麼辦？', a: '目前自動報價支援 Amazon JP、樂天市場、UNIQLO、GU。其他網站（Yahoo! 拍賣、官網限定等）可以把連結傳到 LINE 官方帳號，由客服人工報價。' },
    ],
  },
  {
    title: '運費與集運',
    en: 'SHIPPING',
    items: [
      { q: '國際運費怎麼算？', a: '依計費重量計算：未滿 1kg 以 1kg 計，超過 1kg 後每 0.5kg 無條件進位。系統自動比較日本流通王與一方物流，套用較便宜的方案（1–5kg 另有 NT$100 處理費）。可在「集運服務」頁試算。' },
      { q: '多件商品可以合併寄送嗎？', a: '可以。商品會先集中到 JaFun 日本倉庫，合併打包後一次寄出，省下重複的國際運費。會員中心可申請合併出貨。' },
      { q: '多久可以收到？', a: '日本倉庫出貨後，空運到台灣約 3-5 個工作天（不含日本國內到倉時間與清關時間）。' },
      { q: '哪些商品不能寄？', a: '酒類、菸品、肉品、生鮮、植物種子、部分藥品與航空危險品（噴霧、鋰電池單獨出貨等）無法承接或需人工確認，詳見集運服務頁的禁運說明。' },
      { q: '什麼是 EZ WAY 實名認證？', a: '台灣海關要求進口包裹收件人完成 EZ WAY App 實名認證。每箱包裹需要一位已完成認證的收件人資料，否則可能卡關。' },
    ],
  },
  {
    title: '付款方式',
    en: 'PAYMENT',
    items: [
      { q: '有哪些付款方式？', a: '結帳使用安全的信用卡付款頁（支援 VISA／Mastercard／AMEX／JCB）。JaFun 不會接觸或儲存你的卡號。' },
      { q: '需要日本信用卡嗎？', a: '不需要。整個流程用台灣的信用卡以台幣結帳，不需要日本信用卡或日幣帳戶。' },
      { q: '可以用優惠碼嗎？', a: '可以，購物車結帳時輸入優惠碼即可折抵；每個優惠碼有使用期限與條件，以後台公告為準。' },
    ],
  },
  {
    title: '配送與退換貨',
    en: 'DELIVERY & RETURNS',
    items: [
      { q: '可以追蹤包裹嗎？', a: '可以。登入會員中心即可查看訂單狀態：已下單 → 日本倉庫收貨 → 集貨打包 → 空運運送中 → 台灣送達。' },
      { q: '沒有註冊會員，要怎麼追蹤訂單？', a: '訪客下單後，可透過 JaFun 官方 LINE 提供訂單編號與下單 Email 查詢訂單狀態：https://line.me/R/ti/p/@659mpzle（@659mpzle）。若之後用同一個 Email 註冊會員，訂單會自動歸戶到會員中心；也可在會員中心輸入訂單編號綁定（Email 需與下單時相同）。' },
      { q: '商品有問題可以退換貨嗎？', a: '日本倉庫收貨時會專人開箱攝影驗貨。若收到商品有破損或與訂單不符，請於到貨 7 天內透過 LINE 客服聯繫，我們會協助處理。代購商品因日本賣家政策，原則上無法因個人因素退換，下單前請確認規格。' },
      { q: '包裹寄丟了怎麼辦？', a: '所有包裹都有物流追蹤；若發生遺失或嚴重延誤，JaFun 客服會協助向物流商求償處理。' },
    ],
  },
  {
    title: '會員與其他',
    en: 'MEMBERSHIP',
    items: [
      { q: '一定要註冊會員嗎？', a: '不用，訪客也能直接結帳。註冊會員（支援 LINE 登入）可以保留訂單紀錄、追蹤包裹、累積優惠與管理常用收件地址。' },
      { q: '訂閱食箱是什麼？', a: '每月主題日本零食箱，由編輯部選品直送到家，有 1／3／6／12 個月方案，可隨時取消續訂。詳見「訂閱食箱」頁。' },
      { q: 'JaFun 點數可以做什麼？', a: '敬請期待。JaFun 點數（積分）功能規劃中，未來會員消費可累積點數並折抵消費，實際累積與使用辦法將另行公告。' },
      { q: '還有其他問題？', a: '右下角的 AI 客服可以即時回答代購、報價、運費與訂單問題，也可以加入 LINE 官方帳號由真人客服協助。' },
    ],
  },
];

function FaqPage({ t, onNav }) {
  const [openKey, setOpenKey] = useStateP('0-0');
  return (
    <div data-screen-label="09 FAQ">
      <section className="page-hero">
        <div className="crumb">HOME / 常見問題</div>
        <h1>常見問題</h1>
        <div className="jp">よ く あ る 質 問 · FAQ</div>
        <p className="desc">關於代購、運費、付款、配送與會員的常見問題都整理在這裡；找不到答案時，右下角 AI 客服與 LINE 真人客服隨時為你服務。</p>
      </section>

      <section className="section section-narrow" style={{ paddingTop: 60, paddingBottom: 100 }}>
        {FAQ_SECTIONS.map((section, sectionIndex) => (
          <div key={section.title} style={{ marginBottom: 56 }}>
            <div className="section-eyebrow">{section.en}</div>
            <h2 style={{ fontFamily: 'var(--serif)', fontSize: 26, margin: '8px 0 22px', letterSpacing: 2, fontWeight: 500 }}>{section.title}</h2>
            <div style={{ background: '#fff', border: '1px solid var(--jf-line)' }}>
              {section.items.map((item, itemIndex) => {
                const key = `${sectionIndex}-${itemIndex}`;
                const open = openKey === key;
                return (
                  <div key={key} style={{ borderBottom: itemIndex < section.items.length - 1 ? '1px solid var(--jf-line)' : 'none' }}>
                    <button
                      type="button"
                      onClick={() => setOpenKey(open ? '' : key)}
                      aria-expanded={open}
                      style={{ width: '100%', textAlign: 'left', padding: '20px 24px', background: 'transparent', border: 'none', cursor: 'pointer', display: 'flex', justifyContent: 'space-between', alignItems: 'center', gap: 16 }}
                    >
                      <span style={{ fontFamily: 'var(--serif)', fontSize: 16, letterSpacing: 1, color: open ? 'var(--jf-red)' : 'var(--jf-ink)' }}>
                        Q　{item.q}
                      </span>
                      <span aria-hidden="true" style={{ fontFamily: 'var(--sans)', fontSize: 18, color: 'var(--jf-mute)', flexShrink: 0 }}>{open ? '−' : '+'}</span>
                    </button>
                    {open && (
                      <div style={{ padding: '0 24px 22px', fontFamily: 'var(--serif)', fontSize: 14, lineHeight: 2, color: 'var(--jf-mute-2)' }}>
                        {item.a}
                      </div>
                    )}
                  </div>
                );
              })}
            </div>
          </div>
        ))}

        <div style={{ marginTop: 40, padding: 32, background: 'var(--jf-paper-2)', textAlign: 'center' }}>
          <div style={{ fontFamily: 'var(--serif)', fontSize: 18, marginBottom: 12 }}>沒有找到你的答案？</div>
          <div style={{ display: 'flex', gap: 12, justifyContent: 'center', flexWrap: 'wrap' }}>
            <button className="btn btn-line" onClick={() => window.openLineOfficial?.()}>LINE 真人客服</button>
            <button className="btn btn-ghost" onClick={() => onNav('contact')}>聯絡我們</button>
          </div>
        </div>
      </section>
    </div>
  );
}

function AboutPage({ t, onNav }) {
  const japanAddress = '〒557-0001 大阪府大阪市西成区山王3-6-17';
  const taiwanAddress = '臺北市中山區南京東路一段15號 3F';
  const japanCompanyRows = [
    ['社名 / 公司名稱', 'KAIN 株式会社（KAIN CO., LTD.）'],
    ['母公司 / 親会社', '日本 ALION'],
    ['所在地 / 公司地址', '〒557-0001\n大阪府大阪市西成区山王3-6-17'],
    ['事業內容 / 事業範圍', '日本商品代購、跨境集運服務'],
  ];
  const taiwanCompanyRows = [
    ['公司名稱', '阿利恩股份有限公司'],
    ['母公司', '日本 ALION'],
    ['公司地址', taiwanAddress],
  ];
  const japanMapQuery = encodeURIComponent(japanAddress);
  const japanMapUrl = `https://www.google.com/maps/search/?api=1&query=${japanMapQuery}`;
  const japanMapEmbedUrl = `https://maps.google.com/maps?hl=zh-TW&q=${japanMapQuery}&z=17&output=embed`;
  const taiwanMapQuery = encodeURIComponent(taiwanAddress);
  const taiwanMapUrl = `https://www.google.com/maps/search/?api=1&query=${taiwanMapQuery}`;
  const taiwanMapEmbedUrl = `https://maps.google.com/maps?hl=zh-TW&q=${taiwanMapQuery}&z=17&output=embed`;

  return (
    <div data-screen-label="06 About">
      <section className="page-hero">
        <div className="crumb">HOME / 關於 JaFun</div>
        <h1>關於 JaFun</h1>
        <div className="jp">私 た ち に つ い て</div>
      </section>

      <section className="section section-narrow" style={{ paddingTop: 80 }}>
        <div style={{ maxWidth: 760, margin: '0 auto', textAlign: 'center' }}>
          <div className="divider-mark">日 本 と 台 湾</div>
          <h2 style={{ fontFamily: 'var(--serif)', fontSize: 42, lineHeight: 1.5, letterSpacing: 2, margin: '40px 0' }}>
            從一個連結，<br />開始你的日本購物。
          </h2>
          <p style={{ fontFamily: 'var(--serif)', fontSize: 17, lineHeight: 2, color: 'var(--jf-mute-2)' }}>
            JaFun 隸屬日本 ALION 集團，由旗下子公司 KAIN 株式会社（大阪）營運，
            台灣在地服務由子公司阿利恩股份有限公司提供。我們相信日本購物不該只是少數人的特權——
            不需要懂日文、不需要日本信用卡、不需要研究運費，只要一個 LINE 就能買到。
          </p>
        </div>

        <div style={{ marginTop: 100, display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', gap: 60, textAlign: 'center' }}>
          {[
            ['10,000+', '累積代購訂單'],
            ['98.7%', '客戶滿意度'],
            ['3-5 天', '空運平均到貨'],
          ].map((s, i) => (
            <div key={i}>
              <div style={{ fontFamily: 'var(--serif)', fontSize: 56, color: 'var(--jf-red)', fontWeight: 500, letterSpacing: 1 }}>{s[0]}</div>
              <div style={{ fontFamily: 'var(--sans)', fontSize: 12, letterSpacing: 3, color: 'var(--jf-mute)', marginTop: 10 }}>{s[1]}</div>
            </div>
          ))}
        </div>
      </section>

      {/* Company info — Japanese style */}
      <section className="section" style={{ background: 'var(--jf-paper-2)', paddingTop: 100 }}>
        <div className="section-narrow">
          <div className="section-head">
            <div className="left">
              <div className="section-eyebrow">COMPANY PROFILE</div>
              <h2 className="section-title">会 社 概 要</h2>
              <div className="section-jp">公 司 資 訊</div>
            </div>
          </div>

          <div className="company-profile-grid">
            <div className="company-profile-card">
              <div className="company-profile-card-head">
                <span>JAPAN OPERATING COMPANY</span>
                <h3>日本營運公司</h3>
              </div>
              {japanCompanyRows.map((row, i) => (
                <div className="company-profile-row" key={i}>
                  <div className="company-profile-label">{row[0]}</div>
                  <div className="company-profile-value">{row[1]}</div>
                </div>
              ))}
              <div className="company-map-panel">
                <iframe
                  title="KAIN 株式会社 Google Map"
                  src={japanMapEmbedUrl}
                  loading="lazy"
                  referrerPolicy="no-referrer-when-downgrade"
                  allowFullScreen
                />
                <div className="company-map-footer">
                  <span>{japanAddress}</span>
                  <a href={japanMapUrl} target="_blank" rel="noopener noreferrer">在 Google Map 開啟</a>
                </div>
              </div>
            </div>

            <div className="company-profile-card">
              <div className="company-profile-card-head">
                <span>TAIWAN SUBSIDIARY</span>
                <h3>台灣子公司</h3>
              </div>
              {taiwanCompanyRows.map((row, i) => (
                <div className="company-profile-row" key={i}>
                  <div className="company-profile-label">{row[0]}</div>
                  <div className="company-profile-value">{row[1]}</div>
                </div>
              ))}
              <div className="company-map-panel">
                <iframe
                  title="阿利恩股份有限公司 Google Map"
                  src={taiwanMapEmbedUrl}
                  loading="lazy"
                  referrerPolicy="no-referrer-when-downgrade"
                  allowFullScreen
                />
                <div className="company-map-footer">
                  <span>{taiwanAddress}</span>
                  <a href={taiwanMapUrl} target="_blank" rel="noopener noreferrer">在 Google Map 開啟</a>
                </div>
              </div>
            </div>
          </div>
        </div>
      </section>

      <CtaBanner t={t} onNav={onNav} />
    </div>
  );
}

const LEGAL_COMPANY = {
  name: 'KAIN 株式会社（KAIN CO., LTD.）',
  service: 'JaFun',
  address: '〒557-0001 大阪府大阪市西成区山王3-6-17',
  taiwanBranch: {
    name: '阿利恩股份有限公司',
    address: '臺北市中山區南京東路一段15號 3F',
  },
  updatedAt: '2026 年 4 月 30 日',
};

function LegalAside({ title }) {
  return (
    <aside className="legal-meta">
      <div style={{ marginBottom: 18 }}>
        <strong>{title}</strong>
        JaFun 由 {LEGAL_COMPANY.name} 營運，以下內容適用於本網站、會員服務、日本商品代購與集運相關流程。
      </div>
      <div style={{ marginBottom: 18 }}>
        <strong>營運公司</strong>
        {LEGAL_COMPANY.name}<br />
        {LEGAL_COMPANY.address}<br />
        台灣分公司：{LEGAL_COMPANY.taiwanBranch.name}<br />
        {LEGAL_COMPANY.taiwanBranch.address}
      </div>
      <div>
        <strong>最後更新</strong>
        {LEGAL_COMPANY.updatedAt}
      </div>
    </aside>
  );
}

function PrivacyPage({ t, onNav }) {
  return (
    <div data-screen-label="09 Privacy Policy">
      <section className="page-hero">
        <div className="crumb">HOME / PRIVACY POLICY</div>
        <h1>隱私權政策</h1>
        <div className="jp">プ ラ イ バ シ ー ポ リ シ ー</div>
        <p className="desc">本政策說明 JaFun 如何取得、使用、保存與保護會員及使用者的個人資料。</p>
      </section>

      <section className="section section-narrow">
        <div className="legal-layout">
          <article className="legal-card">
            <h2>個人情報保護方針</h2>
            <p>
              KAIN 株式会社（以下稱「本公司」）依日本個人情報保護法及相關法令，管理 JaFun 服務中取得的個人資料。
              本公司會在達成利用目的所需範圍內處理個人資料，並採取合理安全管理措施。
            </p>

            <h3>1. 取得的資料</h3>
            <ul>
              <li>會員註冊資料：姓名、Email、LINE 識別資訊、登入紀錄、會員編號。</li>
              <li>訂單與代購資料：商品網址、商品名稱、尺寸顏色、數量、金額、付款狀態、配送狀態。</li>
              <li>配送與客服資料：收件人、地址、電話、客服訊息、包裹照片、驗貨紀錄。</li>
              <li>網站使用資料：Cookie、裝置資訊、瀏覽紀錄、IP 位址、錯誤記錄與分析資料。</li>
            </ul>

            <h3>2. 利用目的</h3>
            <ul>
              <li>提供會員登入、LINE 登入、本人確認與帳戶管理。</li>
              <li>處理日本商品代購、報價、付款、倉儲、驗貨、國際配送與售後服務。</li>
              <li>回覆客服、寄送交易通知、重要公告、帳務與安全通知。</li>
              <li>改善網站體驗、商品推薦、內容品質、廣告與成效分析。</li>
              <li>防止詐欺、不正使用、違反條款行為，並遵守法令或主管機關要求。</li>
            </ul>

            <h3>3. 第三方提供與委託</h3>
            <p>
              本公司不會在未取得本人同意的情況下，將個人資料提供給第三方。但於法令允許、配送、付款、倉儲、客服、系統維運、
              分析工具或防止不正使用所需範圍內，可能委託合作廠商處理必要資料。本公司會要求受託方依適當安全標準管理資料。
            </p>

            <h3>4. 跨境處理</h3>
            <p>
              JaFun 服務連結日本與台灣，資料可能於日本、台灣或雲端服務提供地進行保存或處理。本公司會依適用法令與合理安全措施管理跨境資料處理。
            </p>

            <h3>5. Cookie 與分析工具</h3>
            <p>
              本網站可能使用 Cookie、類似技術與分析工具，用於維持登入狀態、購物車、流量分析、錯誤追蹤與服務改善。
              使用者可透過瀏覽器設定限制 Cookie，但部分功能可能無法正常使用。
            </p>

            <h3>6. 安全管理</h3>
            <p>
              本公司會採取存取權限管理、加密傳輸、紀錄保存、員工教育、委託管理與系統監控等措施，降低個人資料遺失、竄改、洩漏或未授權存取風險。
            </p>

            <h3>7. 查詢、更正、停止利用與刪除</h3>
            <p>
              使用者可依日本個人情報保護法及相關法令，請求揭露、更正、停止利用、刪除或停止第三方提供個人資料。
              本公司會在確認本人身分後，於合理期間內回覆。但如因法令、交易保存或防止不正使用而需保存者，不在此限。
            </p>

            <h3>8. 聯絡窗口</h3>
            <p>
              個人資料相關請求請透過 JaFun 官方 LINE、網站聯絡頁或客服信箱提出。若本政策更新，本公司將於網站公告新版內容。
            </p>
          </article>
          <LegalAside title="Privacy Policy" />
        </div>
      </section>
      <CtaBanner t={t} onNav={onNav} />
    </div>
  );
}

function TermsPage({ t, onNav }) {
  return (
    <div data-screen-label="10 Terms">
      <section className="page-hero">
        <div className="crumb">HOME / TERMS OF SERVICE</div>
        <h1>服務條款</h1>
        <div className="jp">利 用 規 約</div>
        <p className="desc">使用 JaFun 會員、代購、商城、集運、訂閱與相關服務前，請先閱讀本條款。</p>
      </section>

      <section className="section section-narrow">
        <div className="legal-layout">
          <article className="legal-card">
            <h2>JaFun 利用規約</h2>
            <p>
              本服務條款適用於 {LEGAL_COMPANY.name} 營運之 JaFun 網站、會員系統、日本商品代購、集運、訂閱與相關服務。
              使用者完成註冊、登入、送出代購需求或下單，即視為同意本條款。
            </p>

            <h3>1. 服務內容</h3>
            <ul>
              <li>JaFun 提供日本商品網址報價、代購下單、收貨驗貨、合併包裝、國際配送、會員訂單查詢等服務。</li>
              <li>日本官網價格、庫存、尺寸、顏色與配送條件可能隨時變動，最終以下單前確認內容為準。</li>
            </ul>

            <h3>2. 會員帳戶</h3>
            <ul>
              <li>使用者應提供真實、最新且完整的註冊、配送與聯絡資料。</li>
              <li>使用者應妥善保管帳號、密碼、LINE 登入狀態及裝置安全，因管理不當所生損害由使用者自行負責。</li>
              <li>若發現未授權使用、資料異常或安全疑慮，應立即通知 JaFun。</li>
            </ul>

            <h3>3. 報價、付款與費用</h3>
            <ul>
              <li>商品價格以日本網站即時資訊、匯率、服務費、國際運費預估及其他必要費用計算。</li>
              <li>JaFun 服務費、國際運費、匯率與最低服務費會顯示於報價或結帳畫面；實際金額可能因庫存、重量、材積或商品條件調整。</li>
              <li>付款完成後，若日本網站價格或庫存異動，本公司會聯繫使用者確認補差額、替代商品、取消或退款處理。</li>
            </ul>

            <h3>4. 取消、退貨與退款</h3>
            <ul>
              <li>代購商品一經日本店鋪下單，原則上不得任意取消、變更、退貨或退款。</li>
              <li>如商品瑕疵、錯品、缺貨或店鋪取消訂單，本公司會依實際狀況協助處理退款、重購或客服溝通。</li>
              <li>食品、化妝品、貼身衣物、限定商品、客製商品、開封商品及法令或店鋪政策不接受退貨者，不適用任意退換。</li>
            </ul>

            <h3>5. 配送、關稅與禁止商品</h3>
            <ul>
              <li>配送時程為預估值，可能受日本店鋪出貨、海關、航空、天候、假日或物流量影響。</li>
              <li>進口稅費、檢疫、通關文件或法令限制如由收件地主管機關要求，使用者應配合並負擔相關責任。</li>
              <li>使用者不得委託購買違禁品、危險物、仿冒品、酒類、菸草、電子菸、生鮮農漁畜產品、活體動植物、種子、土壤、肉品、含肉加工品、冷藏冷凍或生食商品，或任何違反日本、台灣及配送地法令之商品。</li>
              <li>藥品、醫療器材、隱形眼鏡、食品、健康食品、化妝品、液體、粉末、含電池商品等，可能需要主管機關許可、檢驗或個案確認；JaFun 得拒絕自動下單並改由客服人工審核。</li>
            </ul>

            <h3>6. 內容與智慧財產權</h3>
            <p>
              JaFun 網站上的文字、圖片、版面、商品推薦、文章、商標與系統設計，除第三方權利外，均屬本公司或授權人所有。
              未經同意不得重製、改作、散布、爬取或商業使用。
            </p>

            <h3>7. 服務變更與責任限制</h3>
            <p>
              本公司得因維護、法令、營運或安全需求調整、暫停或終止部分服務。除故意或重大過失外，本公司對間接損害、
              期待利益、資料遺失、第三方網站或物流延誤所生損害，不負超過使用者就該筆交易已支付 JaFun 服務費之責任。
            </p>

            <h3>8. 準據法與管轄</h3>
            <p>
              本條款以日本法為準據法。因本服務所生爭議，除消費者保護法令另有規定外，以本公司所在地有管轄權之日本法院為第一審專屬合意管轄法院。
            </p>
          </article>
          <LegalAside title="Terms of Service" />
        </div>
      </section>
      <CtaBanner t={t} onNav={onNav} />
    </div>
  );
}

function ContactPage({ t, onNav }) {
  return (
    <div data-screen-label="11 Contact">
      <section className="page-hero">
        <div className="crumb">HOME / CONTACT</div>
        <h1>聯絡我們</h1>
        <div className="jp">お 問 い 合 わ せ</div>
        <p className="desc">代購、集運、企業合作、會員與個人資料請求，都可以從這裡開始。</p>
      </section>

      <section className="section section-narrow">
        <div className="contact-grid">
          <div className="contact-card">
            <strong>LINE 代購與客服</strong>
            <div>適合商品網址報價、訂單問題、包裹進度與一般客服。JaFun 官方 LINE：@659mpzle。</div>
            <button className="btn btn-line" onClick={() => window.openLineOfficial?.()} style={{ marginTop: 18, width: '100%', justifyContent: 'center' }}>加入 LINE 官方</button>
          </div>
          <div className="contact-card">
            <strong>企業合作</strong>
            <div>日本品牌代理、跨境販售、批量代購、內容合作與系統開發需求，請留下公司名稱、聯絡人與合作方向。</div>
          </div>
          <div className="contact-card">
            <strong>個人資料窗口</strong>
            <div>如需查詢、更正、停止利用或刪除個人資料，請提供會員 Email 與請求內容，本公司將確認本人身分後處理。</div>
          </div>
        </div>

        <div className="legal-card" style={{ marginTop: 36 }}>
          <h2>公司聯絡資訊</h2>
          <p>
            {LEGAL_COMPANY.name}<br />
            所在地：{LEGAL_COMPANY.address}<br />
            台灣分公司：{LEGAL_COMPANY.taiwanBranch.name}<br />
            台灣地址：{LEGAL_COMPANY.taiwanBranch.address}<br />
            服務名稱：{LEGAL_COMPANY.service}<br />
            聯絡方式：JaFun 官方 LINE @659mpzle、網站客服表單或會員中心訊息。
          </p>
          <div style={{ display: 'flex', flexWrap: 'wrap', gap: 12, marginTop: 28 }}>
            <button className="btn btn-primary" onClick={() => onNav('line')}>前往 LINE 代購</button>
            <button className="btn btn-line" onClick={() => window.openLineOfficial?.()}>加入 LINE 官方</button>
            <button className="btn btn-ghost" onClick={() => onNav('privacy')}>查看隱私權政策</button>
            <button className="btn btn-ghost" onClick={() => onNav('terms')}>查看服務條款</button>
          </div>
        </div>
      </section>
      <CtaBanner t={t} onNav={onNav} />
    </div>
  );
}

const QUOTE_API_BASE = window.JAFUN_QUOTE_API_BASE || 'http://localhost:3001';

function defaultSelections(options) {
  return (options || []).reduce((result, option) => {
    const first = option.values.find(value => value.available);
    if (first) result[option.key] = first.id;
    return result;
  }, {});
}

// 計價統一採大阪倉級距(2026-07 拍板):1–10kg 240、10.5–20kg 200、21kg+ 180,
// 箱重 <6kg 加派送費 NT$100,與前台空運價格表一致。
// (原東京倉 ≤5kg 200/kg 比價已移除;新物流洽談中,價格確定後再調整此表。)
const TAIWAN_AIR_SHIPPING_CARRIERS = [
  {
    name: '大阪倉',
    tiers: [
      { maxKg: 10, rateTWDPerKg: 240 },
      { maxKg: 20, rateTWDPerKg: 200 },
      { maxKg: Infinity, rateTWDPerKg: 180 },
    ],
    handlingFeeTWD: 100,
    handlingFeeMaxKg: 5.5,
  },
];

function roundShippingWeight(value, unit = 0.5) {
  const safeValue = Math.max(unit, Number(value) || unit);
  return Math.ceil((safeValue - Number.EPSILON) / unit) * unit;
}

function taiwanAirShippingEstimate(weightKg, exchangeRate = 0.22) {
  const estimatedWeightKg = Math.max(0.1, Number(weightKg) || 0.1);
  // 未滿 1kg 以 1kg 計費，超過 1kg 後每 0.5kg 無條件進位
  const billableWeightKg = Math.max(1, roundShippingWeight(estimatedWeightKg, 0.5));
  const rate = Number(exchangeRate) > 0 ? Number(exchangeRate) : 0.22;
  const quotes = TAIWAN_AIR_SHIPPING_CARRIERS
    .filter(carrier => billableWeightKg <= (carrier.maxBillableKg || Infinity))
    .map(carrier => {
    const tier = carrier.tiers.find(item => billableWeightKg <= item.maxKg) || carrier.tiers[carrier.tiers.length - 1];
    const deliveryFeeTWD = billableWeightKg <= carrier.handlingFeeMaxKg ? carrier.handlingFeeTWD : 0;
    return {
      carrier,
      tier,
      deliveryFeeTWD,
      totalTWD: Math.round(billableWeightKg * tier.rateTWDPerKg) + deliveryFeeTWD,
    };
  });
  const best = quotes.reduce((cheapest, current) => (current.totalTWD < cheapest.totalTWD ? current : cheapest));
  return {
    internationalShippingDestination: 'taiwan',
    internationalShippingEstimatedWeightKg: Math.ceil(estimatedWeightKg * 10) / 10,
    internationalShippingBillableWeightKg: billableWeightKg,
    internationalShippingRateTWDPerKg: best.tier.rateTWDPerKg,
    internationalShippingRateJPYPerKg: Math.round(best.tier.rateTWDPerKg / rate),
    internationalShippingDeliveryFeeTWD: best.deliveryFeeTWD,
    internationalShippingDeliveryFeeJPY: Math.round(best.deliveryFeeTWD / rate),
    internationalShippingEstimateJPY: Math.round(best.totalTWD / rate),
    internationalShippingEstimateTWD: best.totalTWD,
    internationalShippingTierLabel: `計費 ${billableWeightKg}kg${estimatedWeightKg > 30 ? '（大型品需確認）' : ''}`,
  };
}

// 對外掛載：購物車(auth.jsx)沿用同一套運費計算，確保報價頁與購物車金額一致(單一來源)。
window.taiwanAirShippingEstimate = taiwanAirShippingEstimate

function quoteShippingForQuantity(basePricing, quantity) {
  const baseQuantity = Math.max(1, Number(basePricing.quantity) || 1);
  const baseEstimatedWeight = Number(basePricing.internationalShippingEstimatedWeightKg || basePricing.internationalShippingBillableWeightKg || 0);
  if (!baseEstimatedWeight) {
    return {
      internationalShippingEstimateTWD: Number(basePricing.internationalShippingEstimateTWD) || 0,
    };
  }
  const productWeightKg = (baseEstimatedWeight / baseQuantity) * Math.max(1, Number(quantity) || 1);
  const result = taiwanAirShippingEstimate(productWeightKg + 0.1, basePricing.exchangeRate);
  return { ...result, internationalShippingEstimatedWeightKg: Math.ceil(productWeightKg * 10) / 10 };
}

function shippingTierRange(billableWeightKg) {
  if (billableWeightKg <= 1) return '≤ 1kg';
  const lower = (billableWeightKg - 0.5).toFixed(1);
  const upper = billableWeightKg.toFixed(1);
  return `${lower} ~ ${upper}kg`;
}

function quoteShippingDetailLabel(pricing) {
  if (!pricing?.internationalShippingBillableWeightKg) return '日本';
  const handling = Number(pricing.internationalShippingDeliveryFeeTWD) > 0
    ? ` + 處理費 NT$ ${pricing.internationalShippingDeliveryFeeTWD}`
    : '';
  const estimatedW = pricing.internationalShippingEstimatedWeightKg?.toFixed(1);
  const tierRange = shippingTierRange(pricing.internationalShippingBillableWeightKg);
  const weightStr = estimatedW ? `商品約 ${estimatedW}kg · 運費區間 ${tierRange}` : `運費區間 ${tierRange}`;
  return `${weightStr} · NT$ ${pricing.internationalShippingRateTWDPerKg}/kg${handling}　※ 實際運費以到倉秤重為準，多退少補`;
}

function calculateQuotePricing(basePricing, quantity) {
  const q = Math.max(1, Number(quantity) || 1);
  const unitPriceJPY = Math.max(1, Number(basePricing.unitPriceJPY || 0));
  const itemSubtotalJPY = unitPriceJPY * q;
  const itemSubtotalTWD = Math.round(itemSubtotalJPY * basePricing.exchangeRate);
  const serviceFeeTWD = Math.max(basePricing.serviceFeeMinimumTWD, Math.round(itemSubtotalTWD * basePricing.serviceFeeRate));
  const shipping = quoteShippingForQuantity(basePricing, q);
  return {
    ...basePricing,
    ...shipping,
    unitPriceJPY,
    quantity: q,
    itemSubtotalJPY,
    itemSubtotalTWD,
    serviceFeeTWD,
    totalTWD: itemSubtotalTWD + (Number(basePricing.domesticShippingTWD) || 0) + serviceFeeTWD + shipping.internationalShippingEstimateTWD,
  };
}

async function requestQuote(url, quantity, selections) {
  if (window.jafunProductSearch?.quote) {
    return window.jafunProductSearch.quote(url, quantity, selections, { entryPoint: 'url_quote_search' });
  }
  throw new Error('商品資料無法解析');
}

function readableLineQuoteError(message) {
  return window.jafunProductSearch?.readableError(message)
    || '商品資料無法解析，請改用 LINE 人工確認或重新輸入商品需求。';
}

async function requestLineProductSearch(value, quantity, selections) {
  if (window.jafunProductSearch?.resolve) {
    return window.jafunProductSearch.resolve(value, {
      preferredMode: window.jafunProductSearch.looksLikeProductUrl(value) ? 'url' : 'keyword',
      quantity,
      selections,
      urlEntryPoint: 'line_url_search',
      keywordEntryPoint: 'line_keyword_search',
      fallbackEntryPoint: 'line_url_ai_fallback',
      fallbackToRecommend: true,
    });
  }
  const quote = await requestQuote(value, quantity, selections);
  return { mode: 'url', query: value, quote, recommendation: null, parseWarning: '' };
}

function isQuoteableCandidateUrl(value) {
  const url = String(value || '');
  return /item\.rakuten\.co\.jp\/|amazon\.co\.jp\/(?:.*\/)?(?:dp|gp\/product)\/|uniqlo\.com\/jp\/|gu-global\.com\/jp\//i.test(url);
}

function optionSummary(product, selections) {
  return (product.options || []).map(option => {
    const value = option.values.find(v => v.id === selections[option.key]);
    return value ? `${option.label}: ${value.label}` : '';
  }).filter(Boolean).join(' / ');
}

function QuoteLinePage({
  t,
  onNav,
  initialUrl,
  initialSelections = {},
  initialQuantity = 1,
  initialCheckout = '',
  initialAuthStatus = '',
  initialAuthMessage = '',
  onAddToCart,
  authSession,
}) {
  const [url, setUrl] = useStateP(initialUrl || '');
  const [stage, setStage] = useStateP(0);
  const [quantity, setQuantity] = useStateP(Math.max(1, Number(initialQuantity) || 1));
  const [quote, setQuote] = useStateP(null);
  const [recommendation, setRecommendation] = useStateP(null);
  const [selectedOptions, setSelectedOptions] = useStateP(initialSelections || {});
  const [message, setMessage] = useStateP('');
  const [fallbackQuoteNotice, setFallbackQuoteNotice] = useStateP('');
  const [quoteAdded, setQuoteAdded] = useStateP(false);
  const [quoteCheckoutOpen, setQuoteCheckoutOpen] = useStateP(false);
  const [quoteCheckoutStartStep, setQuoteCheckoutStartStep] = useStateP(authSession?.token ? 'recipient' : 'entry');
  const [lineAuthNotice, setLineAuthNotice] = useStateP(() => (
    initialAuthStatus
      ? { status: initialAuthStatus, message: initialAuthMessage || '' }
      : null
  ));
  const [quoteOrderDone, setQuoteOrderDone] = useStateP(false);
  const [quoteOrderNumber, setQuoteOrderNumber] = useStateP('');
  const [optionLoading, setOptionLoading] = useStateP(false);
  const authReturnHandledRef = useRefP(false);
  const pricing = quote ? calculateQuotePricing(quote.pricing, quantity, selectedOptions, quote.product) : null;

  const parse = async (nextUrl = '') => {
    const incomingUrl = typeof nextUrl === 'string' ? nextUrl : '';
    const normalizedInput = (incomingUrl || url).trim();
    setQuoteAdded(false);
    setMessage('');
    setFallbackQuoteNotice('');
    setRecommendation(null);
    if (!normalizedInput) {
      setMessage('請先貼上日本商品網址。');
      return;
    }
    if (!window.jafunProductSearch?.looksLikeProductUrl?.(normalizedInput)) {
      setStage(0);
      setMessage('這不是商品網址，請貼上日本商品頁連結（目前支援 Amazon JP、樂天市場、UNIQLO、GU）。');
      return;
    }
    if (incomingUrl && incomingUrl !== url) {
      setUrl(normalizedInput);
    }
    setStage(1);
    try {
      const incomingSelections = incomingUrl && Object.keys(initialSelections || {}).length
        ? initialSelections
        : selectedOptions;
      const result = await requestLineProductSearch(normalizedInput, quantity, incomingUrl ? incomingSelections : selectedOptions);
      const data = result.quote || null;
      setQuote(data);
      setRecommendation(result.recommendation || null);
      if (data) {
        setSelectedOptions(data.selectedOptions || defaultSelections(data.product.options));
        setStage(2);
        setFallbackQuoteNotice(result.parseWarning ? '原商品頁資料無法直接解析，已改用 AI 幫你找到可估價的相近商品。' : '');
        setMessage(result.parseWarning ? '原商品頁資料無法直接解析，已改用 AI 幫你找到可估價的相近商品。' : '已取得商品報價，請確認規格與金額。');
        return;
      }
      setStage(2);
      setFallbackQuoteNotice('');
      setMessage(result.parseWarning ? '商品資料無法解析，已改用 AI 整理相近商品候選。' : '已整理相近商品候選，請開啟商品頁或交由 LINE 客服確認。');
    } catch (error) {
      setStage(0);
      setQuote(null);
      setRecommendation(null);
      const apiMessage = error.message === 'Failed to fetch'
        ? '目前無法連線到報價服務，請稍後再試或聯繫客服。'
        : readableLineQuoteError(error.message || '解析失敗，請稍後再試。');
      setMessage(apiMessage);
    }
  };

  useEffectP(() => {
    const incomingUrl = (initialUrl || '').trim();
    if (!incomingUrl || quote || stage !== 0) return;
    parse(incomingUrl);
  }, [initialUrl]);

  useEffectP(() => {
    if (!initialAuthStatus) return;
    setLineAuthNotice({ status: initialAuthStatus, message: initialAuthMessage || '' });
  }, [initialAuthStatus, initialAuthMessage]);

  useEffectP(() => {
    if (initialCheckout !== 'quote' || authReturnHandledRef.current || !quote || !pricing) return;
    authReturnHandledRef.current = true;
    setQuoteAdded(true);
    setQuoteOrderDone(false);
    setQuoteCheckoutStartStep('entry');
    setQuoteCheckoutOpen(true);
  }, [initialCheckout, quote, pricing]);

  const quoteCartItem = () => {
    if (!quote || !pricing) return null;
    const summary = optionSummary(quote.product, selectedOptions);
    return {
      id: quote.product.productId,
      cartId: `quote-${quote.product.source}-${quote.product.productId}-${Object.values(selectedOptions).join('-')}`,
      region: `貼網址代購 · ${quote.product.sourceLabel}`,
      name: quote.product.title,
      jname: quote.product.subtitle,
      price: pricing.totalTWD,
      jpy: pricing.itemSubtotalJPY,
      img: quote.product.image,
      optionSummary: summary,
      sourceLabel: quote.product.sourceLabel,
      sourceUrl: quote.product.sourceUrl,
      sourceType: quote.product.source,
      selectedOptions,
      feeIncluded: true,
      shippingIncluded: true,
      quotePricing: pricing,
    };
  };

  const addQuoteToCart = () => {
    const item = quoteCartItem();
    if (!item) return null;
    onAddToCart?.(item, 1);
    setQuoteAdded(true);
    return item;
  };

  const openQuoteCheckout = (startStep = 'entry') => {
    setQuoteOrderDone(false);
    setQuoteCheckoutStartStep(authSession?.token ? 'recipient' : startStep);
    setQuoteCheckoutOpen(true);
  };

  const addQuoteAndOpenGuide = () => {
    if (!quoteAdded) {
      const item = addQuoteToCart();
      if (!item) return;
    }
    openQuoteCheckout('entry');
  };

  const applyOptionValueLocally = (value) => {
    if (!quote) return false;
    const nextPriceJPY = Number(value.priceJPY) > 0
      ? Number(value.priceJPY)
      : Number(value.priceJPYDelta || 0)
      ? Math.max(1, quote.pricing.unitPriceJPY + Number(value.priceJPYDelta))
      : 0;
    const nextImage = value.image || '';
    if (!nextPriceJPY && !nextImage) return false;
    setQuote(prev => {
      if (!prev) return prev;
      return {
        ...prev,
        product: {
          ...prev.product,
          ...(nextImage ? { image: nextImage } : {}),
          ...(nextPriceJPY ? { priceJPY: nextPriceJPY } : {}),
        },
        pricing: nextPriceJPY ? { ...prev.pricing, unitPriceJPY: nextPriceJPY } : prev.pricing,
      };
    });
    return true;
  };

  const updateQuoteOption = async (optionKey, value) => {
    if (!value?.available || !quote || optionLoading) return;
    if (selectedOptions[optionKey] === value.id) return;
    const nextSelections = { ...selectedOptions, [optionKey]: value.id };
    setSelectedOptions(nextSelections);
    setQuoteAdded(false);
    const hasRealOptions = (quote.product.options || []).some(option =>
      option.values.some(item => item.id !== 'default'));
    if (!hasRealOptions) {
      setMessage('已更新規格；下單前 JaFun 會再次確認日本原站價格與庫存。');
      return;
    }
    // 先用回應內已有的規格資料即時更新圖片與價格，再向後端確認完整報價
    applyOptionValueLocally(value);
    setOptionLoading(true);
    setMessage('正在依選擇的規格重新確認報價...');
    try {
      const nextQuote = await requestQuote(value.sourceUrl || quote.product.sourceUrl, quantity, nextSelections);
      setQuote(nextQuote);
      setSelectedOptions(nextQuote.selectedOptions || nextSelections);
      setMessage('已依選擇的規格更新報價。');
    } catch (error) {
      const appliedLocally = Number(value.priceJPY) > 0 || Number(value.priceJPYDelta || 0) || value.image;
      setMessage(appliedLocally
        ? '已依規格資料更新估價；下單前 JaFun 會再次確認日本原站價格與庫存。'
        : readableLineQuoteError(error.message || '規格報價更新失敗，請稍後再試。'));
    } finally {
      setOptionLoading(false);
    }
  };

  const resetQuote = () => {
    setUrl('');
    setQuote(null);
    setRecommendation(null);
    setStage(0);
    setMessage('');
    setFallbackQuoteNotice('');
    setQuoteAdded(false);
  };

  const candidates = recommendation?.candidates || [];
  const searchLinks = recommendation?.searchLinks || [];
  const showUrlCard = !quote || stage < 2;
  const showMessage = Boolean(message && (!quote || stage < 2));
  const showFallbackQuoteNotice = Boolean(fallbackQuoteNotice && quote);
  const messageIsError = /失敗|支援|請先|無法|錯誤|連線|不承接|禁運|限制/.test(message || '');
  const quoteRestrictionNotice = quote?.importRestrictionNotice || null;
  const quoteRequiresManualReview = quoteRestrictionNotice?.severity === 'manual_review';

  return (
    <div data-screen-label="02 URL Quote" className="line-quote-page">
      <section className="line-quote-shell">
        <div className="line-quote-header">
          <div>
            <div className="section-eyebrow">LINE OFFICIAL ORDER</div>
            <h1>選擇規格 / 確認報價</h1>
            <p>{quote ? `${quote.product.sourceLabel} · ${quote.product.brand || quote.product.source}` : '貼上日本商品網址，JaFun 會自動解析商品、規格與即時報價。'}</p>
          </div>
          {quote && (
            <button className="line-quote-change" onClick={resetQuote}>更換商品或需求</button>
          )}
        </div>

        <div className="line-quote-progress" aria-label="報價流程">
          <span className={stage >= 1 || quote ? 'is-done' : 'is-active'}>01 貼網址 / 輸入需求</span>
          <span className={quote ? 'is-active' : recommendation ? 'is-done' : ''}>02 選擇規格</span>
          <span className={quote && pricing ? 'is-active' : ''}>03 確認報價</span>
        </div>
        {showFallbackQuoteNotice && (
          <div className="line-quote-message">{fallbackQuoteNotice}</div>
        )}

        {showUrlCard && (
          <div className="line-quote-url-card">
            <div>
              <div className="section-eyebrow">STEP 01 · SEARCH</div>
              <h2>貼上商品連結</h2>
            </div>
            <div className="line-quote-url-row">
              <input
                data-url-quote-input
                value={url}
                onChange={e => setUrl(e.target.value)}
                placeholder="https://www.amazon.co.jp/..."
                onKeyDown={e => e.key === 'Enter' && parse()}
              />
              <button className="btn btn-primary" onClick={() => parse()} disabled={stage === 1}>
                {stage === 1 ? '搜尋中...' : '取得報價'}
              </button>
            </div>
            {stage === 1 && <div className="line-quote-loading">正在判斷輸入內容、搜尋商品與計算報價...</div>}
            {showMessage && (
              <div className={`line-quote-message ${messageIsError ? 'is-error' : ''}`}>{message}</div>
            )}
          </div>
        )}

        {recommendation && !quote && (
          <div className="line-quote-candidates">
            <div>
              <div className="section-eyebrow">AI PRODUCT SEARCH</div>
              <h2>已整理相近商品候選</h2>
              <p>{recommendation.intentSummary || `依「${recommendation.originalQuery || url}」整理日本平台相近商品。`}</p>
            </div>
            {candidates.length > 0 ? (
              <div className="line-quote-candidate-list">
                {candidates.slice(0, 3).map((candidate, index) => (
                  <div className="line-quote-candidate" key={`${candidate.url}-${index}`}>
                    {candidate.image && <img src={candidate.image} alt="" />}
                    <div>
                      <span>{candidate.sourceLabel || '日本平台'}</span>
                      <strong>{candidate.title}</strong>
                      <p>{candidate.reason || recommendation.recommendedReason}</p>
                      {isQuoteableCandidateUrl(candidate.url) ? (
                        <button className="line-quote-inline-action" onClick={() => parse(candidate.url)}>用這個商品估價</button>
                      ) : (
                        <a className="line-quote-inline-action" href={candidate.url} target="_blank" rel="noreferrer">開啟搜尋結果</a>
                      )}
                    </div>
                  </div>
                ))}
              </div>
            ) : (
              <div className="line-quote-message">目前先提供日本平台搜尋入口，若需要 JaFun 確認規格與是否可寄送，可以交由 LINE 客服協助。</div>
            )}
            <div className="line-quote-candidate-actions">
              {searchLinks.slice(0, 2).map(link => (
                <a key={link.url} href={link.url} target="_blank" rel="noreferrer">{link.sourceLabel} 搜尋</a>
              ))}
              <button className="btn btn-line" onClick={() => window.openLineOfficial?.()}>交由 LINE 客服確認</button>
            </div>
          </div>
        )}

        {quote && pricing && (
          <div className="line-quote-layout">
            <div className="line-quote-product-card">
              <div className="line-quote-product-head">
                <img src={quote.product.image} alt="" />
                <div>
                  <div className="line-quote-source">{quote.product.sourceLabel} · {quote.product.brand}</div>
                  <h2>{quote.product.title}</h2>
                  <p>{quote.product.subtitle || `${quote.product.sourceLabel} 商品頁即時解析`}</p>
                  {quoteRestrictionNotice && (
                    <div className="import-restriction-alert">
                      <strong>{quoteRestrictionNotice.title}</strong>
                      <span>{quoteRestrictionNotice.message}</span>
                    </div>
                  )}
                  <div className="line-quote-quantity">
                    <span>數量</span>
                    <button onClick={() => setQuantity(Math.max(1, quantity - 1))} aria-label="減少數量">-</button>
                    <strong>{quantity}</strong>
                    <button onClick={() => setQuantity(quantity + 1)} aria-label="增加數量">+</button>
                  </div>
                </div>
              </div>

              <div className="line-quote-options">
                {quote.product.options.length ? quote.product.options.map(option => (
                  <div key={option.key} className="line-quote-option-group">
                    <div className="line-quote-option-label">{option.label}</div>
                    <div className="line-quote-option-values">
                      {option.values.map(value => {
                        const active = selectedOptions[option.key] === value.id;
                        return (
                          <button
                            key={value.id}
                            className={`line-option-chip ${active ? 'is-active' : ''}`}
                            disabled={!value.available}
                            onClick={() => updateQuoteOption(option.key, value)}
                          >
                            {value.label}
                            {value.priceJPYDelta ? (
                              <small>{value.priceJPYDelta > 0 ? '+' : '-'}¥{Math.abs(value.priceJPYDelta).toLocaleString()}</small>
                            ) : null}
                          </button>
                        );
                      })}
                    </div>
                  </div>
                )) : (
                  <div className="line-quote-empty-options">此商品目前沒有可切換規格。</div>
                )}
                {optionLoading && <div className="line-quote-message">規格報價更新中...</div>}
              </div>
            </div>

            <aside className="line-quote-summary-card">
              <div className="section-eyebrow">STEP 03 · QUOTE</div>
              <h2>確認報價</h2>
              <div className="line-quote-price-list">
                {[
                  ['商品價格', `¥${pricing.itemSubtotalJPY.toLocaleString()}`, `NT$ ${pricing.itemSubtotalTWD.toLocaleString()}`],
                  ...(pricing.domesticShippingTWD > 0
                    ? [['日本國內運費', `¥${pricing.domesticShippingJPY.toLocaleString()}`, `NT$ ${pricing.domesticShippingTWD.toLocaleString()}`]]
                    : []),
                  [`服務費 ${Math.round(pricing.serviceFeeRate * 100)}%`, '最低 NT$ 80', `NT$ ${pricing.serviceFeeTWD.toLocaleString()}`],
                  ['國際運費預估', quoteShippingDetailLabel(pricing), `NT$ ${pricing.internationalShippingEstimateTWD.toLocaleString()}`],
                  ['即時匯率', 'JPY → TWD', pricing.exchangeRate.toFixed(3)],
                ].map(row => (
                  <div key={row[0]}>
                    <span>{row[0]}</span>
                    <small>{row[1]}</small>
                    <strong>{row[2]}</strong>
                  </div>
                ))}
              </div>
              <div className="line-quote-total">
                <span>預估總額</span>
                <strong>NT$ {pricing.totalTWD.toLocaleString()}</strong>
              </div>
              <div className="line-quote-actions">
                {quoteRequiresManualReview ? (
                  <>
                    <button className="btn btn-line" onClick={() => window.openLineOfficial?.()}>改用 LINE 人工確認</button>
                    <button className="btn btn-ghost" onClick={() => onNav('ship')}>查看禁運說明</button>
                  </>
                ) : (
                  <>
                    <button className="btn btn-primary" onClick={addQuoteAndOpenGuide}>
                      {quoteAdded ? '已加入購物車，繼續結帳' : '加入購物車'}
                    </button>
                    <button className="btn btn-ghost" onClick={addQuoteAndOpenGuide}>確認報價</button>
                  </>
                )}
              </div>
              {quoteAdded && (
                <div className="line-quote-added">
                  已加入購物車
                  <button onClick={() => onNav('cart')}>查看購物車</button>
                </div>
              )}
            </aside>
          </div>
        )}

        {quoteOrderDone && (
          <div className="line-quote-order-done">
            <div>注文受付</div>
            <h3>代購需求已建立</h3>
            <p>訂單編號 #{quoteOrderNumber || 'LOCAL-QUOTE'}，下單前 JaFun 會重新確認官網價格、尺寸與庫存。</p>
          </div>
        )}
      </section>
      {quoteCheckoutOpen && quote && pricing && (
        <QuoteOrderModal
          quote={quote}
          pricing={pricing}
          quantity={quantity}
          selectedOptions={selectedOptions}
          cartItem={quoteCartItem()}
          authSession={authSession}
          initialStep={quoteCheckoutStartStep}
          lineAuthNotice={lineAuthNotice}
          onNav={onNav}
          onClose={() => setQuoteCheckoutOpen(false)}
          onAddToCart={() => { if (!quoteAdded) addQuoteToCart(); setQuoteCheckoutOpen(false); }}
          onDone={(orderNumber) => {
            setQuoteOrderNumber(orderNumber || '');
            setQuoteOrderDone(true);
            setQuoteCheckoutOpen(false);
            setStage(3);
          }}
        />
      )}
    </div>
  );
}

function QuoteOrderModal({ quote, pricing, quantity, selectedOptions, cartItem, authSession, initialStep = '', lineAuthNotice = null, onNav, onClose, onAddToCart, onDone }) {
  const [step, setStep] = useStateP(initialStep || (authSession?.token ? 'recipient' : 'entry'));
  const [email, setEmail] = useStateP(authSession?.user?.email || '');
  const [recipientName, setRecipientName] = useStateP('');
  const [phone, setPhone] = useStateP('');
  const [city, setCity] = useStateP('台北市');
  const [district, setDistrict] = useStateP('');
  const [postalCode, setPostalCode] = useStateP('');
  const [addressLine, setAddressLine] = useStateP('');
  const [note, setNote] = useStateP('');
  const [loading, setLoading] = useStateP(false);
  const [error, setError] = useStateP('');
  const [fieldErrors, setFieldErrors] = useStateP({});
  const [lineLoading, setLineLoading] = useStateP(false);
  const [authNotice, setAuthNotice] = useStateP(lineAuthNotice);
  const [savedAddresses, setSavedAddresses] = useStateP([]);
  const [selectedAddressId, setSelectedAddressId] = useStateP('');
  const [addressAutoFilled, setAddressAutoFilled] = useStateP(false);
  const summary = optionSummary(quote.product, selectedOptions);

  useEffectP(() => {
    if (lineAuthNotice) setAuthNotice(lineAuthNotice);
  }, [lineAuthNotice?.status, lineAuthNotice?.message]);

  const applySavedAddress = (address) => {
    if (!address) return;
    setSelectedAddressId(address.id || '');
    setRecipientName(address.recipientName || '');
    setPhone(address.phone || '');
    setCity(address.city || '台北市');
    setDistrict(address.district || '');
    setPostalCode(address.postalCode || '');
    setAddressLine(address.addressLine || '');
    setNote(address.note || '');
  };

  useEffectP(() => {
    if (!authSession?.token) {
      setSavedAddresses([]);
      setSelectedAddressId('');
      setAddressAutoFilled(false);
      return undefined;
    }
    let disposed = false;
    window.authRequest('/auth/account', { token: authSession.token })
      .then(payload => {
        if (disposed) return;
        const addresses = payload.addresses || [];
        setSavedAddresses(addresses);
        const preferred = addresses.find(address => address.isDefault) || addresses[0];
        if (preferred && !addressAutoFilled && !recipientName && !addressLine) {
          applySavedAddress(preferred);
          setAddressAutoFilled(true);
        }
      })
      .catch(() => {
        if (!disposed) setSavedAddresses([]);
      });
    return () => {
      disposed = true;
    };
  }, [authSession?.token]);

  const startLineLogin = async () => {
    setLineLoading(true);
    setError('');
    setAuthNotice(null);
    try {
      const returnUrl = new URL(window.location.origin + '/line');
      returnUrl.searchParams.set('url', quote.product.sourceUrl);
      returnUrl.searchParams.set('qty', String(Math.max(1, quantity)));
      returnUrl.searchParams.set('checkout', 'quote');
      returnUrl.searchParams.set('authAction', 'quote-checkout');
      Object.entries(selectedOptions || {}).forEach(([key, value]) => {
        if (value) returnUrl.searchParams.set(`opt_${key}`, value);
      });
      const config = await window.authRequest(`/auth/line/login-url?returnUrl=${encodeURIComponent(returnUrl.toString())}`);
      if (config.configured && config.url) {
        window.location.href = config.url;
        return;
      }
      onClose();
      onNav('login');
    } catch (err) {
      setError(err.message || 'LINE 登入目前無法啟動，請改用訪客下單。');
    } finally {
      setLineLoading(false);
    }
  };

  const submit = async (event) => {
    event.preventDefault();
    setError('');
    const errors = window.validateTaiwanRecipient({ email, recipientName, phone, district, addressLine });
    setFieldErrors(errors);
    if (Object.keys(errors).length) {
      setError('請依紅字提示確認收件資料後再送出。');
      return;
    }
    setLoading(true);
    try {
      const body = {
        contactEmail: email,
        recipient: {
          name: recipientName,
          phone,
          email,
          country: 'TW',
          city,
          district,
          postalCode,
          addressLine,
          note,
        },
        items: [{
          cartId: cartItem?.cartId || quote.product.productId,
          productId: quote.product.productId,
          sku: quote.product.sku || '',
          sourceUrl: quote.product.sourceUrl,
          sourceType: quote.product.source,
          feeIncluded: true,
          shippingIncluded: true,
          selectedOptions,
          name: quote.product.title,
          japaneseName: quote.product.subtitle || '',
          image: quote.product.image || '',
          sourceLabel: quote.product.sourceLabel || '',
          optionSummary: summary,
          quantity,
          unitPriceTWD: Math.max(1, Math.round(pricing.itemSubtotalTWD / Math.max(1, quantity))),
        }],
        totals: {
          subtotalTWD: pricing.itemSubtotalTWD,
          serviceFeeTWD: pricing.serviceFeeTWD,
          shippingTWD: pricing.internationalShippingEstimateTWD,
          totalTWD: pricing.totalTWD,
        },
      };
      const payload = await window.authRequest('/payments/stripe/checkout-sessions', {
        method: 'POST',
        token: authSession?.token,
        body: { order: body },
      });
      if (!payload.url) {
        throw new Error('付款頁面建立失敗，請稍後再試。');
      }
      // Guest checkout: remember this order so it can be auto-bound to the member
      // account if the visitor registers/logs in afterwards.
      if (!authSession?.token) {
        window.writePendingGuestOrder?.(payload.order);
      }
      window.location.href = payload.url;
    } catch (err) {
      setError(err.message || '下單失敗，請稍後再試。');
    } finally {
      setLoading(false);
    }
  };

  return (
    <div style={{ position: 'fixed', inset: 0, zIndex: 180, background: 'rgba(20,20,20,.55)', display: 'flex', alignItems: 'center', justifyContent: 'center', padding: 20, overflowY: 'auto', backdropFilter: 'blur(4px)' }} onClick={onClose}>
      <div onClick={e => e.stopPropagation()} style={{ width: 920, height: 'calc(100vh - 40px)', maxHeight: 'calc(100vh - 40px)', overflow: 'hidden', background: '#fff', boxShadow: '0 40px 80px -20px rgba(0,0,0,.4)', display: 'grid', gridTemplateColumns: '.9fr 1.1fr', minHeight: 0, position: 'relative' }}>
        <button
          type="button"
          aria-label="關閉結帳視窗"
          onClick={onClose}
          style={{ position: 'absolute', top: 14, right: 16, zIndex: 5, width: 36, height: 36, display: 'grid', placeItems: 'center', background: '#fff', border: '1px solid var(--jf-line)', borderRadius: '50%', cursor: 'pointer', fontSize: 16, color: 'var(--jf-ink)', lineHeight: 1 }}
        >
          ✕
        </button>
        <div style={{ background: 'var(--jf-paper-2)', padding: 34, display: 'flex', flexDirection: 'column', minHeight: 0, overflowY: 'auto', WebkitOverflowScrolling: 'touch' }}>
          <button onClick={onClose} style={{ alignSelf: 'flex-start', fontFamily: 'var(--sans)', fontSize: 11, letterSpacing: 2, color: 'var(--jf-mute)', marginBottom: 22 }}>← 返回報價</button>
          <img src={quote.product.image} alt="" style={{ width: 118, height: 118, objectFit: 'cover', borderRadius: 8, background: '#fff', marginBottom: 18 }} />
          <div style={{ fontFamily: 'var(--sans)', fontSize: 11, letterSpacing: 2, color: 'var(--jf-red)', marginBottom: 8 }}>{quote.product.sourceLabel} · {quote.product.brand}</div>
          <h3 style={{ fontFamily: 'var(--serif)', fontSize: 20, lineHeight: 1.5, margin: '0 0 8px', fontWeight: 500 }}>{quote.product.title}</h3>
          <p style={{ fontFamily: 'var(--serif)', fontSize: 13, color: 'var(--jf-mute-2)', lineHeight: 1.7, margin: '0 0 18px' }}>{summary || quote.product.subtitle}</p>
          <div style={{ marginTop: 'auto', borderTop: '1px solid var(--jf-line)', paddingTop: 18, display: 'grid', gap: 10, fontFamily: 'var(--serif)', fontSize: 13 }}>
            <div style={{ display: 'flex', justifyContent: 'space-between' }}><span style={{ color: 'var(--jf-mute)' }}>商品價格</span><span>NT$ {pricing.itemSubtotalTWD.toLocaleString()}</span></div>
            <div style={{ display: 'flex', justifyContent: 'space-between' }}><span style={{ color: 'var(--jf-mute)' }}>服務費</span><span>NT$ {pricing.serviceFeeTWD.toLocaleString()}</span></div>
            <div style={{ display: 'grid', gap: 2 }}>
              <div style={{ display: 'flex', justifyContent: 'space-between' }}><span style={{ color: 'var(--jf-mute)' }}>國際運費</span><span>NT$ {pricing.internationalShippingEstimateTWD.toLocaleString()}</span></div>
              <small style={{ color: 'var(--jf-mute)', fontFamily: 'var(--sans)', fontSize: 11 }}>{quoteShippingDetailLabel(pricing)}</small>
            </div>
            <div style={{ display: 'flex', justifyContent: 'space-between', fontSize: 18, color: 'var(--jf-red)', fontWeight: 600 }}><span>預估總額</span><span>NT$ {pricing.totalTWD.toLocaleString()}</span></div>
          </div>
        </div>

        <div style={{ padding: 38, overflowY: 'auto', minHeight: 0, WebkitOverflowScrolling: 'touch' }}>
          {step === 'entry' ? (
            <div className="line-checkout-entry">
              <div className="section-eyebrow">CHECKOUT</div>
              <h3>加入購物車成功</h3>
              {authNotice && (
                <div className={`line-checkout-auth-notice ${authNotice.status === 'success' ? 'is-success' : 'is-error'}`}>
                  <strong>{authNotice.status === 'success' ? 'LINE 登入成功' : 'LINE 登入未完成'}</strong>
                  <span>
                    {authNotice.message || (authNotice.status === 'success'
                      ? '已回到剛剛的下單畫面，可以繼續填寫收件資料。'
                      : '目前無法完成 LINE 登入，請重新操作或改用訪客結帳。')}
                  </span>
                </div>
              )}
              <div className="line-checkout-choice is-line">
                <div>
                  <strong>{authSession?.token ? '已登入 LINE 會員' : '用 LINE 註冊 / 登入'}</strong>
                  <span>{authSession?.token ? '可用會員身分繼續結帳，保留訂單紀錄與到貨通知。' : '保留訂單紀錄、到貨通知與後續優惠。'}</span>
                </div>
                <button className="btn" disabled={lineLoading} onClick={authSession?.token ? () => setStep('recipient') : startLineLogin}>
                  {lineLoading ? '連線中...' : (authSession?.token ? '繼續填寫收件資料' : '使用 LINE 繼續')}
                </button>
              </div>
              <div className="line-checkout-choice">
                <div>
                  <strong>不註冊，直接結帳</strong>
                  <span>填寫 Email 與台灣收件資料後前往付款。</span>
                </div>
                <button className="btn btn-primary" onClick={() => setStep('recipient')}>訪客直接結帳</button>
              </div>
              <button className="line-checkout-secondary" onClick={() => { onAddToCart(); onClose(); onNav('cart'); }}>先查看購物車</button>
              {error && <div style={{ marginTop: 14, padding: 12, background: 'var(--jf-paper-2)', color: 'var(--jf-red)', fontFamily: 'var(--serif)', fontSize: 13 }}>{error}</div>}
            </div>
          ) : (
            <form onSubmit={submit} noValidate>
              <div style={{ fontFamily: 'var(--sans)', fontSize: 11, letterSpacing: 4, color: 'var(--jf-red)', marginBottom: 8 }}>TAIWAN DELIVERY</div>
              <h3 style={{ fontFamily: 'var(--serif)', fontSize: 24, margin: '0 0 18px', letterSpacing: 2, fontWeight: 500 }}>台灣收件資料</h3>
              <Field label="Email">
                <input type="email" value={email} onChange={e => { setEmail(e.target.value); setFieldErrors(prev => ({ ...prev, email: '' })); }} placeholder="you@email.com" required style={inputStyle} />
                <FieldErrorText message={fieldErrors.email} />
              </Field>
              {authSession?.token && savedAddresses.length > 0 && (
                <Field label="常用收件地址">
                  <select
                    value={selectedAddressId}
                    onChange={e => {
                      const address = savedAddresses.find(item => item.id === e.target.value);
                      if (address) {
                        applySavedAddress(address);
                        setAddressAutoFilled(true);
                      } else {
                        setSelectedAddressId('');
                      }
                    }}
                    style={inputStyle}
                  >
                    <option value="">不套用常用地址</option>
                    {savedAddresses.map(address => (
                      <option key={address.id} value={address.id}>
                        {address.isDefault ? '預設｜' : ''}{address.recipientName}・{address.city}{address.district || ''}{address.addressLine}
                      </option>
                    ))}
                  </select>
                </Field>
              )}
              <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 14 }}>
                <Field label="收件人姓名">
                  <input value={recipientName} onChange={e => { setRecipientName(e.target.value); setFieldErrors(prev => ({ ...prev, recipientName: '' })); }} placeholder="王小明" required style={inputStyle} />
                  <FieldErrorText message={fieldErrors.recipientName} />
                </Field>
                <Field label="手機">
                  <input value={phone} onChange={e => { setPhone(e.target.value); setFieldErrors(prev => ({ ...prev, phone: '' })); }} placeholder="09xx xxx xxx" required style={inputStyle} />
                  <FieldErrorText message={fieldErrors.phone} />
                </Field>
              </div>
              <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr 110px', gap: 14 }}>
                <Field label="縣市">
                  <select value={city} onChange={e => setCity(e.target.value)} style={inputStyle}>{TAIWAN_CITIES_FOR_CHECKOUT.map(item => <option key={item} value={item}>{item}</option>)}</select>
                </Field>
                <Field label="區域">
                  <input value={district} onChange={e => { setDistrict(e.target.value); setFieldErrors(prev => ({ ...prev, district: '' })); }} placeholder="大安區" required style={inputStyle} />
                  <FieldErrorText message={fieldErrors.district} />
                </Field>
                <Field label="郵遞區號"><input value={postalCode} onChange={e => setPostalCode(e.target.value.replace(/\D/g, '').slice(0, 5))} placeholder="106" style={inputStyle} /></Field>
              </div>
              <Field label="詳細地址">
                <input value={addressLine} onChange={e => { setAddressLine(e.target.value); setFieldErrors(prev => ({ ...prev, addressLine: '' })); }} placeholder="請填寫路名、巷弄、門牌、樓層" required style={inputStyle} />
                <FieldErrorText message={fieldErrors.addressLine} />
              </Field>
              <Field label="備註（選填）"><textarea value={note} onChange={e => setNote(e.target.value)} placeholder="例如：下單前請先 LINE 確認尺寸" style={{ ...inputStyle, minHeight: 74, resize: 'vertical' }} /></Field>
              {error && <div style={{ marginBottom: 14, padding: 12, background: 'var(--jf-paper-2)', color: 'var(--jf-red)', fontFamily: 'var(--serif)', fontSize: 13 }}>{error}</div>}
              <button className="btn btn-primary" disabled={loading} style={{ width: '100%', justifyContent: 'center', padding: '15px 0', opacity: loading ? .7 : 1 }}>{loading ? '建立付款頁面中...' : '前往付款'}</button>
              <button type="button" onClick={() => setStep('entry')} style={{ width: '100%', marginTop: 12, color: 'var(--jf-mute)', fontFamily: 'var(--sans)', fontSize: 12, letterSpacing: 2 }}>返回選擇方式</button>
            </form>
          )}
        </div>
      </div>
    </div>
  );
}

Object.assign(window, {
  LinePage: QuoteLinePage,
  ShopPage,
  ProductDetailPage,
  ShipPage,
  LearnPage,
  ArticlePage,
  AboutPage,
  PrivacyPage,
  TermsPage,
  ContactPage,
  FaqPage,
});
