// feed.jsx — hero feed wall, growth chart, icon set, reveal hook.

// ── icon set (simple geometric line icons) ──────────────────────
const Icon = ({ name }) => {
  const p = { fill: 'none', stroke: 'currentColor', strokeWidth: 1.8, strokeLinecap: 'round', strokeLinejoin: 'round' };
  const paths = {
    grid: <><rect x="3" y="3" width="7" height="7" rx="1.6" {...p} /><rect x="14" y="3" width="7" height="7" rx="1.6" {...p} /><rect x="3" y="14" width="7" height="7" rx="1.6" {...p} /><rect x="14" y="14" width="7" height="7" rx="1.6" {...p} /></>,
    play: <><rect x="3" y="4" width="18" height="16" rx="3.4" {...p} /><path d="M10 9l5 3-5 3z" fill="currentColor" stroke="none" /></>,
    cam: <><rect x="3" y="6.5" width="18" height="13" rx="3" {...p} /><path d="M8.5 6.5l1.4-2.2h4.2l1.4 2.2" {...p} /><circle cx="12" cy="13" r="3.4" {...p} /></>,
    target: <><circle cx="12" cy="12" r="8.2" {...p} /><circle cx="12" cy="12" r="3.4" {...p} /><path d="M12 1.6v3M12 19.4v3M1.6 12h3M19.4 12h3" {...p} /></>,
    spark: <><path d="M12 3l1.9 5.6L19.5 10l-5.6 1.9L12 17.5l-1.9-5.6L4.5 10l5.6-1.4z" {...p} /><path d="M18.5 15.5l.7 2 .7-2-.7-.6z" fill="currentColor" stroke="none" /></>,
    chat: <><path d="M4 5.5h16a1.5 1.5 0 011.5 1.5v8a1.5 1.5 0 01-1.5 1.5H9l-4 3.5V17H4a1.5 1.5 0 01-1.5-1.5v-9A1.5 1.5 0 014 5.5z" {...p} /></>,
    ig: <><rect x="3.5" y="3.5" width="17" height="17" rx="5" {...p} /><circle cx="12" cy="12" r="4.2" {...p} /><circle cx="17.2" cy="6.8" r="1.1" fill="currentColor" stroke="none" /></>,
    line: <><rect x="3" y="3.5" width="18" height="14" rx="6" {...p} /><path d="M9 19.5l2.5-2.2" {...p} /></>,
    mail: <><rect x="3" y="5.5" width="18" height="13" rx="2.6" {...p} /><path d="M4 7l8 6 8-6" {...p} /></>,
  };
  return <svg viewBox="0 0 24 24" aria-hidden="true">{paths[name] || null}</svg>;
};

// ── reveal on scroll ────────────────────────────────────────────
// Base state is ALWAYS visible. We only pre-hide elements that are BELOW the
// fold on load, reveal them as they scroll in, and guarantee a full reveal via
// a timeout fallback — so captures, exports and observer-less contexts never
// see blank sections.
function useReveal() {
  React.useEffect(() => {
    if (window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches) return;
    const els = Array.from(document.querySelectorAll('.rv'));
    const vh = window.innerHeight || 800;
    const hidden = els.filter((el) => el.getBoundingClientRect().top > vh * 0.85);
    hidden.forEach((el) => el.classList.add('pre'));
    let io;
    if ('IntersectionObserver' in window) {
      io = new IntersectionObserver((entries) => {
        entries.forEach((e) => {
          if (e.isIntersecting) {
            const el = e.target;
            const d = Number(el.dataset.delay || 0);
            setTimeout(() => el.classList.remove('pre'), d);
            io.unobserve(el);
          }
        });
      }, { threshold: 0.12, rootMargin: '0px 0px -8% 0px' });
      hidden.forEach((el) => io.observe(el));
    } else {
      hidden.forEach((el) => el.classList.remove('pre'));
    }
    // safety net: never leave anything hidden
    const fallback = setTimeout(() => els.forEach((el) => el.classList.remove('pre')), 2200);
    return () => { if (io) io.disconnect(); clearTimeout(fallback); };
  }, []);
}

