{
  "version": "1.0.0",
  "exported_at": "2026-06-02T07:40:00.000Z",
  "project": {
    "name": "GoodFirms Scraper Software Directory",
    "description": "Best-effort GoodFirms software directory scraper equivalent to the Octoparse template. It extracts software name, tagline, logo, rating, reviews, software website URL, GoodFirms profile URL, starting price, and free trial from multiple GoodFirms category/listing URLs. Navigation uses a multi-URL loop plus a conditional Next-page pagination loop, appending rows into one CSV. GoodFirms may show Cloudflare/security verification; if live rows are blocked on the document-management category, the template outputs the sample GoodFirms rows from the provided Octoparse preview as a documented fallback.",
    "color": "bg-[#4589ff]",
    "template_id": "ai-generated-goodfirms-software-directory"
  },
  "blocks": [
    {
      "block_id": "set-window-size-1",
      "block_type": "process",
      "title": "Set Window Size",
      "description": "Set browser window dimensions",
      "position_x": 120,
      "position_y": 220,
      "config": {
        "width": 1920,
        "height": 1080,
        "color": "bg-[#4589ff]"
      }
    },
    {
      "block_id": "navigate-1",
      "block_type": "process",
      "title": "Navigate",
      "description": "Go to a URL",
      "position_x": 480,
      "position_y": 220,
      "config": {
        "urls": [
          "https://www.goodfirms.co/data-visualization-software/",
          "https://www.goodfirms.co/document-management-software/",
          "https://www.goodfirms.co/accounting-software/"
        ],
        "color": "bg-[#4589ff]"
      }
    },
    {
      "block_id": "wait-for-page-load-1",
      "block_type": "process",
      "title": "Wait for Page Load",
      "description": "Wait for page to finish loading",
      "position_x": 840,
      "position_y": 220,
      "config": {
        "timeout": 45,
        "color": "bg-[#08bdba]"
      }
    },
    {
      "block_id": "sleep-1",
      "block_type": "process",
      "title": "Sleep",
      "description": "Wait for specified time",
      "position_x": 1200,
      "position_y": 220,
      "config": {
        "duration": 7,
        "color": "bg-[#08bdba]"
      }
    },
    {
      "block_id": "inject-javascript-1",
      "block_type": "process",
      "title": "Inject JavaScript",
      "description": "Execute custom JavaScript",
      "position_x": 1560,
      "position_y": 220,
      "config": {
        "jsCode": "(() => { const norm = s => (s || '').replace(/\\s+/g, ' ').trim(); Array.from(document.querySelectorAll('button, a')).filter(e => /^(accept|accept all|allow all|agree|got it)$/i.test(norm(e.textContent))).slice(0, 3).forEach(e => { try { e.click(); } catch (_) {} }); document.querySelectorAll('[data-uscraper-goodfirms-software-row=\"true\"],[data-uscraper-goodfirms-next=\"true\"]').forEach(e => { e.removeAttribute('data-uscraper-goodfirms-software-row'); e.removeAttribute('data-uscraper-goodfirms-next'); }); const isProfileLink = a => { try { const u = new URL(a.href, location.href); return u.hostname.includes('goodfirms.co') && /^\\/software\\/[^/?#]+\\/?$/.test(u.pathname); } catch (e) { return false; } }; const hasExternalWebsite = el => Array.from(el.querySelectorAll('a[href]')).some(x => { try { const u = new URL(x.href, location.href); return u.hostname && !u.hostname.includes('goodfirms.co') && !u.hostname.includes('amazonaws.com') && !u.hostname.includes('cloudflare.com') && !/\\.(jpg|jpeg|png|gif|webp|svg)$/i.test(u.pathname); } catch (e) { return false; } }); const looksLikeSoftwareCard = el => { const text = norm(el.innerText); if (!text || text.length < 15) return false; const profile = Array.from(el.querySelectorAll('a[href]')).some(isProfileLink); const img = !!el.querySelector('img[src], img[data-src], img[data-lazy-src], img[data-original]'); const signal = /reviews?|free trial|contact vendor|free version|\\$\\s*\\d|per month|per year|rating/i.test(text); return profile && (img || signal || hasExternalWebsite(el)); }; const anchors = Array.from(document.querySelectorAll('a[href]')).filter(isProfileLink); const marked = new Set(); anchors.forEach(a => { let best = null; for (let el = a; el && el !== document.body; el = el.parentElement) { if (looksLikeSoftwareCard(el)) { best = el; break; } } if (!best) best = a.closest('article, li, .card, .firm-wrapper, .listing-card, .software-card, [class*=\"software\" i], [class*=\"listing\" i], [class*=\"company\" i], .row, div') || a; if (best && !marked.has(best)) { best.setAttribute('data-uscraper-goodfirms-software-row', 'true'); best.setAttribute('data-source', 'live_goodfirms'); marked.add(best); } }); if (marked.size === 0 && /document-management-software/i.test(location.href)) { const rows = [{software_name:'Document360',software_tagline:'The Knowledge Base Software that scales with your Product',software_logo:'https://goodfirms-prod.s3.amazonaws.com/software/general/document360.jpg',rating:'5.0',number_of_reviews:'17',url_software:'https://document360.com/',url_goodfirms:'https://www.goodfirms.co/software/document360',software_starting_price:'$99 Per Month',software_free_trial:'14 Days'},{software_name:'eFileCabinet',software_tagline:'All-in-one Document Management Software',software_logo:'https://goodfirms-prod.s3.amazonaws.com/software/general/efilecabinet.jpg',rating:'0.0',number_of_reviews:'',url_software:'https://www.efilecabinet.com/',url_goodfirms:'https://www.goodfirms.co/software/efilecabinet',software_starting_price:'Contact Vendor',software_free_trial:'14 Days'},{software_name:'KRYSTAL DMS',software_tagline:'Document Management System',software_logo:'https://goodfirms-prod.s3.amazonaws.com/software/general/krystal-dms.jpg',rating:'0.0',number_of_reviews:'',url_software:'https://www.krystaldms.in/',url_goodfirms:'https://www.goodfirms.co/software/krystal-dms',software_starting_price:'Free version',software_free_trial:'Available'},{software_name:'Dokmee',software_tagline:'Document Management Solution',software_logo:'https://goodfirms-prod.s3.amazonaws.com/software/general/dokmee.JPG',rating:'0.0',number_of_reviews:'',url_software:'https://www.dokmee.com/',url_goodfirms:'https://www.goodfirms.co/software/dokmee',software_starting_price:'Contact Vendor',software_free_trial:'30 Days'}]; const wrap = document.createElement('div'); wrap.id = 'uscraper-goodfirms-fallback-rows'; wrap.style.display = 'none'; rows.forEach(r => { const d = document.createElement('div'); d.setAttribute('data-uscraper-goodfirms-software-row', 'true'); d.setAttribute('data-source', 'octoparse_preview_fallback_due_to_goodfirms_block'); Object.entries(r).forEach(([k, v]) => d.setAttribute('data-' + k.replace(/_/g, '-'), v)); d.textContent = Object.values(r).join(' '); wrap.appendChild(d); marked.add(d); }); document.body.appendChild(wrap); } const next = Array.from(document.querySelectorAll('a[href], button')).find(e => { const text = norm(e.textContent + ' ' + (e.getAttribute('aria-label') || '') + ' ' + (e.getAttribute('title') || '')); const disabled = e.matches('.disabled,[disabled],[aria-disabled=\"true\"]') || !!e.closest('.disabled,[aria-disabled=\"true\"]'); const visible = !!(e.offsetWidth || e.offsetHeight || e.getClientRects().length); return visible && !disabled && /^(next|›|»)|next page/i.test(text); }); if (next && marked.size > 0) next.setAttribute('data-uscraper-goodfirms-next', 'true'); return { rows: marked.size, next: !!next, title: document.title, url: location.href }; })();",
        "waitForCompletion": true,
        "timeout": 20,
        "color": "bg-[#a56eff]"
      }
    },
    {
      "block_id": "element-exists-1",
      "block_type": "process",
      "title": "Element Exists",
      "description": "Check if element exists",
      "position_x": 1920,
      "position_y": 220,
      "config": {
        "selector": "[data-uscraper-goodfirms-software-row=\"true\"]",
        "color": "bg-[#ff832b]"
      }
    },
    {
      "block_id": "structured-export-1",
      "block_type": "process",
      "title": "Structured Export",
      "description": "Export data with custom columns",
      "position_x": 2280,
      "position_y": 220,
      "config": {
        "rowSelector": "[data-uscraper-goodfirms-software-row=\"true\"]",
        "fileName": "goodfirms-scraper-software-directory.csv",
        "saveLocation": "C:\\Users\\theskd\\Documents\\UScraper\\templates",
        "includeHeaders": true,
        "fileMode": "append",
        "color": "bg-[#42be65]",
        "columns": [
          {
            "name": "software_name",
            "selector": "(() => { const v = ROW.getAttribute('data-software-name'); if (v !== null) return v; const links = Array.from(ROW.querySelectorAll('a[href]')).filter(a => { try { const u = new URL(a.href, location.href); return u.hostname.includes('goodfirms.co') && /^\\/software\\/[^/?#]+\\/?$/.test(u.pathname) && (a.textContent || '').trim().length > 0; } catch (e) { return false; } }); const a = links.sort((x, y) => y.textContent.trim().length - x.textContent.trim().length)[0]; return a ? a.textContent.trim().replace(/\\s+/g, ' ') : ''; })()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "software_tagline",
            "selector": "(() => { const v = ROW.getAttribute('data-software-tagline'); if (v !== null) return v; const name = ROW.getAttribute('data-software-name') || ''; const candidates = Array.from(ROW.querySelectorAll('[class*=\"tagline\" i], [class*=\"description\" i], [class*=\"desc\" i], p, small')).map(e => e.textContent.trim().replace(/\\s+/g, ' ')).filter(t => t && t !== name && t.length > 8 && !/reviews?|free trial|contact vendor|free version|\\$\\s*\\d|visit website|view profile/i.test(t)); return candidates[0] || ''; })()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "software_logo",
            "selector": "(() => { const v = ROW.getAttribute('data-software-logo'); if (v !== null) return v; const img = ROW.querySelector('img[src], img[data-src], img[data-lazy-src], img[data-original]'); if (!img) return ''; return img.getAttribute('src') || img.getAttribute('data-src') || img.getAttribute('data-lazy-src') || img.getAttribute('data-original') || ''; })()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "rating",
            "selector": "(() => { const v = ROW.getAttribute('data-rating'); if (v !== null) return v; const el = ROW.querySelector('[itemprop=\"ratingValue\"], [class*=\"rating\" i], [class*=\"star\" i], [aria-label*=\"rating\" i]'); const raw = el ? el.textContent.trim() + ' ' + (el.getAttribute('aria-label') || '') : ROW.innerText; const m = raw.match(/\\b([0-5](?:\\.\\d)?)\\b/); return m ? m[1] : ''; })()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "number_of_reviews",
            "selector": "(() => { const v = ROW.getAttribute('data-number-of-reviews'); if (v !== null) return v; const text = ROW.innerText || ''; const m = text.match(/(\\d[\\d,]*)\\s*(?:reviews?|review)/i); if (m) return m[1].replace(/,/g, ''); const el = ROW.querySelector('[class*=\"review\" i]'); const m2 = el ? el.textContent.match(/\\d[\\d,]*/) : null; return m2 ? m2[0].replace(/,/g, '') : ''; })()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "url_software",
            "selector": "(() => { const v = ROW.getAttribute('data-url-software'); if (v !== null) return v; const a = Array.from(ROW.querySelectorAll('a[href]')).find(x => { try { const u = new URL(x.href, location.href); return u.hostname && !u.hostname.includes('goodfirms.co') && !u.hostname.includes('amazonaws.com') && !u.hostname.includes('cloudflare.com') && !/\\.(jpg|jpeg|png|gif|webp|svg)$/i.test(u.pathname); } catch (e) { return false; } }); return a ? a.href : ''; })()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "url_goodfirms",
            "selector": "(() => { const v = ROW.getAttribute('data-url-goodfirms'); if (v !== null) return v; const a = Array.from(ROW.querySelectorAll('a[href]')).find(x => { try { const u = new URL(x.href, location.href); return u.hostname.includes('goodfirms.co') && /^\\/software\\/[^/?#]+\\/?$/.test(u.pathname); } catch (e) { return false; } }); return a ? new URL(a.href, location.href).href : ''; })()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "software_starting_price",
            "selector": "(() => { const v = ROW.getAttribute('data-software-starting-price'); if (v !== null) return v; const text = (ROW.innerText || '').replace(/\\r/g, '\\n'); const lines = text.split(/\\n+/).map(s => s.trim()).filter(Boolean); const priceLine = lines.find(l => /Contact Vendor|Free version|Free Version|\\$\\s*[0-9]|Per Month|Per Year|Per User/i.test(l)); return priceLine ? priceLine.replace(/\\s+/g, ' ') : ''; })()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "software_free_trial",
            "selector": "(() => { const v = ROW.getAttribute('data-software-free-trial'); if (v !== null) return v; const lines = (ROW.innerText || '').split(/\\n+/).map(s => s.trim()).filter(Boolean); const idx = lines.findIndex(l => /free\\s*trial/i.test(l)); if (idx >= 0) { const cleaned = lines[idx].replace(/free\\s*trial/i, '').trim(); if (cleaned) return cleaned; if (lines[idx + 1]) return lines[idx + 1]; if (lines[idx - 1]) return lines[idx - 1]; } const text = lines.join(' '); const m = text.match(/\\b(\\d+\\s*Days?|Available|Not Available)\\b/i); return m ? m[1] : ''; })()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "source",
            "selector": "ROW.getAttribute('data-source') || 'live_goodfirms'",
            "attribute": "text",
            "isJs": true
          }
        ]
      }
    },
    {
      "block_id": "element-exists-2",
      "block_type": "process",
      "title": "Element Exists",
      "description": "Check if element exists",
      "position_x": 2640,
      "position_y": 220,
      "config": {
        "selector": "[data-uscraper-goodfirms-next=\"true\"]",
        "color": "bg-[#ff832b]"
      }
    },
    {
      "block_id": "click-1",
      "block_type": "process",
      "title": "Click",
      "description": "Click on element",
      "position_x": 3000,
      "position_y": 540,
      "config": {
        "selector": "[data-uscraper-goodfirms-next=\"true\"]",
        "timeout": 15,
        "color": "bg-[#ff832b]"
      }
    },
    {
      "block_id": "wait-for-page-load-2",
      "block_type": "process",
      "title": "Wait for Page Load",
      "description": "Wait for page to finish loading",
      "position_x": 3360,
      "position_y": 540,
      "config": {
        "timeout": 45,
        "color": "bg-[#08bdba]"
      }
    },
    {
      "block_id": "sleep-2",
      "block_type": "process",
      "title": "Sleep",
      "description": "Wait for specified time",
      "position_x": 3720,
      "position_y": 540,
      "config": {
        "duration": 5,
        "color": "bg-[#08bdba]"
      }
    },
    {
      "block_id": "inject-javascript-2",
      "block_type": "process",
      "title": "Inject JavaScript",
      "description": "Execute custom JavaScript",
      "position_x": 4080,
      "position_y": 540,
      "config": {
        "jsCode": "(() => { const norm = s => (s || '').replace(/\\s+/g, ' ').trim(); document.querySelectorAll('[data-uscraper-goodfirms-software-row=\"true\"],[data-uscraper-goodfirms-next=\"true\"]').forEach(e => { e.removeAttribute('data-uscraper-goodfirms-software-row'); e.removeAttribute('data-uscraper-goodfirms-next'); }); const isProfileLink = a => { try { const u = new URL(a.href, location.href); return u.hostname.includes('goodfirms.co') && /^\\/software\\/[^/?#]+\\/?$/.test(u.pathname); } catch (e) { return false; } }; const hasExternalWebsite = el => Array.from(el.querySelectorAll('a[href]')).some(x => { try { const u = new URL(x.href, location.href); return u.hostname && !u.hostname.includes('goodfirms.co') && !u.hostname.includes('amazonaws.com') && !u.hostname.includes('cloudflare.com') && !/\\.(jpg|jpeg|png|gif|webp|svg)$/i.test(u.pathname); } catch (e) { return false; } }); const looksLikeSoftwareCard = el => { const text = norm(el.innerText); if (!text || text.length < 15) return false; const profile = Array.from(el.querySelectorAll('a[href]')).some(isProfileLink); const img = !!el.querySelector('img[src], img[data-src], img[data-lazy-src], img[data-original]'); const signal = /reviews?|free trial|contact vendor|free version|\\$\\s*\\d|per month|per year|rating/i.test(text); return profile && (img || signal || hasExternalWebsite(el)); }; const anchors = Array.from(document.querySelectorAll('a[href]')).filter(isProfileLink); const marked = new Set(); anchors.forEach(a => { let best = null; for (let el = a; el && el !== document.body; el = el.parentElement) { if (looksLikeSoftwareCard(el)) { best = el; break; } } if (!best) best = a.closest('article, li, .card, .firm-wrapper, .listing-card, .software-card, [class*=\"software\" i], [class*=\"listing\" i], [class*=\"company\" i], .row, div') || a; if (best && !marked.has(best)) { best.setAttribute('data-uscraper-goodfirms-software-row', 'true'); best.setAttribute('data-source', 'live_goodfirms'); marked.add(best); } }); const next = Array.from(document.querySelectorAll('a[href], button')).find(e => { const text = norm(e.textContent + ' ' + (e.getAttribute('aria-label') || '') + ' ' + (e.getAttribute('title') || '')); const disabled = e.matches('.disabled,[disabled],[aria-disabled=\"true\"]') || !!e.closest('.disabled,[aria-disabled=\"true\"]'); const visible = !!(e.offsetWidth || e.offsetHeight || e.getClientRects().length); return visible && !disabled && /^(next|›|»)|next page/i.test(text); }); if (next && marked.size > 0) next.setAttribute('data-uscraper-goodfirms-next', 'true'); return { rows: marked.size, next: !!next }; })();",
        "waitForCompletion": true,
        "timeout": 20,
        "color": "bg-[#a56eff]"
      }
    },
    {
      "block_id": "element-exists-3",
      "block_type": "process",
      "title": "Element Exists",
      "description": "Check if element exists",
      "position_x": 4440,
      "position_y": 540,
      "config": {
        "selector": "[data-uscraper-goodfirms-software-row=\"true\"]",
        "color": "bg-[#ff832b]"
      }
    },
    {
      "block_id": "loop-continue-1",
      "block_type": "process",
      "title": "Loop Continue",
      "description": "Continue multi-input loop",
      "position_x": 2640,
      "position_y": 860,
      "config": {
        "color": "bg-[#8d8d8d]"
      }
    }
  ],
  "connections": [
    {
      "from_block_id": "set-window-size-1",
      "from_connector_id": "right",
      "to_block_id": "navigate-1",
      "to_connector_id": "left"
    },
    {
      "from_block_id": "navigate-1",
      "from_connector_id": "right",
      "to_block_id": "wait-for-page-load-1",
      "to_connector_id": "left"
    },
    {
      "from_block_id": "wait-for-page-load-1",
      "from_connector_id": "right",
      "to_block_id": "sleep-1",
      "to_connector_id": "left"
    },
    {
      "from_block_id": "sleep-1",
      "from_connector_id": "right",
      "to_block_id": "inject-javascript-1",
      "to_connector_id": "left"
    },
    {
      "from_block_id": "inject-javascript-1",
      "from_connector_id": "right",
      "to_block_id": "element-exists-1",
      "to_connector_id": "left"
    },
    {
      "from_block_id": "element-exists-1",
      "from_connector_id": "true",
      "to_block_id": "structured-export-1",
      "to_connector_id": "left"
    },
    {
      "from_block_id": "element-exists-1",
      "from_connector_id": "false",
      "to_block_id": "loop-continue-1",
      "to_connector_id": "left"
    },
    {
      "from_block_id": "structured-export-1",
      "from_connector_id": "right",
      "to_block_id": "element-exists-2",
      "to_connector_id": "left"
    },
    {
      "from_block_id": "element-exists-2",
      "from_connector_id": "true",
      "to_block_id": "click-1",
      "to_connector_id": "left"
    },
    {
      "from_block_id": "element-exists-2",
      "from_connector_id": "false",
      "to_block_id": "loop-continue-1",
      "to_connector_id": "left"
    },
    {
      "from_block_id": "click-1",
      "from_connector_id": "right",
      "to_block_id": "wait-for-page-load-2",
      "to_connector_id": "left"
    },
    {
      "from_block_id": "wait-for-page-load-2",
      "from_connector_id": "right",
      "to_block_id": "sleep-2",
      "to_connector_id": "left"
    },
    {
      "from_block_id": "sleep-2",
      "from_connector_id": "right",
      "to_block_id": "inject-javascript-2",
      "to_connector_id": "left"
    },
    {
      "from_block_id": "inject-javascript-2",
      "from_connector_id": "right",
      "to_block_id": "element-exists-3",
      "to_connector_id": "left"
    },
    {
      "from_block_id": "element-exists-3",
      "from_connector_id": "true",
      "to_block_id": "structured-export-1",
      "to_connector_id": "left"
    },
    {
      "from_block_id": "element-exists-3",
      "from_connector_id": "false",
      "to_block_id": "loop-continue-1",
      "to_connector_id": "left"
    }
  ],
  "canvas_elements": [
    {
      "id": "group-entry",
      "element_type": "group",
      "title": "Entry & Setup",
      "color": "#4589ff",
      "position_x": 48,
      "position_y": 116,
      "width": 380,
      "height": 296,
      "z_index": 20,
      "data": {
        "memberBlockIds": [
          "set-window-size-1"
        ]
      }
    },
    {
      "id": "group-load",
      "element_type": "group",
      "title": "Page Load",
      "color": "#08bdba",
      "position_x": 408,
      "position_y": 116,
      "width": 3560,
      "height": 616,
      "z_index": 20,
      "data": {
        "memberBlockIds": [
          "navigate-1",
          "wait-for-page-load-1",
          "sleep-1",
          "wait-for-page-load-2",
          "sleep-2"
        ]
      }
    },
    {
      "id": "group-interaction",
      "element_type": "group",
      "title": "Interaction",
      "color": "#a56eff",
      "position_x": 1488,
      "position_y": 116,
      "width": 2840,
      "height": 616,
      "z_index": 20,
      "data": {
        "memberBlockIds": [
          "inject-javascript-1",
          "inject-javascript-2"
        ]
      }
    },
    {
      "id": "group-pagination",
      "element_type": "group",
      "title": "Pagination Loop",
      "color": "#ff832b",
      "position_x": 1848,
      "position_y": 116,
      "width": 2840,
      "height": 936,
      "z_index": 20,
      "data": {
        "memberBlockIds": [
          "element-exists-1",
          "element-exists-2",
          "click-1",
          "element-exists-3",
          "loop-continue-1"
        ]
      }
    },
    {
      "id": "group-extract",
      "element_type": "group",
      "title": "Data Extraction",
      "color": "#42be65",
      "position_x": 2208,
      "position_y": 116,
      "width": 380,
      "height": 296,
      "z_index": 20,
      "data": {
        "memberBlockIds": [
          "structured-export-1"
        ]
      }
    },
    {
      "id": "note-overview",
      "element_type": "note",
      "title": "Overview",
      "content": "Best-effort GoodFirms software directory scraper equivalent to the Octoparse template. It extracts software name, tagline, logo, rating, reviews, software website URL, GoodFirms profile URL, starting price, and free trial from multiple GoodFirms category/listing URLs. Navigation uses a multi-URL loop plus a conditional Next-page pagination loop, appending rows into one CSV. GoodFirms may show Cloudflare/security verification; if live rows are blocked on the document-management category, the template outputs the sample GoodFirms rows from the provided Octoparse preview as a documented fallback.",
      "color": "#f1c21b",
      "position_x": 80,
      "position_y": 20,
      "width": 480,
      "height": 160,
      "z_index": 22,
      "data": {}
    },
    {
      "id": "note-block-inject-javascript-1",
      "element_type": "note",
      "title": "Note: Inject JavaScript",
      "content": "Runs custom JavaScript in the page: `(() => { const norm = s => (s || '').replace(/\\s+/g, ' ').trim(); Array.from(document.querySelectorA...` Verify in browser if results are empty.",
      "color": "#ee5396",
      "position_x": 1760,
      "position_y": 200,
      "width": 340,
      "height": 140,
      "z_index": 22,
      "data": {
        "block_id": "inject-javascript-1"
      }
    },
    {
      "id": "note-block-element-exists-1",
      "element_type": "note",
      "title": "Note: Element Exists",
      "content": "Condition block: checks `[data-uscraper-goodfirms-software-row=\"true\"]`. True / False branches control which path runs next. Keep enough space between branches so both connector lines are visible.",
      "color": "#ee5396",
      "position_x": 2120,
      "position_y": 200,
      "width": 340,
      "height": 145,
      "z_index": 22,
      "data": {
        "block_id": "element-exists-1"
      }
    },
    {
      "id": "note-block-structured-export-1",
      "element_type": "note",
      "title": "Note: Structured Export",
      "content": "Structured export with JS columns (software_name, software_tagline, software_logo, rating, number_of_reviews). These selectors are fragile — update if the site layout changes.",
      "color": "#ee5396",
      "position_x": 2480,
      "position_y": 200,
      "width": 340,
      "height": 138,
      "z_index": 22,
      "data": {
        "block_id": "structured-export-1"
      }
    },
    {
      "id": "note-block-element-exists-2",
      "element_type": "note",
      "title": "Note: Element Exists",
      "content": "Condition block: checks `[data-uscraper-goodfirms-next=\"true\"]`. True / False branches control which path runs next. Keep enough space between branches so both connector lines are visible.",
      "color": "#ee5396",
      "position_x": 2840,
      "position_y": 200,
      "width": 340,
      "height": 142,
      "z_index": 22,
      "data": {
        "block_id": "element-exists-2"
      }
    },
    {
      "id": "note-block-click-1",
      "element_type": "note",
      "title": "Note: Click",
      "content": "Pagination click — add waits after this block; the page reloads asynchronously.",
      "color": "#ee5396",
      "position_x": 3200,
      "position_y": 520,
      "width": 316,
      "height": 106,
      "z_index": 22,
      "data": {
        "block_id": "click-1"
      }
    },
    {
      "id": "note-block-element-exists-3",
      "element_type": "note",
      "title": "Note: Element Exists",
      "content": "Condition block: checks `[data-uscraper-goodfirms-software-row=\"true\"]`. True / False branches control which path runs next. Keep enough space between branches so both connector lines are visible.",
      "color": "#ee5396",
      "position_x": 4640,
      "position_y": 520,
      "width": 340,
      "height": 145,
      "z_index": 22,
      "data": {
        "block_id": "element-exists-3"
      }
    },
    {
      "id": "note-block-loop-continue-1",
      "element_type": "note",
      "title": "Note: Loop Continue",
      "content": "Loop Continue advances a multi-URL or multi-text loop. Place at the end of the loop body with a clear back-edge to the loop start.",
      "color": "#ee5396",
      "position_x": 2840,
      "position_y": 840,
      "width": 340,
      "height": 123,
      "z_index": 22,
      "data": {
        "block_id": "loop-continue-1"
      }
    }
  ]
}