/* 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, or a film,
                + 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. */

/* The address a search engine should keep for the page on screen: the catalogue,
   or one product by its slug. All of them are one HTML file, so the tag cannot
   be written in the markup and is set here, with the address a link preview shows. */
function setProductsCanonical(slug) {
  if (typeof document === 'undefined') return;
  const href = `https://avasolutions.studio/products${slug ? `?p=${slug}` : ''}`;
  let link = document.querySelector('link[rel="canonical"]');
  if (!link) { link = document.createElement('link'); link.setAttribute('rel', 'canonical'); document.head.appendChild(link); }
  link.setAttribute('href', href);
  const og = document.querySelector('meta[property="og:url"]');
  if (og) og.setAttribute('content', href);
}

/* 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>
  );
}

/* A product film (product.film, e.g. Ava Support) shown inline on the product
   page in place of live demo rows. Wide screens get the 16:9 cut, narrow phones
   the 9:16 cut; the choice follows the viewport, so turning a phone sideways
   swaps the cut. No autoplay: the film has a soundtrack, and sound needs a tap.
   Same control bar skin as VideoModal. */
const FILM_TALL_MQ = '(max-width: 640px)';

function FilmPlayer({ film, productName, ui }) {
  const shellRef = React.useRef(null);
  const videoRef = React.useRef(null);
  const [tall, setTall] = React.useState(() => {
    try { return Boolean(film.tall) && window.matchMedia(FILM_TALL_MQ).matches; } catch { return false; }
  });
  const [playing, setPlaying] = React.useState(false);
  const [muted, setMuted] = React.useState(false);
  const [cur, setCur] = React.useState(0);
  const [dur, setDur] = React.useState(0);
  const [fs, setFs] = React.useState(false);

  React.useEffect(() => {
    if (!film.tall || !window.matchMedia) return;
    const mq = window.matchMedia(FILM_TALL_MQ);
    const onChange = () => setTall(mq.matches);
    if (mq.addEventListener) mq.addEventListener('change', onChange); else mq.addListener(onChange);
    return () => { if (mq.removeEventListener) mq.removeEventListener('change', onChange); else mq.removeListener(onChange); };
  }, [film.tall]);

  // The two cuts differ in length and edit, so a swapped cut starts over.
  React.useEffect(() => { setPlaying(false); setCur(0); setDur(0); }, [tall]);

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

  const cut = (tall && film.tall) || film.wide;

  const togglePlay = React.useCallback(() => {
    const v = videoRef.current;
    if (!v) return;
    if (!v.paused) { v.pause(); return; }
    const p = v.play();
    // A browser that refuses sound still lets the film play muted.
    if (p && p.catch) p.catch(() => { v.muted = true; v.play().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) v.muted = !v.muted;
  }, []);

  // iPhone Safari has no element fullscreen; its native video fullscreen stands in.
  const toggleFs = React.useCallback(() => {
    const el = shellRef.current;
    const v = videoRef.current;
    if (document.fullscreenElement) { document.exitFullscreen(); return; }
    if (el && el.requestFullscreen) el.requestFullscreen();
    else if (v && v.webkitEnterFullscreen) v.webkitEnterFullscreen();
  }, []);

  const pct = dur > 0 ? (cur / dur) * 100 : 0;
  const title = (ui && ui.filmTitle) || 'Video';

  return (
    <div className={`prod-film ${tall ? 'prod-film--tall' : ''} ${fs ? 'is-fullscreen' : ''}`} ref={shellRef}>
      <div className="vplayer">
        <div className="vplayer__stage" onClick={togglePlay}>
          <video
            key={cut.video}
            ref={videoRef}
            className="vplayer__video"
            src={cut.video}
            poster={cut.poster || undefined}
            playsInline
            preload="metadata"
            aria-label={`${productName} · ${title}`}
            onLoadedMetadata={(e) => setDur(e.target.duration || 0)}
            onTimeUpdate={(e) => setCur(e.target.currentTime || 0)}
            onPlay={() => setPlaying(true)}
            onPause={() => setPlaying(false)}
            onVolumeChange={(e) => setMuted(e.target.muted)}
          />
          {!playing && (
            <button
              type="button"
              className="vplayer__big"
              aria-label={(ui && ui.filmPlay) || '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>
  );
}

/* ---------- 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 }} />;
  }
  if (product.film && product.film.wide && product.film.wide.poster) {
    return (
      <div className="prod-card-vignette prod-card-vignette--poster" aria-hidden="true">
        <img src={product.film.wide.poster} alt="" loading="lazy" />
      </div>
    );
  }
  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) ------------------------------------------
   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. Since 2026-09-20 these cards live on the partner's own page
   (/products.html?p=nogis, PartnerDetail); the catalogue overview only carries a
   teaser to it (PartnerFeature). Structural data comes from window.PARTNER_CASES
   (components.jsx); localized copy from L.partnerCases (i18n). The cards are
   intentionally NOT part of window.PRODUCTS, so they never appear in the footer
   or inside the catalogue's own category groups. */

// 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>
  );
}

/* Catalogue teaser for the partner page. The seven demo cards used to render
   inline here; they now live on /products.html?p=nogis so the partner reads as
   one solution in the list instead of a second catalogue inside the first. */
function PartnerFeature() {
  const { L } = useLang();
  const pc = (L && L.partnerCases) || {};
  const copy = pc.product || {};
  const partner = window.PARTNER_PRODUCT;
  const list = window.PARTNER_CASES || [];
  const cases = pc.cases || {};
  if (!partner || !list.length) return null;
  const href = `/products.html?p=${partner.slug}`;
  const track = (entrypoint) => window.avaTrack?.('partner_click', { partner: 'nogis', entrypoint });
  return (
    <section className="pcases" aria-label={pc.aria || 'NOGIS partner'} 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={href} onClick={() => track('partner_feature_logo')} title="NOGIS">
            <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>
        )}
        <Reveal delay={140}>
          <a className="pfeature" href={href} onClick={() => track('partner_feature')}>
            <ul className="pfeature-list">
              {list.map((c) => (
                <li key={c.slug} className="pfeature-row">
                  <span className="mono pfeature-sys">{c.system}</span>
                  <span className="pfeature-name">{(cases[c.slug] || {}).name || c.system}</span>
                </li>
              ))}
            </ul>
            <span className="pfeature-foot">
              <span className="mono pfeature-cta">{copy.openLabel || 'Open the partner solution'} →</span>
              {copy.teaserNote && <span className="mono pfeature-note">{copy.teaserNote}</span>}
            </span>
          </a>
        </Reveal>
      </div>
    </section>
  );
}