// ── hero feed wall ──────────────────────────────────────────────
const FTile = ({ id, kind, badge, card, src }) => {
  if (card) {
    return (
      <div className={`ftile ${kind} ${card.cls}`}>
        <div className="cardtext">
          <div className="big">{card.big}</div>
          <div className="lbl">{card.lbl}</div>
        </div>
      </div>
    );
  }
  return (
    <div className={`ftile ${kind}`}>
      <image-slot id={id} src={src} radius="16" placeholder={badge ? badge.ph : 'Drop a photo'}></image-slot>
      {badge && <div className="badge">{badge.ic} <b>{badge.n}</b></div>}
    </div>
  );
};

const HEART = '\u2665';
// Sample feed imagery — local placeholders under miseba/img/ (downloaded from
// Unsplash). Swap these for the client's real photography before a client launch.
const UN = (name) => 'miseba/img/' + name + '.jpg';
const colA = [
  { id: 'mfeed-a1', kind: 't1', src: UN('food1'), badge: { ph: '料理', ic: HEART, n: '2.4k' } },
  { id: 'mfeed-a2', kind: 't2', card: { cls: 'card-pop', big: 'REEL', lbl: '1.2M views' } },
  { id: 'mfeed-a3', kind: 't3', src: UN('interior'), badge: { ph: '店内', ic: HEART, n: '980' } },
  { id: 'mfeed-a4', kind: 't2', src: UN('menu'), badge: { ph: 'メニュー', ic: HEART, n: '3.1k' } },
];
const colB = [
  { id: 'mfeed-b1', kind: 't2', card: { cls: 'card-ink', big: '映え', lbl: 'before / after' } },
  { id: 'mfeed-b2', kind: 't3', src: UN('drink'), badge: { ph: 'ドリンク', ic: HEART, n: '5.6k' } },
  { id: 'mfeed-b3', kind: 't1', src: UN('staff'), badge: { ph: 'スタッフ', ic: HEART, n: '1.8k' } },
  { id: 'mfeed-b4', kind: 't2', card: { cls: 'card-sun', big: 'SAVE', lbl: '×5.2 保存' } },
];
const colC = [
  { id: 'mfeed-c1', kind: 't3', src: UN('exterior'), badge: { ph: '外観', ic: HEART, n: '742' } },
  { id: 'mfeed-c2', kind: 't2', src: UN('dessert'), badge: { ph: 'デザート', ic: HEART, n: '4.2k' } },
  { id: 'mfeed-c3', kind: 't1', card: { cls: 'card-pop', big: '+320%', lbl: 'followers' } },
  { id: 'mfeed-c4', kind: 't2', src: UN('table'), badge: { ph: 'テーブル', ic: HEART, n: '2.0k' } },
];

const FeedCol = ({ items, cls }) => (
  <div className={`fcol ${cls}`}>
    {items.map((it) => <FTile key={it.id} {...it} />)}
    {items.map((it) => <FTile key={it.id + '-dup'} {...it} id={it.id + '-dup'} />)}
  </div>
);

function FeedWall() {
  return (
    <div className="feedwall rv" data-delay="120">
      <div className="feedwall-cols">
        <FeedCol items={colA} cls="fcol-a" />
        <FeedCol items={colB} cls="fcol-b" />
        <FeedCol items={colC} cls="fcol-c" />
      </div>
    </div>
  );
}

