/* Products page — two levels driven by the ?p=<slug> query parameter:
   • no slug  → calm overview catalogue (grouped cards, no iframes).
   • ?p=slug  → product detail (hero + live interactive demo rows + copy).
   Plain <a> links do full navigations, so the browser handles history, scroll
   and shareable/indexable URLs natively (no client-side router). A product may
   ship 1..N interactive HTML presentations; each becomes its own demo row. */

/* Fixed design viewport for the scaled live preview — presentations are built
   full-screen, so we render at this width and CSS-scale down to fit the column. */
const PROD_BASE_W = 1440;
const PROD_BASE_H = 900;

function stabilizePresentationFrameFocusZoom(frame) {
  try {
    const doc = frame && frame.contentDocument;
    if (!doc || !doc.head) return;
    const meta = doc.querySelector('meta[name="viewport"]');
    if (meta && !/interactive-widget=/.test(meta.getAttribute('content') || '')) {
      const content = meta.getAttribute('content') || 'width=device-width,initial-scale=1';
      meta.setAttribute('content', `${content},interactive-widget=overlays-content`);
    }
    if (doc.getElementById('ava-mobile-focus-stability')) return;
    const style = doc.createElement('style');
    style.id = 'ava-mobile-focus-stability';
    style.textContent = '@media (max-width:900px), (pointer:coarse){input:not([type="range"]),textarea,select{font-size:16px!important}}';
    doc.head.appendChild(style);
  } catch (error) {
    // Presentations are same-origin today; if that changes, the parent page still works.
  }
}

/* Merge structural product data (window.PRODUCTS) with localized copy (i18n). */
function useProductCopy(product) {
  const { L, lang } = useLang();
  const all = (L && L.products) || {};
  const copy = (product && all[product.slug]) || {};
  return {
    lang: lang || 'en',
    ui: all._ui || {},
    tagline: copy.tagline || (product && product.tagline) || '',
    heroTagline: copy.heroTagline || null,
    bullets: copy.bullets || (product && product.bullets) || [],
    summary: copy.summary || copy.tagline || (product && product.tagline) || '',
    sections: copy.sections || [],
    demos: copy.demos || [],
  };
}

/* The "HTML side" of a row — tabs + scaled live wide-format preview.
   `eager` mounts the iframe on first paint (used for the lead demo) so the
   product opens with a live preview already visible instead of a shimmer. */
function ProductMedia({ presentations, selected, onSelect, onExpand, productName, eager, badge }) {
  const frameRef = React.useRef(null);
  const scaleRef = React.useRef(null);
  const [loaded, setLoaded] = React.useState(!!eager);
  const [previewReady, setPreviewReady] = React.useState(false);
  const [frameLoaded, setFrameLoaded] = React.useState(false);
  const active = presentations[selected] || presentations[0];
  const openActive = React.useCallback(() => {
    if (active) onExpand(active);
  }, [active, onExpand]);
  const onPreviewKeyDown = React.useCallback((e) => {
    if (e.key !== 'Enter' && e.key !== ' ') return;
    e.preventDefault();
    openActive();
  }, [openActive]);

  // Lazy: only mount the iframe once the frame is near the viewport
  // (skipped when `eager`, since it is already loaded).
  React.useEffect(() => {
    if (loaded) return;
    const el = frameRef.current;
    if (!el) return;
    const io = new IntersectionObserver((entries) => {
      entries.forEach((e) => { if (e.isIntersecting) { setLoaded(true); io.disconnect(); } });
    }, { rootMargin: '600px' });
    io.observe(el);
    return () => io.disconnect();
  }, [loaded]);

  // Scale the full-size preview down to the column width on desktop and mobile.
  // This keeps each demo in a 1440×900 wide viewport instead of letting it reflow
  // into a phone layout inside the iframe.
  React.useLayoutEffect(() => {
    if (!loaded) return;
    const outer = frameRef.current;
    const scaler = scaleRef.current;
    if (!outer || !scaler) return;
    const apply = () => {
      const w = outer.clientWidth;
      const scale = w / PROD_BASE_W;
      scaler.style.transform = `scale(${scale})`;
      outer.style.height = `${PROD_BASE_H * scale}px`;
      setPreviewReady(true);
    };
    apply();
    const ro = new ResizeObserver(apply);
    ro.observe(outer);
    return () => ro.disconnect();
  }, [loaded]);

  return (
    <div className="prod-media">
      {presentations.length > 1 && (
        <div className="prod-tabs" role="tablist" aria-label={`${productName} demos`}>
          {presentations.map((p, i) => (
            <button
              key={p.url}
              type="button"
              role="tab"
              aria-selected={i === selected}
              className={`prod-tab mono ${i === selected ? 'is-active' : ''}`}
              onClick={() => onSelect(i)}
            >
              {p.label}
            </button>
          ))}
        </div>
      )}

      <div
        className="prod-frame prod-frame--live"
        ref={frameRef}
        role="button"
        tabIndex={0}
        aria-label={`Open ${productName} ${active.label || 'demo'} fullscreen`}
        onClick={openActive}
        onKeyDown={onPreviewKeyDown}
      >
        {loaded && (
          <div
            className="prod-frame-scale"
            ref={scaleRef}
            style={{ width:PROD_BASE_W, height:PROD_BASE_H, opacity:previewReady ? 1 : 0 }}
          >
            <iframe
              key={active.url}
              src={active.url}
              title={`${productName} — ${active.label}`}
              loading="lazy"
              tabIndex="-1"
              onLoad={(event) => {
                stabilizePresentationFrameFocusZoom(event.currentTarget);
                setFrameLoaded(true);
              }}
              style={{ width:PROD_BASE_W, height:PROD_BASE_H, border:'none' }}
            />
          </div>
        )}
        {/* Shimmer stays visible until the iframe actually paints (design). */}
        {!(loaded && frameLoaded && previewReady) && (
          <div className="prod-frame-shimmer" aria-hidden="true" />
        )}
        {badge && (
          <div className="prod-frame-badgewrap" aria-hidden="true">
            <span className="prod-frame-badge mono">{badge} ⤢</span>
          </div>
        )}
      </div>
    </div>
  );
}

function PresentationModal({ presentation, productName, onClose }) {
  const viewportRef = React.useRef(null);
  const scaleRef = React.useRef(null);

  React.useEffect(() => {
    const onKey = (e) => { if (e.key === 'Escape') onClose(); };
    window.addEventListener('keydown', onKey);
    document.body.style.overflow = 'hidden';
    return () => { window.removeEventListener('keydown', onKey); document.body.style.overflow = ''; };
  }, [onClose]);

  React.useEffect(() => {
    const viewport = viewportRef.current;
    const scaler = scaleRef.current;
    if (!viewport || !scaler) return;
    const apply = () => {
      const scale = Math.min(viewport.clientWidth / PROD_BASE_W, viewport.clientHeight / PROD_BASE_H);
      scaler.style.transform = `scale(${scale})`;
    };
    apply();
    const ro = new ResizeObserver(apply);
    ro.observe(viewport);
    window.addEventListener('resize', apply);
    return () => { ro.disconnect(); window.removeEventListener('resize', apply); };
  }, []);

  return (
    <div className="prod-modal" role="dialog" aria-modal="true" aria-label={`${productName} demo`}>
      <button className="prod-modal__backdrop" type="button" aria-label="Close demo" onClick={onClose} />
      <div className="prod-modal__panel">
        <div className="prod-modal__bar">
          <span className="mono">{productName}{presentation.label ? ` · ${presentation.label}` : ''}</span>
          <button className="prod-modal__close" type="button" aria-label="Close" onClick={onClose}>×</button>
        </div>
        <div
          className="prod-modal__viewport"
          ref={viewportRef}
          style={{ flex:1, minHeight:0, overflow:'hidden', background:'#080808', display:'flex', alignItems:'center', justifyContent:'center' }}
        >
          <div
            className="prod-modal__scale"
            ref={scaleRef}
            style={{ width:PROD_BASE_W, height:PROD_BASE_H, flex:'0 0 auto', transformOrigin:'center center' }}
          >
            <iframe
              src={presentation.url}
              title={`${productName} — ${presentation.label} (wide screen)`}
              className="prod-modal__frame"
              onLoad={(event) => stabilizePresentationFrameFocusZoom(event.currentTarget)}
              style={{ display:'block', width:PROD_BASE_W, height:PROD_BASE_H, border:'none', background:'#fff' }}
            />
          </div>
        </div>
      </div>
    </div>
  );
}