/* ---------- Detail (partner) ------------------------------------------------
   The partner's own page, reached from the 11th row of the solutions list. Same
   shell as a product page (back link, chapter bar, hero, demos, prev/next, CTA)
   with the partner's own colour accents, the seven live NOGIS demos and the
   joint-solutions block the footer deep-links into (#nogis-solutions). */
function PartnerDetail({ products, onDiscuss }) {
  const { L, lang } = useLang();
  const pc = (L && L.partnerCases) || {};
  const ui = ((L && L.products) || {})._ui || {};
  const copy = pc.product || {};
  const partner = window.PARTNER_PRODUCT || {};
  const list = window.PARTNER_CASES || [];
  const cases = pc.cases || {};
  const casesHref = nogisCasesUrl(lang);
  const bullets = copy.bullets || [];
  const prev = products[products.length - 1];
  const next = products[0];
  const demoCountLabel = `${String(list.length).padStart(2, '0')} ${list.length === 1 ? (ui.demoCountOne || 'live demo') : (ui.demoCountMany || 'live demos')}`;

  React.useEffect(() => {
    if (typeof document === 'undefined') return;
    document.title = `${partner.name || 'NOGIS'} — Ava Solutions`;
    let m = document.querySelector('meta[name="description"]');
    if (!m) { m = document.createElement('meta'); m.setAttribute('name', 'description'); document.head.appendChild(m); }
    if (copy.summary) m.setAttribute('content', copy.summary);
    setProductsCanonical(partner.slug || 'nogis');
  }, [partner.name, partner.slug, copy.summary]);

  return (
    <div className="prod-detail prod-detail--partner black-on-cream">
      <section className="prod-detail-hero" style={{ padding:'150px 0 24px' }}>
        <div className="wrap">
          <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>{(copy.cta && copy.cta.button) || (ui.cta && ui.cta.button) || 'Discuss the task'}</span>
                <span aria-hidden="true">→</span>
              </button>
            )}
          </div>
          <div className="mono prod-detail-chapter">
            <span>
              {ui.solutionWord || 'Solution'} {String(products.length + 1).padStart(2, '0')}
              <span className="prod-detail-chapter-sep" aria-hidden="true"> · </span>
              {copy.badge || 'Partner'}
            </span>
            <span>{demoCountLabel}</span>
          </div>
          <Reveal>
            <h1 className="prod-detail-h1">
              {partner.name || 'NOGIS'}
              {copy.badge && <span className="prod-detail-soon prod-detail-partnerbadge">{copy.badge}</span>}
            </h1>
          </Reveal>
          {copy.tagline && (
            <Reveal delay={80}>
              <p className="prod-detail-tagline">
                {copy.tagline.pre}
                <span className="prod-detail-tagline-hl prod-detail-tagline-hl--partner">{copy.tagline.accent}</span>
              </p>
            </Reveal>
          )}
          {copy.summary && (
            <Reveal delay={140} className="text-wrap-pretty prod-detail-desc">{copy.summary}</Reveal>
          )}
          {bullets.length > 0 && (
            <Reveal delay={200}>
              <ul className="prod-copy-bullets pcase-bullets prod-partner-bullets" style={{ marginTop:28 }}>
                {bullets.map((b, i) => <li key={i} className="mono">{b}</li>)}
              </ul>
            </Reveal>
          )}
          <Reveal delay={240} className="prod-partner-visit">
            <a
              className="pcases-logochip"
              href={casesHref}
              target="_blank"
              rel="noopener noreferrer"
              onClick={() => window.avaTrack?.('partner_click', { partner: 'nogis', entrypoint: 'partner_page_logo' })}
              title="NOGIS — We Digitize the Planet"
            >
              <img src="assets/nogis-logo.svg" alt="NOGIS" height="18" style={{ display:'block' }} loading="lazy" />
            </a>
            <a
              className="mono prod-partner-visit-link"
              href={casesHref}
              target="_blank"
              rel="noopener noreferrer"
              onClick={() => window.avaTrack?.('partner_click', { partner: 'nogis', entrypoint: 'partner_page_visit' })}
            >{copy.visit || 'nogis.io'} ↗</a>
          </Reveal>
        </div>
      </section>

      <section className="pcases" aria-label={pc.aria || 'NOGIS partner cases'} style={{ padding:'8px 0 8px' }}>
        <div className="wrap">
          <div className="page-chapter"><span>{pc.title || ui.demosTitle}</span><span>{String(list.length).padStart(2, '0')}</span></div>
          <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>

      <section className="prod-prevnext" style={{ padding:'56px 0' }}>
        <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>

      {copy.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' }}>{copy.cta.line}</Reveal>
            {copy.cta.sub && (
              <Reveal delay={90} className="text-wrap-pretty" style={{ fontSize:'clamp(15px,1.25vw,20px)', lineHeight:1.55, opacity:.7, maxWidth:'60ch' }}>{copy.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>{copy.cta.button}</span>
                <span aria-hidden="true" style={{ fontSize:15, lineHeight:1 }}>→</span>
              </a>
              {copy.cta.note && <span className="mono" style={{ opacity:.55 }}>{copy.cta.note}</span>}
            </Reveal>
          </div>
        </section>
      )}
    </div>
  );
}

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

