// ============================================================
// YCI · Camada de PROTÓTIPO NAVEGÁVEL (modo cliente)
// Envolve a mesma composição de seções/artboards do canvas, mas
// renderiza um player de apresentação: device frame (mobile/desktop),
// navegação sequencial, hotspots dos fluxos-chave, índice e guia.
// Esconde as notas do designer. Toggle para revelar o canvas designer.
//
// Depende (window): React, ReactDOM, DesignCanvas, DCSection, DCArtboard,
// e o mapa window.YCI_HOTSPOTS (hotspots.js).
// ============================================================

(function () {
  const HOTSPOTS = (typeof window !== 'undefined' && window.YCI_HOTSPOTS) || {};

  // ---- Comentários (Supabase via REST/anon, sem login) ----
  const SB = () => (typeof window !== 'undefined' && window.YCI_SUPABASE && window.YCI_SUPABASE.url && window.YCI_SUPABASE.anonKey) ? window.YCI_SUPABASE : null;
  const sbHeaders = (cfg, extra) => Object.assign({ apikey: cfg.anonKey, Authorization: 'Bearer ' + cfg.anonKey, 'Content-Type': 'application/json' }, extra || {});
  async function sbList(cfg) {
    const r = await fetch(cfg.url + '/rest/v1/yci_comments?board=eq.' + encodeURIComponent(cfg.board) + '&order=created_at.asc&select=*', { headers: sbHeaders(cfg) });
    if (!r.ok) throw new Error('list ' + r.status);
    return r.json();
  }
  async function sbInsert(cfg, row) {
    const r = await fetch(cfg.url + '/rest/v1/yci_comments', { method: 'POST', headers: sbHeaders(cfg, { Prefer: 'return=representation' }), body: JSON.stringify(row) });
    if (!r.ok) throw new Error('insert ' + r.status);
    return (await r.json())[0];
  }
  async function sbPatch(cfg, id, patch) {
    const r = await fetch(cfg.url + '/rest/v1/yci_comments?id=eq.' + id, { method: 'PATCH', headers: sbHeaders(cfg, { Prefer: 'return=representation' }), body: JSON.stringify(patch) });
    if (!r.ok) throw new Error('patch ' + r.status);
    return (await r.json())[0];
  }
  async function sbDelete(cfg, id) {
    const r = await fetch(cfg.url + '/rest/v1/yci_comments?id=eq.' + id, { method: 'DELETE', headers: sbHeaders(cfg) });
    if (!r.ok) throw new Error('delete ' + r.status);
  }
  const fmtTime = (iso) => { try { return new Date(iso).toLocaleString('pt-BR', { day: '2-digit', month: 'short', hour: '2-digit', minute: '2-digit' }); } catch { return ''; } };

  // ---- pareamento mobile/desktop por convenção de id ----
  const ALIAS = {
    'home-hibrida': { base: 'home', device: 'mobile' },
    'home-desktop': { base: 'home', device: 'desktop' },
    'rev-kanban': { base: 'rev-kanban', device: 'desktop' },     // par de rev-kanban-m (id sem -d)
    'vida-mag-index-d': { base: 'vida-mag', device: 'desktop' }, // desktop da Revista índice
  };
  const DESKTOP_ONLY = new Set(['g-header', 'g-mega', 'g-footer', 'g-sticky', 'g-sponsor']);
  const SUFFIXES = [['-desktop', 'desktop'], ['-desk', 'desktop'], ['-d', 'desktop'],
                    ['-mobile', 'mobile'], ['-mob', 'mobile'], ['-m', 'mobile']];

  function classify(id) {
    if (ALIAS[id]) return ALIAS[id];
    for (const [s, dev] of SUFFIXES) if (id.endsWith(s)) return { base: id.slice(0, -s.length), device: dev };
    return { base: id, device: DESKTOP_ONLY.has(id) ? 'desktop' : 'mobile' };
  }
  function cleanLabel(l) {
    if (!l) return '';
    return String(l).replace(/\s*·\s*(Mobile|Desktop)\b.*$/i, '').trim() || String(l);
  }

  // Submenu de área (drill-down do menu) — espelha a IA/SUBNAV do design.
  // Clicar um item do menu (topo/drawer) abre um painel com as subpáginas.
  const SUBNAV = {
    'O Clube': [
      ['Abertura · 70 anos', 'clube-70'], ['Governança / Diretoria', 'clube-dir'],
      ['Clubes conveniados', 'rev-conv-list'], ['Meio Ambiente · Marina Viva', 'clube-ambiente'],
      ['Sedes & estrutura', 'clube-onde'], ['Estrutura / instalações', 'clube-estrutura'],
      ['Flats', 'rev-flats-m'], ['Imprensa · Press kit', 'clube-imprensa'],
    ],
    'Vida': [
      ['Esportes › Náutica & Vela', 'vida-esportes'], ['Semana de Vela · SIVI', 'naut-semana'],
      ['Agenda', 'vida-agenda'], ['Eventos sociais', 'eventos-sociais'],
      ['Gastronomia', 'gastronomia'], ['Newsletter semanal', 'vida-newsletter'],
      ['Notícias', 'rev-noticias'],
    ],
    'Notícias': [
      ['Portal de Notícias', 'rev-noticias'], ['Todas as notícias', 'vida-news'],
      ['Newsletter semanal', 'vida-newsletter'],
    ],
    'Serviços': [
      ['Índice de Serviços', 'serv-index'], ['Marina & Combustíveis', 'serv-marina'],
      ['Travessia da balsa', 'serv-travessia'], ['Taxas & Regulamentos', 'serv-taxas'],
      ['Horários & Links úteis', 'serv-horarios'],
    ],
  };
  // Texto do item de menu → chave de área do SUBNAV.
  const MENU_AREAS = [
    [/^o clube$/i, 'O Clube'], [/vida no iate clube/i, 'Vida'],
    [/^not[íi]cias$/i, 'Notícias'], [/^servi[çc]os$/i, 'Serviços'],
  ];

  // ---- walk: <DCSection> → <DCArtboard> (achata Fragments/arrays/nulls) ----
  function collectSections(children) {
    const sections = [];
    React.Children.forEach(children, (sec) => {
      if (!sec || sec.type !== window.DCSection) return;
      const sid = sec.props.id ?? sec.props.title;
      const arts = [];
      const pushArt = (node) => {
        React.Children.forEach(node, (a) => {
          if (!a) return;
          if (Array.isArray(a)) { a.forEach(pushArt); return; }
          if (a.type === React.Fragment) { pushArt(a.props.children); return; }
          if (a.type === window.DCArtboard) {
            const id = a.props.id ?? a.props.label;
            if (id) arts.push({ id, label: a.props.label, el: a, width: a.props.width, height: a.props.height });
          }
        });
      };
      pushArt(sec.props.children);
      sections.push({ id: sid, title: sec.props.title, subtitle: sec.props.subtitle, artboards: arts });
    });
    return sections;
  }

  function buildPages(sections) {
    const pages = [], byKey = {}, idxByArt = {};
    sections.forEach((sec) => {
      sec.artboards.forEach((a) => {
        const { base, device } = classify(a.id);
        const key = sec.id + '::' + base;
        let pg = byKey[key];
        if (!pg) {
          pg = { key, sectionId: sec.id, sectionTitle: sec.title, label: cleanLabel(a.label),
                 mobile: null, desktop: null, labelSet: false, order: pages.length };
          byKey[key] = pg; pages.push(pg);
        }
        pg[device] = a;
        if (device === 'mobile' || !pg.labelSet) { pg.label = cleanLabel(a.label); if (device === 'mobile') pg.labelSet = true; }
        idxByArt[a.id] = pg.order;
      });
    });
    return { pages, idxByArt };
  }

  // ====================================================================
  // Player de apresentação
  // ====================================================================
  function Player({ pages, idxByArt, onDesigner }) {
    const startIdx = React.useMemo(() => {
      // deep-link via ?screen=ID ; senão Home
      try {
        const p = new URLSearchParams(location.search).get('screen');
        if (p && idxByArt[p] != null) return idxByArt[p];
      } catch {}
      return idxByArt['home-hibrida'] ?? 0;
    }, [idxByArt]);

    const [idx, setIdx] = React.useState(startIdx);
    const [device, setDevice] = React.useState('mobile');
    const [indexOpen, setIndexOpen] = React.useState(false);
    const [mapOpen, setMapOpen] = React.useState(false);
    const [submenu, setSubmenu] = React.useState(null); // área aberta no submenu (drill-down)
    const [guideOpen, setGuideOpen] = React.useState(() => {
      try { return localStorage.getItem('yci-guide-seen') !== '1'; } catch { return true; }
    });
    const [viewMode, setViewMode] = React.useState('full'); // 'full' (viewport cheio) | 'frame' (moldura)
    const [anchored, setAnchored] = React.useState([]);
    // comentários
    const sbCfg = SB();
    const [comments, setComments] = React.useState([]);
    const [commentMode, setCommentMode] = React.useState(false);
    const [composing, setComposing] = React.useState(null); // {xp,yp,cx,cy}
    const [activePin, setActivePin] = React.useState(null);  // {id,cx,cy}
    const [panelOpen, setPanelOpen] = React.useState(false);
    const [sbErr, setSbErr] = React.useState(false);
    const authorRef = React.useRef((() => { try { return localStorage.getItem('yci-author') || ''; } catch { return ''; } })());
    const bodyInpRef = React.useRef(null);
    const nameInpRef = React.useRef(null);

    const stageRef = React.useRef(null);
    const viewportRef = React.useRef(null);
    const contentRef = React.useRef(null);

    const page = pages[idx];
    const shownDevice = page[device] ? device : (page.mobile ? 'mobile' : 'desktop');
    const art = page[shownDevice];
    const isFallback = shownDevice !== device;
    const coordHots = ((art && HOTSPOTS[art.id]) || []).filter((h) => !/logo|entrar|jornada/i.test(h.label || ''));
    const allHots = [...anchored, ...coordHots];

    const go = (d) => setIdx((i) => (i + d + pages.length) % pages.length);
    const goToArt = (id) => { const t = idxByArt[id]; if (t != null) { setIdx(t); setIndexOpen(false); setSubmenu(null); } };
    const closeGuide = () => { setGuideOpen(false); try { localStorage.setItem('yci-guide-seen', '1'); } catch {} };

    // ---- comentários: carregar do Supabase uma vez ----
    React.useEffect(() => {
      if (!sbCfg) return;
      let off = false;
      sbList(sbCfg).then((rows) => { if (!off) setComments(rows || []); }).catch(() => { if (!off) setSbErr(true); });
      return () => { off = true; };
    }, []);

    const screenComments = art ? comments.filter((c) => c.screen_id === art.id) : [];

    const startComment = (e) => {
      if (!commentMode || !art) return;
      if (e.target.closest('.yci-cpin')) return;
      const r = contentRef.current.getBoundingClientRect();
      const xp = Math.max(0, Math.min(100, (e.clientX - r.left) / r.width * 100));
      const yp = Math.max(0, Math.min(100, (e.clientY - r.top) / r.height * 100));
      setActivePin(null);
      setComposing({ xp, yp, cx: e.clientX, cy: e.clientY });
    };
    const cancelCompose = () => setComposing(null);
    const submitCompose = async () => {
      const body = (bodyInpRef.current && bodyInpRef.current.value || '').trim();
      if (!body || !sbCfg || !art) { setComposing(null); return; }
      let author = authorRef.current;
      if (!author && nameInpRef.current) author = (nameInpRef.current.value || '').trim();
      if (author) { authorRef.current = author; try { localStorage.setItem('yci-author', author); } catch {} }
      const row = { board: sbCfg.board, screen_id: art.id, device: shownDevice, x: composing.xp, y: composing.yp, author: author || 'Anônimo', body };
      setComposing(null);
      try { const saved = await sbInsert(sbCfg, row); setComments((cs) => [...cs, saved]); }
      catch { setSbErr(true); }
    };
    const toggleResolve = async (c) => {
      try { const u = await sbPatch(sbCfg, c.id, { resolved: !c.resolved }); setComments((cs) => cs.map((x) => x.id === c.id ? u : x)); } catch { setSbErr(true); }
    };
    const removeComment = async (c) => {
      try { await sbDelete(sbCfg, c.id); setComments((cs) => cs.filter((x) => x.id !== c.id)); setActivePin(null); } catch { setSbErr(true); }
    };
    const jumpToComment = (c) => {
      const t = idxByArt[c.screen_id]; if (t == null) return;
      const pg = pages[t];
      if (pg.mobile && pg.mobile.id === c.screen_id) setDevice('mobile');
      else if (pg.desktop && pg.desktop.id === c.screen_id) setDevice('desktop');
      setIdx(t); setPanelOpen(false); setCommentMode(true);
      setTimeout(() => setActivePin({ id: c.id, cx: 0, cy: 0 }), 60);
    };

    // ---- ajuste de zoom/altura do device frame (imperativo, sem loop) ----
    const fit = React.useCallback(() => {
      const stage = stageRef.current, vp = viewportRef.current, content = contentRef.current;
      if (!stage || !vp || !content) return;
      const screen = content.querySelector('.wf-screen');
      if (!screen) return;
      content.style.zoom = '1';
      const sw = screen.offsetWidth, sh = screen.offsetHeight;
      const cs = getComputedStyle(stage);
      const availW = stage.clientWidth - parseFloat(cs.paddingLeft) - parseFloat(cs.paddingRight);
      const availH = stage.clientHeight - parseFloat(cs.paddingTop) - parseFloat(cs.paddingBottom);
      let zoom, vh;
      if (viewMode === 'full') {
        // viewport cheio: preenche a largura (no máx. 1:1), rola a altura toda
        zoom = Math.max(0.2, Math.min(1, availW / sw));
        vh = availH;
        vp.style.width = (sw * zoom) + 'px';
        vp.style.height = vh + 'px';
      } else {
        const bz = shownDevice === 'mobile' ? { w: 16, h: 34 } : { w: 2, h: 36 };
        zoom = Math.max(0.2, Math.min(1, (availW - bz.w) / sw));
        vh = shownDevice === 'mobile' ? Math.min(availH - bz.h, 900) : (availH - bz.h);
        vp.style.width = (sw * zoom) + 'px';
        vp.style.height = Math.max(140, Math.min(vh, sh * zoom)) + 'px';
      }
      content.style.zoom = String(zoom);
    }, [shownDevice, viewMode]);

    // Hotspots de alta frequência ancorados aos elementos REAIS (precisão por
    // pixel, independente da altura da tela): logo → Home, "Entrar" → Login, e
    // as jornadas da Home. Os demais hotspots continuam por coordenada (mapa).
    // Tiles de jornada da Home (.wf-jt) → destino por texto.
    const TILE_MAP = [
      [/sou s[óo]cio/i, 'socio-login-rec', 'Sou sócio → Área do Sócio'],
      [/associ/i, 'assoc-mob', 'Quero me associar → Associe-se'],
      [/visito/i, 'clube-70', 'Visito o clube → O Clube'],
      [/velejo|compito/i, 'vida-esportes', 'Velejo / compito → Esportes'],
    ];
    // Tiles de hub (.wf-jt) ESCOPADOS por tela — evita ambiguidade (ex.: "Travessia"
    // existe em Serviços e no painel do Sócio com destinos diferentes).
    const HUB_TILES = {
      'serv-index': [[/marina/i, 'serv-marina'], [/combust/i, 'serv-marina'], [/travessia/i, 'serv-travessia'],
                     [/taxas/i, 'serv-taxas'], [/regulamento/i, 'serv-taxas'], [/links? [úu]te/i, 'serv-horarios']],
      'socio-home': [[/documento/i, 'socio-docs'], [/ouvidoria/i, 'socio-ouv'], [/travessia|vagas|seco/i, 'socio-travessia']],
      'naut-hub': [[/classes/i, 'esp-classes']],
    };
    // Painel do sócio: abas do topo (7) + bottom-nav (.wf-botnav) → página (texto EXATO).
    // Sprint Revisão Crítica: nav estável — Enquetes/Entregas entram; "Mais" agrupa o resto.
    const SOCIO_NAV = [
      [/^in[íi]cio$/i, 'socio-home'], [/^comunicados?$/i, 'socio-com'],
      [/^documentos?$/i, 'socio-docs'], [/^enquetes?$/i, 'rev-enquetes'],
      [/^entregas?$/i, 'rev-kanban'], [/^ouvidoria$/i, 'socio-ouv'], [/^[ée]tica$/i, 'socio-etica'],
    ];
    // Coluna "Navegar" + Imprensa + sedes do rodapé → seção (por texto do link).
    // (Gastronomia saiu do rodapé na Revisão Crítica — vive sob Vida; entrada via Vida.)
    const FOOT_MAP = [
      [/^o clube$/i, 'clube-70'], [/vida no iate clube/i, 'vida-esportes'],
      [/^not[íi]cias$/i, 'rev-noticias'],
      [/^servi[çc]os$/i, 'serv-index'], [/associe-se/i, 'assoc-mob'], [/imprensa/i, 'clube-imprensa'],
      [/ilhabela|s[ãa]o sebasti|saco do sombrio|sede sp/i, 'clube-onde'],
    ];
    const baseId = (id) => (id || '').replace(/-d$/, '');
    const navText = (el) => (el.textContent || '').replace(/[▾▼]/g, '').trim();
    const measureAnchors = React.useCallback(() => {
      const content = contentRef.current;
      const screen = content && content.querySelector('.wf-screen');
      if (!screen) { setAnchored([]); return; }
      const sr = screen.getBoundingClientRect();
      if (!sr.width || !sr.height) { setAnchored([]); return; }
      const curId = baseId(art && art.id);
      const pct = (el) => { const r = el.getBoundingClientRect(); return {
        top: (r.top - sr.top) / sr.height * 100, left: (r.left - sr.left) / sr.width * 100,
        width: r.width / sr.width * 100, height: r.height / sr.height * 100 }; };
      const out = [];
      const add = (el, to, label) => { if (el && to) out.push({ ...pct(el), to, label }); };
      // logo → Início (apenas no topo)
      const logo = screen.querySelector('.wf-logo');
      if (logo) { const p = pct(logo); if (p.top > -2 && p.top < 60) out.push({ ...p, to: 'home-hibrida', label: 'Logo · Início' }); }
      // "Entrar / Área do Sócio" (botão do header) → Login
      const entrar = Array.from(screen.querySelectorAll('.wf-btn')).find((b) => /(entrar|área do s[óo]cio)/i.test(b.textContent || ''));
      if (entrar) { const p = pct(entrar); if (p.top > -2 && p.top < 60) out.push({ ...p, to: 'socio-login-rec', label: 'Entrar · Área do Sócio' }); }
      // "Associe-se" (CTA do header) → Associe-se
      const assoc = Array.from(screen.querySelectorAll('.wf-btn')).find((b) => /^associe-se$/i.test((b.textContent || '').trim()));
      if (assoc) { const p = pct(assoc); if (p.top > -2 && p.top < 80) out.push({ ...p, to: 'assoc-mob', label: 'Associe-se' }); }
      // tiles de jornada da Home
      screen.querySelectorAll('.wf-jt').forEach((t) => {
        const m = TILE_MAP.find(([re]) => re.test(t.textContent || ''));
        if (m) add(t, m[1], 'Jornada · ' + m[2]);
      });
      // MENU do topo (.wf-nav) → abre o SUBMENU da área (drill-down às subpáginas)
      screen.querySelectorAll('.wf-nav a').forEach((a) => {
        const m = MENU_AREAS.find(([re]) => re.test(navText(a)));
        if (m) out.push({ ...pct(a), menu: m[1], label: 'Menu · ' + navText(a) });
      });
      // HAMBÚRGUER (mobile) → abre o drawer
      const burger = screen.querySelector('.wf-burger');
      if (burger) add(burger, 'g-menu-mob', 'Abrir menu');
      // DRAWER mobile (g-menu-mob): área → submenu; "Área do Sócio" → login direto
      if (curId === 'g-menu-mob') {
        screen.querySelectorAll('.wf-h.sm').forEach((el) => {
          const t = navText(el), box = el.closest('.wf-between') || el;
          const m = MENU_AREAS.find(([re]) => re.test(t));
          if (m) out.push({ ...pct(box), menu: m[1], label: 'Menu · ' + t });
          else if (/área do s[óo]cio/i.test(t)) add(box, 'socio-login-rec', 'Menu · Área do Sócio');
        });
      }
      // TILES de hub (Serviços / painel do sócio) → página específica
      const hub = HUB_TILES[curId];
      if (hub) screen.querySelectorAll('.wf-jt').forEach((t) => {
        const m = hub.find(([re]) => re.test(t.textContent || ''));
        if (m) add(t, m[1], 'Tile · ' + (t.textContent || '').replace(/acessar.*/i, '').trim());
      });
      // PAINEL DO SÓCIO (telas com SocioShell): abas do topo (.wf-h.sm) + bottom-nav
      // (.wf-botnav .it) → página, por texto EXATO; + "Sair" → Início.
      if (screen.querySelector('.wf-botnav')) {
        screen.querySelectorAll('.wf-botnav .it, .wf-h.sm').forEach((el) => {
          const m = SOCIO_NAV.find(([re]) => re.test((el.textContent || '').trim()));
          if (m) add(el, m[1], 'Sócio · ' + (el.textContent || '').trim());
        });
        const sair = Array.from(screen.querySelectorAll('.wf-data, .wf-t')).find((el) => /^sair$/i.test((el.textContent || '').trim()));
        if (sair) add(sair, 'home-hibrida', 'Sair → Início');
      }
      // RODAPÉ: coluna "Navegar" + Imprensa (links de texto) → seção. Presente em
      // quase toda tela, dá navegação de site mesmo no mobile (sem o menu desktop).
      screen.querySelectorAll('.wf-foot .wf-t').forEach((el) => {
        const m = FOOT_MAP.find(([re]) => re.test((el.textContent || '').trim()));
        if (m) add(el, m[1], 'Rodapé · ' + (el.textContent || '').trim());
      });
      const assinar = Array.from(screen.querySelectorAll('.wf-foot .wf-btn')).find((b) => /assinar/i.test(b.textContent || ''));
      if (assinar) add(assinar, 'vida-newsletter', 'Rodapé · Newsletter');
      // CTA "Quero me associar" (landing Associe-se) → fluxo de lead (LGPD)
      const leadCta = Array.from(screen.querySelectorAll('.wf-btn')).find((b) => /quero me associar/i.test(b.textContent || ''));
      if (leadCta) add(leadCta, 'rev-lead', 'Quero me associar → Lead');
      setAnchored(out);
    }, [art]);

    React.useLayoutEffect(() => { fit(); measureAnchors(); if (viewportRef.current) viewportRef.current.scrollTop = 0; }, [idx, device, viewMode, fit, measureAnchors]);
    React.useEffect(() => {
      const onR = () => { fit(); measureAnchors(); };
      window.addEventListener('resize', onR);
      window.addEventListener('load', onR);
      const ro = ('ResizeObserver' in window) ? new ResizeObserver(onR) : null;
      if (ro && contentRef.current) ro.observe(contentRef.current);
      const t = setTimeout(onR, 250); // re-medir após fontes/imagens (a altura muda)
      return () => { window.removeEventListener('resize', onR); window.removeEventListener('load', onR); if (ro) ro.disconnect(); clearTimeout(t); };
    }, [fit, measureAnchors, idx, device]);

    // ---- teclado ----
    React.useEffect(() => {
      const onKey = (e) => {
        if (e.key === 'ArrowRight') { e.preventDefault(); go(1); }
        else if (e.key === 'ArrowLeft') { e.preventDefault(); go(-1); }
        else if (e.key === 'Escape') { setIndexOpen(false); setGuideOpen(false); setMapOpen(false); setSubmenu(null); }
      };
      document.addEventListener('keydown', onKey);
      return () => document.removeEventListener('keydown', onKey);
    });

    const onStageClick = (e) => {
      if (e.target.closest('.yci-hot') || e.target.closest('.yci-cpin') || e.target.closest('.yci-composer') || e.target.closest('.yci-cpop')) return;
      if (composing || activePin) { setComposing(null); setActivePin(null); }
    };

    const Ico = ({ d, ...p }) => (
      <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round" {...p}><path d={d} /></svg>
    );

    return (
      <div className="yci-proto">
        {/* ---------- Barra ---------- */}
        <div className="yci-bar">
          <div className="yci-brand">
            <img className="mono" src="assets/bertolani-mono-blue.svg" alt="Bertolani" />
            <span className="nm">Novo Site YCI</span>
            <span className="wf">Wireframe · Fase 1</span>
          </div>
          <div className="yci-now">
            <span className="t">{page.label}</span>
            <span className="s">{idx + 1} / {pages.length} · {page.sectionTitle}</span>
          </div>
          <span className="sp" />
          <div className="yci-seg" role="group" aria-label="Dispositivo">
            <button className={device === 'mobile' ? 'on' : ''} onClick={() => setDevice('mobile')} title="Visualizar em mobile (~390)">
              <Ico d="M7 4h10v16H7zM11 18h2" /><span className="yci-label-collapse">Mobile</span>
            </button>
            <button className={device === 'desktop' ? 'on' : ''} onClick={() => setDevice('desktop')} title="Visualizar em desktop (~1240)">
              <Ico d="M3 5h18v11H3zM9 20h6M12 16v4" /><span className="yci-label-collapse">Desktop</span>
            </button>
          </div>
          <button className="yci-btn" onClick={() => setViewMode((m) => (m === 'full' ? 'frame' : 'full'))}
            title={viewMode === 'full' ? 'Mostrar com moldura (celular / desktop)' : 'Ver em tela cheia (sem moldura)'}>
            {viewMode === 'full'
              ? <Ico d="M7 4h10v16H7zM11 18h2" />
              : <Ico d="M15 3h6v6M9 21H3v-6M21 3l-7 7M3 21l7-7" />}
            <span className="yci-label-collapse">{viewMode === 'full' ? 'Moldura' : 'Tela cheia'}</span>
          </button>
          {sbCfg && (
            <React.Fragment>
              <button className={'yci-btn' + (commentMode ? ' on' : '')} onClick={() => { setCommentMode((m) => !m); setComposing(null); setActivePin(null); }} title="Modo comentar: clique na tela para deixar um comentário">
                <Ico d="M21 11.5a8.38 8.38 0 01-.9 3.8 8.5 8.5 0 01-7.6 4.7 8.38 8.38 0 01-3.8-.9L3 21l1.9-5.7a8.38 8.38 0 01-.9-3.8 8.5 8.5 0 014.7-7.6 8.38 8.38 0 013.8-.9h.5a8.48 8.48 0 018 8v.5z" /><span className="yci-label-collapse">Comentar</span>
              </button>
              <button className="yci-btn" onClick={() => setPanelOpen(true)} title="Ver todos os comentários">
                <Ico d="M21 15a2 2 0 01-2 2H7l-4 4V5a2 2 0 012-2h14a2 2 0 012 2z" /><span className="yci-label-collapse">Comentários</span>
                {comments.length ? <span className="yci-badge">{comments.length}</span> : null}
              </button>
            </React.Fragment>
          )}
          <button className="yci-btn" onClick={() => setMapOpen(true)} title="Mapa do site (arquitetura + fluxo)">
            <Ico d="M9 18l-6 3V6l6-3 6 3 6-3v15l-6 3-6-3zM9 3v15M15 6v15" /><span className="yci-label-collapse">Mapa</span>
          </button>
          <button className="yci-btn" onClick={() => setIndexOpen(true)} title="Índice de telas (sitemap)">
            <Ico d="M8 6h13M8 12h13M8 18h13M3 6h.01M3 12h.01M3 18h.01" /><span className="yci-label-collapse">Índice</span>
          </button>
          <button className="yci-btn ghost" onClick={() => setGuideOpen(true)} title="Como navegar e comentar">
            <Ico d="M12 17h.01M12 13a2 2 0 10-2-3M12 21a9 9 0 110-18 9 9 0 010 18z" />
          </button>
          <button className="yci-btn ghost" onClick={onDesigner} title="Ver o canvas do designer (com notas)">
            <Ico d="M3 3h18v14H3zM8 21h8M12 17v4" /><span className="yci-label-collapse">Designer</span>
          </button>
        </div>

        {/* ---------- Palco ---------- */}
        <div className={'yci-stage' + (viewMode === 'full' ? ' full' : '')} ref={stageRef} onClick={onStageClick}>
          <button className="yci-arrow prev" onClick={(e) => { e.stopPropagation(); go(-1); }} title="Tela anterior (←)">
            <Ico d="M15 18l-6-6 6-6" />
          </button>

          <div className={'yci-device is-' + shownDevice + (viewMode === 'full' ? ' frameless' : '')} onClick={(e) => e.stopPropagation()}>
            {viewMode === 'frame' && (shownDevice === 'mobile'
              ? <div className="yci-notch"><i /></div>
              : <div className="yci-chrome"><div className="dots"><i /><i /><i /></div><div className="url">yci.org.br{art ? ' / ' + art.id : ''}</div></div>)}
            <div className="yci-viewport" ref={viewportRef}
              onScroll={() => { if (composing) setComposing(null); if (activePin) setActivePin(null); }}>
              <div className={'yci-content' + (commentMode ? ' yci-commenting' : '')} ref={contentRef}
                key={art ? art.id : 'none'} onClick={startComment}>
                {art ? art.el.props.children : null}
                <div className="yci-hotlayer">
                  {allHots.map((h, i) => (
                    <button key={i} className="yci-hot" title={h.label}
                      style={{ top: h.top + '%', left: h.left + '%', width: h.width + '%', height: h.height + '%' }}
                      onClick={(e) => { e.stopPropagation(); if (h.menu) setSubmenu(h.menu); else goToArt(h.to); }} />
                  ))}
                </div>
                <div className="yci-commentlayer">
                  {screenComments.map((c, i) => (
                    <button key={c.id} data-cid={c.id} className={'yci-cpin' + (c.resolved ? ' done' : '') + (activePin && activePin.id === c.id ? ' open' : '')}
                      style={{ top: c.y + '%', left: c.x + '%' }} title={(c.author || 'Anônimo') + ': ' + c.body}
                      onClick={(e) => { e.stopPropagation(); setComposing(null); setActivePin(activePin && activePin.id === c.id ? null : { id: c.id, cx: e.clientX, cy: e.clientY }); }}>{i + 1}</button>
                  ))}
                </div>
              </div>
            </div>
            {isFallback && <div className="yci-fallback">{device === 'desktop' ? 'Desktop não desenhado · mobile-first' : 'Tela de desktop'}</div>}
          </div>

          <button className="yci-arrow next" onClick={(e) => { e.stopPropagation(); go(1); }} title="Próxima tela (→)">
            <Ico d="M9 18l6-6-6-6" />
          </button>

          <div className="yci-count">{page.label} · {idx + 1}/{pages.length}</div>
        </div>

        {/* ---------- Guia rápido ---------- */}
        {guideOpen && (
          <div className="yci-guide" onClick={(e) => e.stopPropagation()}>
            <button className="cl" onClick={closeGuide} title="Fechar">×</button>
            <h4>Como navegar</h4>
            <div className="wfn">Protótipo de wireframe · baixa fidelidade</div>
            <ol>
              <li>Veja em <b>Tela cheia</b> ou com <b>Moldura</b>, e troque entre <b>Mobile</b> e <b>Desktop</b> no topo.</li>
              <li><b>Navegue clicando</b> nos botões e links das telas, como num site — os fluxos principais já respondem. Use <b>← →</b> ou o <b>Índice</b> para ir a qualquer tela.</li>
              <li><b>Comentar:</b> clique em <b>Comentar</b> e depois em qualquer ponto da tela; <b>Comentários</b> lista tudo.</li>
            </ol>
            <div className="note">É um wireframe: sem cor, foto ou fonte final. O feedback aqui é sobre <b>estrutura e fluxo</b> — nem toda tela está ligada.</div>
          </div>
        )}

        {/* ---------- Índice (árvore por sitemap) ---------- */}
        {indexOpen && (
          <>
            <div className="yci-scrim" onClick={() => setIndexOpen(false)} />
            <IndexDrawer idxByArt={idxByArt} pages={pages} current={idx} onPick={(t) => { setIdx(t); setIndexOpen(false); }} />
          </>
        )}

        {/* ---------- Mapa do site (visual) ---------- */}
        {mapOpen && (
          <SiteMapView idxByArt={idxByArt} pages={pages} current={idx}
            onPick={(t) => { setIdx(t); setMapOpen(false); }} onClose={() => setMapOpen(false)} />
        )}

        {submenu && (
          <SubMenuPanel area={submenu} items={SUBNAV[submenu] || []} idxByArt={idxByArt}
            onPick={(id) => goToArt(id)} onClose={() => setSubmenu(null)} />
        )}

        {/* fecha composer/popover ao clicar fora */}
        {(composing || activePin) && <div className="yci-popscrim" onClick={() => { setComposing(null); setActivePin(null); }} />}

        {/* ---------- Comentários: composer ---------- */}
        {composing && (
          <div className="yci-composer" onClick={(e) => e.stopPropagation()}
            style={{ top: Math.min(composing.cy, window.innerHeight - 220), left: Math.min(composing.cx, window.innerWidth - 268) }}>
            <div className="hd">Novo comentário · {page.label}</div>
            {!authorRef.current && <input ref={nameInpRef} className="ci" placeholder="Seu nome" />}
            <textarea ref={bodyInpRef} className="cb" placeholder="Comente sobre esta tela…" autoFocus
              onKeyDown={(e) => { if (e.key === 'Enter' && (e.metaKey || e.ctrlKey)) submitCompose(); else if (e.key === 'Escape') cancelCompose(); }} />
            <div className="row"><button onClick={cancelCompose}>Cancelar</button><button className="primary" onClick={submitCompose}>Comentar</button></div>
          </div>
        )}

        {/* ---------- Comentários: popover ---------- */}
        {activePin && (() => {
          const c = comments.find((x) => x.id === activePin.id);
          if (!c) return null;
          const left = Math.min(Math.max(12, activePin.cx || 80), window.innerWidth - 268);
          const top = Math.min(Math.max(70, activePin.cy || 90), window.innerHeight - 190);
          return (
            <div className="yci-cpop" onClick={(e) => e.stopPropagation()} style={{ top, left }}>
              <div className="hd"><b>{c.author || 'Anônimo'}</b><span>{fmtTime(c.created_at)}</span></div>
              <div className="bd">{c.body}</div>
              <div className="row">
                <button onClick={() => toggleResolve(c)}>{c.resolved ? 'Reabrir' : 'Resolver'}</button>
                <button className="del" onClick={() => removeComment(c)}>Excluir</button>
                <button className="primary" onClick={() => setActivePin(null)}>Fechar</button>
              </div>
            </div>
          );
        })()}

        {/* ---------- Comentários: painel (lista central) ---------- */}
        {panelOpen && (
          <>
            <div className="yci-scrim" onClick={() => setPanelOpen(false)} />
            <CommentsPanel comments={comments} pages={pages} idxByArt={idxByArt} onJump={jumpToComment} />
          </>
        )}

        {sbErr && <div className="yci-sberr" onClick={() => setSbErr(false)}>Comentários offline — não foi possível conectar. (toque para ocultar)</div>}
      </div>
    );
  }

  // Submenu de área (drill-down do menu): painel com as subpáginas da área clicada.
  function SubMenuPanel({ area, items, idxByArt, onPick, onClose }) {
    const title = area === 'Vida' ? 'Vida no Iate Clube' : area;
    return ReactDOM.createPortal(
      <div className="yci-submenu" onClick={onClose}>
        <div className="card" onClick={(e) => e.stopPropagation()}>
          <div className="hd"><span className="t">{title}</span>
            <button className="cl" onClick={onClose} title="Fechar (Esc)">×</button></div>
          <div className="bd">
            {items.filter(([, id]) => idxByArt[id] != null).map(([label, id]) => (
              <button key={id + label} className="it" onClick={() => onPick(id)}>
                <span className="lb">{label}</span><span className="ar">›</span>
              </button>
            ))}
          </div>
        </div>
      </div>,
      document.body
    );
  }

  // Índice em ÁRVORE espelhando window.YCI_SITEMAP (áreas → páginas → subpáginas).
  function IndexDrawer({ idxByArt, pages, current, onPick }) {
    const SITEMAP = (typeof window !== 'undefined' && window.YCI_SITEMAP) || [];
    const [open, setOpen] = React.useState(() => {
      const o = {}; SITEMAP.forEach((a, i) => { o[i] = !a.internal; }); return o;
    });
    const refIdx = new Set();
    const walk = (ns) => ns.forEach((n) => { if (n.id != null && idxByArt[n.id] != null) refIdx.add(idxByArt[n.id]); if (n.children) walk(n.children); });
    SITEMAP.forEach((a) => a.children && walk(a.children));
    const outras = pages.filter((p) => !refIdx.has(p.order));

    const renderNode = (n, depth, key) => {
      const t = n.id != null ? idxByArt[n.id] : null;
      const pg = t != null ? pages[t] : null;
      const kids = n.children || [];
      const pad = { paddingLeft: 12 + depth * 15 };
      return (
        <div key={key} className="tnode">
          {pg
            ? <button className={'it' + (t === current ? ' on' : '')} style={pad} onClick={() => onPick(t)}>
                <span className="txt"><span className="lb">{n.label}</span>{n.route && <span className="rt">{n.route}</span>}</span>
                <span className="dv">{(pg.mobile ? 'M' : '') + (pg.desktop ? 'D' : '')}</span>
              </button>
            : <div className="subhd" style={pad}>{n.label}{n.route && <span className="rt">{n.route}</span>}</div>}
          {kids.length > 0 && <div className="tkids">{kids.map((k, i) => renderNode(k, depth + 1, key + '.' + i))}</div>}
        </div>
      );
    };

    return (
      <div className="yci-index yci-tree" onClick={(e) => e.stopPropagation()}>
        <div className="hd">
          <div className="t">Índice · por área do site</div>
          <div className="s">{pages.length} telas · {SITEMAP.filter((a) => !a.internal).length} áreas</div>
        </div>
        <div className="bd">
          {SITEMAP.map((area, ai) => (
            <div key={ai} className={'area' + (area.internal ? ' internal' : '')}>
              <button className="ahd" onClick={() => setOpen((o) => ({ ...o, [ai]: !o[ai] }))}>
                <span className="chev">{open[ai] ? '▾' : '▸'}</span>
                <span className="an">{area.area}</span>
                {area.route && <span className="ar">{area.route}</span>}
              </button>
              {open[ai] && <div className="abd">{(area.children || []).map((n, i) => renderNode(n, 0, ai + '.' + i))}</div>}
            </div>
          ))}
          {outras.length > 0 && (
            <div className="area">
              <div className="ahd static"><span className="an">Outras</span><span className="ar">não mapeadas no sitemap</span></div>
              <div className="abd">{outras.map((p) => (
                <button key={p.key} className={'it' + (p.order === current ? ' on' : '')} style={{ paddingLeft: 12 }} onClick={() => onPick(p.order)}>
                  <span className="txt"><span className="lb">{p.label}</span></span>
                  <span className="dv">{(p.mobile ? 'M' : '') + (p.desktop ? 'D' : '')}</span>
                </button>
              ))}</div>
            </div>
          )}
        </div>
      </div>
    );
  }

  // Mapa do site VISUAL — colunas por área, subpáginas indentadas (fluxo pai→filho).
  function SiteMapView({ idxByArt, pages, current, onPick, onClose }) {
    const SITEMAP = (typeof window !== 'undefined' && window.YCI_SITEMAP) || [];
    const renderCard = (n, depth, key) => {
      const t = n.id != null ? idxByArt[n.id] : null;
      const pg = t != null ? pages[t] : null;
      const kids = n.children || [];
      return (
        <React.Fragment key={key}>
          <button className={'mc' + (t === current ? ' on' : '') + (pg ? '' : ' grp')}
            style={{ marginLeft: depth * 14 }} onClick={() => pg && onPick(t)} disabled={!pg}>
            <span className="mlb">{n.label}</span>
            {pg && <span className="mdv">{(pg.mobile ? 'M' : '') + (pg.desktop ? 'D' : '')}</span>}
          </button>
          {kids.map((k, i) => renderCard(k, depth + 1, key + '.' + i))}
        </React.Fragment>
      );
    };
    return ReactDOM.createPortal(
      <div className="yci-mapwrap" onClick={onClose}>
        <div className="yci-maphd">
          <span className="t">Mapa do site · arquitetura &amp; fluxo</span>
          <span className="s">clique numa tela para abrir</span>
          <button className="cl" onClick={onClose} title="Fechar (Esc)">×</button>
        </div>
        <div className="yci-mapcols" onClick={(e) => e.stopPropagation()}>
          {SITEMAP.map((area, ai) => (
            <div key={ai} className={'mcol' + (area.internal ? ' internal' : '')}>
              <div className="mcolhd"><span className="mn">{area.area}</span>{area.route && <span className="mr">{area.route}</span>}</div>
              <div className="mcolbd">{(area.children || []).map((n, i) => renderCard(n, 0, ai + '.' + i))}</div>
            </div>
          ))}
        </div>
      </div>,
      document.body
    );
  }

  function CommentsPanel({ comments, pages, idxByArt, onJump }) {
    const groups = {}; const order = [];
    comments.forEach((c) => { if (!groups[c.screen_id]) { groups[c.screen_id] = []; order.push(c.screen_id); } groups[c.screen_id].push(c); });
    const label = (sid) => { const t = idxByArt[sid]; return t != null ? pages[t].label : sid; };
    const open = comments.filter((c) => !c.resolved).length;
    return (
      <div className="yci-index yci-cpanel" onClick={(e) => e.stopPropagation()}>
        <div className="hd">
          <div className="t">Comentários</div>
          <div className="s">{comments.length} no total · {open} em aberto · {order.length} telas</div>
        </div>
        <div className="bd">
          {comments.length === 0 && <div className="yci-cempty">Nenhum comentário ainda.<br />Ative <b>Comentar</b> no topo e clique em qualquer ponto de uma tela.</div>}
          {order.map((sid) => (
            <div key={sid}>
              <div className="sec">{label(sid)}</div>
              {groups[sid].map((c) => (
                <button key={c.id} className={'cit' + (c.resolved ? ' done' : '')} onClick={() => onJump(c)}>
                  <div className="ca">{c.author || 'Anônimo'} <span>· {fmtTime(c.created_at)}</span>{c.resolved ? <em> · resolvido</em> : null}</div>
                  <div className="cb">{c.body}</div>
                </button>
              ))}
            </div>
          ))}
        </div>
      </div>
    );
  }

  // ====================================================================
  // <Prototype> — decide entre player (cliente) e canvas (designer)
  // ====================================================================
  function Prototype({ children }) {
    const initDesigner = (() => { try { return new URLSearchParams(location.search).get('designer') === '1'; } catch { return false; } })();
    const [designer, setDesigner] = React.useState(initDesigner);

    const sections = React.useMemo(() => collectSections(children), [children]);
    const { pages, idxByArt } = React.useMemo(() => buildPages(sections), [sections]);

    if (designer) {
      return (
        <React.Fragment>
          {React.createElement(window.DesignCanvas, null, children)}
          <button className="yci-fab" onClick={() => setDesigner(false)} title="Voltar ao protótipo do cliente">‹ Protótipo</button>
        </React.Fragment>
      );
    }
    return <Player pages={pages} idxByArt={idxByArt} onDesigner={() => setDesigner(true)} />;
  }

  window.Prototype = Prototype;
})();