/* A demo shipped as a rendered MP4 (a capture of the live animated demo). Real
   <video> gives a native, draggable timeline — so the control bar below is a
   thin on-brand skin over the actual video: play/pause, a seek bar you drag
   left/right, elapsed/total time, mute and fullscreen. Autoplays muted + loops
   (browsers only allow autoplay when muted). Opted in via `presentation.video`. */
function fmtTime(t) {
  if (!isFinite(t) || t < 0) t = 0;
  const m = Math.floor(t / 60);
  const s = Math.floor(t % 60);
  return `${m}:${String(s).padStart(2, '0')}`;
}

function VideoModal({ presentation, productName, onClose }) {
  const panelRef = React.useRef(null);
  const videoRef = React.useRef(null);
  const [playing, setPlaying] = React.useState(false);
  const [muted, setMuted] = React.useState(true);
  const [cur, setCur] = React.useState(0);
  const [dur, setDur] = React.useState(0);
  const [fs, setFs] = React.useState(false);

  const togglePlay = React.useCallback(() => {
    const v = videoRef.current;
    if (!v) return;
    if (v.paused) v.play(); else v.pause();
  }, []);

  React.useEffect(() => {
    const onKey = (e) => {
      if (e.key === 'Escape') { onClose(); return; }
      if (e.key === ' ' || e.key === 'k') { e.preventDefault(); togglePlay(); }
    };
    window.addEventListener('keydown', onKey);
    document.body.style.overflow = 'hidden';
    return () => { window.removeEventListener('keydown', onKey); document.body.style.overflow = ''; };
  }, [onClose, togglePlay]);

  React.useEffect(() => {
    const onFs = () => setFs(!!document.fullscreenElement);
    document.addEventListener('fullscreenchange', onFs);
    return () => document.removeEventListener('fullscreenchange', onFs);
  }, []);

  // Kick off muted autoplay (browsers only allow autoplay when muted). React
  // doesn't reliably reflect the `muted` attribute onto the DOM property.
  React.useEffect(() => {
    const v = videoRef.current;
    if (!v) return;
    v.muted = true;
    const p = v.play();
    if (p && p.catch) p.catch(() => {});
  }, []);

  const onSeek = React.useCallback((e) => {
    const v = videoRef.current;
    if (!v) return;
    const t = Number(e.target.value);
    v.currentTime = t;
    setCur(t);
  }, []);

  const toggleMute = React.useCallback(() => {
    const v = videoRef.current;
    if (!v) return;
    v.muted = !v.muted;
    setMuted(v.muted);
  }, []);

  const toggleFs = React.useCallback(() => {
    const el = panelRef.current;
    if (!el) return;
    if (document.fullscreenElement) document.exitFullscreen();
    else if (el.requestFullscreen) el.requestFullscreen();
  }, []);

  const pct = dur > 0 ? (cur / dur) * 100 : 0;

  return (
    <div className="prod-modal" role="dialog" aria-modal="true" aria-label={`${productName} demo`}>
      <button className="prod-modal__backdrop" type="button" aria-label="Close demo" onClick={onClose} />
      <div className="prod-modal__panel prod-modal__panel--video" ref={panelRef}>
        <div className="prod-modal__bar">
          <span className="mono">{productName}{presentation.label ? ` · ${presentation.label}` : ''}</span>
          <button className="prod-modal__close" type="button" aria-label="Close" onClick={onClose}>×</button>
        </div>
        <div className="vplayer">
          <div className="vplayer__stage" onClick={togglePlay}>
            <video
              ref={videoRef}
              className="vplayer__video"
              src={presentation.video}
              poster={presentation.poster || undefined}
              playsInline
              autoPlay
              loop
              muted
              preload="auto"
              onLoadedMetadata={(e) => setDur(e.target.duration || 0)}
              onTimeUpdate={(e) => setCur(e.target.currentTime || 0)}
              onPlay={() => setPlaying(true)}
              onPause={() => setPlaying(false)}
            />
            {!playing && (
              <button
                type="button"
                className="vplayer__big"
                aria-label="Play"
                onClick={(e) => { e.stopPropagation(); togglePlay(); }}
              >
                <span className="vplayer__big-tri" aria-hidden="true" />
              </button>
            )}
          </div>
          <div className="vplayer__bar">
            <button type="button" className="vplayer__btn" aria-label={playing ? 'Pause' : 'Play'} onClick={togglePlay}>
              {playing ? '❚❚' : '▶'}
            </button>
            <span className="vplayer__time mono">{fmtTime(cur)}</span>
            <input
              type="range"
              className="vplayer__seek"
              min="0"
              max={dur || 0}
              step="0.05"
              value={cur}
              onChange={onSeek}
              aria-label="Seek"
              style={{ '--vplayer-fill': `${pct}%` }}
            />
            <span className="vplayer__time mono">{fmtTime(dur)}</span>
            <button type="button" className="vplayer__btn" aria-label={muted ? 'Unmute' : 'Mute'} onClick={toggleMute}>
              {muted ? '🔇' : '🔊'}
            </button>
            <button type="button" className="vplayer__btn" aria-label="Fullscreen" onClick={toggleFs}>
              {fs ? '⤡' : '⤢'}
            </button>
          </div>
        </div>
      </div>
    </div>
  );
}

/* ---------- Catalogue mini-previews ----------------------------------------
   Each product card carries a LIVE mini of its real app: a dark app window that
   auto-tours 2–3 of the product's actual screens (list → detail → analytics,
   etc.), each animating in, so the screen genuinely changes — not a screenshot.
   Pure DOM + CSS + a tiny React timer; no iframes/images. The tour only runs
   while the card is on screen (IntersectionObserver), speeds up on hover, and
   freezes under prefers-reduced-motion. Shared window chrome + scene primitives
   live in styles.css under ".pv-app"/".win-*"; per-app bits under ".pv--slug". */

// Drives the scene cycle: advances only while visible, faster while hovered,
// paused entirely under reduced motion. Returns the current scene + a ref/hooks.
function useTour(count, base, fast) {
  base = base || 2600; fast = fast || 1600;
  const [scene, setScene] = React.useState(0);
  const [hot, setHot] = React.useState(false);
  const [vis, setVis] = React.useState(false);
  const ref = React.useRef(null);
  React.useEffect(() => {
    const el = ref.current;
    if (!el || !window.IntersectionObserver) { setVis(true); return; }
    const io = new IntersectionObserver((es) => setVis(es[0].isIntersecting), { threshold: 0.2 });
    io.observe(el);
    return () => io.disconnect();
  }, []);
  React.useEffect(() => {
    if (!vis) return;
    // Note: the catalogue mini-previews cycle even under prefers-reduced-motion —
    // the owner wants them alive on iOS (where Reduce Motion is commonly on).
    // Larger/hero motion elsewhere still honours the setting.
    const id = setInterval(() => setScene((s) => (s + 1) % count), hot ? fast : base);
    return () => clearInterval(id);
  }, [vis, hot, count, base, fast]);
  return { scene, ref, setHot };
}

// The shared dark "app window": traffic-light dots, a per-screen title, an
// action button, and a stage that hosts the active scene (keyed so it re-enters).
function AppWin({ mod, titles, act, tour, children }) {
  return (
    <div className={`pv pv-app ${mod}`} ref={tour.ref} aria-hidden="true"
      onMouseEnter={() => tour.setHot(true)} onMouseLeave={() => tour.setHot(false)}>
      <div className="win-bar">
        <span className="win-dots"><i /><i /><i /></span>
        <span className="win-title" key={tour.scene}>{titles[tour.scene]}</span>
        <span className="win-act">{act || '＋'}</span>
      </div>
      <div className="win-stage">{children}</div>
    </div>
  );
}

