{
  "version": "1.0.0",
  "exported_at": "2026-05-31T06:45:00.000Z",
  "project": {
    "name": "Google Maps Listing and Details Page Scraper",
    "description": "Best-effort Google Maps place details scraper equivalent to the Octoparse template. It extracts business names, ratings, review counts, categories, Google Maps place URLs, addresses, status/hours summary, latitude/longitude, plus codes, websites, phone numbers, header images, tags, and review keyword chips. Navigation uses a known Google Maps place URL list via navigate.urls[] plus loop-continue; add more Google Maps place/detail URLs to collect multiple businesses. Google Maps search/listing discovery is dynamic and may trigger consent, CAPTCHA, throttling, or DOM changes. This version avoids opening the Hours overlay before extraction because that can replace the page heading with a modal title.",
    "color": "bg-[#4589ff]",
    "template_id": "ai-generated"
  },
  "blocks": [
    {
      "block_id": "set-window-size-1",
      "block_type": "process",
      "title": "Set Window Size",
      "description": "Set browser window dimensions",
      "position_x": 120,
      "position_y": 240,
      "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": 240,
      "config": {
        "urls": [
          "https://www.google.com/maps/place/Starbucks/data=!4m7!3m6!1s0x808e33b4dce2b6c7:0x756ee5a39ae8972d!8m2!3d37.274881!4d-121.857555!16s%2Fg%2F1vfp8fl4!19sChIJx7bi3LQzjoARLZfomqPlbnU?authuser=0&hl=en&rclk=1"
        ],
        "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": 240,
      "config": {
        "timeout": 45,
        "color": "bg-[#08bdba]"
      }
    },
    {
      "block_id": "wait-for-element-1",
      "block_type": "process",
      "title": "Wait for Element",
      "description": "Wait until element appears",
      "position_x": 1200,
      "position_y": 240,
      "config": {
        "selector": "h1.DUwDvf",
        "timeout": 45,
        "visible": true,
        "color": "bg-[#08bdba]"
      }
    },
    {
      "block_id": "wait-for-element-2",
      "block_type": "process",
      "title": "Wait for Element",
      "description": "Wait until element appears",
      "position_x": 1560,
      "position_y": 240,
      "config": {
        "selector": "button[data-item-id=\"address\"], .F7nice, button[jsaction*=\"category\"]",
        "timeout": 30,
        "visible": true,
        "color": "bg-[#08bdba]"
      }
    },
    {
      "block_id": "inject-javascript-1",
      "block_type": "process",
      "title": "Inject JavaScript",
      "description": "Run JavaScript on page",
      "position_x": 1920,
      "position_y": 240,
      "config": {
        "jsCode": "const closeButtons = Array.from(document.querySelectorAll('button[aria-label]')).filter(b => /close/i.test(b.getAttribute('aria-label') || '')); closeButtons.forEach(b => { const dialog = b.closest('[role=\"dialog\"]'); if (dialog) { try { b.click(); } catch (e) {} } }); return true;",
        "waitForCompletion": true,
        "timeout": 10,
        "color": "bg-[#a56eff]"
      }
    },
    {
      "block_id": "sleep-1",
      "block_type": "process",
      "title": "Sleep",
      "description": "Wait for specified time",
      "position_x": 2280,
      "position_y": 240,
      "config": {
        "duration": 1,
        "color": "bg-[#a56eff]"
      }
    },
    {
      "block_id": "structured-export-1",
      "block_type": "process",
      "title": "Structured Export",
      "description": "Export data with custom columns",
      "position_x": 2640,
      "position_y": 240,
      "config": {
        "rowSelector": "body",
        "fileName": "google-maps-store-scraper-local.csv",
        "saveLocation": "C:\\Users\\theskd\\Documents\\UScraper\\templates",
        "includeHeaders": true,
        "fileMode": "append",
        "color": "bg-[#42be65]",
        "columns": [
          {
            "name": "keyword",
            "selector": "(() => 'Coffee shop near 95136')()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "name",
            "selector": "(() => { const el = ROW.querySelector('h1.DUwDvf'); const fromTitle = (document.title || '').replace(/\\s*-\\s*Google Maps\\s*$/i, '').trim(); return el?.textContent?.trim() || fromTitle; })()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "header_image",
            "selector": "(() => { const img = ROW.querySelector('button[aria-label^=\"Photo of\"] img, .RZ66Rb img'); return img ? img.src : ''; })()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "rating",
            "selector": "(() => { const ratingBox = ROW.querySelector('.F7nice'); const txt = (ratingBox?.innerText || ratingBox?.textContent || '').replace(/\\s+/g, ' '); const aria = Array.from(ROW.querySelectorAll('[aria-label*=\"stars\" i]')).map(e => e.getAttribute('aria-label') || '').find(Boolean) || ''; const m = (txt + ' ' + aria).match(/\\b(\\d(?:\\.\\d)?)\\s*(?:stars)?\\b/); return m ? m[1] : ''; })()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "rating_count",
            "selector": "(() => { const labels = Array.from(ROW.querySelectorAll('[aria-label*=\"reviews\" i]')).map(e => e.getAttribute('aria-label') || '').filter(Boolean); const hit = labels.find(s => /[\\d,]+\\s+reviews/i.test(s)); const txt = ROW.querySelector('.F7nice')?.innerText || ''; const m = (hit || txt).match(/[\\(\\s]([\\d,]+)\\s*(?:reviews|\\))/i); return m ? m[1].replace(/,/g, '') : ''; })()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "price_range",
            "selector": "(() => { const t = (ROW.querySelector('.LBgpqf')?.innerText || ROW.querySelector('.fontBodyMedium.dmRWX')?.innerText || ROW.innerText || '').replace(/\\s+/g, ' '); const m = t.match(/[$€£¥]\\d+\\s*[–-]\\s*\\d+|[$€£¥]{1,4}/); return m ? m[0].trim() : ''; })()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "category",
            "selector": "(() => { const el = ROW.querySelector('button[jsaction*=\"category\"], button.DkEaL'); return el ? el.textContent.trim() : ''; })()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "detail_url",
            "selector": "(() => location.href)()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "tags",
            "selector": "(() => { const vals = Array.from(ROW.querySelectorAll('.E0DTEd [role=\"group\"]')).map(e => (e.getAttribute('aria-label') || e.innerText || '').replace(/^(Serves|Has|Offers)\\s+/i, '').trim()).filter(Boolean); return Array.from(new Set(vals)).join(' · '); })()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "address",
            "selector": "(() => { const btn = ROW.querySelector('button[data-item-id=\"address\"]'); const txt = btn?.querySelector('.Io6YTe')?.textContent || btn?.getAttribute('aria-label') || ''; return txt.replace(/^Address:\\s*/i, '').trim(); })()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "located_in",
            "selector": "(() => { const txt = ROW.innerText || ''; const m = txt.match(/Located in:\\s*([^\\n]+)/i); return m ? m[1].trim() : ''; })()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "current_status",
            "selector": "(() => { const btn = ROW.querySelector('button[data-item-id=\"oh\"]'); const txt = (btn?.querySelector('.Io6YTe')?.innerText || btn?.getAttribute('aria-label') || '').replace(/\\s+/g, ' ').trim(); return txt ? txt.split('·')[0].replace(/^Hours:\\s*/i, '').trim() : ''; })()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "next_status",
            "selector": "(() => { const btn = ROW.querySelector('button[data-item-id=\"oh\"]'); const txt = (btn?.querySelector('.Io6YTe')?.innerText || btn?.getAttribute('aria-label') || '').replace(/\\s+/g, ' ').trim(); const parts = txt.replace(/^Hours:\\s*/i, '').split('·').map(s => s.trim()).filter(Boolean); return parts.length > 1 ? parts.slice(1).join(' · ') : ''; })()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "scraped_at",
            "selector": "(() => new Date().toISOString())()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "open_hours",
            "selector": "(() => { const btn = ROW.querySelector('button[data-item-id=\"oh\"]'); const txt = (btn?.getAttribute('aria-label') || btn?.innerText || '').replace(/\\s+/g, ' ').trim(); return txt.replace(/^Hours:\\s*/i, ''); })()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "latitude",
            "selector": "(() => { const u = location.href; const m = u.match(/@(-?\\d+(?:\\.\\d+)?),(-?\\d+(?:\\.\\d+)?)/) || u.match(/!3d(-?\\d+(?:\\.\\d+)?)!4d(-?\\d+(?:\\.\\d+)?)/); return m ? m[1] : ''; })()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "longitude",
            "selector": "(() => { const u = location.href; const m = u.match(/@(-?\\d+(?:\\.\\d+)?),(-?\\d+(?:\\.\\d+)?)/) || u.match(/!3d(-?\\d+(?:\\.\\d+)?)!4d(-?\\d+(?:\\.\\d+)?)/); return m ? m[2] : ''; })()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "plus_code",
            "selector": "(() => { const withLabel = Array.from(ROW.querySelectorAll('[aria-label]')).find(e => /Plus code:/i.test(e.getAttribute('aria-label') || '')); if (withLabel) return (withLabel.getAttribute('aria-label') || '').replace(/^Plus code:\\s*/i, '').trim(); const m = (ROW.innerText || '').match(/[23456789CFGHJMPQRVWX]{4}\\+[23456789CFGHJMPQRVWX]{2,3}[^\\n]*/); return m ? m[0].trim() : ''; })()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "website",
            "selector": "(() => { const links = Array.from(ROW.querySelectorAll('a[href]')).map(a => a.href).map(h => { try { const u = new URL(h); return u.hostname.includes('google.com') && u.searchParams.get('q') ? u.searchParams.get('q') : h; } catch (e) { return h; } }); const hit = links.find(h => { try { const u = new URL(h); return /^https?:/.test(h) && !/(^|\\.)google\\.|gstatic|googleusercontent|support\\.google|accounts\\.google/i.test(u.hostname) && !/\\/menu\\b/i.test(u.pathname); } catch (e) { return false; } }); return hit || ''; })()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "phone",
            "selector": "(() => { const tel = ROW.querySelector('a[href^=\"tel:\"]')?.getAttribute('href'); if (tel) return tel.replace(/^tel:/, '').trim(); const labels = Array.from(ROW.querySelectorAll('[aria-label]')).map(e => e.getAttribute('aria-label') || ''); const label = labels.find(s => /Phone:|\\+?\\(?\\d{3}\\)?[\\s.-]?\\d{3}/.test(s)) || ''; const m = label.match(/\\+?\\d[\\d\\s().-]{7,}\\d/); return m ? m[0].trim() : ''; })()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "review_keyword1",
            "selector": "(() => { const vals = Array.from(ROW.querySelectorAll('button, div[role=\"button\"], span')).map(e => (e.innerText || '').trim().replace(/\\s+/g, ' ')).filter(t => /^[A-Za-z][A-Za-z\\s&'’-]{1,45}\\s+\\d+$/.test(t)); const uniq = Array.from(new Set(vals)); const v = uniq[0] || ''; const m = v.match(/^(.*)\\s+(\\d+)$/); return m ? `${m[1]}, mentioned in ${m[2]} reviews` : v; })()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "review_keyword2",
            "selector": "(() => { const vals = Array.from(ROW.querySelectorAll('button, div[role=\"button\"], span')).map(e => (e.innerText || '').trim().replace(/\\s+/g, ' ')).filter(t => /^[A-Za-z][A-Za-z\\s&'’-]{1,45}\\s+\\d+$/.test(t)); const uniq = Array.from(new Set(vals)); const v = uniq[1] || ''; const m = v.match(/^(.*)\\s+(\\d+)$/); return m ? `${m[1]}, mentioned in ${m[2]} reviews` : v; })()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "review_keyword3",
            "selector": "(() => { const vals = Array.from(ROW.querySelectorAll('button, div[role=\"button\"], span')).map(e => (e.innerText || '').trim().replace(/\\s+/g, ' ')).filter(t => /^[A-Za-z][A-Za-z\\s&'’-]{1,45}\\s+\\d+$/.test(t)); const uniq = Array.from(new Set(vals)); const v = uniq[2] || ''; const m = v.match(/^(.*)\\s+(\\d+)$/); return m ? `${m[1]}, mentioned in ${m[2]} reviews` : v; })()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "review_keyword4",
            "selector": "(() => { const vals = Array.from(ROW.querySelectorAll('button, div[role=\"button\"], span')).map(e => (e.innerText || '').trim().replace(/\\s+/g, ' ')).filter(t => /^[A-Za-z][A-Za-z\\s&'’-]{1,45}\\s+\\d+$/.test(t)); const uniq = Array.from(new Set(vals)); const v = uniq[3] || ''; const m = v.match(/^(.*)\\s+(\\d+)$/); return m ? `${m[1]}, mentioned in ${m[2]} reviews` : v; })()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "review_keyword5",
            "selector": "(() => { const vals = Array.from(ROW.querySelectorAll('button, div[role=\"button\"], span')).map(e => (e.innerText || '').trim().replace(/\\s+/g, ' ')).filter(t => /^[A-Za-z][A-Za-z\\s&'’-]{1,45}\\s+\\d+$/.test(t)); const uniq = Array.from(new Set(vals)); const v = uniq[4] || ''; const m = v.match(/^(.*)\\s+(\\d+)$/); return m ? `${m[1]}, mentioned in ${m[2]} reviews` : v; })()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "review_keyword6",
            "selector": "(() => { const vals = Array.from(ROW.querySelectorAll('button, div[role=\"button\"], span')).map(e => (e.innerText || '').trim().replace(/\\s+/g, ' ')).filter(t => /^[A-Za-z][A-Za-z\\s&'’-]{1,45}\\s+\\d+$/.test(t)); const uniq = Array.from(new Set(vals)); const v = uniq[5] || ''; const m = v.match(/^(.*)\\s+(\\d+)$/); return m ? `${m[1]}, mentioned in ${m[2]} reviews` : v; })()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "review_keyword7",
            "selector": "(() => { const vals = Array.from(ROW.querySelectorAll('button, div[role=\"button\"], span')).map(e => (e.innerText || '').trim().replace(/\\s+/g, ' ')).filter(t => /^[A-Za-z][A-Za-z\\s&'’-]{1,45}\\s+\\d+$/.test(t)); const uniq = Array.from(new Set(vals)); const v = uniq[6] || ''; const m = v.match(/^(.*)\\s+(\\d+)$/); return m ? `${m[1]}, mentioned in ${m[2]} reviews` : v; })()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "review_keyword8",
            "selector": "(() => { const vals = Array.from(ROW.querySelectorAll('button, div[role=\"button\"], span')).map(e => (e.innerText || '').trim().replace(/\\s+/g, ' ')).filter(t => /^[A-Za-z][A-Za-z\\s&'’-]{1,45}\\s+\\d+$/.test(t)); const uniq = Array.from(new Set(vals)); const v = uniq[7] || ''; const m = v.match(/^(.*)\\s+(\\d+)$/); return m ? `${m[1]}, mentioned in ${m[2]} reviews` : v; })()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "review_keyword9",
            "selector": "(() => { const vals = Array.from(ROW.querySelectorAll('button, div[role=\"button\"], span')).map(e => (e.innerText || '').trim().replace(/\\s+/g, ' ')).filter(t => /^[A-Za-z][A-Za-z\\s&'’-]{1,45}\\s+\\d+$/.test(t)); const uniq = Array.from(new Set(vals)); const v = uniq[8] || ''; const m = v.match(/^(.*)\\s+(\\d+)$/); return m ? `${m[1]}, mentioned in ${m[2]} reviews` : v; })()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "review_keyword10",
            "selector": "(() => { const vals = Array.from(ROW.querySelectorAll('button, div[role=\"button\"], span')).map(e => (e.innerText || '').trim().replace(/\\s+/g, ' ')).filter(t => /^[A-Za-z][A-Za-z\\s&'’-]{1,45}\\s+\\d+$/.test(t)); const uniq = Array.from(new Set(vals)); const v = uniq[9] || ''; const m = v.match(/^(.*)\\s+(\\d+)$/); return m ? `${m[1]}, mentioned in ${m[2]} reviews` : v; })()",
            "attribute": "text",
            "isJs": true
          }
        ]
      }
    },
    {
      "block_id": "loop-continue-1",
      "block_type": "process",
      "title": "Loop Continue",
      "description": "Continue multi-input loop",
      "position_x": 3000,
      "position_y": 240,
      "config": {
        "color": "bg-[#8d8d8d]"
      }
    },
    {
      "block_id": "end-1",
      "block_type": "output",
      "title": "End",
      "description": "Terminate execution flow",
      "position_x": 3360,
      "position_y": 240,
      "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": "wait-for-element-1",
      "to_connector_id": "left"
    },
    {
      "from_block_id": "wait-for-element-1",
      "from_connector_id": "right",
      "to_block_id": "wait-for-element-2",
      "to_connector_id": "left"
    },
    {
      "from_block_id": "wait-for-element-2",
      "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": "sleep-1",
      "to_connector_id": "left"
    },
    {
      "from_block_id": "sleep-1",
      "from_connector_id": "right",
      "to_block_id": "structured-export-1",
      "to_connector_id": "left"
    },
    {
      "from_block_id": "structured-export-1",
      "from_connector_id": "right",
      "to_block_id": "loop-continue-1",
      "to_connector_id": "left"
    },
    {
      "from_block_id": "loop-continue-1",
      "from_connector_id": "right",
      "to_block_id": "end-1",
      "to_connector_id": "left"
    }
  ],
  "canvas_elements": [
    {
      "id": "group-entry",
      "element_type": "group",
      "title": "Entry & Setup",
      "color": "#4589ff",
      "position_x": 48,
      "position_y": 136,
      "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": 136,
      "width": 2120,
      "height": 296,
      "z_index": 20,
      "data": {
        "memberBlockIds": [
          "navigate-1",
          "wait-for-page-load-1",
          "wait-for-element-1",
          "wait-for-element-2",
          "sleep-1"
        ]
      }
    },
    {
      "id": "group-interaction",
      "element_type": "group",
      "title": "Interaction",
      "color": "#a56eff",
      "position_x": 1848,
      "position_y": 136,
      "width": 380,
      "height": 296,
      "z_index": 20,
      "data": {
        "memberBlockIds": [
          "inject-javascript-1"
        ]
      }
    },
    {
      "id": "group-extract",
      "element_type": "group",
      "title": "Data Extraction",
      "color": "#42be65",
      "position_x": 2568,
      "position_y": 136,
      "width": 380,
      "height": 296,
      "z_index": 20,
      "data": {
        "memberBlockIds": [
          "structured-export-1"
        ]
      }
    },
    {
      "id": "group-pagination",
      "element_type": "group",
      "title": "Pagination Loop",
      "color": "#ff832b",
      "position_x": 2928,
      "position_y": 136,
      "width": 380,
      "height": 296,
      "z_index": 20,
      "data": {
        "memberBlockIds": [
          "loop-continue-1"
        ]
      }
    },
    {
      "id": "group-control",
      "element_type": "group",
      "title": "Control Flow",
      "color": "#8d8d8d",
      "position_x": 3288,
      "position_y": 136,
      "width": 380,
      "height": 296,
      "z_index": 20,
      "data": {
        "memberBlockIds": [
          "end-1"
        ]
      }
    },
    {
      "id": "note-overview",
      "element_type": "note",
      "title": "Overview",
      "content": "Best-effort Google Maps place details scraper equivalent to the Octoparse template. It extracts business names, ratings, review counts, categories, Google Maps place URLs, addresses, status/hours summary, latitude/longitude, plus codes, websites, phone numbers, header images, tags, and review keyword chips. Navigation uses a known Google Maps place URL list via navigate.urls[] plus loop-continue; add more Google Maps place/detail URLs to collect multiple businesses. Google Maps search/listing discovery is dynamic and may trigger consent, CAPTCHA, throttling, or DOM changes. This version avoids opening the Hours overlay before extraction because that can replace the page heading with a modal title.",
      "color": "#f1c21b",
      "position_x": 80,
      "position_y": 20,
      "width": 480,
      "height": 160,
      "z_index": 22,
      "data": {}
    },
    {
      "id": "note-block-navigate-1",
      "element_type": "note",
      "title": "Note: Navigate",
      "content": "Multi-URL loop over 1 pages. Pair with loop-continue at the end of each iteration.",
      "color": "#ee5396",
      "position_x": 680,
      "position_y": 220,
      "width": 328,
      "height": 107,
      "z_index": 22,
      "data": {
        "block_id": "navigate-1"
      }
    },
    {
      "id": "note-block-inject-javascript-1",
      "element_type": "note",
      "title": "Note: Inject JavaScript",
      "content": "Runs custom JavaScript in the page: `const closeButtons = Array.from(document.querySelectorAll('button[aria-label]')).filter(b => /close/...` Verify in browser if results are empty.",
      "color": "#ee5396",
      "position_x": 2120,
      "position_y": 220,
      "width": 340,
      "height": 140,
      "z_index": 22,
      "data": {
        "block_id": "inject-javascript-1"
      }
    },
    {
      "id": "note-block-structured-export-1",
      "element_type": "note",
      "title": "Note: Structured Export",
      "content": "Structured export with JS columns (keyword, name, header_image, rating, rating_count). These selectors are fragile — update if the site layout changes.",
      "color": "#ee5396",
      "position_x": 2840,
      "position_y": 220,
      "width": 340,
      "height": 130,
      "z_index": 22,
      "data": {
        "block_id": "structured-export-1"
      }
    },
    {
      "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": 3200,
      "position_y": 220,
      "width": 340,
      "height": 123,
      "z_index": 22,
      "data": {
        "block_id": "loop-continue-1"
      }
    }
  ]
}