function ProductCard({ globalIndex, product }) {
  const { ui, tagline, bullets } = useProductCopy(product);
  const demoCount = (product.presentations || product.renders || []).length;
  const demoLabel = product.film
    ? `01 ${ui.filmWord || 'video'}`
    : 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);
    setProductsCanonical(null);
  }, [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>
        );
      })}

      <PartnerFeature />

      <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;
  });
  // The prev/next cycle spans every row of the solutions list: our products
  // plus the partner entry, which owns the last slot and its own page.
  const nav = products.concat(window.PARTNER_PRODUCT ? [window.PARTNER_PRODUCT] : []);
  const idx = products.indexOf(product);
  const navIdx = nav.indexOf(product);
  const prev = nav[(navIdx - 1 + nav.length) % nav.length];
  const next = nav[(navIdx + 1) % nav.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);
    setProductsCanonical(product.slug);
  }, [product.name, product.slug, summary]);

  const film = product.film || null;
  const demoCount = product.renders ? (product.renders || []).length : presentations.length;
  const demoCountLabel = film
    ? `01 ${ui.filmWord || 'video'}`
    : `${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>

      {film ? (
        <div className="prod-detail-demos prod-detail-film">
          <div className="wrap">
            <div className="page-chapter"><span>{ui.filmTitle || 'Video'}</span><span>01</span></div>
            <Reveal delay={60}>
              <FilmPlayer film={film} productName={product.name} ui={ui} />
            </Reveal>
          </div>
        </div>
      ) : 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 partner = window.PARTNER_PRODUCT || null;
  const isPartner = Boolean(partner && slug === partner.slug);
  const product = products.find((p) => p.slug === slug) || null;

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

  return (
    <>
      {isPartner
        ? <PartnerDetail products={products} onDiscuss={onDiscuss} />
        : 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;