// AVA CRM — reservations list → guest profile → analytics.
function CrmScene({ i }) {
  if (i === 0) {
    const rows = [
      { av: 'av-0', nm: 'Amir K.', s: '×4', t: '19:30', pc: 'pc-green', st: 'Seated', neu: true },
      { av: 'av-1', nm: 'Sofia N.', s: '×2', t: '20:00', pc: 'pc-blue', st: 'Booked' },
      { av: 'av-2', nm: 'Lena M.', s: '×6', t: '20:30', pc: 'pc-amber', st: 'VIP' },
    ];
    return (
      <div className="win-scene win-list">
        {rows.map((r, k) => (
          <div className={`win-row${r.neu ? ' is-new' : ''}`} key={k} style={{ animationDelay: `${k * 90}ms` }}>
            <span className={`win-av ${r.av}`} /><span className="win-nm">{r.nm}</span>
            <span className="win-sub">{r.s}</span><span className="win-sub">{r.t}</span>
            <span className={`win-pill ${r.pc}`}><i />{r.st}</span>
          </div>
        ))}
      </div>
    );
  }
  if (i === 1) {
    const stats = [['24', 'visits'], ['$1.2k', 'spend'], ['4.9', 'rating']];
    return (
      <div className="win-scene win-mid">
        <div className="win-head">
          <span className="win-av av-1 win-head-av" />
          <div className="win-head-id"><b>Sofia N.</b><span className="win-tier">◆ GOLD</span></div>
          <span className="win-pill pc-green win-pop"><i />Seated</span>
        </div>
        <div className="win-tiles">
          {stats.map(([v, l], k) => <div key={k} style={{ animationDelay: `${120 + k * 90}ms` }}><b>{v}</b><span>{l}</span></div>)}
        </div>
      </div>
    );
  }
  const bars = [0.5, 0.72, 0.56, 0.88, 0.64, 1];
  return (
    <div className="win-scene win-mid">
      <div className="win-kpi"><b>82%</b><span>occupancy · this week</span></div>
      <div className="win-chart">{bars.map((h, k) => <span key={k} className={k >= 4 ? 'hi' : ''} style={{ '--h': h, animationDelay: `${k * 70}ms` }} />)}</div>
    </div>
  );
}
function PreviewCrm() {
  const tour = useTour(3);
  return <AppWin mod="pv--crm" titles={['Reservations', 'Sofia N.', 'Analytics']} tour={tour}><CrmScene key={tour.scene} i={tour.scene} /></AppWin>;
}

// AVA POS — order/checkout → payment → shift report.
function PosScene({ i }) {
  if (i === 0) {
    const items = [['Ribeye Steak', '×1', '$24'], ['Caesar Salad', '×2', '$18'], ['Lemonade', '×3', '$9']];
    return (
      <div className="win-scene win-list">
        {items.map((it, k) => (
          <div className="win-row" key={k} style={{ animationDelay: `${k * 90}ms` }}>
            <span className="win-nm">{it[0]}</span><span className="win-sub">{it[1]}</span><span className="win-amt">{it[2]}</span>
          </div>
        ))}
        <div className="pos-total win-pop"><span>Total</span><b>$51.00</b></div>
      </div>
    );
  }
  if (i === 1) {
    return (
      <div className="win-scene win-mid">
        <div className="win-kpi"><b>$51.00</b><span>amount due</span></div>
        <div className="pos-methods"><span className="on">Card</span><span>Cash</span><span>QR</span></div>
        <span className="win-pill pc-green win-pop pos-paid"><i />Paid</span>
      </div>
    );
  }
  const bars = [0.55, 0.7, 0.5, 0.85, 0.65, 0.95];
  return (
    <div className="win-scene win-mid">
      <div className="win-tiles">{[['$2.4k', 'sales'], ['37', 'orders'], ['$65', 'avg']].map(([v, l], k) => <div key={k} style={{ animationDelay: `${k * 80}ms` }}><b>{v}</b><span>{l}</span></div>)}</div>
      <div className="win-chart">{bars.map((h, k) => <span key={k} className={k >= 4 ? 'hi' : ''} style={{ '--h': h, animationDelay: `${k * 70}ms` }} />)}</div>
    </div>
  );
}
function PreviewPos() {
  const tour = useTour(3);
  return <AppWin mod="pv--pos" titles={['Order #A-24', 'Payment', 'Shift']} tour={tour}><PosScene key={tour.scene} i={tour.scene} /></AppWin>;
}

// KOSHI — P&L dashboard → transactions → budgets.
function KoshiScene({ i }) {
  if (i === 0) {
    const bars = [0.45, 0.6, 0.52, 0.72, 0.8, 1];
    return (
      <div className="win-scene win-mid">
        <div className="win-kpi"><b>$128k</b><span>net profit · <em className="win-up">▲ 12%</em></span></div>
        <div className="win-chart">{bars.map((h, k) => <span key={k} className={k >= 4 ? 'hi' : ''} style={{ '--h': h, animationDelay: `${k * 70}ms` }} />)}</div>
      </div>
    );
  }
  if (i === 1) {
    const tx = [['Client payment', '+$4.2k', 'pos'], ['Supplier · Metro', '−$1.1k', 'neg'], ['Deposit · March', '+$820', 'pos']];
    return (
      <div className="win-scene win-list">
        {tx.map((r, k) => (
          <div className="win-row" key={k} style={{ animationDelay: `${k * 90}ms` }}>
            <span className={`win-io ${r[2] === 'pos' ? 'in' : 'out'}`} /><span className="win-nm">{r[0]}</span>
            <span className={`win-amt ${r[2]}`}>{r[1]}</span>
          </div>
        ))}
      </div>
    );
  }
  const bud = [['Payroll', 0.82], ['Rent', 0.45], ['Marketing', 0.68]];
  return (
    <div className="win-scene win-list">
      {bud.map((r, k) => (
        <div className="win-row win-progrow" key={k} style={{ animationDelay: `${k * 90}ms` }}>
          <span className="win-nm">{r[0]}</span>
          <span className="win-prog"><b style={{ '--h': r[1] }} /></span>
          <span className="win-sub">{Math.round(r[1] * 100)}%</span>
        </div>
      ))}
    </div>
  );
}
function PreviewKoshi() {
  const tour = useTour(3);
  return <AppWin mod="pv--koshi" titles={['P&L', 'Transactions', 'Budgets']} tour={tour}><KoshiScene key={tour.scene} i={tour.scene} /></AppWin>;
}

// Ava AI Assistant — live chat → knowledge base → human handoff.
function AsstScene({ i }) {
  if (i === 0) {
    return (
      <div className="win-scene win-chat">
        <span className="win-bub in" style={{ animationDelay: '0ms' }}>Table for 4 tonight?</span>
        <span className="win-bub out" style={{ animationDelay: '260ms' }}>Yes — 8:30pm works. Book it?</span>
        <span className="win-typing" style={{ animationDelay: '620ms' }}><i /><i /><i /></span>
      </div>
    );
  }
  if (i === 1) {
    const docs = [['Menu & prices', 'pc-green', 'Synced'], ['Booking policy', 'pc-green', 'Synced'], ['FAQ · 42 answers', 'pc-blue', 'Live']];
    return (
      <div className="win-scene win-list">
        {docs.map((d, k) => (
          <div className="win-row" key={k} style={{ animationDelay: `${k * 90}ms` }}>
            <span className="win-doc" /><span className="win-nm">{d[0]}</span><span className={`win-pill ${d[1]}`}><i />{d[2]}</span>
          </div>
        ))}
      </div>
    );
  }
  return (
    <div className="win-scene win-mid">
      <span className="win-bub in" style={{ animationDelay: '0ms' }}>I'd like to file a complaint…</span>
      <div className="win-head win-pop asst-hand">
        <span className="win-av av-0 win-head-av" />
        <div className="win-head-id"><b>Handed to Anna</b><span>human agent · online</span></div>
        <span className="win-pill pc-green"><i />Live</span>
      </div>
    </div>
  );
}
function PreviewAssistant() {
  const tour = useTour(3);
  return <AppWin mod="pv--asst" titles={['Ava AI Assistant', 'Knowledge', 'Handoff']} act="✦" tour={tour}><AsstScene key={tour.scene} i={tour.scene} /></AppWin>;
}

