/* bloo.app — "What's inside" deep-dive: interactive file explorer.
   Self-contained. Exports window.PackUI = { Explorer, FileBlock, FileViewer, fmtSize }.
   Content bodies come from window.PackRaw (lazy-loaded by the page). This file
   only carries structure + purposes; it never hard-codes file bodies. */
(function () {
const { Card, Badge, Switch, Input, Button } = window.BlooAppDesignSystem_ab7292;
const { Icon } = window.Site;
const { useT } = window.I18n;

/* ---- glyphs ---- */
const GChevron = (p) => <Icon d={<path d="M9 6l6 6-6 6" />} size={p.size || 14} sw={2} />;
const GFolder  = (p) => <Icon d={<path d="M3 7a2 2 0 012-2h4l2 2h8a2 2 0 012 2v8a2 2 0 01-2 2H5a2 2 0 01-2-2z" />} size={p.size || 15} sw={1.75} />;
const GFile    = (p) => <Icon d={<><path d="M14 2H6a2 2 0 00-2 2v16a2 2 0 002 2h12a2 2 0 002-2V8z" /><path d="M14 2v6h6" /></>} size={p.size || 15} sw={1.75} />;
const GSearch  = (p) => <Icon d={<><circle cx="11" cy="11" r="7" /><path d="M21 21l-4.35-4.35" /></>} size={p.size || 16} sw={1.9} />;
const GClose   = (p) => <Icon d={<path d="M18 6L6 18M6 6l12 12" />} size={p.size || 18} sw={2} />;

function fmtSize(b) {
  if (b == null) return '';
  return b < 1024 ? b + ' B' : (b / 1024).toFixed(1) + ' KB';
}

const exStyles = {
  /* file viewer panel (dark — reads as "the real file") */
  fileBlock: { border: '1px solid var(--border-subtle)', borderRadius: 'var(--radius-md)', overflow: 'hidden', background: 'var(--surface-inverse)', minWidth: 0, maxWidth: '100%' },
  fileBar: { display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 12, padding: '9px 12px 9px 14px', borderBottom: '1px solid rgba(255,255,255,.10)', background: 'rgba(255,255,255,.03)' },
  fileBarName: { fontFamily: 'var(--font-mono)', fontSize: 12, color: 'rgba(255,255,255,.72)', whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' },
  fileBarRight: { display: 'flex', alignItems: 'center', gap: 10, flex: 'none' },
  fileBarSize: { fontFamily: 'var(--font-mono)', fontSize: 11, color: 'rgba(255,255,255,.38)' },
  excerptTag: { fontFamily: 'var(--font-mono)', fontSize: 10, letterSpacing: '.08em', textTransform: 'uppercase', color: 'rgba(255,255,255,.5)', border: '1px solid rgba(255,255,255,.16)', borderRadius: 'var(--radius-full)', padding: '2px 8px', whiteSpace: 'nowrap' },
  fileFoot: { display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 12, padding: '10px 14px', borderTop: '1px solid rgba(255,255,255,.10)', background: 'rgba(255,255,255,.03)', fontFamily: 'var(--font-mono)', fontSize: 11.5, color: 'rgba(255,255,255,.72)', textDecoration: 'none' },
  filePre: { margin: 0, padding: '14px 16px', overflow: 'auto', fontFamily: 'var(--font-mono)', fontSize: 12.5, lineHeight: 1.65, color: 'rgba(255,255,255,.86)', whiteSpace: 'pre-wrap', wordBreak: 'break-word', tabSize: 2 },

  /* modal */
  scrim: { position: 'fixed', inset: 0, zIndex: 60, background: 'color-mix(in oklch, var(--neutral-900) 45%, transparent)', backdropFilter: 'blur(2px)', display: 'flex', alignItems: 'center', justifyContent: 'center', padding: 'var(--space-6)' },
  modal: { width: 'min(880px, 100%)', maxHeight: '86vh', display: 'flex', flexDirection: 'column', background: 'var(--surface-card)', borderRadius: 'var(--radius-xl)', boxShadow: 'var(--shadow-xl)', overflow: 'hidden' },
  modalHead: { display: 'flex', alignItems: 'flex-start', justifyContent: 'space-between', gap: 16, padding: '18px 20px', borderBottom: '1px solid var(--border-subtle)' },
  modalTitle: { fontFamily: 'var(--font-mono)', fontSize: 13, color: 'var(--text-strong)', wordBreak: 'break-all', margin: 0 },
  modalPurpose: { fontSize: 13, color: 'var(--text-muted)', margin: '5px 0 0', lineHeight: 1.5, maxWidth: 620 },
  iconBtn: { flex: 'none', display: 'inline-flex', alignItems: 'center', justifyContent: 'center', width: 32, height: 32, borderRadius: 'var(--radius-sm)', border: '1px solid var(--border-subtle)', background: 'var(--surface-card)', color: 'var(--text-muted)', cursor: 'pointer' },
  modalBody: { padding: '16px 20px 20px', overflow: 'auto' },

  /* explorer */
  grid: { display: 'grid', gridTemplateColumns: '308px minmax(0, 1fr)', gap: 24, alignItems: 'start' },
  controls: { display: 'flex', flexDirection: 'column', gap: 18, position: 'sticky', top: 80 },
  ctrlLabel: { fontFamily: 'var(--font-mono)', fontSize: 11, letterSpacing: '.1em', color: 'var(--text-subtle)', marginBottom: 2 },
  toggleRow: { display: 'flex', flexDirection: 'column', gap: 12 },
  swLabel: { display: 'flex', alignItems: 'baseline', gap: 8 },
  swName: { fontSize: 14, color: 'var(--text-body)', fontWeight: 500 },
  swCount: { fontFamily: 'var(--font-mono)', fontSize: 11, color: 'var(--text-subtle)' },
  summary: { display: 'flex', flexDirection: 'column', gap: 4, padding: '14px 16px', borderRadius: 'var(--radius-md)', background: 'var(--surface-brand-subtle)', border: '1px solid var(--border-subtle)' },
  summaryNum: { fontFamily: 'var(--font-display)', fontWeight: 600, fontSize: 30, letterSpacing: '-.02em', color: 'var(--brand)', lineHeight: 1 },
  summaryText: { fontSize: 12.5, color: 'var(--text-muted)', lineHeight: 1.45 },

  treeWrap: { border: '1px solid var(--border-subtle)', borderRadius: 'var(--radius-lg)', background: 'var(--surface-card)', overflow: 'hidden', minWidth: 0 },
  treeScroll: { maxHeight: 620, overflow: 'auto', padding: '8px 6px' },
  rowDir: { display: 'flex', alignItems: 'center', gap: 8, width: '100%', border: 'none', background: 'none', font: 'inherit', cursor: 'pointer', textAlign: 'left', padding: '6px 8px', borderRadius: 'var(--radius-sm)', color: 'var(--text-strong)' },
  rowFile: { display: 'flex', alignItems: 'center', gap: 8, width: '100%', border: 'none', background: 'none', font: 'inherit', textAlign: 'left', padding: '6px 8px', borderRadius: 'var(--radius-sm)' },
  dirName: { fontFamily: 'var(--font-mono)', fontSize: 13, fontWeight: 600, color: 'var(--text-strong)' },
  dirCount: { fontFamily: 'var(--font-mono)', fontSize: 11, color: 'var(--text-subtle)' },
  fileName: { fontFamily: 'var(--font-mono)', fontSize: 13, color: 'var(--text-body)' },
  filePurpose: { fontSize: 12, color: 'var(--text-subtle)', lineHeight: 1.4, margin: '1px 0 0' },
  fileSize: { fontFamily: 'var(--font-mono)', fontSize: 10.5, color: 'var(--text-subtle)', flex: 'none' },
  viewTag: { fontFamily: 'var(--font-mono)', fontSize: 10, letterSpacing: '.04em', color: 'var(--brand)', border: '1px solid var(--border-brand)', borderRadius: 'var(--radius-full)', padding: '1px 7px', flex: 'none' },
  chev: { flex: 'none', display: 'inline-flex', color: 'var(--text-subtle)', transition: 'transform var(--duration-fast) var(--ease-standard)' },
  folderIco: { flex: 'none', display: 'inline-flex', color: 'var(--brand)' },
  fileIco: { flex: 'none', display: 'inline-flex', color: 'var(--text-subtle)' },
};

/* ============================ FileBlock (excerpt only) ============================
   Shows a short, deliberately-truncated peek — never the full file. The complete
   files ship only inside the pack; the footer links to pricing. */
function FileBlock({ path, size, content, maxHeight = 300, onFootClick }) {
  const { t } = useT();
  const ready = typeof content === 'string';
  return (
    <div style={exStyles.fileBlock}>
      <div style={exStyles.fileBar}>
        <span style={exStyles.fileBarName}>{path}</span>
        <div style={exStyles.fileBarRight}>
          {size != null && <span style={exStyles.fileBarSize}>{fmtSize(size)}</span>}
          <span style={exStyles.excerptTag}>{t('excerpt', 'trecho')}</span>
        </div>
      </div>
      <pre style={{ ...exStyles.filePre, maxHeight }}>{ready ? content : t('Loading…', 'Carregando…')}</pre>
      <a href="#get" style={exStyles.fileFoot} onClick={onFootClick}>
        <span>{t('Full file included in the pack', 'Arquivo completo incluído no pacote')}</span>
        <span aria-hidden="true">{'\u2192'}</span>
      </a>
    </div>
  );
}

/* ============================ FileViewer (modal) ============================ */
function FileViewer({ file, content, onClose }) {
  React.useEffect(() => {
    function onKey(e) { if (e.key === 'Escape') onClose(); }
    document.addEventListener('keydown', onKey);
    return () => document.removeEventListener('keydown', onKey);
  }, [onClose]);
  if (!file) return null;
  return ReactDOM.createPortal(
    <div style={exStyles.scrim} onClick={onClose} role="dialog" aria-modal="true">
      <div style={exStyles.modal} onClick={(e) => e.stopPropagation()}>
        <div style={exStyles.modalHead}>
          <div style={{ minWidth: 0 }}>
            <p style={exStyles.modalTitle}>{file.path}</p>
            {file.purpose && <p style={exStyles.modalPurpose}>{file.purpose}</p>}
          </div>
          <button type="button" style={exStyles.iconBtn} onClick={onClose} aria-label="Close"><GClose /></button>
        </div>
        <div style={exStyles.modalBody}>
          <FileBlock path={file.path} size={file.size} content={content} maxHeight="none" onFootClick={onClose} />
        </div>
      </div>
    </div>,
    document.body
  );
}

/* ============================ tree data ============================ */
const REF_FILES = [
  { name: 'general-code-review.md',      size: 1994, raw: 'references/general-code-review.md',      purpose: 'General pass — correctness, errors, security, data integrity, maintainability.' },
  { name: 'pull-request-review.md',      size: 1951, raw: 'references/pull-request-review.md',      purpose: 'PR as a unit — scope, size, tests, description accuracy, compatibility.' },
  { name: 'large-pr-review.md',          size: 3851, raw: 'references/large-pr-review.md',          purpose: 'Chunked workflow for big diffs — triage, chunk plan, synthesis pass.' },
  { name: 'security-review.md',          size: 2243, raw: 'references/security-review.md',          purpose: 'Auth, secrets, input, payments, personal data — attacker + payoff per finding.' },
  { name: 'performance-review.md',       size: 1870, raw: 'references/performance-review.md',       purpose: 'Cost at realistic scale — N+1, O(n²), I/O, memory, contention.' },
  { name: 'accessibility-review.md',     size: 2127, raw: 'references/accessibility-review.md',     purpose: 'WCAG 2.1 AA on UI changes — semantics, labels, keyboard, contrast.' },
  { name: 'architecture-review.md',      size: 3824, raw: 'references/architecture-review.md',      purpose: 'Layering & boundaries — soft hints, enforced only against a named pattern.' },
  { name: 'flutter-review.md',           size: 2670, raw: 'references/flutter-review.md',           purpose: 'Dart/Flutter failure modes — build() cost, disposal, async gaps.' },
  { name: 'react-review.md',             size: 2429, raw: 'references/react-review.md',             purpose: 'React/TS failure modes — effect deps, derived state, re-fetch races.' },
  { name: 'api-review.md',               size: 1833, raw: 'references/api-review.md',               purpose: 'REST/GraphQL/RPC surfaces — contract, breaking changes, versioning.' },
];

function refsDir(open) {
  return { type: 'dir', name: 'references', defaultOpen: !!open, children: REF_FILES.map((r) => ({ type: 'file', ...r })) };
}
function skillsDir(refsOpen) {
  return { type: 'dir', name: 'skills', children: [
    { type: 'dir', name: 'code-review', children: [
      { type: 'file', name: 'SKILL.md', size: 3476, raw: 'skills/code-review.md', purpose: 'The review orchestrator — routes to the right specialized pass.' },
      refsDir(refsOpen),
    ] },
    { type: 'dir', name: 'update-bloo-pack',    children: [{ type: 'file', name: 'SKILL.md', size: 3788, raw: 'skills/update-bloo-pack.md',    purpose: 'Upgrade the pack; preserve filled conventions.' }] },
    { type: 'dir', name: 'refresh-conventions', children: [{ type: 'file', name: 'SKILL.md', size: 2818, raw: 'skills/refresh-conventions.md', purpose: 'Re-infer stack, hot paths, high-risk areas.' }] },
    { type: 'dir', name: 'customize-review',    children: [{ type: 'file', name: 'SKILL.md', size: 2019, raw: 'skills/customize-review.md',    purpose: 'Add team rules & severity tuning.' }] },
    { type: 'dir', name: 'review-the-review',   children: [{ type: 'file', name: 'SKILL.md', size: 3011, raw: 'skills/review-the-review.md',   purpose: 'Score a review against the 8-dimension rubric.' }] },
  ] };
}

function buildTree(en) {
  const roots = [];
  roots.push({ type: 'dir', name: '.bloo/', always: true, children: [
    { type: 'file', name: 'conventions.md', size: 673, raw: 'conventions.md', purpose: "Your team's five review conventions — stack, settled style, hot paths, high-risk areas, architecture." },
    { type: 'file', name: 'pack-version', size: 6, raw: null, purpose: 'Installed pack version. Read by /update-bloo-pack to compute the upgrade.' },
  ] });
  if (en.claude) roots.push({ type: 'dir', name: '.claude/', tool: 'Claude Code', children: [
    { type: 'file', name: 'CLAUDE.md', size: 1259, raw: 'CLAUDE.md', purpose: 'Project memory — points Claude at the review standard and lifecycle skills.' },
    skillsDir(false),
  ] });
  if (en.cursor) roots.push({ type: 'dir', name: '.cursor/', tool: 'Cursor', children: [ skillsDir(false) ] });
  if (en.copilot) roots.push({ type: 'dir', name: '.github/', tool: 'GitHub Copilot', children: [
    { type: 'file', name: 'copilot-instructions.md', size: 2131, raw: 'copilot-instructions.md', purpose: 'Repository-wide Copilot instructions — the review standard + conventions block.' },
    { type: 'dir', name: 'instructions', children: [
      { type: 'file', name: 'flutter-review.instructions.md', size: 1870, raw: 'instructions/flutter-review.instructions.md', purpose: 'Auto-applies to *.dart — Flutter review checks.' },
      { type: 'file', name: 'react-review.instructions.md', size: 1816, raw: 'instructions/react-review.instructions.md', purpose: 'Auto-applies to *.tsx / *.jsx — React review checks.' },
    ] },
    skillsDir(false),
  ] });
  if (en.stubs) roots.push({ type: 'dir', name: 'optional — offered at install', stubs: true, children: [
    { type: 'file', name: 'CUSTOMIZATIONS.md', size: 1192, raw: 'stubs/CUSTOMIZATIONS.md', purpose: 'Track local edits so /update-bloo-pack stays re-applicable. → repo root' },
    { type: 'file', name: 'PULL_REQUEST_TEMPLATE.md', size: 1201, raw: 'stubs/pull-request-template.md', purpose: 'PR template with an AI-review section. → .github/' },
    { type: 'dir', name: 'docs/checklists', children: [
      { type: 'file', name: 'pr-review-checklist.md', size: 1889, raw: 'checklists/pr-review-checklist.md', purpose: 'Human PR checklist.' },
      { type: 'file', name: 'security-checklist.md', size: 2181, raw: 'checklists/security-checklist.md', purpose: 'Human security checklist.' },
      { type: 'file', name: 'performance-checklist.md', size: 1534, raw: 'checklists/performance-checklist.md', purpose: 'Human performance checklist.' },
      { type: 'file', name: 'maintainability-checklist.md', size: 1973, raw: 'checklists/maintainability-checklist.md', purpose: 'Human maintainability checklist.' },
      { type: 'file', name: 'architecture-checklist.md', size: 1567, raw: 'checklists/architecture-checklist.md', purpose: 'Human architecture checklist.' },
    ] },
    { type: 'dir', name: 'docs', children: [
      { type: 'file', name: 'evaluation-rubric.md', size: 2602, raw: 'docs/evaluation-rubric.md', purpose: 'The 8-dimension rubric (0–2 each) used by /review-the-review.' },
      { type: 'file', name: 'pr-size-guidance.md', size: 3136, raw: 'docs/pr-size-guidance.md', purpose: 'Evidence-backed PR size bands and AI-review caveats.' },
      { type: 'file', name: 'onboarding-checklist.md', size: 2076, raw: 'docs/onboarding-checklist.md', purpose: 'Install → smoke test → commit → share with the team.' },
      { type: 'file', name: 'team-adoption.md', size: 3373, raw: 'docs/team-adoption.md', purpose: 'One-week rollout plan for a team.' },
      { type: 'file', name: 'multi-repo-checklist.md', size: 2755, raw: 'docs/multi-repo-checklist.md', purpose: 'Same standard across many repos.' },
      { type: 'file', name: 'monorepo-guide.md', size: 5593, raw: 'docs/monorepo-guide.md', purpose: 'Per-package conventions and chunked cross-package review.' },
      { type: 'file', name: 'ci-integration-example.md', size: 4806, raw: 'docs/ci-integration-example.md', purpose: 'GitHub Actions pattern — blockers-only PR comments.' },
      { type: 'file', name: 'team-review-policy.md', size: 1961, raw: 'stubs/team-review-policy.md', purpose: 'Review-policy snippet for a handbook or wiki.' },
      { type: 'file', name: 'release-readiness-template.md', size: 1557, raw: 'stubs/release-readiness-template.md', purpose: 'Pre-release readiness template.' },
      { type: 'file', name: 'review-comment-template.md', size: 1923, raw: 'stubs/review-comment-template.md', purpose: 'Consistent single-comment format.' },
    ] },
  ] });
  return roots;
}

function countFiles(nodes) {
  let n = 0;
  for (const node of nodes) {
    if (node.type === 'file') n++;
    else if (node.children) n += countFiles(node.children);
  }
  return n;
}

function filterTree(nodes, q) {
  if (!q) return nodes;
  const out = [];
  for (const node of nodes) {
    if (node.type === 'file') {
      if (node.name.toLowerCase().includes(q) || (node.purpose || '').toLowerCase().includes(q)) out.push(node);
    } else {
      const kids = filterTree(node.children || [], q);
      if (kids.length || node.name.toLowerCase().includes(q)) out.push({ ...node, children: kids.length ? kids : (node.name.toLowerCase().includes(q) ? node.children : []) });
    }
  }
  return out;
}

/* ============================ tree render ============================ */
function DirRow({ node, depth, forceOpen, onOpenFile, pathPrefix }) {
  const [open, setOpen] = React.useState(node.defaultOpen !== false);
  const isOpen = forceOpen || open;
  const fileCount = countFiles(node.children || []);
  const childPrefix = (pathPrefix || '') + node.name.replace(/\/$/, '') + '/';
  return (
    <div>
      <button type="button" style={{ ...exStyles.rowDir, paddingLeft: 8 + depth * 16 }} onClick={() => setOpen((o) => !o)}
        onMouseEnter={(e) => { e.currentTarget.style.background = 'var(--surface-sunken)'; }}
        onMouseLeave={(e) => { e.currentTarget.style.background = 'none'; }} aria-expanded={isOpen}>
        <span style={{ ...exStyles.chev, transform: isOpen ? 'rotate(90deg)' : 'rotate(0deg)' }}><GChevron /></span>
        <span style={exStyles.folderIco}><GFolder /></span>
        <span style={exStyles.dirName}>{node.name}</span>
        {node.always && <Badge tone="neutral">always</Badge>}
        {node.tool && <Badge tone="brand">{node.tool}</Badge>}
        <span style={{ ...exStyles.dirCount, marginLeft: 'auto' }}>{fileCount} {fileCount === 1 ? 'file' : 'files'}</span>
      </button>
      {isOpen && (node.children || []).map((c, i) => (
        <TreeNode key={childPrefix + i} node={c} depth={depth + 1} forceOpen={forceOpen} onOpenFile={onOpenFile} pathPrefix={childPrefix} />
      ))}
    </div>
  );
}

function FileRow({ node, depth, onOpenFile, pathPrefix }) {
  const viewable = !!node.raw;
  const fullPath = (pathPrefix || '') + node.name;
  const inner = (
    <>
      <span style={exStyles.fileIco}><GFile /></span>
      <span style={{ minWidth: 0, flex: 1 }}>
        <span style={{ display: 'flex', alignItems: 'center', gap: 8, flexWrap: 'wrap' }}>
          <span style={exStyles.fileName}>{node.name}</span>
          {viewable && <span style={exStyles.viewTag}>view</span>}
        </span>
        {node.purpose && <p style={exStyles.filePurpose}>{node.purpose}</p>}
      </span>
      <span style={exStyles.fileSize}>{fmtSize(node.size)}</span>
    </>
  );
  if (!viewable) {
    return <div style={{ ...exStyles.rowFile, paddingLeft: 8 + depth * 16, cursor: 'default', alignItems: 'flex-start' }}>{inner}</div>;
  }
  return (
    <button type="button" style={{ ...exStyles.rowFile, paddingLeft: 8 + depth * 16, cursor: 'pointer', alignItems: 'flex-start' }}
      onClick={() => onOpenFile({ path: fullPath, size: node.size, raw: node.raw, purpose: node.purpose })}
      onMouseEnter={(e) => { e.currentTarget.style.background = 'var(--surface-sunken)'; }}
      onMouseLeave={(e) => { e.currentTarget.style.background = 'none'; }}>
      {inner}
    </button>
  );
}

function TreeNode({ node, depth, forceOpen, onOpenFile, pathPrefix }) {
  if (node.type === 'dir') return <DirRow node={node} depth={depth} forceOpen={forceOpen} onOpenFile={onOpenFile} pathPrefix={pathPrefix} />;
  return <FileRow node={node} depth={depth} onOpenFile={onOpenFile} pathPrefix={pathPrefix} />;
}

/* ============================ Explorer ============================ */
function Explorer({ raw }) {
  const { t } = useT();
  const [en, setEn] = React.useState({ claude: true, cursor: true, copilot: true, stubs: false });
  const [q, setQ] = React.useState('');
  const [openFile, setOpenFile] = React.useState(null);

  const tree = React.useMemo(() => buildTree(en), [en]);
  const query = q.trim().toLowerCase();
  const shown = React.useMemo(() => filterTree(tree, query), [tree, query]);
  const total = countFiles(tree);
  const matched = countFiles(shown);

  const toolCount = (key) => {
    const sub = buildTree({ claude: key === 'claude', cursor: key === 'cursor', copilot: key === 'copilot', stubs: key === 'stubs' })
      .filter((r) => !r.always);
    return countFiles(sub);
  };

  const swItem = (key, label) => (
    <Switch
      checked={en[key]}
      onChange={(e) => setEn((s) => ({ ...s, [key]: e.target.checked }))}
      label={<span style={exStyles.swLabel}><span style={exStyles.swName}>{label}</span><span style={exStyles.swCount}>{toolCount(key)}</span></span>}
    />
  );

  return (
    <div style={exStyles.grid} className="pack-explorer-grid">
      <div style={exStyles.controls} className="pack-explorer-controls">
        <div>
          <div style={exStyles.ctrlLabel}>{t('TOOLS TO INSTALL', 'FERRAMENTAS A INSTALAR')}</div>
          <div style={exStyles.toggleRow}>
            {swItem('claude', 'Claude Code')}
            {swItem('cursor', 'Cursor')}
            {swItem('copilot', 'GitHub Copilot')}
          </div>
        </div>
        <div>
          <div style={exStyles.ctrlLabel}>{t('OPTIONAL', 'OPCIONAIS')}</div>
          {swItem('stubs', t('Handbook & templates', 'Handbook e templates'))}
        </div>
        <Input value={q} onChange={(e) => setQ(e.target.value)} placeholder={t('Filter files…', 'Filtrar arquivos…')} leadingIcon={<GSearch />} aria-label="Filter files" />
        <div style={exStyles.summary}>
          <span style={exStyles.summaryNum}>{query ? matched : total}</span>
          <span style={exStyles.summaryText}>
            {query
              ? t(`files match “${q.trim()}”`, `arquivos correspondem a “${q.trim()}”`)
              : t('plain-Markdown files committed to your repo. Nothing else runs.', 'arquivos Markdown versionados no seu repositório. Nada mais executa.')}
          </span>
        </div>
      </div>

      <div style={exStyles.treeWrap}>
        <div style={exStyles.treeScroll}>
          {shown.length === 0 && (
            <p style={{ padding: 24, textAlign: 'center', color: 'var(--text-muted)', fontSize: 14 }}>
              {t('No files match. Clear the filter or enable a tool.', 'Nenhum arquivo corresponde. Limpe o filtro ou ative uma ferramenta.')}
            </p>
          )}
          {shown.map((node, i) => (
            <TreeNode key={node.name + i} node={node} depth={0} forceOpen={!!query} onOpenFile={setOpenFile} pathPrefix="" />
          ))}
        </div>
      </div>

      {openFile && (
        <FileViewer file={openFile} content={raw ? raw[openFile.raw] : undefined} onClose={() => setOpenFile(null)} />
      )}
    </div>
  );
}

window.PackUI = { Explorer, FileBlock, FileViewer, fmtSize };
})();