// ── growth chart (animated path draw on reveal) ─────────────────
function GrowthChart() {
  const ref = React.useRef(null);
  const W = 460, H = 170, pad = 14;
  // rising follower curve (slightly organic)
  const pts = [8, 12, 11, 18, 26, 24, 34, 46, 58, 72, 88, 100];
  const stepX = (W - pad * 2) / (pts.length - 1);
  const y = (v) => H - pad - (v / 100) * (H - pad * 2 - 10);
  const coords = pts.map((v, i) => [pad + i * stepX, y(v)]);
  const linePath = coords.map((c, i) => (i ? 'L' : 'M') + c[0].toFixed(1) + ' ' + c[1].toFixed(1)).join(' ');
  const areaPath = `${linePath} L ${coords[coords.length - 1][0].toFixed(1)} ${H - pad} L ${pad} ${H - pad} Z`;
  const last = coords[coords.length - 1];

  React.useEffect(() => {
    const path = ref.current;
    if (!path) return;
    if (window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches) return;
    const len = path.getTotalLength();
    path.style.strokeDasharray = len;
    path.style.strokeDashoffset = len;
    const io = new IntersectionObserver((es) => {
      es.forEach((e) => {
        if (e.isIntersecting) {
          path.style.transition = 'stroke-dashoffset 1.6s cubic-bezier(.2,.7,.3,1)';
          path.style.strokeDashoffset = 0;
          io.disconnect();
        }
      });
    }, { threshold: 0.4 });
    io.observe(path);
    return () => io.disconnect();
  }, []);

  return (
    <svg className="chart" viewBox={`0 0 ${W} ${H}`} preserveAspectRatio="none">
      <defs>
        <linearGradient id="gpop" x1="0" y1="0" x2="0" y2="1">
          <stop offset="0%" stopColor="var(--pop)" stopOpacity="0.34" />
          <stop offset="100%" stopColor="var(--pop)" stopOpacity="0" />
        </linearGradient>
      </defs>
      {[0.25, 0.5, 0.75].map((f) => <line key={f} className="axis" x1={pad} x2={W - pad} y1={pad + f * (H - pad * 2)} y2={pad + f * (H - pad * 2)} />)}
      <path className="area" d={areaPath} />
      <path ref={ref} className="line" d={linePath} />
      <circle className="dot" cx={last[0]} cy={last[1]} r="5" />
    </svg>
  );
}

Object.assign(window, { Icon, useReveal, useEyes, FeedWall, GrowthChart });

// ── eye tracking ────────────────────────────────────────────────
// One pointer handler drives every .eye on the page (logo dot + hero googly
// eyes). Pupils point toward the cursor; on touch they tilt toward each tap.
// Pure DOM writes inside rAF — no React re-render per move.
function useEyes() {
  React.useEffect(() => {
    let raf = null;
    let px = window.innerWidth / 2;
    let py = window.innerHeight * 0.33;
    const apply = () => {
      raf = null;
      document.querySelectorAll('.eye').forEach((eye) => {
        const pupil = eye.querySelector('.pupil');
        if (!pupil) return;
        const r = eye.getBoundingClientRect();
        if (!r.width) return;
        const cx = r.left + r.width / 2;
        const cy = r.top + r.height / 2;
        const dx = px - cx, dy = py - cy;
        const d = Math.hypot(dx, dy) || 1;
        const max = r.width * 0.18;
        const m = Math.min(max, d);
        pupil.style.transform = `translate(${((dx / d) * m).toFixed(1)}px, ${((dy / d) * m).toFixed(1)}px)`;
      });
    };
    const schedule = () => { if (!raf) raf = requestAnimationFrame(apply); };
    const onPointer = (e) => { px = e.clientX; py = e.clientY; schedule(); };
    window.addEventListener('pointermove', onPointer, { passive: true });
    window.addEventListener('pointerdown', onPointer, { passive: true });
    window.addEventListener('scroll', schedule, { passive: true });
    window.addEventListener('resize', schedule, { passive: true });
    apply();
    return () => {
      window.removeEventListener('pointermove', onPointer);
      window.removeEventListener('pointerdown', onPointer);
      window.removeEventListener('scroll', schedule);
      window.removeEventListener('resize', schedule);
      if (raf) cancelAnimationFrame(raf);
    };
  }, []);
}

Object.assign(window, { useEyes });