// TOROS — AI agent: request → live actions → result.
function TorosScene({ i }) {
  if (i === 0) {
    return (
      <div className="win-scene win-mid">
        <div className="toros-prompt"><span className="toros-caret">›</span> Reconcile today's orders &amp; flag issues</div>
        <div className="toros-think"><i /><i /><i /><span>Planning steps…</span></div>
      </div>
    );
  }
  if (i === 1) {
    const steps = [['Read 128 orders', 'pc-green', 'Done'], ['Match payments', 'pc-green', 'Done'], ['Flag 3 mismatches', 'pc-amber', 'Running']];
    return (
      <div className="win-scene win-list">
        {steps.map((s, k) => (
          <div className="win-row" key={k} style={{ animationDelay: `${k * 90}ms` }}>
            <span className={`toros-step ${k < 2 ? 'done' : 'run'}`} /><span className="win-nm">{s[0]}</span><span className={`win-pill ${s[1]}`}><i />{s[2]}</span>
          </div>
        ))}
      </div>
    );
  }
  return (
    <div className="win-scene win-mid toros-done">
      <span className="toros-check win-pop">✓</span>
      <div className="win-kpi"><b>3 issues</b><span>flagged · report ready</span></div>
    </div>
  );
}
function PreviewToros() {
  const tour = useTour(3);
  return <AppWin mod="pv--toros" titles={['Agent', 'Actions', 'Done']} act="✦" tour={tour}><TorosScene key={tour.scene} i={tour.scene} /></AppWin>;
}

// TeamHub — onboarding progress → knowledge test → team console.
function TeamScene({ i }) {
  if (i === 0) {
    const steps = [['Company intro', true], ['Safety training', true], ['Menu test', false]];
    return (
      <div className="win-scene win-mid">
        <div className="team-prog"><div className="team-prog-h"><span>Onboarding</span><b>72%</b></div><span className="win-prog"><b style={{ '--h': 0.72 }} /></span></div>
        <div className="win-list team-steps">
          {steps.map((s, k) => (
            <div className="win-row" key={k} style={{ animationDelay: `${k * 80}ms` }}>
              <span className={`win-ck${s[1] ? ' on' : ''}`}>{s[1] ? '✓' : ''}</span><span className="win-nm">{s[0]}</span>
            </div>
          ))}
        </div>
      </div>
    );
  }
  if (i === 1) {
    return (
      <div className="win-scene win-mid">
        <div className="team-q">Which allergen is in pesto?</div>
        <div className="team-opts">
          <span>Soy</span><span className="ok">Nuts ✓</span><span>Gluten</span>
        </div>
      </div>
    );
  }
  return (
    <div className="win-scene win-mid">
      <div className="win-tiles">{[['48', 'staff'], ['92%', 'trained'], ['6', 'new']].map(([v, l], k) => <div key={k} style={{ animationDelay: `${k * 80}ms` }}><b>{v}</b><span>{l}</span></div>)}</div>
      <div className="team-people"><span className="win-av av-0" /><span className="win-av av-1" /><span className="win-av av-2" /><span className="team-more">+45</span></div>
    </div>
  );
}
function PreviewTeam() {
  const tour = useTour(3);
  return <AppWin mod="pv--team" titles={['Onboarding', 'Test', 'Team']} tour={tour}><TeamScene key={tour.scene} i={tour.scene} /></AppWin>;
}

// Solo Track — order pipeline → documents & payments → logistics.
function TrackScene({ i }) {
  if (i === 0) {
    const rows = [['#1024 · Villa', 'pc-blue', 'Production'], ['#1025 · Cafe', 'pc-amber', 'Sourcing'], ['#1026 · Hotel', 'pc-green', 'Ready']];
    return (
      <div className="win-scene win-list">
        {rows.map((r, k) => (
          <div className="win-row" key={k} style={{ animationDelay: `${k * 90}ms` }}>
            <span className="win-nm">Order {r[0]}</span><span className={`win-pill ${r[1]}`}><i />{r[2]}</span>
          </div>
        ))}
      </div>
    );
  }
  if (i === 1) {
    const rows = [['Invoice #INV-88', '$12.4k', 'pc-green', 'Paid'], ['PO · Supplier', '$4.1k', 'pc-amber', 'Sent']];
    return (
      <div className="win-scene win-list">
        {rows.map((r, k) => (
          <div className="win-row" key={k} style={{ animationDelay: `${k * 90}ms` }}>
            <span className="win-doc" /><span className="win-nm">{r[0]}</span><span className="win-amt">{r[1]}</span><span className={`win-pill ${r[2]}`}><i />{r[3]}</span>
          </div>
        ))}
      </div>
    );
  }
  return (
    <div className="win-scene win-mid">
      <div className="track-route"><span className="track-a" /><span className="track-line"><b /></span><span className="track-b" /></div>
      <div className="win-row"><span className="win-nm">Container MSKU-72</span><span className="win-pill pc-blue"><i />In transit</span></div>
      <div className="track-eta"><span>ETA</span><b>2 days</b></div>
    </div>
  );
}
function PreviewTrack() {
  const tour = useTour(3);
  return <AppWin mod="pv--track" titles={['Pipeline', 'Payments', 'Logistics']} tour={tour}><TrackScene key={tour.scene} i={tour.scene} /></AppWin>;
}

// OnCheck — checklist → photo capture → compliance report.
function CheckScene({ i }) {
  if (i === 0) {
    const items = [['Fridge temp logged', true], ['Surfaces sanitized', true], ['Stock rotated (FIFO)', false], ['Waste recorded', false]];
    return (
      <div className="win-scene win-list">
        {items.map((it, k) => (
          <div className="win-row" key={k} style={{ animationDelay: `${k * 80}ms` }}>
            <span className={`win-ck${it[1] ? ' on' : ''}`}>{it[1] ? '✓' : ''}</span><span className="win-nm">{it[0]}</span>
          </div>
        ))}
      </div>
    );
  }
  if (i === 1) {
    return (
      <div className="win-scene win-mid">
        <div className="check-shot win-pop"><span className="check-cam" /></div>
        <div className="win-row"><span className="win-nm">Photo · storage shelf</span><span className="win-pill pc-green"><i />Captured</span></div>
      </div>
    );
  }
  return (
    <div className="win-scene win-mid">
      <div className="win-kpi"><b>96%</b><span>compliance · today</span></div>
      <div className="win-tiles">{[['18', 'done'], ['1', 'flag'], ['4', 'zones']].map(([v, l], k) => <div key={k} style={{ animationDelay: `${k * 80}ms` }}><b>{v}</b><span>{l}</span></div>)}</div>
    </div>
  );
}
function PreviewCheck() {
  const tour = useTour(3);
  return <AppWin mod="pv--check" titles={['Checklist', 'Capture', 'Report']} tour={tour}><CheckScene key={tour.scene} i={tour.scene} /></AppWin>;
}

// doit.ok — kanban board → task thread → time tracking.
function DoitScene({ i }) {
  if (i === 0) {
    return (
      <div className="win-scene doit-board">
        <div className="doit-col"><span className="doit-h">To do</span><span className="doit-card" /><span className="doit-card" /></div>
        <div className="doit-col"><span className="doit-h">Doing</span><span className="doit-card hi" /></div>
        <div className="doit-col"><span className="doit-h">Done</span><span className="doit-card" /></div>
      </div>
    );
  }
  if (i === 1) {
    return (
      <div className="win-scene win-mid">
        <div className="doit-title"><span className="doit-chk" />Redesign menu board</div>
        <div className="win-row doit-msg"><span className="win-av av-0" /><span className="doit-line" /></div>
        <div className="win-row doit-msg"><span className="win-av av-1" /><span className="doit-line s" /></div>
        <div className="doit-subs"><span className="win-pill pc-blue"><i />3 subtasks</span><span className="win-pill pc-green"><i />2 done</span></div>
      </div>
    );
  }
  return (
    <div className="win-scene win-mid doit-time">
      <div className="win-kpi"><b>02:14:30</b><span>tracked · this task</span></div>
      <div className="doit-cost"><span>≈ cost</span><b>$186</b></div>
    </div>
  );
}
function PreviewDoit() {
  const tour = useTour(3);
  return <AppWin mod="pv--doit" titles={['Board', 'Task', 'Time']} tour={tour}><DoitScene key={tour.scene} i={tour.scene} /></AppWin>;
}

// AVA ERP — purchase requests → receiving → warehouse stock.
function ErpScene({ i }) {
  if (i === 0) {
    const rows = [['Tomatoes · 40kg', 'pc-green', 'Approved'], ['Olive oil · 20L', 'pc-green', 'Approved'], ['Flour · 100kg', 'pc-amber', 'Pending']];
    return (
      <div className="win-scene win-list">
        {rows.map((r, k) => (
          <div className="win-row" key={k} style={{ animationDelay: `${k * 90}ms` }}>
            <span className="win-nm">{r[0]}</span><span className={`win-pill ${r[1]}`}><i />{r[2]}</span>
          </div>
        ))}
      </div>
    );
  }
  if (i === 1) {
    const rows = [['Tomatoes', '40/40kg', 'pc-green', 'Received'], ['Olive oil', '20/20L', 'pc-green', 'Received'], ['Flour', '60/100kg', 'pc-blue', 'Counting']];
    return (
      <div className="win-scene win-list">
        {rows.map((r, k) => (
          <div className="win-row" key={k} style={{ animationDelay: `${k * 90}ms` }}>
            <span className={`win-ck${r[2] === 'pc-green' ? ' on' : ''}`}>{r[2] === 'pc-green' ? '✓' : ''}</span>
            <span className="win-nm">{r[0]}</span><span className="win-sub">{r[1]}</span><span className={`win-pill ${r[2]}`}><i />{r[3]}</span>
          </div>
        ))}
      </div>
    );
  }
  const stock = [['Tomatoes', 0.8, ''], ['Olive oil', 0.45, ''], ['Flour', 0.15, 'low']];
  return (
    <div className="win-scene win-list">
      {stock.map((r, k) => (
        <div className="win-row win-progrow" key={k} style={{ animationDelay: `${k * 90}ms` }}>
          <span className="win-nm">{r[0]}</span>
          <span className={`win-prog${r[2] ? ' low' : ''}`}><b style={{ '--h': r[1] }} /></span>
          {r[2] ? <span className="win-pill pc-red"><i />Low</span> : <span className="win-sub">{Math.round(r[1] * 100)}%</span>}
        </div>
      ))}
    </div>
  );
}
function PreviewErp() {
  const tour = useTour(3);
  return <AppWin mod="pv--erp" titles={['Requests', 'Receiving', 'Warehouse']} tour={tour}><ErpScene key={tour.scene} i={tour.scene} /></AppWin>;
}

const MINI_PREVIEWS = {
  'ava-crm': PreviewCrm,
  'ava-erp': PreviewErp,
  'ava-pos': PreviewPos,
  'koshi': PreviewKoshi,
  'ava-assistant': PreviewAssistant,
  'toros': PreviewToros,
  'teamhub': PreviewTeam,
  'solo-track': PreviewTrack,
  'oncheck': PreviewCheck,
  'doit-ok': PreviewDoit,
};

/* Slug → miniature. Design vignettes (vignettes.js, extracted 1:1 from the
   handoff) take priority; products without one (e.g. doit.ok) fall back to the
   older live "app window" tour, then to the blank panel. */
function MiniPreview({ product }) {
  const vignette = (window.AVA_VIGNETTES || {})[product.slug];
  if (vignette) {
    return <div className="prod-card-vignette" aria-hidden="true" dangerouslySetInnerHTML={{ __html: vignette }} />;
  }
  const Comp = MINI_PREVIEWS[product.slug];
  return (
    <div className="prod-card-preview">
      {Comp ? <Comp /> : <div className="pv pv--blank" aria-hidden="true" />}
    </div>
  );
}

/* ---------- Partner cases (NOGIS) ------------------------------------------
   A partner section rendered at the bottom of the catalogue overview. Each of the
   seven NOGIS GIS systems is shown as a live, scaled iframe preview of its public
   demo on nogis.io, behind a transparent click shield so the iframe can't swallow
   the click — the whole card is one external link to the partner's cases section.
   Structural data comes from window.PARTNER_CASES (components.jsx); localized copy
   from L.partnerCases (i18n). These cards are intentionally NOT part of
   window.PRODUCTS, so they never appear in the footer, home teaser or prev/next. */

// NOGIS demos accept these 26 UI-language codes; the current site locale maps to
// the nearest one (uk → ua; anything unlisted → en). ?lang= is ALWAYS set —
// without it the demos default to Ukrainian.
const NOGIS_LANGS = ['en','ua','ru','fr','de','es','it','pt','ro','pl','cs','nl','bg','tr','sr','me','kk','ar','zh','he','hi','id','ja','ko','fa','vi'];
function nogisLang(lang) {
  if (lang === 'uk') return 'ua';
  return NOGIS_LANGS.indexOf(lang) !== -1 ? lang : 'en';
}
function nogisDemoUrl(file, lang) {
  return `https://nogis.io/demos/${file}?lang=${nogisLang(lang)}`;
}
// EN uses the bare root; every other supported code is prefixed (ru → /ru/,
// uk → /ua/ …). Falls back to the bare root for unlisted codes.
function nogisCasesUrl(lang) {
  const code = nogisLang(lang);
  return code === 'en' ? 'https://nogis.io/#cases' : `https://nogis.io/${code}/#cases`;
}

function PartnerCaseCard({ caseData, copy, ui, lang }) {
  const frameRef = React.useRef(null);
  const scaleRef = React.useRef(null);
  const [mounted, setMounted] = React.useState(false); // iframe added to the DOM (lazy)
  const [loaded, setLoaded] = React.useState(false);   // iframe fired onLoad
  const [failed, setFailed] = React.useState(false);   // load errored or timed out
  const [ready, setReady] = React.useState(false);     // scale transform applied

  const demoUrl = nogisDemoUrl(caseData.demo, lang);
  const href = nogisCasesUrl(lang);
  const name = copy.name || caseData.system;
  const bullets = copy.bullets || [];
  const openLabel = (ui && ui.open) || 'Open on nogis.io';

  // Lazy-mount: only add the iframe once the card nears the viewport, so seven
  // cross-origin demos don't all load at once.
  React.useEffect(() => {
    if (mounted) return;
    const el = frameRef.current;
    if (!el) return;
    if (!window.IntersectionObserver) { setMounted(true); return; }
    const io = new IntersectionObserver((entries) => {
      entries.forEach((e) => { if (e.isIntersecting) { setMounted(true); io.disconnect(); } });
    }, { rootMargin: '400px' });
    io.observe(el);
    return () => io.disconnect();
  }, [mounted]);

  // Scale the full 1440×900 demo down to the card width (same mechanism as
  // ProductMedia): CSS scale on a fixed-size wrapper, height driven off the scale.
  React.useLayoutEffect(() => {
    if (!mounted) return;
    const outer = frameRef.current;
    const scaler = scaleRef.current;
    if (!outer || !scaler) return;
    const apply = () => {
      const w = outer.clientWidth;
      const scale = w / PROD_BASE_W;
      scaler.style.transform = `scale(${scale})`;
      outer.style.height = `${PROD_BASE_H * scale}px`;
      setReady(true);
    };
    apply();
    const ro = new ResizeObserver(apply);
    ro.observe(outer);
    return () => ro.disconnect();
  }, [mounted]);

  // Fallback: if the cross-origin demo hasn't loaded within 8s (nogis.io slow or
  // unreachable), drop the shimmer and show a static NOGIS placeholder with the
  // same link — the section never looks broken.
  React.useEffect(() => {
    if (!mounted || loaded) return;
    const id = setTimeout(() => setFailed((f) => (loaded ? f : true)), 8000);
    return () => clearTimeout(id);
  }, [mounted, loaded]);

  const showShimmer = mounted && !failed && !(loaded && ready);

  return (
    <a
      className="pcase"
      href={href}
      target="_blank"
      rel="noopener noreferrer"
      data-cat={caseData.cat}
      onClick={() => window.avaTrack?.('partner_click', { partner: 'nogis', case: caseData.slug })}
    >
      <div className="pcase-head">
        <span className="mono pcase-sys">{caseData.system}</span>
        {copy.category && <span className="mono pcase-cat">{copy.category}</span>}
        <span className="pcase-badge" aria-hidden="true">
          <img src="assets/nogis-logo.svg" alt="" height="11" style={{ display:'block' }} loading="lazy" />
        </span>
      </div>

      <div className="pcase-frame" ref={frameRef}>
        {mounted && !failed && (
          <div
            className="pcase-scale"
            ref={scaleRef}
            style={{ width:PROD_BASE_W, height:PROD_BASE_H, opacity:ready ? 1 : 0 }}
          >
            <iframe
              src={demoUrl}
              title={`${name} — NOGIS live demo`}
              loading="lazy"
              tabIndex="-1"
              onLoad={() => setLoaded(true)}
              onError={() => setFailed(true)}
              style={{ width:PROD_BASE_W, height:PROD_BASE_H, border:'none', background:'#0b1220' }}
            />
          </div>
        )}
        {showShimmer && <div className="prod-frame-shimmer" aria-hidden="true" />}
        {failed && (
          <div className="pcase-fallback">
            <img src="assets/nogis-logo.svg" alt="NOGIS" height="26" />
            <span className="mono">{caseData.system}</span>
          </div>
        )}
        {/* Transparent click shield over the iframe: keeps the demo from swallowing
            the click so the whole card navigates to the partner site. */}
        <span className="pcase-shield" aria-hidden="true" />
        <span className="pcase-openbadge mono" aria-hidden="true">nogis.io ↗</span>
      </div>

      <h3 className="pcase-name">{name}</h3>
      {copy.desc && <p className="body pcase-desc">{copy.desc}</p>}
      {bullets.length > 0 && (
        <ul className="pcase-bullets">
          {bullets.map((b, i) => <li key={i} className="mono">{b}</li>)}
        </ul>
      )}
      <div className="pcase-foot">
        <span className="mono pcase-cta">{openLabel} ↗</span>
      </div>
    </a>
  );
}

// Compact "Joint solutions" sub-block: the five domains delivered jointly with
// NOGIS, rendered as quiet rows (name + one-line desc) under the demo cards.
function PartnerSolutions({ copy, lang }) {
  const sol = copy.solutions || {};
  const items = sol.items || {};
  const list = window.PARTNER_SOLUTIONS || [];
  if (!list.length) return null;
  return (
    <div className="pcase-solutions" id="nogis-solutions">
      <div className="pcase-sol-head">
        <h3 className="pcase-sol-title">{sol.title || 'Joint solutions'}</h3>
        <a
          className="mono pcase-sol-link"
          href={nogisCasesUrl(lang)}
          target="_blank"
          rel="noopener noreferrer"
          onClick={() => window.avaTrack?.('partner_click', { partner: 'nogis', entrypoint: 'partner_solutions_header' })}
        >
          nogis.io ↗
        </a>
      </div>
      {sol.note && <p className="body pcase-sol-note">{sol.note}</p>}
      <ul className="pcase-sol-list">
        {list.map((s) => {
          const it = items[s.slug] || {};
          return (
            <li key={s.slug} className="pcase-sol-row">
              <span className="pcase-sol-name">{it.name || s.slug}</span>
              {it.desc && <span className="body pcase-sol-desc">{it.desc}</span>}
            </li>
          );
        })}
      </ul>
    </div>
  );
}

function PartnerCasesSection() {
  const { L, lang } = useLang();
  const pc = (L && L.partnerCases) || {};
  const cases = pc.cases || {};
  const list = window.PARTNER_CASES || [];
  if (!list.length) return null;
  return (
    <section className="pcases" aria-label={pc.aria || 'NOGIS partner cases'} style={{ padding:'56px 0 8px' }}>
      <div className="wrap">
        <div className="page-chapter pcases-chapter">
          <span>{pc.eyebrow || '// NOGIS · PARTNER'}</span>
          <span>{String(list.length).padStart(2, '0')}</span>
        </div>
        <div className="pcases-titlerow">
          <Reveal>
            <h2 className="prod-group-title pcases-title">{pc.title || 'Systems in operation'}</h2>
          </Reveal>
          <a
            className="pcases-logochip"
            href={nogisCasesUrl(lang)}
            target="_blank"
            rel="noopener noreferrer"
            onClick={() => window.avaTrack?.('partner_click', { partner: 'nogis', entrypoint: 'partner_cases_header' })}
            title="NOGIS — We Digitize the Planet"
          >
            <img src="assets/nogis-logo.svg" alt="NOGIS" height="18" style={{ display:'block' }} loading="lazy" />
          </a>
        </div>
        {pc.note && (
          <Reveal delay={100}>
            <p className="body prod-group-note pcases-note">{pc.note}</p>
          </Reveal>
        )}
        <div className="pcase-grid">
          {list.map((c, i) => (
            <Reveal key={c.slug} delay={(i % 2) * 80}>
              <PartnerCaseCard caseData={c} copy={cases[c.slug] || {}} ui={pc} lang={lang} />
            </Reveal>
          ))}
        </div>
        <Reveal delay={80}>
          <PartnerSolutions copy={pc} lang={lang} />
        </Reveal>
      </div>
    </section>
  );
}

/* ---------- Overview (catalogue) ------------------------------------------- */

function ProductCard({ globalIndex, product }) {
  const { ui, tagline, bullets } = useProductCopy(product);
  const demoCount = (product.presentations || product.renders || []).length;
  const demoLabel = demoCount > 0
    ? `${String(demoCount).padStart(2, '0')} ${demoCount === 1 ? (ui.demoCountOne || 'live demo') : (ui.demoCountMany || 'live demos')}`
    : null;
  return (
    <a className="prod-card" href={`/products.html?p=${product.slug}`}>
      <div className="prod-card-top">
        <span className="mono prod-card-num">{String(globalIndex + 1).padStart(2, '0')}</span>
        {product.comingSoon
          ? <span className="mono prod-card-badge">{ui.comingSoon || 'Coming soon'}</span>
          : <span className="prod-card-arrow" aria-hidden="true">→</span>}
      </div>
      <MiniPreview product={product} />
      <h3 className="prod-card-name">{product.name}</h3>
      <p className="body prod-card-tagline">{tagline}</p>
      {bullets.length > 0 && (
        <ul className="prod-card-bullets">
          {bullets.map((b, i) => <li key={i} className="mono">{b}</li>)}
        </ul>
      )}
      <div className="prod-card-foot">
        <span className="mono prod-card-democount">{demoLabel}</span>
        <span className="mono prod-card-explore">{ui.explore} →</span>
      </div>
    </a>
  );
}

/* Russian-style plural picker for the "NN решений" counters (EN degrades to
   one/many). */
function pluralWord(n, forms) {
  if (!forms) return '';
  if (forms.few) {
    const m10 = n % 10, m100 = n % 100;
    if (m10 === 1 && m100 !== 11) return forms.one;
    if (m10 >= 2 && m10 <= 4 && (m100 < 12 || m100 > 14)) return forms.few;
    return forms.many;
  }
  return n === 1 ? forms.one : forms.many;
}

function ProductsOverview({ products }) {
  const { L } = useLang();
  const ui = ((L && L.products) || {})._ui || {};
  const ov = ui.overview || {};
  React.useEffect(() => {
    if (typeof document === 'undefined') return;
    document.title = `${ov.eyebrow || 'Solutions'} — Ava Solutions`;
    let m = document.querySelector('meta[name="description"]');
    if (!m) { m = document.createElement('meta'); m.setAttribute('name', 'description'); document.head.appendChild(m); }
    if (ov.intro) m.setAttribute('content', ov.intro);
  }, [ov.eyebrow, ov.intro]);
  const indexOf = (p) => products.indexOf(p);
  const groups = ov.groups || {};
  // Display order of categories; any unknown category falls to the end.
  const order = ['sales', 'finance', 'team', 'ai'];
  const seen = [];
  products.forEach((p) => { if (seen.indexOf(p.category) === -1) seen.push(p.category); });
  const cats = order.filter((c) => seen.indexOf(c) !== -1)
    .concat(seen.filter((c) => order.indexOf(c) === -1));
  const demoTotal = products.reduce((n, p) => n + ((p.presentations || p.renders || []).length), 0);
  const stats = ov.stats && [
    { n: products.length, label: ov.stats.solutions },
    { n: demoTotal, label: ov.stats.demos },
    { n: cats.length, label: ov.stats.directions },
  ];

  return (
    <div className="prod-overview black-on-cream">
      <section style={{ padding:'150px 0 24px' }}>
        <div className="wrap">
          <div className="page-chapter"><span>{ov.eyebrow}</span><span>{ov.eyebrowRight}</span></div>
          <Reveal>
            <h1 className="display" style={{ marginTop:48, maxWidth:'15ch', fontSize:'clamp(28px,4.4vw,78px)', lineHeight:1.08 }}>
              {ov.title && ov.title.pre}
              <span className="prod-accent">{ov.title && ov.title.accent}</span>
            </h1>
          </Reveal>
          <Reveal delay={120} className="text-wrap-pretty" style={{ marginTop:28, maxWidth:620, fontSize:'clamp(15px,1.25vw,20px)', lineHeight:1.55, color:'rgba(10,10,10,.58)' }}>
            {ov.intro}
          </Reveal>
          {stats && (
            <Reveal delay={200} className="prod-ov-stats">
              {stats.map((s, i) => (
                <div key={i} className="prod-ov-stat">
                  <span className="prod-ov-stat-num">{String(s.n).padStart(2, '0')}</span>
                  <span className="prod-ov-stat-label">{s.label}</span>
                </div>
              ))}
            </Reveal>
          )}
        </div>
      </section>

      {/* Sticky group navigation (design) */}
      <nav className="prod-gnav" aria-label={ov.eyebrow}>
        <div className="wrap prod-gnav-inner">
          {cats.map((cat) => {
            const count = products.filter((p) => p.category === cat).length;
            const g = groups[cat] || {};
            return (
              <a key={cat} className="mono prod-gnav-link" href={`#g-${cat}`}>
                {(ui.categories && ui.categories[cat]) || g.title || cat} · {String(count).padStart(2, '0')}
              </a>
            );
          })}
        </div>
      </nav>

      {cats.map((cat, gi) => {
        const items = products.filter((p) => p.category === cat);
        if (items.length === 0) return null;
        const g = groups[cat] || {};
        const countLabel = `${String(items.length).padStart(2, '0')} ${pluralWord(items.length, ov.solutionsWord)}`;
        return (
          <section key={cat} id={`g-${cat}`} className="prod-group" style={{ padding:'56px 0 0' }}>
            <div className="wrap">
              <div className="prod-group-chapter mono">
                <span>({String(gi + 1).padStart(2, '0')} / {String(cats.length).padStart(2, '0')})</span>
                <span>{countLabel}</span>
              </div>
              <div className="prod-group-head">
                <h2 className="prod-group-title">{g.title || cat}</h2>
                {g.note && <p className="body prod-group-note">{g.note}</p>}
              </div>
              <div className="prod-card-grid">
                {items.map((p, i) => (
                  <Reveal key={p.slug} delay={i * 60}>
                    <ProductCard globalIndex={indexOf(p)} product={p} />
                  </Reveal>
                ))}
              </div>
            </div>
          </section>
        );
      })}

      <PartnerCasesSection />

      <div style={{ paddingBottom:120 }} />
    </div>
  );
}

/* ---------- Detail (single product) ---------------------------------------- */

/* One demo row — caption on one side, the live scaled preview on the other. */
function DemoRow({ index, presentation, demo, productName, onExpand, eager, ui }) {
  const reversed = index % 2 === 1;
  const label = (demo && demo.label) || presentation.label || `Demo ${index + 1}`;
  return (
    <section className={`prod-row ${reversed ? 'prod-row--reversed' : ''}`}>
      <div className="wrap">
        <div className="prod-row-grid">
          <div className="prod-copy">
            <Reveal>
              <div className="mono prod-copy-num">{String(index + 1).padStart(2, '0')}</div>
              <h3 className="display display-sm prod-demo-title" style={{ marginTop:12 }}>{label}</h3>
              {demo && demo.caption && (
                <p className="text-wrap-pretty prod-demo-caption" style={{ marginTop:16 }}>{demo.caption}</p>
              )}
              <button type="button" className="btn btn--dark prod-demo-open" onClick={() => onExpand(presentation)} style={{ marginTop:24 }}>
                <span>{(ui && ui.openDemo) || 'Open demo'}</span>
                <span aria-hidden="true">⤢</span>
              </button>
            </Reveal>
          </div>
          <div className="prod-media-col">
            <Reveal delay={120}>
              <ProductMedia
                presentations={[presentation]}
                selected={0}
                onSelect={() => {}}
                onExpand={onExpand}
                productName={productName}
                eager={eager}
                badge={ui && ui.expandBadge}
              />
            </Reveal>
          </div>
        </div>
      </div>
    </section>
  );
}

/* One render row — a drawn (non-HTML) concept image instead of a live iframe.
   Used for coming-soon products that ship illustrative SVG renders (window
   product.renders) rather than interactive presentations. The image is a plain
   <img> (animated SVGs still play in <img>); clicking opens it full size. */
function RenderRow({ index, render, label, caption, productName, ui }) {
  const reversed = index % 2 === 1;
  return (
    <section className={`prod-row ${reversed ? 'prod-row--reversed' : ''}`}>
      <div className="wrap">
        <div className="prod-row-grid">
          <div className="prod-copy">
            <Reveal>
              <div className="mono prod-copy-num">{String(index + 1).padStart(2, '0')}</div>
              <h3 className="display display-sm" style={{ marginTop:12 }}>{label}</h3>
              {caption && (
                <p className="body-lg text-wrap-pretty" style={{ marginTop:16, maxWidth:460, color:'var(--muted)' }}>{caption}</p>
              )}
              <a className="mono prod-render-link" href={render.url} target="_blank" rel="noopener" style={{ marginTop:24 }}>
                {(ui && ui.viewRender) || 'Open full size'} ↗
              </a>
            </Reveal>
          </div>
          <div className="prod-media-col">
            <Reveal delay={120}>
              <a
                className="prod-frame prod-frame--render"
                href={render.url}
                target="_blank"
                rel="noopener"
                aria-label={`${productName} — ${label}`}
              >
                <img src={render.url} alt={`${productName} — ${label}`} loading="lazy" />
              </a>
            </Reveal>
          </div>
        </div>
      </div>
    </section>
  );
}

function ProductDetail({ products, product, onExpand, onDiscuss }) {
  const { ui, summary, heroTagline, bullets, sections, demos, lang } = useProductCopy(product);
  // Resolve any {lang} placeholder in demo URLs so a product can ship a
  // language-matched demo (e.g. OnCheck: demo-en.html / demo-ru.html). Only
  // EN/RU demo assets exist today, so new site languages fall back to EN assets.
  const demoAssetLang = lang === 'ru' ? 'ru' : 'en';
  const presentations = (product.presentations || []).map((p) => {
    const has = (s) => typeof s === 'string' && s.indexOf('{lang}') !== -1;
    if (!has(p.url) && !has(p.video) && !has(p.poster)) return p;
    const out = { ...p };
    if (has(p.url)) out.url = p.url.replace('{lang}', demoAssetLang);
    if (has(p.video)) out.video = p.video.replace('{lang}', demoAssetLang);
    if (has(p.poster)) out.poster = p.poster.replace('{lang}', demoAssetLang);
    return out;
  });
  const idx = products.indexOf(product);
  const prev = products[(idx - 1 + products.length) % products.length];
  const next = products[(idx + 1) % products.length];

  // Per-product <title> + meta description for sharing / SEO.
  React.useEffect(() => {
    if (typeof document === 'undefined') return;
    document.title = `${product.name} — Ava Solutions`;
    let m = document.querySelector('meta[name="description"]');
    if (!m) { m = document.createElement('meta'); m.setAttribute('name', 'description'); document.head.appendChild(m); }
    if (summary) m.setAttribute('content', summary);
  }, [product.name, summary]);

  const demoCount = product.renders ? (product.renders || []).length : presentations.length;
  const demoCountLabel = `${String(demoCount).padStart(2, '0')} ${demoCount === 1 ? (ui.demoCountOne || 'live demo') : (ui.demoCountMany || 'live demos')}`;

  return (
    <div className="prod-detail black-on-cream">
      <section className="prod-detail-hero" style={{ padding:'150px 0 24px' }}>
        <div className="wrap">
          {/* Product-specific header: back to catalogue + open the chat to discuss this task. */}
          <div className="prod-detail-header">
            <a className="mono prod-detail-back" href="/products.html">← {ui.back}</a>
            {onDiscuss && (
              <button type="button" className="btn btn--dark prod-detail-discuss" onClick={onDiscuss}>
                <span>{(ui.cta && ui.cta.button) || 'Discuss the task'}</span>
                <span aria-hidden="true">→</span>
              </button>
            )}
          </div>
          {/* Chapter bar, e.g. "Solution 08 · Operations — 03 live demos". */}
          <div className="mono prod-detail-chapter">
            <span>
              {ui.solutionWord || 'Solution'} {String(idx + 1).padStart(2, '0')}
              <span className="prod-detail-chapter-sep" aria-hidden="true"> · </span>
              {(ui.categories && ui.categories[product.category]) || product.category}
            </span>
            <span>{demoCountLabel}</span>
          </div>
          <Reveal>
            <h1 className="prod-detail-h1">
              {product.name}
              {product.comingSoon && <span className="prod-detail-soon">{ui.comingSoon || 'Coming soon'}</span>}
            </h1>
          </Reveal>
          {heroTagline && (
            <Reveal delay={80}>
              <p className="prod-detail-tagline">
                {heroTagline.pre}
                <span className="prod-detail-tagline-hl">{heroTagline.accent}</span>
              </p>
            </Reveal>
          )}
          <Reveal delay={140} className="text-wrap-pretty prod-detail-desc">
            {summary}
          </Reveal>
          {bullets.length > 0 && (
            <Reveal delay={200}>
              <ul className="prod-copy-bullets" style={{ marginTop:28 }}>
                {bullets.map((b, i) => <li key={i} className="mono">{b}</li>)}
              </ul>
            </Reveal>
          )}
          {ui.aiAssistant && (
            <Reveal delay={240}>
              <div className="prod-ai-note">
                <span className="prod-ai-note-icon" aria-hidden="true">✦</span>
                <p className="body">{ui.aiAssistant}<sup>*</sup></p>
              </div>
            </Reveal>
          )}
        </div>
      </section>

      {product.renders && product.renders.length > 0 ? (
        <div className="prod-detail-demos">
          <div className="wrap">
            <div className="page-chapter"><span>{ui.rendersTitle || 'Preview'}</span><span>{String(product.renders.length).padStart(2, '0')}</span></div>
            {ui.rendersNote && (
              <p className="mono prod-render-note" style={{ marginTop:14, color:'var(--muted)' }}>{ui.rendersNote}</p>
            )}
          </div>
          {product.renders.map((r, i) => (
            <RenderRow
              key={r.url}
              index={i}
              render={r}
              label={(demos[i] && demos[i].label) || r.label}
              caption={demos[i] && demos[i].caption}
              productName={product.name}
              ui={ui}
            />
          ))}
        </div>
      ) : presentations.length > 0 && (
        <div className="prod-detail-demos">
          <div className="wrap">
            <div className="page-chapter"><span>{ui.demosTitle}</span><span>{String(presentations.length).padStart(2, '0')}</span></div>
          </div>
          {presentations.map((p, i) => (
            <DemoRow key={p.url} index={i} presentation={p} demo={demos[i]} productName={product.name} onExpand={onExpand} eager={i === 0} ui={ui} />
          ))}
        </div>
      )}

      {sections.length > 0 && (
        <section className="prod-detail-sections" style={{ padding:'32px 0 72px' }}>
          <div className="wrap">
            <div className="prod-section-grid">
              {sections.map((s, i) => (
                <Reveal key={i} delay={i * 80} className="prod-section">
                  <div className="mono prod-section-label">({String(i + 1).padStart(2, '0')}) {s.title}</div>
                  <p className="text-wrap-pretty prod-section-body">{s.body}</p>
                </Reveal>
              ))}
            </div>
            {ui.aiNote && (
              <p className="mono prod-foot-note"><sup>*</sup> {ui.aiNote}</p>
            )}
          </div>
        </section>
      )}

      {/* Prev / next — cream strip above the CTA (design). */}
      <section className="prod-prevnext" style={{ padding:'0 0 56px' }}>
        <div className="wrap">
          <div className="prod-nav">
            <a className="mono prod-nav-link" href={`/products.html?p=${prev.slug}`}>← {ui.prev} · {prev.name}</a>
            <a className="mono prod-nav-link" href="/products.html">{ui.back}</a>
            <a className="mono prod-nav-link prod-nav-link--next" href={`/products.html?p=${next.slug}`}>{ui.next} · {next.name} →</a>
          </div>
        </div>
      </section>

      {ui.cta && (
        <section className="prod-detail-cta" style={{ background:'var(--yellow)', color:'var(--black)', padding:'clamp(64px,9vw,110px) 0' }}>
          <div className="wrap" style={{ display:'flex', flexDirection:'column', alignItems:'flex-start', gap:18 }}>
            <Reveal as="h2" className="display" style={{ fontSize:'clamp(24px,3.2vw,56px)', lineHeight:1.1, maxWidth:'20ch' }}>{ui.cta.line}</Reveal>
            {ui.cta.sub && (
              <Reveal delay={90} className="text-wrap-pretty" style={{ fontSize:'clamp(15px,1.25vw,20px)', lineHeight:1.55, opacity:.7, maxWidth:'60ch' }}>{ui.cta.sub}</Reveal>
            )}
            <Reveal delay={150} style={{ display:'flex', flexWrap:'wrap', alignItems:'center', gap:16, marginTop:8 }}>
              <a className="btn btn--dark" href="/#contact">
                <span>{ui.cta.button}</span>
                <span aria-hidden="true" style={{ fontSize:15, lineHeight:1 }}>→</span>
              </a>
              {ui.cta.note && <span className="mono" style={{ opacity:.55 }}>{ui.cta.note}</span>}
            </Reveal>
          </div>
        </section>
      )}
    </div>
  );
}

function ProductsPage({ onDiscuss }) {
  const [expanded, setExpanded] = React.useState(null); // { presentation, productName }
  const products = window.PRODUCTS || [];

  const slug = React.useMemo(() => {
    try { return new URLSearchParams(window.location.search).get('p'); } catch { return null; }
  }, []);
  const product = products.find((p) => p.slug === slug) || null;

  const openExpand = React.useCallback((presentation, productName) => {
    setExpanded({ presentation, productName });
  }, []);

  return (
    <>
      {product
        ? <ProductDetail products={products} product={product} onExpand={(p) => openExpand(p, product.name)} onDiscuss={onDiscuss} />
        : <ProductsOverview products={products} />}
      {expanded && (
        expanded.presentation.video
          ? <VideoModal
              presentation={expanded.presentation}
              productName={expanded.productName}
              onClose={() => setExpanded(null)}
            />
          : <PresentationModal
              presentation={expanded.presentation}
              productName={expanded.productName}
              onClose={() => setExpanded(null)}
            />
      )}
    </>
  );
}

window.ProductsPage = ProductsPage;
